Enhance Home Assistant configuration with new sensors, state classes, and infrastructure monitoring

- Updated proxmox.yaml to include state_class for disk usage sensors and improved availability checks.
- Modified space.yaml to add state_class for the Earth distance sensor.
- Enhanced stats.yaml with state_class for various command line sensors and template sensors to support long-term trend rollups.
- Updated recorder.yaml to refine notes and exclude additional MariaDB snapshot sensors from recording.
- Revised README.md in scripts to correct package paths and add a new monthly log hygiene review automation.
- Introduced infrastructure.yaml for comprehensive observability and monitoring of WAN, DNS, and website states, including automated repairs for uptime breaches.
- Added mariadb_snapshot.py script to collect telemetry snapshots for MariaDB, supporting Home Assistant command line sensors.
pull/1719/head
Carlo Costanzo 1 month ago
parent aa5a919702
commit 7464db41f4

@ -50,10 +50,10 @@ Live collection of plug-and-play Home Assistant packages. Each YAML file in this
| [github_watched_repo_scout.yaml](github_watched_repo_scout.yaml) | Nightly Joanna dispatch that reviews unread notifications from watched GitHub repos, recommends HA-config ideas, refreshes strong-candidate issues, and marks processed watched-repo notifications read. | `automation.github_watched_repo_scout_nightly`, `script.joanna_dispatch`, `script.send_to_logbook` | | [github_watched_repo_scout.yaml](github_watched_repo_scout.yaml) | Nightly Joanna dispatch that reviews unread notifications from watched GitHub repos, recommends HA-config ideas, refreshes strong-candidate issues, and marks processed watched-repo notifications read. | `automation.github_watched_repo_scout_nightly`, `script.joanna_dispatch`, `script.send_to_logbook` |
| [proxmox.yaml](proxmox.yaml) | Proxmox runtime and disk pressure monitoring with Repairs + Joanna dispatch for sustained node degradations, plus nightly Frigate reboot. | `binary_sensor.proxmox*_runtime_healthy`, `sensor.proxmox*_disk_used_percentage`, `repairs.create`, `script.joanna_dispatch`, `button.qemu_docker2_101_reboot` | | [proxmox.yaml](proxmox.yaml) | Proxmox runtime and disk pressure monitoring with Repairs + Joanna dispatch for sustained node degradations, plus nightly Frigate reboot. | `binary_sensor.proxmox*_runtime_healthy`, `sensor.proxmox*_disk_used_percentage`, `repairs.create`, `script.joanna_dispatch`, `button.qemu_docker2_101_reboot` |
| [synology_dsm.yaml](synology_dsm.yaml) | Synology DSM integration health normalization for Carlo-NAS01 and Carlo-NVR, with Repairs + Joanna dispatch on sustained integration, security, or storage problems. | `binary_sensor.carlo_*_synology_problem`, `sensor.carlo_*_synology_problem_summary`, `repairs.create`, `script.joanna_dispatch` | | [synology_dsm.yaml](synology_dsm.yaml) | Synology DSM integration health normalization for Carlo-NAS01 and Carlo-NVR, with Repairs + Joanna dispatch on sustained integration, security, or storage problems. | `binary_sensor.carlo_*_synology_problem`, `sensor.carlo_*_synology_problem_summary`, `repairs.create`, `script.joanna_dispatch` |
| [infrastructure_observability.yaml](infrastructure_observability.yaml) | Normalized WAN/DNS/backup/domain/cert health + website uptime/latency SLO signals for Infrastructure dashboards. | `binary_sensor.infra_website_uptime_slo_breach`, `binary_sensor.infra_website_latency_degraded`, `binary_sensor.infra_*` | | [infrastructure.yaml](infrastructure.yaml) | Normalized WAN/DNS/backup/domain/cert health + website uptime/latency SLO signals for Infrastructure dashboards, plus nightly backup verification and monthly Joanna HA log hygiene review with GitHub issue follow-up. | `binary_sensor.infra_website_uptime_slo_breach`, `binary_sensor.infra_website_latency_degraded`, `automation.infra_backup_nightly_verification`, `automation.infra_monthly_log_hygiene_review`, `script.joanna_dispatch` |
| [onenote_indexer.yaml](onenote_indexer.yaml) | OneNote indexer health/status monitoring for Joanna, failure-repair automation, and a daily duplicate-delete maintenance request. | `sensor.onenote_indexer_last_job_status`, `binary_sensor.onenote_indexer_last_job_successful` | | [onenote_indexer.yaml](onenote_indexer.yaml) | OneNote indexer health/status monitoring for Joanna, failure-repair automation, and a daily duplicate-delete maintenance request. | `sensor.onenote_indexer_last_job_status`, `binary_sensor.onenote_indexer_last_job_successful` |
| [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 snapshots with hourly live metrics, weekly admin/recorder polling, and stats-ready numeric sensors. | `sensor.mariadb_status`, `sensor.database_size` |
| [processmonitor.yaml](processmonitor.yaml) | Root filesystem disk-pressure monitoring with immediate digest/logbook notes at 80%, Joanna review after 10 minutes above 80%, and delayed phone alerts only if the issue stays unresolved after dispatch. | `sensor.disk_use_percent`, `repairs.create`, `script.joanna_dispatch`, `tts.clear_cache` | | [processmonitor.yaml](processmonitor.yaml) | Root filesystem disk-pressure monitoring with immediate digest/logbook notes at 80%, Joanna review after 10 minutes above 80%, and delayed phone alerts only if the issue stays unresolved after dispatch. | `sensor.disk_use_percent`, `repairs.create`, `script.joanna_dispatch`, `tts.clear_cache` |
| [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, include LLM-first routing context for freeform text, relay replies back, ingest `/api/bearclaw/status` telemetry, and expose dispatch plus QMD/memory-index sensors for Infrastructure dashboards. | `rest_command.bearclaw_*`, `sensor.bearclaw_status_telemetry`, `sensor.joanna_*`, `binary_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 plus QMD/memory-index sensors for Infrastructure dashboards. | `rest_command.bearclaw_*`, `sensor.bearclaw_status_telemetry`, `sensor.joanna_*`, `binary_sensor.joanna_*`, `automation.bearclaw_*`, `script.send_to_logbook` |

@ -55,6 +55,7 @@ mqtt:
unique_id: garadget_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: '%'
state_class: measurement
value_template: '{{ value_json.bright }}' value_template: '{{ value_json.bright }}'
- name: "Small Garage Door Since" - name: "Small Garage Door Since"
@ -66,6 +67,7 @@ mqtt:
unique_id: garadget_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: '%'
state_class: measurement
value_template: '{{ value_json.bright }}' value_template: '{{ value_json.bright }}'
input_text: input_text:

@ -3,13 +3,15 @@
# For more info visit https://www.vcloudinfo.com/click-here # For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig # Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# Infrastructure Observability - Normalized infra monitoring signals # Infrastructure - Observability and Joanna review workflows
# WAN/DNS/website/domain/cert state normalized for dashboards. # WAN/DNS/website/domain/cert state normalized for dashboards, plus scheduled infrastructure reviews.
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# Related Issue: 1584 # Related Issue: 1584
# Notes: Home dashboard consumes `infra_*` entities for exceptions-only alerts. # Notes: Home dashboard consumes `infra_*` entities for exceptions-only alerts.
# Notes: Domain warning threshold is <30 days; critical threshold is <14 days. # Notes: Domain warning threshold is <30 days; critical threshold is <14 days.
# Notes: Nightly Duplicati verification is performed by codex_appliance against the Duplicati API because HA backup entities are not available. # Notes: Nightly Duplicati verification is performed by codex_appliance against the Duplicati API because HA backup entities are not available.
# Notes: Monthly HA log hygiene review requests Telegram + 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.
###################################################################### ######################################################################
command_line: command_line:
@ -22,6 +24,7 @@ command_line:
END {if (!found) print "unknown"}' END {if (!found) print "unknown"}'
scan_interval: 300 scan_interval: 300
unit_of_measurement: "%" unit_of_measurement: "%"
state_class: measurement
value_template: "{{ (value | regex_replace('[^0-9.]', '')) or 'unknown' }}" value_template: "{{ (value | regex_replace('[^0-9.]', '')) or 'unknown' }}"
- sensor: - sensor:
@ -33,6 +36,7 @@ command_line:
END {if (!found) print "unknown"}' END {if (!found) print "unknown"}'
scan_interval: 300 scan_interval: 300
unit_of_measurement: "ms" unit_of_measurement: "ms"
state_class: measurement
value_template: "{{ (value | regex_replace('[^0-9.]', '')) or 'unknown' }}" value_template: "{{ (value | regex_replace('[^0-9.]', '')) or 'unknown' }}"
- sensor: - sensor:
@ -411,3 +415,42 @@ automation:
action_taken, action_taken,
verification, verification,
next_action_required=true/false. next_action_required=true/false.
- alias: "Infrastructure - Monthly HA Log Hygiene Review"
id: infra_monthly_log_hygiene_review
description: "Ask Joanna monthly to review Home Assistant logs, create a GitHub issue with noisy entries, and send Telegram recommendations only."
mode: single
trigger:
- platform: time
at: "03:20:00"
condition:
- condition: template
value_template: "{{ now().day == 1 }}"
variables:
trigger_context: "HA automation infra_monthly_log_hygiene_review (Infrastructure - Monthly HA Log Hygiene Review)"
action:
- service: script.joanna_dispatch
data:
trigger_context: "{{ trigger_context }}"
source: "home_assistant_automation.infra_monthly_log_hygiene_review"
summary: "Monthly Home Assistant log hygiene review with GitHub issue and Telegram follow-up"
diagnostics: >-
schedule=day_1@03:20:00,
review_scope=available_home_assistant_logs,
desired_outputs=telegram_follow_up+github_issue,
github_repo=CCOSTAN/Home-AssistantConfig,
approval_required_before_changes=true
request: >-
Review the available Home Assistant log files from the last month and identify noisy,
low-value entries that could be safely suppressed, filtered, slowed, deduplicated, or
retired. Focus on practical Home Assistant-side changes such as recorder exclusions,
logger filtering, scan-interval reductions, entity retirement, or automation de-noising.
Create or refresh a GitHub issue in CCOSTAN/Home-AssistantConfig that captures the noisy
entries, estimated frequency, why each candidate is low-value, and the exact repo files
or integrations likely to change. Then send Carlo a concise Telegram summary with the top
recommendations and the GitHub issue number or link. Do not make any changes from this
review. Wait for explicit follow-up approval first.
- service: script.send_to_logbook
data:
topic: "HOME ASSISTANT"
message: "Joanna monthly Home Assistant log hygiene review dispatched; Telegram summary and GitHub issue requested."

@ -9,6 +9,7 @@
# Notes: Webhook id is bearclaw_maintenance_log_v1 (Joanna -> HA contract). # Notes: Webhook id is bearclaw_maintenance_log_v1 (Joanna -> HA contract).
# Notes: Duplicate event_id values are ignored to prevent double-count totals. # Notes: Duplicate event_id values are ignored to prevent double-count totals.
# Notes: Recent event history string format is "when|amount|note||...". # Notes: Recent event history string format is "when|amount|note||...".
# Notes: Numeric refill interval sensors expose state_class for long-term trend rollups.
###################################################################### ######################################################################
input_number: input_number:
@ -64,17 +65,21 @@ template:
- name: "Water Softener Salt Days Since Last Add" - name: "Water Softener Salt Days Since Last Add"
unique_id: water_softener_salt_days_since_last_add unique_id: water_softener_salt_days_since_last_add
unit_of_measurement: d unit_of_measurement: d
state: >- state_class: measurement
availability: >-
{% set raw = states('input_datetime.water_softener_salt_last_occurred_at') %} {% set raw = states('input_datetime.water_softener_salt_last_occurred_at') %}
{% if raw in ['unknown', 'unavailable', 'none', ''] %} {% if raw in ['unknown', 'unavailable', 'none', ''] %}
unknown false
{% else %} {% else %}
{% set event_ts = as_timestamp(as_local(as_datetime(raw)), default=none) %} {{ as_timestamp(as_local(as_datetime(raw)), default=none) is not none }}
{% if event_ts is none %} {% endif %}
unknown state: >-
{% else %} {% set raw = states('input_datetime.water_softener_salt_last_occurred_at') %}
{{ [((as_timestamp(now()) - event_ts) / 86400), 0] | max | round(1) }} {% set event_ts = as_timestamp(as_local(as_datetime(raw)), default=none) %}
{% endif %} {% if event_ts is not none %}
{{ [((as_timestamp(now()) - event_ts) / 86400), 0] | max | round(1) }}
{% else %}
0
{% endif %} {% endif %}
- name: "Water Softener Salt Last Summary" - name: "Water Softener Salt Last Summary"
@ -98,6 +103,7 @@ template:
- name: "Water Softener Salt Average Days Between Refills" - name: "Water Softener Salt Average Days Between Refills"
unique_id: water_softener_salt_average_days_between_refills unique_id: water_softener_salt_average_days_between_refills
unit_of_measurement: d unit_of_measurement: d
state_class: measurement
state: >- state: >-
{% set raw = states('input_text.water_softener_salt_recent_events') %} {% set raw = states('input_text.water_softener_salt_recent_events') %}
{% if raw in ['unknown', 'unavailable', 'none', ''] %} {% if raw in ['unknown', 'unavailable', 'none', ''] %}

@ -3,168 +3,224 @@
# For more info visit https://www.vcloudinfo.com/click-here # For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig # Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# MariaDB Monitoring - SQL sensor bundle for DB health # MariaDB Monitoring - Snapshot-driven DB health sensors
# Recorder-backed metrics for MariaDB performance and capacity checks. # Recorder-backed metrics for MariaDB health, capacity, and tuning.
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# Notes: Uses SQL integration against recorder_db_url. # Notes: Uses command_line snapshot helpers so expensive MariaDB queries are not forced to run every 30 seconds by the SQL integration.
# Notes: COUNT(*) queries run every 6h; increase scan_interval or disable if slow. # Notes: Live metrics poll hourly; recorder/admin snapshots poll weekly.
# Notes: Numeric template sensors expose state_class where useful so HA can keep long-term statistics efficiently.
###################################################################### ######################################################################
sql: command_line:
- name: "MariaDB Status" - sensor:
db_url: !secret recorder_db_url name: MariaDB Live Snapshot
query: >- unique_id: mariadb_live_snapshot
SELECT 'running' as status command: "python3 /config/shell_scripts/mariadb_snapshot.py live"
FROM information_schema.GLOBAL_STATUS scan_interval: 3600
WHERE VARIABLE_NAME = 'Uptime' command_timeout: 30
AND CAST(VARIABLE_VALUE AS UNSIGNED) > 0; json_attributes:
column: "status" - performance
value_template: "{{ value if value else 'stopped' }}" - connections
- questions
- name: "MariaDB Version" - uptime_seconds
db_url: !secret recorder_db_url value_template: "{{ value_json.status | default('unknown') }}"
query: >-
SELECT @@version as version; - sensor:
column: "version" name: MariaDB Recorder Snapshot
unique_id: mariadb_recorder_snapshot
- name: "MariaDB Performance" command: "python3 /config/shell_scripts/mariadb_snapshot.py recorder"
db_url: !secret recorder_db_url scan_interval: 604800
query: >- command_timeout: 180
SELECT ROUND( json_attributes:
(SELECT VARIABLE_VALUE - database_tables_count
FROM information_schema.GLOBAL_STATUS - database_oldest_record
WHERE VARIABLE_NAME = 'Queries') / - database_total_records
(SELECT VARIABLE_VALUE - database_records_per_day
FROM information_schema.GLOBAL_STATUS value_template: "{{ value_json.database_size_mib | default('unknown') }}"
WHERE VARIABLE_NAME = 'Uptime')
) as performance; - sensor:
column: "performance" name: MariaDB Admin Snapshot
unit_of_measurement: "q/s" unique_id: mariadb_admin_snapshot
command: "python3 /config/shell_scripts/mariadb_snapshot.py admin"
- name: "Database Size" scan_interval: 604800
db_url: !secret recorder_db_url command_timeout: 30
query: >- json_attributes:
SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) as size - version
FROM information_schema.tables - max_connections
WHERE table_schema = 'homeassistant'; - log_file_size_mib
column: "size" - tmp_table_size_mib
unit_of_measurement: "MB" - io_capacity
value_template: "{{ value | float(0) }}" - io_threads_read
- io_threads_write
- name: "Database Tables Count" - table_cache
db_url: !secret recorder_db_url - sort_buffer_mib
query: >- - read_buffer_mib
SELECT COUNT(*) as count - join_buffer_mib
FROM information_schema.tables value_template: "{{ value_json.buffer_pool_gib | default('unknown') }}"
WHERE table_schema = 'homeassistant';
column: "count" template:
unit_of_measurement: "tables" - sensor:
- name: "MariaDB Status"
- name: "Database Oldest Record" unique_id: mariadb_status
db_url: !secret recorder_db_url state: >-
query: >- {% set value = states('sensor.mariadb_live_snapshot') %}
SELECT DATE_FORMAT(FROM_UNIXTIME(MIN(last_updated_ts)), '%Y-%m-%d') as oldest {{ value if value not in ['unknown', 'unavailable', 'none', ''] else 'unknown' }}
FROM states;
column: "oldest" - name: "MariaDB Version"
unique_id: mariadb_version
- name: "Database Total Records" state: >-
db_url: !secret recorder_db_url {% set value = state_attr('sensor.mariadb_admin_snapshot', 'version') %}
query: >- {{ value if value is not none else 'unknown' }}
SELECT COUNT(*) as count
FROM states; - name: "MariaDB Performance"
column: "count" unique_id: mariadb_performance
unit_of_measurement: "records" unit_of_measurement: "q/s"
state_class: measurement
- name: "Database Records Per Day" availability: >-
db_url: !secret recorder_db_url {{ state_attr('sensor.mariadb_live_snapshot', 'performance') is not none }}
query: >- state: >-
SELECT ROUND( {{ state_attr('sensor.mariadb_live_snapshot', 'performance') | float(0) }}
COUNT(*) /
GREATEST(DATEDIFF(NOW(), FROM_UNIXTIME(MIN(last_updated_ts))), 1), - name: "Database Size"
0 unique_id: database_size
) as avg unit_of_measurement: "MiB"
FROM states; state_class: measurement
column: "avg" availability: >-
unit_of_measurement: "records/day" {{ states('sensor.mariadb_recorder_snapshot') not in ['unknown', 'unavailable', 'none', ''] }}
state: >-
- name: "MariaDB Uptime" {{ states('sensor.mariadb_recorder_snapshot') | float(0) }}
db_url: !secret recorder_db_url
query: >- - name: "Database Tables Count"
SELECT VARIABLE_VALUE as value unique_id: database_tables_count
FROM information_schema.GLOBAL_STATUS unit_of_measurement: "tables"
WHERE VARIABLE_NAME = 'Uptime'; state_class: measurement
column: "value" availability: >-
unit_of_measurement: "seconds" {{ state_attr('sensor.mariadb_recorder_snapshot', 'database_tables_count') is not none }}
state: >-
- name: "MariaDB Connections" {{ state_attr('sensor.mariadb_recorder_snapshot', 'database_tables_count') | int(0) }}
db_url: !secret recorder_db_url
query: >- - name: "Database Oldest Record"
SELECT VARIABLE_VALUE as value unique_id: database_oldest_record
FROM information_schema.GLOBAL_STATUS state: >-
WHERE VARIABLE_NAME = 'Threads_connected'; {% set value = state_attr('sensor.mariadb_recorder_snapshot', 'database_oldest_record') %}
column: "value" {{ value if value is not none else 'unknown' }}
unit_of_measurement: "connections"
- name: "Database Total Records"
- name: "MariaDB Questions" unique_id: database_total_records
db_url: !secret recorder_db_url unit_of_measurement: "records"
query: >- state_class: measurement
SELECT VARIABLE_VALUE as value availability: >-
FROM information_schema.GLOBAL_STATUS {{ state_attr('sensor.mariadb_recorder_snapshot', 'database_total_records') is not none }}
WHERE VARIABLE_NAME = 'Questions'; state: >-
column: "value" {{ state_attr('sensor.mariadb_recorder_snapshot', 'database_total_records') | int(0) }}
unit_of_measurement: "queries"
- name: "Database Records Per Day"
- name: "MariaDB Buffer Pool Size" unique_id: database_records_per_day
db_url: !secret recorder_db_url unit_of_measurement: "records/day"
query: >- state_class: measurement
SELECT CONCAT(ROUND(@@innodb_buffer_pool_size / 1024 / 1024 / 1024, 1), ' GB') as value; availability: >-
column: "value" {{ state_attr('sensor.mariadb_recorder_snapshot', 'database_records_per_day') is not none }}
state: >-
- name: "MariaDB Max Connections" {{ state_attr('sensor.mariadb_recorder_snapshot', 'database_records_per_day') | float(0) }}
db_url: !secret recorder_db_url
query: >- - name: "MariaDB Uptime"
SELECT @@max_connections as value; unique_id: mariadb_uptime
column: "value" unit_of_measurement: "s"
availability: >-
- name: "MariaDB Log File Size" {{ state_attr('sensor.mariadb_live_snapshot', 'uptime_seconds') is not none }}
db_url: !secret recorder_db_url state: >-
query: >- {{ state_attr('sensor.mariadb_live_snapshot', 'uptime_seconds') | int(0) }}
SELECT CONCAT(ROUND(@@innodb_log_file_size / 1024 / 1024, 0), ' MB') as value;
column: "value" - name: "MariaDB Connections"
unique_id: mariadb_connections
- name: "MariaDB Tmp Table Size" unit_of_measurement: "connections"
db_url: !secret recorder_db_url state_class: measurement
query: >- availability: >-
SELECT CONCAT(ROUND(@@tmp_table_size / 1024 / 1024, 0), ' MB') as value; {{ state_attr('sensor.mariadb_live_snapshot', 'connections') is not none }}
column: "value" state: >-
{{ state_attr('sensor.mariadb_live_snapshot', 'connections') | int(0) }}
- name: "MariaDB IO Capacity"
db_url: !secret recorder_db_url - name: "MariaDB Questions"
query: >- unique_id: mariadb_questions
SELECT @@innodb_io_capacity as value; unit_of_measurement: "queries"
column: "value" state_class: total_increasing
availability: >-
- name: "MariaDB IO Threads" {{ state_attr('sensor.mariadb_live_snapshot', 'questions') is not none }}
db_url: !secret recorder_db_url state: >-
query: >- {{ state_attr('sensor.mariadb_live_snapshot', 'questions') | int(0) }}
SELECT CONCAT(
'Read: ', @@innodb_read_io_threads, - name: "MariaDB Buffer Pool Size"
', Write: ', @@innodb_write_io_threads unique_id: mariadb_buffer_pool_size
) as value; unit_of_measurement: "GiB"
column: "value" state_class: measurement
availability: >-
- name: "MariaDB Table Cache" {{ states('sensor.mariadb_admin_snapshot') not in ['unknown', 'unavailable', 'none', ''] }}
db_url: !secret recorder_db_url state: >-
query: >- {{ states('sensor.mariadb_admin_snapshot') | float(0) }}
SELECT @@table_open_cache as value;
column: "value" - name: "MariaDB Max Connections"
unique_id: mariadb_max_connections
- name: "MariaDB Buffer Sizes" unit_of_measurement: "connections"
db_url: !secret recorder_db_url state_class: measurement
query: >- availability: >-
SELECT CONCAT( {{ state_attr('sensor.mariadb_admin_snapshot', 'max_connections') is not none }}
'Sort: ', ROUND(@@sort_buffer_size / 1024 / 1024, 0), 'M, ', state: >-
'Read: ', ROUND(@@read_buffer_size / 1024 / 1024, 0), 'M, ', {{ state_attr('sensor.mariadb_admin_snapshot', 'max_connections') | int(0) }}
'Join: ', ROUND(@@join_buffer_size / 1024 / 1024, 0), 'M'
) as value; - name: "MariaDB Log File Size"
column: "value" unique_id: mariadb_log_file_size
unit_of_measurement: "MiB"
state_class: measurement
availability: >-
{{ state_attr('sensor.mariadb_admin_snapshot', 'log_file_size_mib') is not none }}
state: >-
{{ state_attr('sensor.mariadb_admin_snapshot', 'log_file_size_mib') | float(0) }}
- name: "MariaDB Tmp Table Size"
unique_id: mariadb_tmp_table_size
unit_of_measurement: "MiB"
state_class: measurement
availability: >-
{{ state_attr('sensor.mariadb_admin_snapshot', 'tmp_table_size_mib') is not none }}
state: >-
{{ state_attr('sensor.mariadb_admin_snapshot', 'tmp_table_size_mib') | float(0) }}
- name: "MariaDB IO Capacity"
unique_id: mariadb_io_capacity
state_class: measurement
availability: >-
{{ state_attr('sensor.mariadb_admin_snapshot', 'io_capacity') is not none }}
state: >-
{{ state_attr('sensor.mariadb_admin_snapshot', 'io_capacity') | int(0) }}
- name: "MariaDB IO Threads"
unique_id: mariadb_io_threads
state: >-
{% set read = state_attr('sensor.mariadb_admin_snapshot', 'io_threads_read') %}
{% set write = state_attr('sensor.mariadb_admin_snapshot', 'io_threads_write') %}
{% if read is not none and write is not none %}
Read: {{ read }}, Write: {{ write }}
{% else %}
unknown
{% endif %}
- name: "MariaDB Table Cache"
unique_id: mariadb_table_cache
unit_of_measurement: "tables"
state_class: measurement
availability: >-
{{ state_attr('sensor.mariadb_admin_snapshot', 'table_cache') is not none }}
state: >-
{{ state_attr('sensor.mariadb_admin_snapshot', 'table_cache') | int(0) }}
- name: "MariaDB Buffer Sizes"
unique_id: mariadb_buffer_sizes
state: >-
{% set sort = state_attr('sensor.mariadb_admin_snapshot', 'sort_buffer_mib') %}
{% set read = state_attr('sensor.mariadb_admin_snapshot', 'read_buffer_mib') %}
{% set join = state_attr('sensor.mariadb_admin_snapshot', 'join_buffer_mib') %}
{% if sort is not none and read is not none and join is not none %}
Sort: {{ sort }}M, Read: {{ read }}M, Join: {{ join }}M
{% else %}
unknown
{% endif %}

@ -10,13 +10,23 @@
# Notes: Creates HA repair issues when proxmox nodes report updates. # Notes: Creates HA repair issues when proxmox nodes report updates.
# Notes: Adds normalized runtime + disk health signals for dashboard/alerts. # Notes: Adds normalized runtime + disk health signals for dashboard/alerts.
# Notes: Joanna dispatch is reserved for sustained runtime and disk-pressure degradations. # Notes: Joanna dispatch is reserved for sustained runtime and disk-pressure degradations.
# Notes: Normalized disk usage sensors expose state_class for long-term trend rollups.
###################################################################### ######################################################################
template: template:
- sensor: - sensor:
- name: "Proxmox1 Disk Used Percentage" - name: "Proxmox1 Disk Used Percentage"
unique_id: proxmox1_disk_used_percentage unique_id: proxmox1_disk_used_percentage
unit_of_measurement: "%" unit_of_measurement: "%"
state_class: measurement
icon: mdi:harddisk icon: mdi:harddisk
availability: >-
{% set preferred = states('sensor.node_proxmox1_disk_used_percentage') %}
{% set used = states('sensor.node_proxmox1_disk') %}
{% set total = states('sensor.node_proxmox1_max_disk') %}
{{ preferred not in ['unknown', 'unavailable', 'none', ''] or
(used not in ['unknown', 'unavailable', 'none', ''] and
total not in ['unknown', 'unavailable', 'none', ''] and
(total | float(0)) > 0) }}
state: >- state: >-
{% set preferred = states('sensor.node_proxmox1_disk_used_percentage') %} {% set preferred = states('sensor.node_proxmox1_disk_used_percentage') %}
{% if preferred not in ['unknown', 'unavailable', 'none', ''] %} {% if preferred not in ['unknown', 'unavailable', 'none', ''] %}
@ -27,14 +37,23 @@ template:
{% if total > 0 %} {% if total > 0 %}
{{ ((used / total) * 100) | round(1) }} {{ ((used / total) * 100) | round(1) }}
{% else %} {% else %}
{{ none }} 0
{% endif %} {% endif %}
{% endif %} {% endif %}
- name: "Proxmox02 Disk Used Percentage" - name: "Proxmox02 Disk Used Percentage"
unique_id: proxmox02_disk_used_percentage unique_id: proxmox02_disk_used_percentage
unit_of_measurement: "%" unit_of_measurement: "%"
state_class: measurement
icon: mdi:harddisk icon: mdi:harddisk
availability: >-
{% set preferred = states('sensor.node_proxmox02_disk_used_percentage') %}
{% set used = states('sensor.node_proxmox02_disk') %}
{% set total = states('sensor.node_proxmox02_max_disk') %}
{{ preferred not in ['unknown', 'unavailable', 'none', ''] or
(used not in ['unknown', 'unavailable', 'none', ''] and
total not in ['unknown', 'unavailable', 'none', ''] and
(total | float(0)) > 0) }}
state: >- state: >-
{% set preferred = states('sensor.node_proxmox02_disk_used_percentage') %} {% set preferred = states('sensor.node_proxmox02_disk_used_percentage') %}
{% if preferred not in ['unknown', 'unavailable', 'none', ''] %} {% if preferred not in ['unknown', 'unavailable', 'none', ''] %}
@ -45,7 +64,7 @@ template:
{% if total > 0 %} {% if total > 0 %}
{{ ((used / total) * 100) | round(1) }} {{ ((used / total) * 100) | round(1) }}
{% else %} {% else %}
{{ none }} 0
{% endif %} {% endif %}
{% endif %} {% endif %}

@ -17,6 +17,7 @@ sensor:
- earth_distance_mi - earth_distance_mi
value_template: '{{ value_json["speed_mph"] }}' value_template: '{{ value_json["speed_mph"] }}'
unit_of_measurement: "mph" unit_of_measurement: "mph"
state_class: measurement
resource: 'https://api.spacexdata.com/v2/info/roadster' resource: 'https://api.spacexdata.com/v2/info/roadster'
- platform: rest - platform: rest

@ -7,6 +7,7 @@
# Build historical stats for AI/alerting. # Build historical stats for AI/alerting.
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# Contact: @CCOSTAN # Contact: @CCOSTAN
# Notes: Numeric repo and home counters expose state_class for long-term trend rollups.
###################################################################### ######################################################################
### Building out some Historical stats for AI. ##################### ### Building out some Historical stats for AI. #####################
@ -19,6 +20,7 @@ command_line:
scan_interval: 20000 scan_interval: 20000
value_template: "{{ value | int }}" value_template: "{{ value | int }}"
unit_of_measurement: "count" unit_of_measurement: "count"
state_class: measurement
- sensor: - sensor:
name: 'GitHub Open Issues' name: 'GitHub Open Issues'
unique_id: github_open_issues unique_id: github_open_issues
@ -26,6 +28,7 @@ command_line:
scan_interval: 20000 scan_interval: 20000
value_template: '{{ value_json.open_issues }}' value_template: '{{ value_json.open_issues }}'
unit_of_measurement: 'count' unit_of_measurement: 'count'
state_class: measurement
- sensor: - sensor:
name: 'GitHub Stargazers' name: 'GitHub Stargazers'
@ -34,6 +37,7 @@ command_line:
scan_interval: 20000 scan_interval: 20000
value_template: '{{ value_json.stargazers_count }}' value_template: '{{ value_json.stargazers_count }}'
unit_of_measurement: 'count' unit_of_measurement: 'count'
state_class: measurement
sensor: sensor:
- platform: history_stats - platform: history_stats
@ -79,6 +83,7 @@ template:
- name: "Number of Sensors" - name: "Number of Sensors"
unique_id: stats_number_of_sensors unique_id: stats_number_of_sensors
unit_of_measurement: "count" unit_of_measurement: "count"
state_class: measurement
icon: mdi:counter icon: mdi:counter
state: >- state: >-
{{ states.sensor | list | count }} {{ states.sensor | list | count }}
@ -86,6 +91,7 @@ template:
- name: "Number of Automations" - name: "Number of Automations"
unique_id: stats_number_of_automations unique_id: stats_number_of_automations
unit_of_measurement: "count" unit_of_measurement: "count"
state_class: measurement
icon: mdi:robot icon: mdi:robot
state: >- state: >-
{{ states.automation | list | count }} {{ states.automation | list | count }}
@ -93,6 +99,7 @@ template:
- name: "Number of Scripts" - name: "Number of Scripts"
unique_id: stats_number_of_scripts unique_id: stats_number_of_scripts
unit_of_measurement: "count" unit_of_measurement: "count"
state_class: measurement
icon: mdi:script-text icon: mdi:script-text
state: >- state: >-
{{ states.script | list | count }} {{ states.script | list | count }}
@ -100,6 +107,7 @@ template:
- name: "Number of Binary Sensors" - name: "Number of Binary Sensors"
unique_id: stats_number_of_binary_sensors unique_id: stats_number_of_binary_sensors
unit_of_measurement: "count" unit_of_measurement: "count"
state_class: measurement
icon: mdi:binary-sensor icon: mdi:binary-sensor
state: >- state: >-
{{ states.binary_sensor | list | count }} {{ states.binary_sensor | list | count }}
@ -107,6 +115,7 @@ template:
- name: "Number of Devices" - name: "Number of Devices"
unique_id: stats_number_of_devices unique_id: stats_number_of_devices
unit_of_measurement: "count" unit_of_measurement: "count"
state_class: measurement
icon: mdi:account-group icon: mdi:account-group
state: >- state: >-
{{ states.device_tracker | list | count }} {{ states.device_tracker | list | count }}
@ -114,6 +123,7 @@ template:
- name: "Number of Lights" - name: "Number of Lights"
unique_id: stats_number_of_lights unique_id: stats_number_of_lights
unit_of_measurement: "count" unit_of_measurement: "count"
state_class: measurement
icon: mdi:lightbulb icon: mdi:lightbulb
state: > state: >
{{ states.light | list | count }} {{ states.light | list | count }}
@ -121,12 +131,14 @@ template:
- name: "Number of lights on" - name: "Number of lights on"
unique_id: stats_number_of_lights_on unique_id: stats_number_of_lights_on
unit_of_measurement: "count" unit_of_measurement: "count"
state_class: measurement
icon: mdi:binary-sensor icon: mdi:binary-sensor
state: >- state: >-
{{ states.light | selectattr('state', 'eq', 'on') | list | count }} {{ states.light | selectattr('state', 'eq', 'on') | list | count }}
- name: "Number of Smoke Detectors" - name: "Number of Smoke Detectors"
unit_of_measurement: "count" unit_of_measurement: "count"
state_class: measurement
icon: mdi:smoke-detector icon: mdi:smoke-detector
state: > state: >
{% if states('group.protects') == 'on' %} {% if states('group.protects') == 'on' %}
@ -142,6 +154,7 @@ template:
- name: "Number of online Cameras" - name: "Number of online Cameras"
unique_id: stats_number_of_online_cameras unique_id: stats_number_of_online_cameras
unit_of_measurement: "count" unit_of_measurement: "count"
state_class: measurement
icon: mdi:camera icon: mdi:camera
state: > state: >
{{ states.camera | list | count }} {{ states.camera | list | count }}
@ -149,6 +162,7 @@ template:
- name: "Total WiFi Clients" - name: "Total WiFi Clients"
unique_id: total_wifi_clients unique_id: total_wifi_clients
unit_of_measurement: "clients" unit_of_measurement: "clients"
state_class: measurement
icon: mdi:wifi icon: mdi:wifi
state: > state: >
{% set g = states('sensor.unifi_ap_garage_clients') | int(0) %} {% set g = states('sensor.unifi_ap_garage_clients') | int(0) %}

@ -6,7 +6,7 @@
# Recorder Configuration - database retention and exclusions # Recorder Configuration - database retention and exclusions
# Stores HA history while purging noise and controlling DB size. # Stores HA history while purging noise and controlling DB size.
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# Notes: Keeps 180 days (1/2 year); excludes vcloudinfo pings, noisy connectivity telemetry, countdown-style alarm helpers, and other high-churn entities; MariaDB via recorder_db_url. # Notes: Keeps 180 days (1/2 year); excludes vcloudinfo pings, noisy connectivity telemetry, countdown-style alarm helpers, MariaDB snapshot helpers, and other high-churn entities; MariaDB via recorder_db_url.
###################################################################### ######################################################################
db_url: !secret recorder_db_url db_url: !secret recorder_db_url
purge_keep_days: 180 purge_keep_days: 180
@ -100,6 +100,9 @@ exclude:
- sensor.last_alexa - sensor.last_alexa
- sensor.lights_on_count - sensor.lights_on_count
- sensor.low_battery - sensor.low_battery
- sensor.mariadb_admin_snapshot
- sensor.mariadb_live_snapshot
- sensor.mariadb_recorder_snapshot
- sensor.network - sensor.network
- sensor.network_detail - sensor.network_detail
- sensor.pi_hole_ads_blocked_today - sensor.pi_hole_ads_blocked_today

@ -57,7 +57,8 @@ Current automations that kick off automated resolutions (via `script.joanna_disp
| `mqtt_open_repair_on_failure` | MQTT - Open Repair On Failure | [../packages/mqtt_status.yaml](../packages/mqtt_status.yaml) | | `mqtt_open_repair_on_failure` | MQTT - Open Repair On Failure | [../packages/mqtt_status.yaml](../packages/mqtt_status.yaml) |
| `onenote_indexer_daily_delete_maintenance` | OneNote Indexer - Daily Delete Maintenance Request | [../packages/onenote_indexer.yaml](../packages/onenote_indexer.yaml) | | `onenote_indexer_daily_delete_maintenance` | OneNote Indexer - Daily Delete Maintenance Request | [../packages/onenote_indexer.yaml](../packages/onenote_indexer.yaml) |
| `onenote_indexer_failure_open_repair` | OneNote Indexer - Open Repair On Failure | [../packages/onenote_indexer.yaml](../packages/onenote_indexer.yaml) | | `onenote_indexer_failure_open_repair` | OneNote Indexer - Open Repair On Failure | [../packages/onenote_indexer.yaml](../packages/onenote_indexer.yaml) |
| `infra_backup_nightly_verification` | Infrastructure - Backup Nightly Verification | [../packages/infrastructure_observability.yaml](../packages/infrastructure_observability.yaml) | | `infra_backup_nightly_verification` | Infrastructure - Backup Nightly Verification | [../packages/infrastructure.yaml](../packages/infrastructure.yaml) |
| `infra_monthly_log_hygiene_review` | Infrastructure - Monthly HA Log Hygiene Review | [../packages/infrastructure.yaml](../packages/infrastructure.yaml) |
| `docker_state_sync_repairs_dynamic` | Docker State Sync - Repairs (Dynamic) | [../packages/docker_infrastructure.yaml](../packages/docker_infrastructure.yaml) | | `docker_state_sync_repairs_dynamic` | Docker State Sync - Repairs (Dynamic) | [../packages/docker_infrastructure.yaml](../packages/docker_infrastructure.yaml) |
| `docker_group_reconcile_weekly_joanna_review` | Docker Group Reconcile - Weekly Joanna Review | [../packages/docker_infrastructure.yaml](../packages/docker_infrastructure.yaml) | | `docker_group_reconcile_weekly_joanna_review` | Docker Group Reconcile - Weekly Joanna Review | [../packages/docker_infrastructure.yaml](../packages/docker_infrastructure.yaml) |
| `tugtainer_dispatch_joanna_for_available_updates` | Tugtainer - Dispatch Joanna For Available Updates | [../packages/tugtainer_updates.yaml](../packages/tugtainer_updates.yaml) | | `tugtainer_dispatch_joanna_for_available_updates` | Tugtainer - Dispatch Joanna For Available Updates | [../packages/tugtainer_updates.yaml](../packages/tugtainer_updates.yaml) |

@ -0,0 +1,156 @@
#!/usr/bin/env python3
"""Collect MariaDB telemetry snapshots for Home Assistant command_line sensors."""
from __future__ import annotations
import json
import re
import sys
from decimal import Decimal
from pathlib import Path
from typing import Any
from sqlalchemy import create_engine, text
SECRETS_PATH = Path("/config/secrets.yaml")
RECORDER_DB_URL_KEY = "recorder_db_url"
QUERIES = {
"live": """
SELECT
'running' AS status,
ROUND(
MAX(
CASE
WHEN VARIABLE_NAME = 'Queries' THEN CAST(VARIABLE_VALUE AS DECIMAL(20, 0))
END
) /
NULLIF(
MAX(
CASE
WHEN VARIABLE_NAME = 'Uptime' THEN CAST(VARIABLE_VALUE AS DECIMAL(20, 0))
END
),
0
),
0
) AS performance,
MAX(
CASE
WHEN VARIABLE_NAME = 'Threads_connected' THEN CAST(VARIABLE_VALUE AS UNSIGNED)
END
) AS connections,
MAX(
CASE
WHEN VARIABLE_NAME = 'Questions' THEN CAST(VARIABLE_VALUE AS UNSIGNED)
END
) AS questions,
MAX(
CASE
WHEN VARIABLE_NAME = 'Uptime' THEN CAST(VARIABLE_VALUE AS UNSIGNED)
END
) AS uptime_seconds
FROM information_schema.GLOBAL_STATUS
WHERE VARIABLE_NAME IN ('Queries', 'Questions', 'Threads_connected', 'Uptime');
""",
"recorder": """
WITH state_stats AS (
SELECT
MIN(last_updated_ts) AS min_last_updated_ts,
COUNT(*) AS total_records
FROM states
)
SELECT
ROUND(SUM(t.data_length + t.index_length) / 1024 / 1024, 2) AS database_size_mib,
COUNT(*) AS database_tables_count,
DATE_FORMAT(
FROM_UNIXTIME(ss.min_last_updated_ts),
'%Y-%m-%d'
) AS database_oldest_record,
ss.total_records AS database_total_records,
ROUND(
ss.total_records /
GREATEST(DATEDIFF(NOW(), FROM_UNIXTIME(ss.min_last_updated_ts)), 1),
0
) AS database_records_per_day
FROM information_schema.tables t
CROSS JOIN state_stats ss
WHERE t.table_schema = 'homeassistant';
""",
"admin": """
SELECT
@@version AS version,
ROUND(@@innodb_buffer_pool_size / 1024 / 1024 / 1024, 1) AS buffer_pool_gib,
@@max_connections AS max_connections,
ROUND(@@innodb_log_file_size / 1024 / 1024, 0) AS log_file_size_mib,
ROUND(@@tmp_table_size / 1024 / 1024, 0) AS tmp_table_size_mib,
@@innodb_io_capacity AS io_capacity,
@@innodb_read_io_threads AS io_threads_read,
@@innodb_write_io_threads AS io_threads_write,
@@table_open_cache AS table_cache,
ROUND(@@sort_buffer_size / 1024 / 1024, 0) AS sort_buffer_mib,
ROUND(@@read_buffer_size / 1024 / 1024, 0) AS read_buffer_mib,
ROUND(@@join_buffer_size / 1024 / 1024, 0) AS join_buffer_mib;
""",
}
def _load_db_url() -> str:
"""Read recorder_db_url from Home Assistant secrets.yaml."""
secrets_text = SECRETS_PATH.read_text(encoding="utf-8")
match = re.search(
rf"^{re.escape(RECORDER_DB_URL_KEY)}:\s*[\"']?(.*?)[\"']?\s*$",
secrets_text,
re.MULTILINE,
)
if match is None:
raise RuntimeError(f"Missing {RECORDER_DB_URL_KEY} in {SECRETS_PATH}")
return match.group(1)
def _json_safe(value: Any) -> Any:
"""Convert SQLAlchemy result values into JSON-serializable values."""
if isinstance(value, Decimal):
return float(value)
return value
def main() -> int:
"""Run the requested query mode and emit a compact JSON payload."""
mode = sys.argv[1].strip().lower() if len(sys.argv) > 1 else ""
if len(sys.argv) != 2 or mode not in QUERIES:
print(
json.dumps(
{
"error": "usage",
"message": "expected one mode: admin, live, recorder",
},
separators=(",", ":"),
),
file=sys.stderr,
)
return 2
engine = create_engine(_load_db_url(), pool_pre_ping=True)
try:
with engine.connect() as connection:
row = connection.execute(text(QUERIES[mode])).mappings().one()
except Exception as err: # pragma: no cover - runtime safety path
print(
json.dumps(
{"error": "query_failed", "message": str(err)},
separators=(",", ":"),
),
file=sys.stderr,
)
return 1
payload = {key: _json_safe(value) for key, value in row.items()}
print(json.dumps(payload, separators=(",", ":")))
return 0
if __name__ == "__main__":
raise SystemExit(main())
Loading…
Cancel
Save

Powered by TurnKey Linux.