From 527089babb093ca0d36d3997ad544a3fde17025e Mon Sep 17 00:00:00 2001 From: Carlo Costanzo Date: Thu, 16 Jul 2026 17:08:12 -0400 Subject: [PATCH] Improve infrastructure and health telemetry --- config/logbook.yaml | 7 ++ config/packages/docker_infrastructure.yaml | 12 +++- config/packages/infrastructure.yaml | 82 ++++++++++++++++++++-- config/packages/rachio.yaml | 2 + config/packages/sleepiq.yaml | 43 ++++++++++++ config/packages/speedtest.yaml | 69 +++++++----------- config/packages/water_delivery.yaml | 2 +- config/recorder.yaml | 4 ++ 8 files changed, 168 insertions(+), 53 deletions(-) diff --git a/config/logbook.yaml b/config/logbook.yaml index 2f0c3e13..6e972472 100644 --- a/config/logbook.yaml +++ b/config/logbook.yaml @@ -18,6 +18,9 @@ # Notes: Rolling maintenance days-since sensors are live-only display helpers. # Notes: TP-Link lamp voltage diagnostics stay out of Activity; recorder.yaml keeps them live-only. # Notes: WHOIS days-until-expiration countdowns stay out of Activity; actionable domain alert changes remain visible. +# Notes: Dashboard aggregate issue counts stay out of Activity; underlying degraded-state changes remain visible. +# Notes: The SQL-backed sleep-score average stays out of Activity; source scores remain available in Health. +# Notes: Duplicati result helpers stay out of Activity; curated BACKUP entries carry verification outcomes. ###################################################################### exclude: @@ -84,6 +87,7 @@ exclude: - automation.detect_lights_and_adjust_the_brightness_when_turned_on_based_on_time - automation.docker_state_sync_repairs_dynamic - automation.docker_telemetry_template_refresh + - sensor.infra_dashboard_active_issue_count - sensor.hvac_condenser_lines_days_since_cleaned - sensor.front_office_lamp_voltage - sensor.water_softener_salt_days_since_last_add @@ -99,6 +103,8 @@ exclude: - input_text.water_softener_salt_recent_events - counter.water_softener_salt_event_count - sensor.water_softener_salt_last_summary + - input_datetime.infra_duplicati_backup_last_success + - input_text.infra_duplicati_backup_verification_status # BEGIN generated Portainer recorder/logbook noise exclusions # Portainer per-container/status plumbing is live-only; keep aggregate Docker outage sensors historically. - binary_sensor.docker_container_alerts_snoozed @@ -330,6 +336,7 @@ exclude: - sensor.phyn_water_flowing - sensor.sleepnumber_carlo_carlo_pressure - sensor.sleepnumber_carlo_stacey_pressure + - sensor.sleep_score_14_day_averages - sensor.small_garage_reflection_rate - sensor.small_garage_status - sensor.small_garage_time_in_state diff --git a/config/packages/docker_infrastructure.yaml b/config/packages/docker_infrastructure.yaml index 05973686..80c29dca 100644 --- a/config/packages/docker_infrastructure.yaml +++ b/config/packages/docker_infrastructure.yaml @@ -12,8 +12,10 @@ # Notes: Hosts run daily read-only APT pending checks plus Mon/Thu 12:00 APT jobs. # Notes: All Docker hosts use America/New_York so staggered maintenance shares one local window. # Notes: Reboots are handled directly on each host by apt_weekly.sh. +# Notes: Boot reports reuse the registered APT webhook and retry while Home Assistant starts. # Notes: Reboot staggering: docker_14, docker_69, docker_17, docker_10. # Notes: Per-host action-due helpers preserve overdue state across daily checks and HA restarts. +# Notes: Monitored workload count deduplicates Portainer `_container_2` companion entities. # 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. @@ -400,7 +402,15 @@ template: unique_id: docker_monitored_container_count icon: mdi:format-list-numbered state: >- - {{ state_attr('sensor.docker_monitored_switch_inventory', 'entity_id') | default([], true) | count }} + {% set ns = namespace(keys=[]) %} + {% set monitored = state_attr('sensor.docker_monitored_switch_inventory', 'entity_id') | default([], true) %} + {% for switch_entity in monitored %} + {% set key = switch_entity | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') %} + {% if key not in ns.keys %} + {% set ns.keys = ns.keys + [key] %} + {% endif %} + {% endfor %} + {{ ns.keys | count }} - name: "Docker Monitored Unavailable Count" unique_id: docker_monitored_unavailable_count diff --git a/config/packages/infrastructure.yaml b/config/packages/infrastructure.yaml index 0ce2f76b..40711243 100644 --- a/config/packages/infrastructure.yaml +++ b/config/packages/infrastructure.yaml @@ -10,9 +10,11 @@ # ------------------------------------------------------------------- # Related Issue: 1584 # Notes: Home dashboard consumes `infra_*` entities for exceptions-only alerts. +# Notes: WAN outages alert immediately at near-zero throughput or 90% packet loss; performance degradation requires 6 continuous hours below 300/300 Mbps, above 80 ms, above 5% loss, or unavailable telemetry. # Notes: Nightly Duplicati verification runs at 08:00 after the 05:30 Duplicati job and docker_14 reboot window. # Notes: Duplicati transport/API errors are logged only; repairs are reserved for proven failed or stale backups. # Notes: Duplicati failure Repairs enable a recovery poll that clears the Repair after a later successful run. +# Notes: Duplicati verification persists its result and latest successful backup time for the Docker storage dashboard. # Notes: Monthly HA log hygiene review requests Telegram + public-safe GitHub issue follow-up only; Joanna must wait for approval before any changes. # Notes: Numeric WAN telemetry exposes state_class so recorder can keep long-term statistics. # Notes: Docker host root disk usage uses Glances-backed normalized sensors; raw Glances sensors are recorder/logbook-filtered. @@ -22,6 +24,7 @@ # Notes: Promoted IoT DNS consistency compares primary/backup Pi-hole answers for reserved IoT host records. # Notes: Garage compute health uses the same 145 F threshold as the existing temperature notification. # Notes: Immediate website-down states create Repairs; the installed UptimeRobot integration exposes current state, not trend rollups. +# Notes: Dashboard aggregate issue count is live-only; its underlying degraded sensors retain actionable history. ###################################################################### input_text: @@ -40,11 +43,20 @@ input_text: infra_pihole_iot_dns_health_band: name: "Pi-hole IoT DNS health band" max: 20 + infra_duplicati_backup_verification_status: + name: "Duplicati backup verification status" + max: 20 input_boolean: infra_duplicati_backup_repair_active: name: "Duplicati backup repair active" +input_datetime: + infra_duplicati_backup_last_success: + name: "Duplicati backup last success" + has_date: true + has_time: true + command_line: - sensor: name: Infra WAN Packet Loss @@ -193,7 +205,6 @@ template: - name: "Infra Dashboard Active Issue Count" unique_id: infra_dashboard_active_issue_count icon: mdi:alert-circle-check-outline - state_class: measurement state: >- {% set categories = [ 'binary_sensor.infra_wan_quality_degraded', @@ -215,17 +226,55 @@ template: {{ expand(categories) | selectattr('state', 'eq', 'on') | map(attribute='entity_id') | list }} - binary_sensor: - - name: "Infra WAN Quality Degraded" - unique_id: infra_wan_quality_degraded + - name: "Infra WAN Outage" + unique_id: infra_wan_outage device_class: problem + state: >- + {% set loss_raw = states('sensor.infra_wan_packet_loss') %} + {% set download_raw = states('sensor.speedtest_download') %} + {% set upload_raw = states('sensor.speedtest_upload') %} + {% set invalid_values = ['unknown', 'unavailable', 'none', ''] %} + {% set loss = loss_raw | float(0) %} + {% set download = download_raw | float(0) %} + {% set upload = upload_raw | float(0) %} + {{ (loss_raw not in invalid_values and loss >= 90) or + (download_raw not in invalid_values and download <= 1) or + (upload_raw not in invalid_values and upload <= 1) }} + + - name: "Infra WAN Sustained Degradation" + unique_id: infra_wan_sustained_degradation + device_class: problem + delay_on: "06:00:00" + delay_off: "00:30:00" state: >- {% set loss_raw = states('sensor.infra_wan_packet_loss') %} {% set lat_raw = states('sensor.infra_wan_latency_ms') %} - {% set invalid = loss_raw in ['unknown', 'unavailable', 'none', ''] or - lat_raw in ['unknown', 'unavailable', 'none', ''] %} + {% set download_raw = states('sensor.speedtest_download') %} + {% set upload_raw = states('sensor.speedtest_upload') %} + {% set invalid_values = ['unknown', 'unavailable', 'none', ''] %} + {% set invalid = loss_raw in invalid_values or + lat_raw in invalid_values or + download_raw in invalid_values or + upload_raw in invalid_values %} {% set loss = loss_raw | float(0) %} {% set lat = lat_raw | float(0) %} - {{ invalid or loss > 5 or lat > 80 }} + {% set download = download_raw | float(0) %} + {% set upload = upload_raw | float(0) %} + {{ invalid or loss > 5 or lat > 80 or download < 300 or upload < 300 }} + + - name: "Infra WAN Quality Degraded" + unique_id: infra_wan_quality_degraded + device_class: problem + state: >- + {{ is_state('binary_sensor.infra_wan_outage', 'on') or + is_state('binary_sensor.infra_wan_sustained_degradation', 'on') }} + attributes: + evaluation_window: "6 hours" + throughput_threshold_mbps: 300 + latency_threshold_ms: 80 + packet_loss_threshold_percent: 5 + outage_throughput_mbps: 1 + outage_packet_loss_percent: 90 - name: "Infra DNS Pihole Degraded" unique_id: infra_dns_pihole_degraded @@ -992,8 +1041,24 @@ automation: - service: input_boolean.turn_off target: entity_id: input_boolean.infra_duplicati_backup_repair_active + - service: input_text.set_value + target: + entity_id: input_text.infra_duplicati_backup_verification_status + data: + value: "verified" + - service: input_datetime.set_datetime + target: + entity_id: input_datetime.infra_duplicati_backup_last_success + data: + timestamp: >- + {{ as_timestamp(verify_last_success.get('endedAt'), as_timestamp(now())) }} - conditions: "{{ verify_transport_issue }}" sequence: + - service: input_text.set_value + target: + entity_id: input_text.infra_duplicati_backup_verification_status + data: + value: "unverified" - service: script.send_to_logbook data: topic: "BACKUP" @@ -1002,6 +1067,11 @@ automation: status {{ verify_status }} with issue {{ verify_issue }}. No repair card was opened because this is verifier transport state, not a confirmed backup failure. default: + - service: input_text.set_value + target: + entity_id: input_text.infra_duplicati_backup_verification_status + data: + value: "failed" - service: input_boolean.turn_on target: entity_id: input_boolean.infra_duplicati_backup_repair_active diff --git a/config/packages/rachio.yaml b/config/packages/rachio.yaml index effe7978..5ce800f8 100755 --- a/config/packages/rachio.yaml +++ b/config/packages/rachio.yaml @@ -8,6 +8,8 @@ # ------------------------------------------------------------------- # Notes: Attribute audit found native zone metadata and active-run summaries, but no durable off-state last-run/duration attributes. # Notes: Last-run sensors are trigger-based template sensors derived from zone on->off transitions; no input_* helpers are used. +# Video: https://youtu.be/a--1iTDWhSo +# Blog: https://www.vcloudinfo.com/2026/07/home-assistant-sprinkler-automation-rachio.html ###################################################################### template: diff --git a/config/packages/sleepiq.yaml b/config/packages/sleepiq.yaml index 6a1198d9..4f2f8294 100755 --- a/config/packages/sleepiq.yaml +++ b/config/packages/sleepiq.yaml @@ -15,6 +15,8 @@ # (helpers/automations) centralized in this file. # Notes: `group.bed` is occupancy-only so dependent automations get a clean # on/off state from the two in-bed binary sensors. +# Notes: The SQL summary averages each day's highest non-zero score over the +# latest 14 calendar days; source SleepIQ scores remain the history. ###################################################################### homeassistant: customize_glob: @@ -34,6 +36,47 @@ sleepiq: username: !secret sleepiq_username password: !secret sleepiq_password #------------------------------------------- + +sql: + - name: Sleep Score 14 Day Averages + unique_id: sleep_score_14_day_averages + icon: mdi:sleep + unit_of_measurement: score + query: >- + SELECT + ROUND(AVG(CASE + WHEN daily.entity_id = 'sensor.sleepnumber_carlo_carlo_sleep_score' + THEN daily.daily_score END), 1) AS carlo, + ROUND(AVG(CASE + WHEN daily.entity_id = 'sensor.sleepnumber_carlo_stacey_sleep_score' + THEN daily.daily_score END), 1) AS stacey, + COUNT(CASE + WHEN daily.entity_id = 'sensor.sleepnumber_carlo_carlo_sleep_score' + THEN 1 END) AS carlo_nights, + COUNT(CASE + WHEN daily.entity_id = 'sensor.sleepnumber_carlo_stacey_sleep_score' + THEN 1 END) AS stacey_nights + FROM ( + SELECT + states_meta.entity_id, + DATE(FROM_UNIXTIME(states.last_updated_ts)) AS score_day, + MAX(CAST(states.state AS DECIMAL(5,2))) AS daily_score + FROM states + INNER JOIN states_meta + ON states.metadata_id = states_meta.metadata_id + WHERE states_meta.entity_id IN ( + 'sensor.sleepnumber_carlo_carlo_sleep_score', + 'sensor.sleepnumber_carlo_stacey_sleep_score' + ) + AND DATE(FROM_UNIXTIME(states.last_updated_ts)) >= + CURDATE() - INTERVAL 13 DAY + AND CAST(states.state AS DECIMAL(5,2)) > 0 + GROUP BY + states_meta.entity_id, + DATE(FROM_UNIXTIME(states.last_updated_ts)) + ) AS daily + column: carlo + # sensor: # - platform: history_stats # name: Carlo_sleep_hours diff --git a/config/packages/speedtest.yaml b/config/packages/speedtest.yaml index 01affd88..23cc70e6 100644 --- a/config/packages/speedtest.yaml +++ b/config/packages/speedtest.yaml @@ -3,72 +3,51 @@ # For more info visit https://www.vcloudinfo.com/click-here # Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig # ------------------------------------------------------------------- -# Speedtest Alerts - Log internet speed drops/restores to Activity feed +# Speedtest Alerts - Log sustained WAN degradation, outages, and recovery # Related Issue: 1550 -# Uses `script.send_to_logbook` + `sensor.activity_feed`. +# Uses the normalized WAN health sensors plus `script.send_to_logbook`. # ------------------------------------------------------------------- +# Notes: Brief Speedtest transitions do not create Activity entries; outages are immediate and performance degradation requires 6 continuous hours. ###################################################################### automation: - - alias: "Internet Speed is Slow (Logbook)" + - alias: "Internet WAN Degraded (Logbook)" id: notify-carlo-slow-internet-speed - description: "Logs an Activity entry if download or upload speed falls below 300 Mbps." + description: "Logs an Activity entry for an immediate outage or sustained WAN degradation." trigger: - - platform: numeric_state - entity_id: sensor.speedtest_download - below: 300 - - platform: numeric_state - entity_id: sensor.speedtest_upload - below: 300 - - condition: - - condition: state - entity_id: group.bed - state: 'off' - - condition: template # Checks to make sure the system hasn't been restarted in the past 10 minutes - value_template: >- - {% set uptime = states('sensor.ha_uptime') | as_datetime %} - {% set now = now() %} - {{ (now - uptime).total_seconds() / 60 >= 10 }} - + - platform: state + entity_id: binary_sensor.infra_wan_quality_degraded + from: 'off' + to: 'on' action: - service: script.send_to_logbook data: topic: "NETWORK" message: >- + {% set outage = is_state('binary_sensor.infra_wan_outage', 'on') %} Download: {{ states('sensor.speedtest_download') }} Mbps, - Upload: {{ states('sensor.speedtest_upload') }} Mbps. - One or both speeds are below 300 Mbps. + upload: {{ states('sensor.speedtest_upload') }} Mbps, + latency: {{ states('sensor.infra_wan_latency_ms') }} ms, + packet loss: {{ states('sensor.infra_wan_packet_loss') }}%. + {{ 'Immediate WAN outage detected.' if outage else 'WAN performance remained degraded for 6 hours.' }} mode: single - - alias: "Internet Speed Restored (Logbook)" + - alias: "Internet WAN Restored (Logbook)" id: notify-carlo-internet-speed-restored - description: "Logs an Activity entry when download and upload speeds are back above 300/300 Mbps." + description: "Logs an Activity entry when the normalized WAN alert clears." trigger: - - platform: numeric_state - entity_id: sensor.speedtest_download - above: 300 - - platform: numeric_state - entity_id: sensor.speedtest_upload - above: 300 - condition: - - condition: numeric_state - entity_id: sensor.speedtest_download - above: 300 - - condition: numeric_state - entity_id: sensor.speedtest_upload - above: 300 - - condition: template # Checks to make sure the system hasn't been restarted in the past 10 minutes - value_template: >- - {% set uptime = states('sensor.ha_uptime') | as_datetime %} - {% set now = now() %} - {{ (now - uptime).total_seconds() / 60 >= 10 }} + - platform: state + entity_id: binary_sensor.infra_wan_quality_degraded + from: 'on' + to: 'off' action: - service: script.send_to_logbook data: topic: "NETWORK" message: >- Download: {{ states('sensor.speedtest_download') }} Mbps, - Upload: {{ states('sensor.speedtest_upload') }} Mbps. - Internet speeds are back to normal. + Upload: {{ states('sensor.speedtest_upload') }} Mbps, + latency: {{ states('sensor.infra_wan_latency_ms') }} ms, + packet loss: {{ states('sensor.infra_wan_packet_loss') }}%. + WAN alert cleared. mode: single diff --git a/config/packages/water_delivery.yaml b/config/packages/water_delivery.yaml index 8998381e..281663a5 100644 --- a/config/packages/water_delivery.yaml +++ b/config/packages/water_delivery.yaml @@ -7,7 +7,7 @@ # Schedule delivery date and announce night-before + day-before garage door alerts. # ------------------------------------------------------------------- # Related Issue: 541 -# Notes: Set input_datetime.water_delivery_date on /lovelace/water (date only). +# Notes: Set input_datetime.water_delivery_date on /dashboard-infrastructure/home-water (date only). # Notes: Night-before reminder fires at 5:30 PM; garage reminders fire on door open the day before delivery. # Notes: Clear button sets delivery date to yesterday to disable reminders. # Notes: Date helper changes are logged to Activity feed with source context. diff --git a/config/recorder.yaml b/config/recorder.yaml index e778934e..da3212d6 100755 --- a/config/recorder.yaml +++ b/config/recorder.yaml @@ -17,6 +17,8 @@ # Notes: Rolling maintenance days-since sensors are live-only; durable interval/count/total sensors carry the history. # Notes: TP-Link lamp voltage diagnostics are live-only; useful energy/power sensors stay recorded. # Notes: WHOIS days-until-expiration countdowns are live-only; actual expiry timestamps and alert-state changes remain durable. +# Notes: Dashboard aggregate issue counts are live-only; the underlying degraded-state sensors carry useful history. +# Notes: The SQL-backed sleep-score average is live-only; direct nightly scores keep the durable history. ###################################################################### db_url: !secret recorder_db_url purge_keep_days: 180 @@ -183,6 +185,7 @@ exclude: entities: - automation.update_garage_get_status - automation.docker_telemetry_template_refresh + - sensor.infra_dashboard_active_issue_count - sensor.hvac_condenser_lines_days_since_cleaned - sensor.front_office_lamp_voltage - sensor.water_softener_salt_days_since_last_add @@ -424,6 +427,7 @@ exclude: - sensor.ha_uptime - sensor.large_garage_door_since - sensor.small_garage_door_since + - sensor.sleep_score_14_day_averages - sensor.last_alexa - sensor.lights_on_count - sensor.low_battery