Refine maintenance and infrastructure operations

master
Carlo Costanzo 4 weeks ago
parent 38415c15cf
commit f4cf8c7d0b

@ -32,7 +32,7 @@ Live view of the `config/` directory my production Home Assistant instance loads
### How the config is organized
- **Packages (`packages/`)** – complete subsystems that bundle sensors, switches, automations, scripts, and lovelace assets for a single feature (alarm, garage, water shutoff, etc.).
- **Container updates** – [![YAML source: tugtainer_updates](https://img.shields.io/static/v1?label=YAML&message=tugtainer_updates&color=lightgrey&logo=github&logoColor=181717)](packages/tugtainer_updates.yaml) logs Tugtainer reports into Home Assistant persistent notifications and dispatches controlled review work. [![Watch on YouTube](https://img.shields.io/badge/Watch-YouTube-FF0000?logo=youtube&logoColor=white)](https://youtu.be/4NNOkXzUyYw) [![vCloudInfo Blog Post](https://img.shields.io/static/v1?label=vCloudInfo&message=Blog%20Post&color=21759B&logo=wordpress&logoColor=white)](https://www.vcloudinfo.com/2026/07/home-assistant-tugtainer-docker-updates.html)
- **Container updates** – [![YAML source: tugtainer_updates](https://img.shields.io/static/v1?label=YAML&message=tugtainer_updates&color=lightgrey&logo=github&logoColor=181717)](packages/tugtainer_updates.yaml) processes Available reports without persistent alerts, keeps Updated/Failed outcomes visible, and dispatches controlled review work. [![Watch on YouTube](https://img.shields.io/badge/Watch-YouTube-FF0000?logo=youtube&logoColor=white)](https://youtu.be/4NNOkXzUyYw) [![vCloudInfo Blog Post](https://img.shields.io/static/v1?label=vCloudInfo&message=Blog%20Post&color=21759B&logo=wordpress&logoColor=white)](https://www.vcloudinfo.com/2026/07/home-assistant-tugtainer-docker-updates.html)
- **WAN monitoring** – [![YAML source: infrastructure](https://img.shields.io/static/v1?label=YAML&message=infrastructure&color=lightgrey&logo=github&logoColor=181717)](packages/infrastructure.yaml), [![YAML source: speedtest](https://img.shields.io/static/v1?label=YAML&message=speedtest&color=lightgrey&logo=github&logoColor=181717)](packages/speedtest.yaml), and the [WAN dashboard](dashboards/infrastructure/views/04_wan.yaml) separate multi-probe reachability, continuous latency/loss, Ookla capacity, and result freshness. [![Watch on YouTube](https://img.shields.io/badge/Watch-YouTube-FF0000?logo=youtube&logoColor=white)](https://youtu.be/F-PpsKkzcrM) [![vCloudInfo Blog Post](https://img.shields.io/static/v1?label=vCloudInfo&message=Blog%20Post&color=21759B&logo=wordpress&logoColor=white)](https://www.vcloudinfo.com/2026/08/home-assistant-internet-speed-monitoring-ookla.html)
- **Entity cleanup** – [![YAML source: docker_infrastructure](https://img.shields.io/static/v1?label=YAML&message=docker_infrastructure&color=lightgrey&logo=github&logoColor=181717)](packages/docker_infrastructure.yaml), [![YAML source: infrastructure](https://img.shields.io/static/v1?label=YAML&message=infrastructure&color=lightgrey&logo=github&logoColor=181717)](packages/infrastructure.yaml), [recorder](recorder.yaml), and [logbook](logbook.yaml) show how the Portainer and Glances audit kept proven history while removing stale registry noise. [![Watch on YouTube](https://img.shields.io/badge/Watch-YouTube-FF0000?logo=youtube&logoColor=white)](https://youtu.be/aceV7ObKJVA) [![vCloudInfo Blog Post](https://img.shields.io/static/v1?label=vCloudInfo&message=Blog%20Post&color=21759B&logo=wordpress&logoColor=white)](https://www.vcloudinfo.com/2026/07/using-codex-clean-up-home-assistant-entities.html)
- **Automations (`automation/`)** – event-driven YAML broken out by area or device; the legacy `automations.yaml` remains for UI-created flows.

@ -7,7 +7,7 @@
# Operational drill-downs for house utilities, appliances, and infrastructure.
# -------------------------------------------------------------------
# Notes: Stable dashboard key/path remains `dashboard-infrastructure`; the user-facing name is Systems.
# Notes: Home Water and Vacuum are promoted production views; Health remains on Overview.
# Notes: Water, HVAC, and Vacuum are promoted production views; Health remains on Overview.
# Notes: Joanna dispatch telemetry view is available at `/dashboard-infrastructure/joanna`.
# Notes: Network combines WAN, dual Pi-hole, and website health; `/wan`, `/pihole`, and `/website-health` are detail subviews.
######################################################################

@ -6,15 +6,15 @@
# Systems Partial - operations summary
# House-system health strip, active issues, and datacenter status.
# -------------------------------------------------------------------
# Related Issue: 1890
# Related Issue: 1890, 1817
# Notes: The health ribbon mirrors the visible dashboard navigation order and labels.
# Notes: DNS and external-monitoring faults roll up under Network instead of duplicating it.
# Notes: Network trend panels live on Network so Overview stays focused on broad house status.
# Notes: Power & Solar shows realtime flow; accumulated history remains in Energy.
# Notes: Scheduled Proxmox maintenance and sustained WAN quality/reset warnings render amber; critical conditions remain red.
# Notes: Scheduled Proxmox, Docker host, and HVAC maintenance plus sustained WAN quality/reset warnings render amber; critical conditions remain red.
# Notes: Active Issue rows enumerate faults, scheduled maintenance, and reset urgency so drill-down is optional for initial triage.
# Notes: BearClaw scheduled-job warnings/errors roll up under Joanna and appear as an exceptions-only Active Issue row.
# Notes: Home Water and Systems surface normalized Rheem faults; telemetry-only loss renders amber.
# Notes: Water and Systems surface normalized Rheem faults; telemetry-only loss renders amber.
# Notes: Back to Overview is the first control in the landing-view navigation strip.
######################################################################
@ -31,7 +31,7 @@
columns: full
layout_type: custom:grid-layout
layout:
grid-template-columns: repeat(10, minmax(0, 1fr))
grid-template-columns: repeat(6, minmax(0, 1fr))
grid-auto-flow: row
grid-auto-rows: min-content
grid-gap: 12px
@ -58,11 +58,19 @@
variables:
ok_state: '0'
healthy_text: ALL CLEAR
warning_text: WARNING
warning_text: >
[[[
return states['binary_sensor.hvac_maintenance_due']?.state === 'on'
|| states['binary_sensor.docker_host_maintenance_attention']?.state === 'on'
? 'MAINTENANCE'
: 'WARNING';
]]]
raw_suffix: ACTIVE
warning_entity_ids:
- binary_sensor.infra_wan_sustained_degradation
- binary_sensor.rheem_wh_telemetry_stale
- binary_sensor.docker_host_maintenance_attention
- binary_sensor.hvac_maintenance_due
critical_entity_ids:
- binary_sensor.infra_wan_critical
- binary_sensor.infra_dns_pihole_degraded
@ -75,7 +83,7 @@
- type: custom:button-card
template: bearstone_infra_status_kpi
entity: binary_sensor.home_water_problem
name: Home Water
name: Water
icon: mdi:shield-water-outline
variables:
ok_state: 'off'
@ -91,6 +99,21 @@
action: navigate
navigation_path: /dashboard-infrastructure/home-water
- type: custom:button-card
template: bearstone_infra_status_kpi
entity: binary_sensor.hvac_maintenance_due
name: HVAC
icon: mdi:hvac
variables:
ok_state: 'off'
healthy_text: CLEAR
warning_text: MAINTENANCE
warning_entity_ids:
- binary_sensor.hvac_maintenance_due
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/hvac
- type: custom:button-card
template: bearstone_infra_status_kpi
entity: sensor.l10s_vacuum_error
@ -125,6 +148,11 @@
icon: mdi:docker
variables:
ok_state: 'off'
warning_text: MAINTENANCE
warning_entity_ids:
- binary_sensor.docker_host_maintenance_attention
critical_entity_ids:
- binary_sensor.docker_container_telemetry_degraded
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/docker
@ -309,7 +337,70 @@
state_display: '[[[ return "LEAK"; ]]]'
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/home-water
navigation_path: /dashboard-infrastructure/hvac
- type: custom:button-card
template: bearstone_infra_alert_row
entity: sensor.downstairs_ac_runtime_since_last_filter_change
name: Downstairs HVAC filter maintenance due
icon: mdi:air-filter
variables:
alert_kind: numeric_high
threshold: 550
warning_only: true
exception_detail: >
[[[
const value = Number(entity?.state);
return Number.isFinite(value)
? `RUNTIME ${value.toFixed(0)} / 550 H · FILTER CHANGE DUE`
: 'FILTER RUNTIME TELEMETRY UNAVAILABLE';
]]]
state_display: MAINTENANCE
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/hvac
- type: custom:button-card
template: bearstone_infra_alert_row
entity: sensor.upstairs_ac_runtime_since_last_filter_change
name: Upstairs HVAC filter maintenance due
icon: mdi:air-filter
variables:
alert_kind: numeric_high
threshold: 450
warning_only: true
exception_detail: >
[[[
const value = Number(entity?.state);
return Number.isFinite(value)
? `RUNTIME ${value.toFixed(0)} / 450 H · FILTER CHANGE DUE`
: 'FILTER RUNTIME TELEMETRY UNAVAILABLE';
]]]
state_display: MAINTENANCE
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/hvac
- type: custom:button-card
template: bearstone_infra_alert_row
entity: sensor.hvac_condenser_lines_days_since_cleaned
name: HVAC condenser line maintenance due
icon: mdi:pipe-wrench
variables:
alert_kind: numeric_high
threshold: 360
warning_only: true
exception_detail: >
[[[
const value = Number(entity?.state);
return Number.isFinite(value)
? `ELAPSED ${value.toFixed(0)} / 360 DAYS · LINE CLEANING DUE`
: 'CONDENSER MAINTENANCE TELEMETRY UNAVAILABLE';
]]]
state_display: MAINTENANCE
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/hvac
- type: custom:button-card
template: bearstone_infra_alert_row
@ -441,6 +532,57 @@
action: navigate
navigation_path: /dashboard-infrastructure/proxmox
- type: custom:button-card
template: bearstone_infra_alert_row
entity: binary_sensor.docker_host_maintenance_attention
name: Docker host maintenance needs attention
icon: mdi:package-up
variables:
alert_kind: binary_on
warning_only: true
exception_detail: >
[[[
const hosts = [
['docker_10', 'sensor.docker_10_apt_status'],
['docker_13', 'sensor.docker_13_apt_status'],
['docker_14', 'sensor.docker_14_apt_status'],
['docker_17', 'sensor.docker_17_apt_status'],
['docker_69', 'sensor.docker_69_apt_status'],
];
const needsAttention = (raw) => {
const status = String(raw ?? '').toUpperCase();
return ['UNKNOWN', 'UNAVAILABLE', 'NONE', ''].includes(status)
|| status.includes('PENDING')
|| status.includes('REBOOT REQ')
|| status.startsWith('ERROR');
};
return hosts
.filter(([, id]) => needsAttention(states[id]?.state))
.map(([host, id]) => `${host} ${String(states[id]?.state ?? 'unknown').toUpperCase()}`)
.join(' · ');
]]]
state_display: >
[[[
const ids = [
'sensor.docker_10_apt_status',
'sensor.docker_13_apt_status',
'sensor.docker_14_apt_status',
'sensor.docker_17_apt_status',
'sensor.docker_69_apt_status',
];
const count = ids.filter((id) => {
const status = String(states[id]?.state ?? '').toUpperCase();
return ['UNKNOWN', 'UNAVAILABLE', 'NONE', ''].includes(status)
|| status.includes('PENDING')
|| status.includes('REBOOT REQ')
|| status.startsWith('ERROR');
}).length;
return `${count} HOST${count === 1 ? '' : 'S'}`;
]]]
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/docker
- type: custom:button-card
template: bearstone_infra_alert_row
entity: binary_sensor.docker_container_telemetry_degraded

@ -3,17 +3,17 @@
# For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Systems Partial - Home Water
# Protection, live use, hot water, maintenance, outdoor water, and trends.
# Systems Partial - Water
# Protection, live use, hot water, salt, outdoor water, and trends.
# -------------------------------------------------------------------
# Related Issue: 1890
# Related Issue: 1890, 1817
# Notes: NAS, NVR, and unrelated house-temperature cards are omitted; refrigerator temperature stays with home safety.
# Notes: Maintenance rows reuse existing reset scripts through safe more-info flows.
# Notes: HVAC maintenance has moved to the dedicated production HVAC view.
# Notes: Active Phyn leak alerts are shown; the integration's stale latest-test leak flag is not an operator alert.
# Notes: Rheem alert, health, and telemetry faults render as an exception row backed by durable Repairs.
# Notes: Daily usage uses change on the same sum-only statistic configured in Energy.
# Notes: The overview ribbon prioritizes the next maintenance action and frequent controls without hidden grid placeholders.
# Notes: Refrigerator temperature remains a temporary Home Maintenance overview item until a better permanent view is chosen.
# Notes: The overview ribbon keeps water-softener salt maintenance visible and amber when due.
# Notes: Refrigerator temperature remains a temporary Home Safety overview item until a better permanent view is chosen.
######################################################################
# -------------------------------------------------------------------
@ -39,70 +39,25 @@
grid-template-columns: repeat(2, minmax(0, 1fr))
cards:
- type: custom:button-card
template: bearstone_infra_kpi
entity: sensor.downstairs_ac_runtime_since_last_filter_change
name: Next Maintenance
icon: mdi:calendar-clock
template: bearstone_infra_status_kpi
entity: sensor.water_softener_salt_days_since_last_add
name: Salt Maintenance
icon: mdi:shaker-outline
variables:
next_maintenance: >
[[[
const candidates = [];
const addCandidate = (entityId, label, current, limit, unit) => {
if (!Number.isFinite(current) || !Number.isFinite(limit) || limit <= 0) return;
const remaining = Math.ceil(limit - current);
candidates.push({
entity: entityId,
progress: current / limit,
text: remaining <= 0 ? `DUE · ${label}` : `${label} · ${remaining}${unit}`,
});
};
addCandidate(
'sensor.downstairs_ac_runtime_since_last_filter_change',
'DOWNSTAIRS',
Number(states['sensor.downstairs_ac_runtime_since_last_filter_change']?.state),
350,
'H',
);
addCandidate(
'sensor.upstairs_ac_runtime_since_last_filter_change',
'UPSTAIRS',
Number(states['sensor.upstairs_ac_runtime_since_last_filter_change']?.state),
450,
'H',
);
addCandidate(
'sensor.hvac_condenser_lines_days_since_cleaned',
'CONDENSER',
Number(states['sensor.hvac_condenser_lines_days_since_cleaned']?.state),
360,
'D',
);
addCandidate(
'sensor.water_softener_salt_days_since_last_add',
'SALT',
Number(states['sensor.water_softener_salt_days_since_last_add']?.state),
Number(states['sensor.water_softener_salt_average_days_between_refills']?.state),
'D',
);
candidates.sort((a, b) => b.progress - a.progress);
return candidates[0] ?? { entity: entity.entity_id, text: 'NO DATA' };
]]]
default_severity: healthy
numeric_warning_checks:
- entity_id: sensor.water_softener_salt_days_since_last_add
threshold_entity_id: sensor.water_softener_salt_average_days_between_refills
state_display: >
[[[
const text = variables?.next_maintenance?.text ?? 'NO DATA';
return text
.replace('DOWNSTAIRS', 'DOWN')
.replace('UPSTAIRS', 'UP')
.replace('CONDENSER', 'COND');
const current = Number(entity?.state);
const limit = Number(states['sensor.water_softener_salt_average_days_between_refills']?.state);
if (!Number.isFinite(current) || !Number.isFinite(limit) || limit <= 0) return 'NO DATA';
const remaining = Math.ceil(limit - current);
return remaining <= 0 ? 'DUE · SALT' : `${remaining}D LEFT`;
]]]
tap_action:
action: more-info
entity: >
[[[
return variables?.next_maintenance?.entity ?? entity.entity_id;
]]]
- type: custom:button-card
template: bearstone_infra_status_kpi
@ -175,7 +130,7 @@
url_path: https://www.readyrefresh.com/en/login
# -------------------------------------------------------------------
# Home maintenance and outdoor water
# Home safety and outdoor water
# -------------------------------------------------------------------
- type: grid
column_span: 4
@ -202,7 +157,7 @@
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Home Maintenance
name: Home Safety
- type: custom:button-card
template: bearstone_infra_list_row
@ -222,60 +177,6 @@
- color: rgba(198,40,40,1)
- opacity: 1
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.downstairs_ac_runtime_since_last_filter_change
name: Downstairs Filter Runtime
icon: mdi:air-filter
state_display: >
[[[
const value = Number(entity?.state);
return Number.isFinite(value) ? `${value.toFixed(0)} / 350 h` : 'NO DATA';
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: input_datetime.downstairs_last_filter_change
name: Downstairs Filter Changed
icon: mdi:calendar-check-outline
tap_action:
action: more-info
entity: script.reset_downstairs_filter
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.upstairs_ac_runtime_since_last_filter_change
name: Upstairs Filter Runtime
icon: mdi:air-filter
state_display: >
[[[
const value = Number(entity?.state);
return Number.isFinite(value) ? `${value.toFixed(0)} / 450 h` : 'NO DATA';
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: input_datetime.upstairs_last_filter_change
name: Upstairs Filter Changed
icon: mdi:calendar-check-outline
tap_action:
action: more-info
entity: script.reset_upstairs_filter
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.hvac_condenser_lines_days_since_cleaned
name: Condenser Lines
icon: mdi:pipe-wrench
state_display: >
[[[
const value = Number(entity?.state);
return Number.isFinite(value) ? `${value.toFixed(0)} / 360 DAYS` : 'NO DATA';
]]]
tap_action:
action: more-info
entity: script.reset_hvac_condenser_lines_cleaned
- type: custom:vertical-stack-in-card
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml

@ -0,0 +1,262 @@
######################################################################
# @CCOSTAN - Follow Me on X
# For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Systems HVAC Partial - Maintenance overview and details
# Production HVAC maintenance, completion history, and reset access.
# -------------------------------------------------------------------
# Related Issue: 1817
# Notes: Downstairs filter maintenance is due at 550 runtime hours; upstairs remains 450 hours.
# Notes: Reset actions reuse the existing HVAC maintenance scripts; no staging helpers are introduced.
######################################################################
- type: grid
column_span: 4
columns: 1
square: false
cards:
- type: custom:layout-card
grid_options:
columns: full
layout_type: custom:grid-layout
layout:
grid-template-columns: repeat(4, minmax(0, 1fr))
grid-auto-flow: row
grid-auto-rows: min-content
grid-gap: 12px
margin: 0
mediaquery:
"(max-width: 900px)":
grid-template-columns: repeat(2, minmax(0, 1fr))
cards:
- type: custom:button-card
template: bearstone_nav_back
name: Back to Systems
icon: mdi:arrow-left
view_layout:
grid-column: 1 / -1
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/home
- type: custom:button-card
template: bearstone_infra_status_kpi
entity: sensor.downstairs_ac_runtime_since_last_filter_change
name: Next Maintenance
icon: mdi:calendar-clock
variables:
default_severity: healthy
numeric_warning_checks:
- entity_id: sensor.downstairs_ac_runtime_since_last_filter_change
threshold: 550
- entity_id: sensor.upstairs_ac_runtime_since_last_filter_change
threshold: 450
- entity_id: sensor.hvac_condenser_lines_days_since_cleaned
threshold: 360
next_maintenance: >
[[[
const candidates = [];
const addCandidate = (entityId, label, current, limit, unit) => {
if (!Number.isFinite(current) || !Number.isFinite(limit) || limit <= 0) return;
const remaining = Math.ceil(limit - current);
candidates.push({
entity: entityId,
progress: current / limit,
text: remaining <= 0 ? `DUE · ${label}` : `${label} · ${remaining}${unit}`,
});
};
addCandidate(
'sensor.downstairs_ac_runtime_since_last_filter_change',
'DOWNSTAIRS',
Number(states['sensor.downstairs_ac_runtime_since_last_filter_change']?.state),
550,
'H',
);
addCandidate(
'sensor.upstairs_ac_runtime_since_last_filter_change',
'UPSTAIRS',
Number(states['sensor.upstairs_ac_runtime_since_last_filter_change']?.state),
450,
'H',
);
addCandidate(
'sensor.hvac_condenser_lines_days_since_cleaned',
'CONDENSER',
Number(states['sensor.hvac_condenser_lines_days_since_cleaned']?.state),
360,
'D',
);
candidates.sort((a, b) => b.progress - a.progress);
return candidates[0] ?? { entity: entity.entity_id, text: 'NO DATA' };
]]]
state_display: >
[[[
return String(variables?.next_maintenance?.text ?? 'NO DATA')
.replace('DOWNSTAIRS', 'DOWN')
.replace('UPSTAIRS', 'UP')
.replace('CONDENSER', 'COND');
]]]
tap_action:
action: more-info
entity: >
[[[
return variables?.next_maintenance?.entity ?? entity.entity_id;
]]]
- type: custom:button-card
template: bearstone_infra_status_kpi
entity: sensor.downstairs_ac_runtime_since_last_filter_change
name: Downstairs Filter
icon: mdi:air-filter
variables:
ok_state: never
default_severity: healthy
numeric_warning_checks:
- entity_id: sensor.downstairs_ac_runtime_since_last_filter_change
threshold: 550
warning_text: DUE
state_display: >
[[[
const value = Number(entity?.state);
return Number.isFinite(value) ? `${value.toFixed(0)} / 550 H` : 'NO DATA';
]]]
- type: custom:button-card
template: bearstone_infra_status_kpi
entity: sensor.upstairs_ac_runtime_since_last_filter_change
name: Upstairs Filter
icon: mdi:air-filter
variables:
ok_state: never
default_severity: healthy
numeric_warning_checks:
- entity_id: sensor.upstairs_ac_runtime_since_last_filter_change
threshold: 450
warning_text: DUE
state_display: >
[[[
const value = Number(entity?.state);
return Number.isFinite(value) ? `${value.toFixed(0)} / 450 H` : 'NO DATA';
]]]
- type: custom:button-card
template: bearstone_infra_status_kpi
entity: sensor.hvac_condenser_lines_days_since_cleaned
name: Condenser Lines
icon: mdi:pipe-wrench
variables:
ok_state: never
default_severity: healthy
numeric_warning_checks:
- entity_id: sensor.hvac_condenser_lines_days_since_cleaned
threshold: 360
warning_text: DUE
state_display: >
[[[
const value = Number(entity?.state);
return Number.isFinite(value) ? `${value.toFixed(0)} / 360 D` : 'NO DATA';
]]]
- type: grid
column_span: 4
columns: 1
square: false
cards:
- type: custom:layout-card
grid_options:
columns: full
layout_type: custom:grid-layout
layout:
grid-template-columns: repeat(2, minmax(0, 1fr))
grid-auto-flow: row
grid-auto-rows: min-content
grid-gap: 12px
margin: 0
mediaquery:
"(max-width: 900px)":
grid-template-columns: repeat(1, minmax(0, 1fr))
cards:
- type: custom:vertical-stack-in-card
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Filter Maintenance
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.downstairs_ac_runtime_since_last_filter_change
name: Downstairs Filter Runtime
icon: mdi:air-filter
variables:
numeric_warning_threshold: 550
state_display: >
[[[
const value = Number(entity?.state);
return Number.isFinite(value) ? `${value.toFixed(0)} / 550 h` : 'NO DATA';
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: input_datetime.downstairs_last_filter_change
name: Downstairs Filter Changed
icon: mdi:calendar-check-outline
tap_action:
action: more-info
entity: script.reset_downstairs_filter
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.upstairs_ac_runtime_since_last_filter_change
name: Upstairs Filter Runtime
icon: mdi:air-filter
variables:
numeric_warning_threshold: 450
state_display: >
[[[
const value = Number(entity?.state);
return Number.isFinite(value) ? `${value.toFixed(0)} / 450 h` : 'NO DATA';
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: input_datetime.upstairs_last_filter_change
name: Upstairs Filter Changed
icon: mdi:calendar-check-outline
tap_action:
action: more-info
entity: script.reset_upstairs_filter
- type: custom:vertical-stack-in-card
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Outdoor Condenser
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.hvac_condenser_lines_days_since_cleaned
name: Days Since Line Cleaning
icon: mdi:pipe-wrench
variables:
numeric_warning_threshold: 360
state_display: >
[[[
const value = Number(entity?.state);
return Number.isFinite(value) ? `${value.toFixed(0)} / 360 DAYS` : 'NO DATA';
]]]
tap_action:
action: more-info
entity: script.reset_hvac_condenser_lines_cleaned
- type: custom:button-card
template: bearstone_infra_list_row
entity: input_datetime.hvac_condenser_lines_last_cleaned
name: Last Line Cleaning
icon: mdi:calendar-check-outline
tap_action:
action: more-info
entity: script.reset_hvac_condenser_lines_cleaned

@ -11,6 +11,7 @@
# Notes: The compact ribbon template supports Overview status and daily controls.
# Notes: Mobile templates extend the ribbon with larger accessible tap targets and text.
# Notes: Named cards keep the icon beside the name; icons never consume a decorative row.
# Notes: Status KPIs support optional numeric warning checks for threshold-based summaries.
######################################################################
bearstone_infra_base:
@ -694,6 +695,20 @@ bearstone_infra_list_row:
show_name: true
show_state: true
show_label: false
variables:
numeric_warning_threshold: ''
numeric_warning_active: >
[[[
const thresholdRaw = variables?.numeric_warning_threshold;
const threshold = Number(thresholdRaw);
const value = Number(entity?.state);
return thresholdRaw !== undefined
&& thresholdRaw !== null
&& String(thresholdRaw).trim() !== ''
&& Number.isFinite(threshold)
&& Number.isFinite(value)
&& value >= threshold;
]]]
tap_action:
action: more-info
styles:
@ -701,8 +716,18 @@ bearstone_infra_list_row:
- border-radius: 14px
- padding: 12px 12px
- box-shadow: none
- border: 1px solid rgba(0,0,0,0.03)
- background: rgba(0,0,0,0.02)
- border: >
[[[
return variables?.numeric_warning_active
? '1px solid rgba(245,124,0,0.35)'
: '1px solid rgba(0,0,0,0.03)';
]]]
- background: >
[[[
return variables?.numeric_warning_active
? 'rgba(255,243,224,0.75)'
: 'rgba(0,0,0,0.02)';
]]]
- box-sizing: border-box
grid:
- grid-template-areas: "\"i n s\""
@ -711,7 +736,12 @@ bearstone_infra_list_row:
- column-gap: 10px
icon:
- width: 18px
- color: var(--primary-color)
- color: >
[[[
return variables?.numeric_warning_active
? 'rgba(230,81,0,1)'
: 'var(--primary-color)';
]]]
name:
- font-weight: 700
- font-size: 14px
@ -725,7 +755,14 @@ bearstone_infra_list_row:
- justify-self: end
- font-weight: 700
- font-size: 13px
- opacity: 0.8
- color: >
[[[
return variables?.numeric_warning_active
? 'rgba(230,81,0,1)'
: 'var(--primary-text-color)';
]]]
- opacity: >
[[[ return variables?.numeric_warning_active ? 1 : 0.8; ]]]
- padding-left: 8px
- white-space: nowrap
@ -935,10 +972,23 @@ bearstone_infra_status_kpi:
const anyOn = (ids) => ids.some((id) => String(states[id]?.state ?? '').toLowerCase() === 'on');
const critical = anyOn(normalizeIds(variables?.critical_entity_ids));
const warning = anyOn(normalizeIds(variables?.warning_entity_ids));
const numericWarning = (Array.isArray(variables?.numeric_warning_checks)
? variables.numeric_warning_checks
: []).some((check) => {
const entityId = String(check?.entity_id ?? '').trim();
const thresholdEntityId = String(check?.threshold_entity_id ?? '').trim();
const value = Number(states[entityId]?.state);
const threshold = thresholdEntityId
? Number(states[thresholdEntityId]?.state)
: Number(check?.threshold);
return entityId && Number.isFinite(value) && Number.isFinite(threshold) && value >= threshold;
});
const warningState = String(variables?.warning_state ?? '').toLowerCase();
const okState = String(variables?.ok_state ?? 'off').toLowerCase();
if (critical) return 'critical';
if (warning || (warningState && raw === warningState)) return 'warning';
if (warning || numericWarning || (warningState && raw === warningState)) return 'warning';
const defaultSeverity = String(variables?.default_severity ?? '').toLowerCase();
if (['healthy', 'warning', 'critical'].includes(defaultSeverity)) return defaultSeverity;
return raw === okState ? 'healthy' : 'critical';
]]]
state_display: >

@ -3,13 +3,13 @@
# For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Systems View - Home Water
# Systems View - Water
# Leak protection, current use, hot water, maintenance, and trends.
# -------------------------------------------------------------------
# Notes: Promoted from the approved Home Water pilot.
# Notes: Promoted from the approved Water pilot.
######################################################################
title: Home Water
title: Water
path: home-water
type: sections
icon: mdi:home-flood

@ -0,0 +1,22 @@
######################################################################
# @CCOSTAN - Follow Me on X
# For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Systems HVAC View - Filter and condenser maintenance
# Runtime, completion dates, and safe reset-script access.
# -------------------------------------------------------------------
# Related Issue: 1817
# Notes: Production view at `/dashboard-infrastructure/hvac`; promoted after live staging review.
######################################################################
title: HVAC
path: hvac
type: sections
icon: mdi:hvac
theme: default
max_columns: 4
dense_section_placement: true
badges: []
sections: !include /config/dashboards/infrastructure/partials/hvac_sections.yaml
cards: []

@ -4,7 +4,7 @@
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Overview Section - priority ribbon and daily controls
# Related Issue: 1843
# Related Issue: 1843, 1817
# Systems first, then frequent entry points, climate, and vacuum access.
# -------------------------------------------------------------------
# Notes: Equal-width pills keep the whole ribbon compact and consistent.
@ -47,6 +47,9 @@ cards:
raw_suffix: ACTIVE
warning_entity_ids:
- binary_sensor.infra_wan_sustained_degradation
- binary_sensor.rheem_wh_telemetry_stale
- binary_sensor.docker_host_maintenance_attention
- binary_sensor.hvac_maintenance_due
critical_entity_ids:
- binary_sensor.infra_wan_critical
- binary_sensor.infra_dns_pihole_degraded

@ -201,6 +201,9 @@ sections:
raw_suffix: ACTIVE
warning_entity_ids:
- binary_sensor.infra_wan_sustained_degradation
- binary_sensor.rheem_wh_telemetry_stale
- binary_sensor.docker_host_maintenance_attention
- binary_sensor.hvac_maintenance_due
critical_entity_ids:
- binary_sensor.infra_wan_critical
- binary_sensor.infra_dns_pihole_degraded

@ -80,6 +80,7 @@ exclude:
- sensor.docker_*_disk_used_percentage
- input_text.docker_*_disk_pressure_band
- switch.*_container
- switch.*_container_*
- "*alarm_panel_1*"
- "*alarm_panel_2*"
entities:
@ -106,203 +107,17 @@ exclude:
- 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
- input_datetime.docker_container_alerts_snooze_until
- sensor.docker_monitored_container_count
- sensor.docker_monitored_switch_inventory
- sensor.docker_monitored_unavailable_count
- switch.docker_monitored_containers
- binary_sensor.carlo_hass_status
- binary_sensor.cloudflared_kch_status
- binary_sensor.cloudflared_kch_status_2
- binary_sensor.cloudflared_wp_status
- binary_sensor.cloudflared_wp_status_2
- binary_sensor.codex_appliance_status
- binary_sensor.codex_appliance_status_2
- binary_sensor.codex_appliance_status_3
- binary_sensor.codex_update_helper_d17_status
- binary_sensor.college_budget_app_status
- binary_sensor.college_budget_app_status_2
- binary_sensor.cruise_tracker_status
- binary_sensor.cruise_tracker_status_2
- binary_sensor.crystalsoftwashsolutions_status
- binary_sensor.dashy_status
- binary_sensor.dashy_status_2
- binary_sensor.docker_files_codex_appliance_run_6404b6f209c6_status
- binary_sensor.docker_files_codex_appliance_run_9c89dd69eada_status
- binary_sensor.docker_files_codex_appliance_run_f05ee34fde70_status
- binary_sensor.docker_socket_proxy_status
- binary_sensor.docker_socket_proxy_status_2
- binary_sensor.docker17_status
- binary_sensor.docker2_status
- binary_sensor.docker69_status
- binary_sensor.duplicati_status
- binary_sensor.duplicati_status_2
- binary_sensor.esphome_status
- binary_sensor.esphome_status_2
- binary_sensor.foodie_tracker_status
- binary_sensor.foodie_tracker_status_2
- binary_sensor.frigate_status
- binary_sensor.frigate_status_2
- binary_sensor.games_hub_status
- binary_sensor.games_hub_status_2
- binary_sensor.home_assistant_status
- binary_sensor.home_assistant_status_2
- binary_sensor.imposter_status
- binary_sensor.imposter_status_2
- binary_sensor.kingcrafthomes_status
- binary_sensor.kingcrafthomes_status_2
- binary_sensor.lmediaservices_status
- binary_sensor.lmediaservices_status_2
- binary_sensor.mariadb_backup_status
- binary_sensor.mariadb_backup_status_2
- binary_sensor.mariadb_status
- binary_sensor.mariadb_status_2
- binary_sensor.matter_server_status
- binary_sensor.matter_server_status_2
- binary_sensor.mqtt_status
- binary_sensor.mqtt_status_2
- binary_sensor.nebula_sync_status
# Current retained Portainer telemetry is live-only; aggregate Docker history remains recorded.
- binary_sensor.nebula_sync_status_2
- binary_sensor.onenote_indexer_status
- binary_sensor.onenote_indexer_status_2
- binary_sensor.paiges_bookshelf_status
- binary_sensor.panel_notes_status
- binary_sensor.panel_notes_status_2
- binary_sensor.pihole_secondary_status
- binary_sensor.pihole_secondary_status_2
- binary_sensor.pihole_status
- binary_sensor.pihole_status_2
- binary_sensor.poker_tracker_status
- binary_sensor.poker_tracker_status_2
- binary_sensor.portainer_agent_status
- binary_sensor.portainer_agent_status_2
- binary_sensor.portainer_status
- binary_sensor.portainer_status_2
- binary_sensor.postgres_webhooks_backup_status
- binary_sensor.postgres_webhooks_engine_status
- binary_sensor.postgres_webhooks_engine_status_2
- binary_sensor.rc_price_checker_status
- binary_sensor.rc_price_checker_status_2
- binary_sensor.redis_webhooks_engine_status
- binary_sensor.redis_webhooks_engine_status_2
- binary_sensor.rvtools_ppt_web_status
- binary_sensor.rvtools_ppt_web_status_2
- binary_sensor.teslamate_backup_status
- binary_sensor.teslamate_database_status
- binary_sensor.teslamate_grafana_status
- binary_sensor.teslamate_status
- binary_sensor.tugtainer_agent_status
- binary_sensor.tugtainer_agent_status_2
- binary_sensor.tugtainer_socket_proxy_status
- binary_sensor.tugtainer_socket_proxy_status_2
- binary_sensor.tugtainer_status
- binary_sensor.tugtainer_status_2
- binary_sensor.unifi_status
- binary_sensor.unifi_status_2
- binary_sensor.webhooks_engine_status
- binary_sensor.webhooks_engine_status_2
- binary_sensor.wordpress_db_backup_status
- binary_sensor.wordpress_db_status
- binary_sensor.wordpress_db_status_2
- binary_sensor.wordpress_wp_status
- binary_sensor.wordpress_wp_status_2
- binary_sensor.wyze_bridge_status
- binary_sensor.wyze_bridge_status_2
- sensor.cloudflared_kch_state
- sensor.cloudflared_kch_state_2
- sensor.cloudflared_wp_state
- sensor.cloudflared_wp_state_2
- sensor.codex_appliance_state
- sensor.codex_appliance_state_2
- sensor.codex_update_helper_d17_state
- sensor.codex_update_helper_d69_state
- sensor.college_budget_app_state
- sensor.college_budget_app_state_2
- sensor.cruise_tracker_state
- sensor.cruise_tracker_state_2
- sensor.crystalsoftwashsolutions_state
- sensor.dashy_state
- sensor.dashy_state_2
- sensor.docker_files_codex_appliance_run_6404b6f209c6_state
- sensor.docker_files_codex_appliance_run_9c89dd69eada_state
- sensor.docker_files_codex_appliance_run_f05ee34fde70_state
- sensor.docker_socket_proxy_state
- sensor.docker_socket_proxy_state_2
- sensor.duplicati_state
- sensor.duplicati_state_2
- sensor.esphome_state
- sensor.esphome_state_2
- sensor.foodie_tracker_state
- sensor.foodie_tracker_state_2
- sensor.frigate_state
- sensor.frigate_state_2
- sensor.games_hub_state
- sensor.games_hub_state_2
- sensor.home_assistant_state
- sensor.home_assistant_state_2
- sensor.imposter_state
- sensor.imposter_state_2
- sensor.kingcrafthomes_state
- sensor.kingcrafthomes_state_2
- sensor.lmediaservices_state
- sensor.lmediaservices_state_2
- sensor.mariadb_backup_state
- sensor.mariadb_backup_state_2
- sensor.mariadb_state
- sensor.mariadb_state_2
- sensor.matter_server_state
- sensor.matter_server_state_2
- sensor.mqtt_state
- sensor.mqtt_state_2
- sensor.nebula_sync_state
- sensor.nebula_sync_state_2
- sensor.onenote_indexer_state
- sensor.onenote_indexer_state_2
- sensor.paiges_bookshelf_state
- sensor.panel_notes_state
- sensor.panel_notes_state_2
- sensor.pihole_secondary_state
- sensor.pihole_secondary_state_2
- sensor.pihole_state
- sensor.pihole_state_2
- sensor.poker_tracker_state
- sensor.poker_tracker_state_2
- sensor.portainer_agent_state
- sensor.portainer_agent_state_2
- sensor.portainer_state
- sensor.portainer_state_2
- sensor.postgres_webhooks_backup_state
- sensor.postgres_webhooks_engine_state
- sensor.postgres_webhooks_engine_state_2
- sensor.rc_price_checker_state
- sensor.rc_price_checker_state_2
- sensor.redis_webhooks_engine_state
- sensor.redis_webhooks_engine_state_2
- sensor.rvtools_ppt_web_state
- sensor.rvtools_ppt_web_state_2
- sensor.teslamate_backup_state
- sensor.teslamate_database_state
- sensor.teslamate_grafana_state
- sensor.teslamate_state
- sensor.tugtainer_agent_state
- sensor.tugtainer_agent_state_2
- sensor.tugtainer_socket_proxy_state
- sensor.tugtainer_socket_proxy_state_2
- sensor.tugtainer_state
- sensor.tugtainer_state_2
- sensor.unifi_state
- sensor.unifi_state_2
- sensor.webhooks_engine_state
- sensor.webhooks_engine_state_2
- sensor.wordpress_db_backup_state
- sensor.wordpress_db_state
- sensor.wordpress_db_state_2
- sensor.wordpress_wp_state
- sensor.portainer_agent_cpu_usage_total_2
- sensor.portainer_agent_memory_usage_percentage_2
- sensor.portainer_cpu_usage_total_2
- sensor.portainer_memory_usage_percentage_2
- sensor.wordpress_wp_state_2
- sensor.wyze_bridge_state
- sensor.wyze_bridge_state_2
# END generated Portainer recorder/logbook noise exclusions
- binary_sensor.onenote_indexer_job_failed
- binary_sensor.onenote_indexer_index_healthy

@ -11,6 +11,7 @@
# Notes: adjusting the severity levels for each component
# Notes: Log Severities (in order of increasing severity):
# Notes: notset, debug, info, warn, warning, error, fatal, critical
# Notes: Portainer image-distribution checks are update-only noise; Tugtainer owns update reporting.
######################################################################
default: info
logs:
@ -56,6 +57,7 @@ logs:
homeassistant.loader: warn
homeassistant.components.websocket_api: error
pubnub: critical
pyportainer.watcher: error
py.warnings: error
pyhap.hap_server: warn
queueman: error

@ -45,7 +45,7 @@ Kitchen Show note: `kiosk_tablet.yaml` uses Kiosk Satellite's native ESPHome ent
| [![YAML source: alarm](https://img.shields.io/static/v1?label=YAML&message=alarm&color=lightgrey&logo=github&logoColor=181717)](alarm.yaml) | NodeMCU-powered perimeter monitoring with arm/disarm helpers and rich notifications. | `binary_sensor.mcu*_gpio*`, `group.family`, notify + siren scripts |
| [![YAML source: alexa_media_player](https://img.shields.io/static/v1?label=YAML&message=alexa_media_player&color=lightgrey&logo=github&logoColor=181717)](alexa_media_player.yaml) | Alexa Media helper sensors including stable bedroom wake-alarm wrappers for Carlo and Stacey plus a combined next-wake view. | `sensor.last_alexa`, `sensor.bedroom_next_wake_alarm`, `sensor.bedroom_next_wake_alarm_source`, `binary_sensor.bedroom_next_wake_alarm_active` |
| [![YAML source: fridge](https://img.shields.io/static/v1?label=YAML&message=fridge&color=lightgrey&logo=github&logoColor=181717)](fridge.yaml) | SmartThinQ fridge monitoring with 6-minute raw door-open alerts plus fridge/freezer status announcements. | `binary_sensor.refrigerator_door_open`, `script.notify_engine`, `script.speech_engine` |
| [![YAML source: climate](https://img.shields.io/static/v1?label=YAML&message=climate&color=lightgrey&logo=github&logoColor=181717)](climate.yaml) | Nest climate schedules plus 350-hour downstairs/450-hour upstairs/9-month AC filter reminders and 360-day condenser-line cleanout reminders with snooze and changed/cleaned actions. [![Watch on YouTube](https://img.shields.io/badge/Watch-YouTube-FF0000?logo=youtube&logoColor=white)](https://youtu.be/nScl2JxYSB8) | `input_datetime.*_filter_snooze_until`, `sensor.*_ac_runtime_since_last_filter_change`, `sensor.hvac_condenser_lines_days_since_cleaned`, `script.notify_engine_two_button`, mobile app action events |
| [![YAML source: climate](https://img.shields.io/static/v1?label=YAML&message=climate&color=lightgrey&logo=github&logoColor=181717)](climate.yaml) | Nest climate schedules plus 550-hour downstairs/450-hour upstairs/9-month AC filter reminders, native per-unit NFC tag change capture, per-unit change counts/cadence, and 360-day condenser-line cleanout reminders with snooze and changed/cleaned actions. [![Watch on YouTube](https://img.shields.io/badge/Watch-YouTube-FF0000?logo=youtube&logoColor=white)](https://youtu.be/nScl2JxYSB8) | `input_datetime.*_filter_snooze_until`, `sensor.*_ac_runtime_since_last_filter_change`, `sensor.*_ac_filter_days_since_last_change`, `sensor.*_ac_filter_change_count`, `sensor.hvac_condenser_lines_days_since_cleaned`, `script.notify_engine_two_button`, `mobile_app_notification_action`, `tag` |
| [![YAML source: garadget](https://img.shields.io/static/v1?label=YAML&message=garadget&color=lightgrey&logo=github&logoColor=181717)](garadget.yaml) | MQTT-based garage door control plus arrival helpers, entry prompts, nighttime reminders, and camera context. | `cover.large_garage_door`, `cover.small_garage_door`, `group.garage_doors`, `script.open_large_garage_door_if_ready` |
| [![YAML source: august](https://img.shields.io/static/v1?label=YAML&message=august&color=lightgrey&logo=github&logoColor=181717)](august.yaml) | Front-door August smart lock with Alexa Show camera pop-up when unlocked. | `lock.front_door`, media_player actions for front doorbell camera |
| [![YAML source: holiday](https://img.shields.io/static/v1?label=YAML&message=holiday&color=lightgrey&logo=github&logoColor=181717)](holiday.yaml) | REST-driven US holiday + flag sensors plus the inspectable exterior lighting mode. | `sensor.holiday`, `sensor.flag`, `sensor.holiday_lighting_mode`, `sensor.holiday_lighting_scene`, JSON feed at `config/www/json_data/holidays.json` |
@ -56,14 +56,13 @@ Kitchen Show note: `kiosk_tablet.yaml` uses Kiosk Satellite's native ESPHome ent
| [![YAML source: docker_infrastructure](https://img.shields.io/static/v1?label=YAML&message=docker_infrastructure&color=lightgrey&logo=github&logoColor=181717)](docker_infrastructure.yaml) | Docker host patching telemetry with persisted daily maintenance deadlines, container/stack Repairs automation, retained Portainer health telemetry, retired Portainer image-update controls, 20-minute Joanna escalation for persistent container outages including stuck `restarting`/`created` states, and weekly scheduled prune actions across docker_10/13/14/17/69. [![Watch on YouTube](https://img.shields.io/badge/Watch-YouTube-FF0000?logo=youtube&logoColor=white)](https://youtu.be/aceV7ObKJVA) [![vCloudInfo Blog Post](https://img.shields.io/static/v1?label=vCloudInfo&message=Blog%20Post&color=21759B&logo=wordpress&logoColor=white)](https://www.vcloudinfo.com/2026/07/using-codex-clean-up-home-assistant-entities.html) | `sensor.docker_*_apt_status`, `input_datetime.apt_docker_*_action_due`, `binary_sensor.portainer_status_2`, `sensor.portainer_cpu_usage_total_2`, `sensor.portainer_memory_usage_percentage_2`, `binary_sensor.*_stack_status`, `sensor.docker_stacks_down_count`, `repairs.create`, `repairs.remove`, `script.joanna_dispatch` |
| [![YAML source: proxmox](https://img.shields.io/static/v1?label=YAML&message=proxmox&color=lightgrey&logo=github&logoColor=181717)](proxmox.yaml) | Proxmox update detection with amber dashboard maintenance state, 02:15 Joanna patch orchestration, kernel-refresh handoff hints, sequential migration and capacity gates, plus runtime and disk pressure monitoring. Docker14 recovery startup is pre-authorized only after expected VM placement and memory headroom are verified. Update availability does not create Repairs. | `binary_sensor.node_proxmox*_updates_packages`, `sensor.node_proxmox*_total_updates`, `script.joanna_dispatch`, `binary_sensor.proxmox*_runtime_healthy`, `sensor.proxmox*_disk_used_percentage` |
| [![YAML source: synology_dsm](https://img.shields.io/static/v1?label=YAML&message=synology_dsm&color=lightgrey&logo=github&logoColor=181717)](synology_dsm.yaml) | Synology DSM integration health normalization for Carlo-NAS01 and Carlo-NVR, with outage-aware Joanna-first handling for lone post-outage volume warnings and Repairs escalation for persistent or non-outage problems. | `binary_sensor.carlo_*_synology_problem`, `sensor.carlo_*_synology_problem_summary`, `binary_sensor.powerwall_grid_status`, `repairs.create`, `script.joanna_dispatch` |
| [![YAML source: infrastructure](https://img.shields.io/static/v1?label=YAML&message=infrastructure&color=lightgrey&logo=github&logoColor=181717)](infrastructure.yaml) | Corroborated multi-probe WAN reachability, continuous latency/loss quality tiers, separate Ookla CLI capacity snapshots, normalized DNS/backup/domain/cert health, Nebula Sync and promoted IoT primary/backup Pi-hole consistency monitoring with Joanna dispatch, Glances-backed Docker host disk pressure with Joanna-only warning cleanup and critical Repairs, immediate website-down Repairs/Joanna dispatch plus uptime/latency SLO signals, nightly backup verification, and monthly Joanna HA log hygiene review with public-safe GitHub issue follow-up. [![Watch on YouTube](https://img.shields.io/badge/Watch-YouTube-FF0000?logo=youtube&logoColor=white)](https://youtu.be/F-PpsKkzcrM) [![vCloudInfo Blog Post](https://img.shields.io/static/v1?label=vCloudInfo&message=Blog%20Post&color=21759B&logo=wordpress&logoColor=white)](https://www.vcloudinfo.com/2026/08/home-assistant-internet-speed-monitoring-ookla.html) | `sensor.infra_wan_reachability`, `binary_sensor.infra_wan_capacity_degraded`, `binary_sensor.infra_wan_capacity_stale`, `binary_sensor.infra_wan_sustained_degradation`, `binary_sensor.infra_wan_severe_degradation`, `binary_sensor.infra_wan_critical`, `sensor.ookla_speedtest_download`, `sensor.ookla_speedtest_upload`, `sensor.infra_nebula_sync_dns_consistency`, `sensor.infra_pihole_iot_dns_consistency`, `binary_sensor.infra_nebula_sync_degraded`, `binary_sensor.infra_pihole_iot_dns_degraded`, `sensor.docker_*_disk_used_percentage`, `automation.infra_nebula_sync_health_dispatch`, `automation.infra_pihole_iot_dns_drift_dispatch`, `automation.docker_host_disk_pressure_monitor`, `automation.infra_website_down_repair_and_dispatch`, `binary_sensor.infra_website_uptime_slo_breach`, `binary_sensor.infra_website_latency_degraded`, `automation.infra_backup_nightly_verification`, `script.joanna_dispatch` |
| [![YAML source: infrastructure](https://img.shields.io/static/v1?label=YAML&message=infrastructure&color=lightgrey&logo=github&logoColor=181717)](infrastructure.yaml) | Corroborated multi-probe WAN reachability, continuous latency/loss quality tiers, separate Ookla CLI capacity snapshots, normalized DNS/backup/domain/cert health, Nebula Sync and promoted IoT primary/backup Pi-hole consistency monitoring with Joanna dispatch, Glances-backed Docker host disk pressure with Joanna-only warning cleanup and critical Repairs, quiet Bear Stone and website-down Repairs/Joanna remediation without phone alerts, uptime/latency SLO signals, nightly backup verification, and monthly Joanna HA log hygiene review with public-safe GitHub issue follow-up. [![Watch on YouTube](https://img.shields.io/badge/Watch-YouTube-FF0000?logo=youtube&logoColor=white)](https://youtu.be/F-PpsKkzcrM) [![vCloudInfo Blog Post](https://img.shields.io/static/v1?label=vCloudInfo&message=Blog%20Post&color=21759B&logo=wordpress&logoColor=white)](https://www.vcloudinfo.com/2026/08/home-assistant-internet-speed-monitoring-ookla.html) | `sensor.infra_wan_reachability`, `binary_sensor.infra_wan_capacity_degraded`, `binary_sensor.infra_wan_capacity_stale`, `binary_sensor.infra_wan_sustained_degradation`, `binary_sensor.infra_wan_severe_degradation`, `binary_sensor.infra_wan_critical`, `sensor.ookla_speedtest_download`, `sensor.ookla_speedtest_upload`, `sensor.infra_nebula_sync_dns_consistency`, `sensor.infra_pihole_iot_dns_consistency`, `binary_sensor.infra_nebula_sync_degraded`, `binary_sensor.infra_pihole_iot_dns_degraded`, `sensor.docker_*_disk_used_percentage`, `automation.infra_nebula_sync_health_dispatch`, `automation.infra_pihole_iot_dns_drift_dispatch`, `automation.docker_host_disk_pressure_monitor`, `automation.infrastructure_website_down_repair_and_dispatch`, `binary_sensor.infra_website_uptime_slo_breach`, `binary_sensor.infra_website_latency_degraded`, `automation.infra_backup_nightly_verification`, `script.joanna_dispatch` |
| [![YAML source: speedtest](https://img.shields.io/static/v1?label=YAML&message=speedtest&color=lightgrey&logo=github&logoColor=181717)](speedtest.yaml) | Logs corroborated WAN outages, sustained latency/loss degradation, slow Ookla capacity advisories, and recovery without letting one throughput snapshot declare the WAN down. [![Watch on YouTube](https://img.shields.io/badge/Watch-YouTube-FF0000?logo=youtube&logoColor=white)](https://youtu.be/F-PpsKkzcrM) [![vCloudInfo Blog Post](https://img.shields.io/static/v1?label=vCloudInfo&message=Blog%20Post&color=21759B&logo=wordpress&logoColor=white)](https://www.vcloudinfo.com/2026/08/home-assistant-internet-speed-monitoring-ookla.html) | `binary_sensor.infra_wan_quality_degraded`, `binary_sensor.infra_wan_outage`, `binary_sensor.infra_wan_capacity_degraded`, `sensor.ookla_speedtest_download`, `sensor.ookla_speedtest_upload`, `script.send_to_logbook` |
| [![YAML source: uptime](https://img.shields.io/static/v1?label=YAML&message=uptime&color=lightgrey&logo=github&logoColor=181717)](uptime.yaml) | UptimeRobot-backed Bear Stone and vCloudInfo down alerts with repeating failure notices and one recovery push only after a confirmed `down` to `up` transition. API availability recovery stays silent. | `sensor.bear_stone`, `sensor.vcloudinfo_com`, `alert.*_down_alert`, `script.notify_engine` |
| [![YAML source: onenote_indexer](https://img.shields.io/static/v1?label=YAML&message=onenote_indexer&color=lightgrey&logo=github&logoColor=181717)](onenote_indexer.yaml) | Dedicated-appliance OneNote indexer health/status monitoring for Joanna, explicit index-health confirmation, and failure-repair automation. Native indexer reconciliation remains the maintenance owner. | `sensor.onenote_indexer_last_job_status`, `binary_sensor.onenote_indexer_last_job_successful`, `binary_sensor.onenote_indexer_index_healthy` |
| [![YAML source: mqtt_status](https://img.shields.io/static/v1?label=YAML&message=mqtt_status&color=lightgrey&logo=github&logoColor=181717)](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` |
| [![YAML source: mariadb](https://img.shields.io/static/v1?label=YAML&message=mariadb&color=lightgrey&logo=github&logoColor=181717)](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` |
| [![YAML source: processmonitor](https://img.shields.io/static/v1?label=YAML&message=processmonitor&color=lightgrey&logo=github&logoColor=181717)](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` |
| [![YAML source: tugtainer_updates](https://img.shields.io/static/v1?label=YAML&message=tugtainer_updates&color=lightgrey&logo=github&logoColor=181717)](tugtainer_updates.yaml) | Tugtainer container update notifications via webhook + persistent alerts, with immediate Joanna recovery dispatch for `### Failed:` / `### Rolled-back:` reports and every `### Available:` report. Home Assistant core participates in the 07:00 automatic rollout after a mandatory Tugtainer pre-update config check, with HTTP health and image rollback safeguards. Protected Tugtainer control-plane containers are updated one host at a time through their owning Compose stacks; duplicate Portainer image-update entities are auto-disabled because Tugtainer and Compose own rollout decisions. [![Watch on YouTube](https://img.shields.io/badge/Watch-YouTube-FF0000?logo=youtube&logoColor=white)](https://youtu.be/4NNOkXzUyYw) [![vCloudInfo Blog Post](https://img.shields.io/static/v1?label=vCloudInfo&message=Blog%20Post&color=21759B&logo=wordpress&logoColor=white)](https://www.vcloudinfo.com/2026/07/home-assistant-tugtainer-docker-updates.html) | `automation.tugtainer_disable_duplicate_portainer_image_updates`, `persistent_notification.create`, `event: tugtainer_available_detected`, `script.joanna_dispatch`, `input_datetime.tugtainer_last_update` |
| [![YAML source: tugtainer_updates](https://img.shields.io/static/v1?label=YAML&message=tugtainer_updates&color=lightgrey&logo=github&logoColor=181717)](tugtainer_updates.yaml) | Tugtainer container update reports via webhook, with Available-only reports processed without persistent alerts, visible `### Updated:` / `### Failed:` / `### Rolled-back:` outcomes, immediate Joanna recovery dispatch for failures, and Joanna review dispatch for every `### Available:` report. Home Assistant core participates in the 07:00 automatic rollout after a mandatory Tugtainer pre-update config check, with HTTP health and image rollback safeguards. Protected Tugtainer control-plane containers are updated one host at a time through their owning Compose stacks; duplicate Portainer image-update entities are auto-disabled because Tugtainer and Compose own rollout decisions. [![Watch on YouTube](https://img.shields.io/badge/Watch-YouTube-FF0000?logo=youtube&logoColor=white)](https://youtu.be/4NNOkXzUyYw) [![vCloudInfo Blog Post](https://img.shields.io/static/v1?label=vCloudInfo&message=Blog%20Post&color=21759B&logo=wordpress&logoColor=white)](https://www.vcloudinfo.com/2026/07/home-assistant-tugtainer-docker-updates.html) | `automation.tugtainer_disable_duplicate_portainer_image_updates`, `persistent_notification.create`, `event: tugtainer_available_detected`, `script.joanna_dispatch`, `input_datetime.tugtainer_last_update` |
| [![YAML source: printer](https://img.shields.io/static/v1?label=YAML&message=printer&color=lightgrey&logo=github&logoColor=181717)](printer.yaml) | Epson ink watchdog with one-day and one-week mobile snooze actions for low-ink reminders. | `input_datetime.printer_ink_snooze_until`, `sensor.epson_*`, mobile app action events |
| [![YAML source: bearclaw](https://img.shields.io/static/v1?label=YAML&message=bearclaw&color=lightgrey&logo=github&logoColor=181717)](bearclaw.yaml) | Joanna/BearClaw bridge automations forward Telegram commands to codex_appliance, include LLM-first routing context for freeform text, relay replies, deliver compact adaptive TeslaMate road-trip milestone, destination-arrival, and final summaries to Carlo and Stacey, emit an active-trip vacation signal, ingest `/api/bearclaw/status` telemetry, and expose dispatch, distinct BearClaw working-memory and federated OneNote KB health, compact scheduled-job warning/error health for Systems, user-keyed 7/28/90 step trends, and personal records for Overview Health. Full Joanna reports add directional route comparisons, charging strategy, trip-aware morning context, and a private final route map. Retired QMD telemetry is not imported. Road-trip companion: [![Watch on YouTube](https://img.shields.io/badge/Watch-YouTube-FF0000?logo=youtube&logoColor=white)](https://youtu.be/9-9T6v17NEw) [![vCloudInfo Blog Post](https://img.shields.io/static/v1?label=vCloudInfo&message=Blog%20Post&color=21759B&logo=wordpress&logoColor=white)](https://www.vcloudinfo.com/2026/07/teslamate-road-trip-reports-codex.html). | `rest_command.bearclaw_*`, `event: tesla_road_trip_active`, `sensor.bearclaw_status_telemetry`, `sensor.bearclaw_scheduled_job_health`, `sensor.joanna_onenote_kb_health`, `sensor.joanna_*`, `binary_sensor.joanna_*`, `automation.bearclaw_*`, `script.notify_engine`, `script.send_to_logbook` |
| [![YAML source: telegram_bot](https://img.shields.io/static/v1?label=YAML&message=telegram_bot&color=lightgrey&logo=github&logoColor=181717)](telegram_bot.yaml) | Legacy Telegram transport marker for BearClaw; the shared `joanna_send_telegram` helper now forwards through the codex_appliance direct Telegram API. | `rest_command.bearclaw_telegram_send`, `script.joanna_send_telegram` |
@ -116,7 +115,7 @@ When a package has a dedicated blog post or video, I link it right inside the YA
| [![YAML source: tesla_model_y](https://img.shields.io/static/v1?label=YAML&message=tesla_model_y&color=lightgrey&logo=github&logoColor=181717)](tesla_model_y.yaml) | Garage-close timestamp used as a physical arrival signal for low-battery plug-in reminders. | [![Watch on YouTube](https://img.shields.io/badge/Watch-YouTube-FF0000?logo=youtube&logoColor=white)](https://youtu.be/wFO0H5zgnzk) · [![vCloudInfo Blog Post](https://img.shields.io/static/v1?label=vCloudInfo&message=Blog%20Post&color=21759B&logo=wordpress&logoColor=white)](https://www.vcloudinfo.com/2026/05/home-assistant-garage-automations-arrival-alerts-tesla.html) |
| [![YAML source: llmvision](https://img.shields.io/static/v1?label=YAML&message=llmvision&color=lightgrey&logo=github&logoColor=181717)](llmvision.yaml) | LLM Vision package reminder flow that turns a front-door camera event into a stored package-present state and phone alert. | [![Watch on YouTube](https://img.shields.io/badge/Watch-YouTube-FF0000?logo=youtube&logoColor=white)](https://youtu.be/nAhCezFetvI) |
| [![YAML source: maintenance_log](https://img.shields.io/static/v1?label=YAML&message=maintenance_log&color=lightgrey&logo=github&logoColor=181717)](maintenance_log.yaml) + [![YAML source: 09_salt_trends](https://img.shields.io/static/v1?label=YAML&message=09_salt_trends&color=lightgrey&logo=github&logoColor=181717)](../dashboards/infrastructure/views/09_salt_trends.yaml) + [![YAML source: home_water_sections](https://img.shields.io/static/v1?label=YAML&message=home_water_sections&color=lightgrey&logo=github&logoColor=181717)](../dashboards/infrastructure/partials/home_water_sections.yaml) | Water softener salt maintenance flow with a reusable webhook, duplicate protection, helper-backed history, template sensors, and Salt Trends dashboard views. | [![Watch on YouTube](https://img.shields.io/badge/Watch-YouTube-FF0000?logo=youtube&logoColor=white)](https://youtu.be/_OqTv9i-jAs) · [![vCloudInfo Blog Post](https://img.shields.io/static/v1?label=vCloudInfo&message=Blog%20Post&color=21759B&logo=wordpress&logoColor=white)](https://www.vcloudinfo.com/2026/07/home-assistant-water-softener-salt-automation.html) · [Issue #1816](https://github.com/CCOSTAN/Home-AssistantConfig/issues/1816) |
| [![YAML source: climate](https://img.shields.io/static/v1?label=YAML&message=climate&color=lightgrey&logo=github&logoColor=181717)](climate.yaml) + [![YAML source: home_water_sections](https://img.shields.io/static/v1?label=YAML&message=home_water_sections&color=lightgrey&logo=github&logoColor=181717)](../dashboards/infrastructure/partials/home_water_sections.yaml) | HVAC blower-failure evidence, runtime-based AC filter reminders, reset actions, and 360-day condenser-line cleanout reminders. | [![Watch on YouTube](https://img.shields.io/badge/Watch-YouTube-FF0000?logo=youtube&logoColor=white)](https://youtu.be/nScl2JxYSB8) · [Issue #273](https://github.com/CCOSTAN/Home-AssistantConfig/issues/273) |
| [![YAML source: climate](https://img.shields.io/static/v1?label=YAML&message=climate&color=lightgrey&logo=github&logoColor=181717)](climate.yaml) + [![YAML source: home_water_sections](https://img.shields.io/static/v1?label=YAML&message=home_water_sections&color=lightgrey&logo=github&logoColor=181717)](../dashboards/infrastructure/partials/home_water_sections.yaml) | HVAC blower-failure evidence, runtime-based AC filter reminders, two-unit native NFC tag change capture, per-unit change counts/cadence, reset actions, and 360-day condenser-line cleanout reminders. | [![Watch on YouTube](https://img.shields.io/badge/Watch-YouTube-FF0000?logo=youtube&logoColor=white)](https://youtu.be/nScl2JxYSB8) · [Issue #1817](https://github.com/CCOSTAN/Home-AssistantConfig/issues/1817) · [Issue #273](https://github.com/CCOSTAN/Home-AssistantConfig/issues/273) |
| [![YAML source: climate](https://img.shields.io/static/v1?label=YAML&message=climate&color=lightgrey&logo=github&logoColor=181717)](climate.yaml) + [![YAML source: printer](https://img.shields.io/static/v1?label=YAML&message=printer&color=lightgrey&logo=github&logoColor=181717)](printer.yaml) + [![YAML source: vacation_mode](https://img.shields.io/static/v1?label=YAML&message=vacation_mode&color=lightgrey&logo=github&logoColor=181717)](vacation_mode.yaml) | Snoozeable Home Assistant mobile notifications for AC filters, printer ink, and Vacation Mode missed-visit reminders. | [![Watch on YouTube](https://img.shields.io/badge/Watch-YouTube-FF0000?logo=youtube&logoColor=white)](https://youtu.be/y47KSflS1aw) · [![vCloudInfo Blog Post](https://img.shields.io/static/v1?label=vCloudInfo&message=Blog%20Post&color=21759B&logo=wordpress&logoColor=white)](https://www.vcloudinfo.com/2026/06/home-assistant-notification-snooze-buttons.html) · [![vCloudInfo Blog Post](https://img.shields.io/static/v1?label=vCloudInfo&message=Blog%20Post&color=21759B&logo=wordpress&logoColor=white)](https://www.vcloudinfo.com/2020/11/home-assistant-ios-actionable-notifications-example.html) |
| [![YAML source: holiday](https://img.shields.io/static/v1?label=YAML&message=holiday&color=lightgrey&logo=github&logoColor=181717)](holiday.yaml) | How the holiday/flag sensor works and drives lighting playlists. | [![vCloudInfo Blog Post](https://img.shields.io/static/v1?label=vCloudInfo&message=Blog%20Post&color=21759B&logo=wordpress&logoColor=white)](https://www.vcloudinfo.com/2019/02/breaking-down-the-flag-sensor-in-home-assistant.html) |
| [![YAML source: lightning](https://img.shields.io/static/v1?label=YAML&message=lightning&color=lightgrey&logo=github&logoColor=181717)](lightning.yaml) | Blitzortung detector wiring, guarded strike alerts, one lightning speech path, and snooze workflow. | [![vCloudInfo Blog Post](https://img.shields.io/static/v1?label=vCloudInfo&message=Blog%20Post&color=21759B&logo=wordpress&logoColor=white)](https://www.vcloudinfo.com/2020/08/adding-a-lightning-sensor-to-home-assistant.html) |

@ -6,9 +6,11 @@
# Climate Control - Seasonal HVAC automations and sensors.
# Thermostat helpers for upstairs/downstairs comfort.
# -------------------------------------------------------------------
# Related Issue: 1571, 273
# Notes: Filter due alerts use 350h downstairs/450h upstairs runtime thresholds, a 9-month hard limit, 3-day snooze, and Filter Changed push actions.
# Related Issue: 1571, 273, 1817
# Notes: Filter due alerts use 550h downstairs/450h upstairs runtime thresholds, a 9-month hard limit, 3-day snooze, and Filter Changed push actions.
# Notes: Condenser line cleanout uses a single 360-day reminder for both HVAC systems.
# Notes: Native NFC tag triggers call the per-unit reset scripts; a short single-run guard suppresses duplicate phone scans.
# Notes: Filter history helpers restore across restarts; reset scripts seed the prior input_datetime when history is empty.
# Notes: The snooze notification pattern is also covered at https://youtu.be/y47KSflS1aw.
# Video: https://youtu.be/nScl2JxYSB8
######################################################################
@ -83,6 +85,18 @@ template:
and current > target
)
}}
- name: "HVAC Maintenance Due"
unique_id: hvac_maintenance_due
device_class: problem
icon: mdi:hvac
availability: >-
{{ has_value('sensor.downstairs_ac_runtime_since_last_filter_change')
and has_value('sensor.upstairs_ac_runtime_since_last_filter_change')
and has_value('sensor.hvac_condenser_lines_days_since_cleaned') }}
state: >-
{{ states('sensor.downstairs_ac_runtime_since_last_filter_change') | float(0) >= 550
or states('sensor.upstairs_ac_runtime_since_last_filter_change') | float(0) >= 450
or states('sensor.hvac_condenser_lines_days_since_cleaned') | float(0) >= 360 }}
sensor:
- name: "Downstairs AC Cooling Numeric"
@ -107,6 +121,133 @@ template:
0
{% endif %}
- name: "Downstairs AC Filter Days Since Last Change"
unique_id: downstairs_ac_filter_days_since_last_change
unit_of_measurement: d
state_class: measurement
icon: mdi:calendar-clock
availability: >-
{% set raw = states('input_datetime.downstairs_last_filter_change') %}
{{ raw not in ['unknown', 'unavailable', 'none', ''] and as_timestamp(as_datetime(raw), default=none) is not none }}
state: >-
{% set raw = states('input_datetime.downstairs_last_filter_change') %}
{% set changed_ts = as_timestamp(as_datetime(raw), default=none) %}
{% if changed_ts is not none %}
{{ [((as_timestamp(now()) - changed_ts) / 86400) | int(0), 0] | max }}
{% else %}
0
{% endif %}
- name: "Upstairs AC Filter Days Since Last Change"
unique_id: upstairs_ac_filter_days_since_last_change
unit_of_measurement: d
state_class: measurement
icon: mdi:calendar-clock
availability: >-
{% set raw = states('input_datetime.upstairs_last_filter_change') %}
{{ raw not in ['unknown', 'unavailable', 'none', ''] and as_timestamp(as_datetime(raw), default=none) is not none }}
state: >-
{% set raw = states('input_datetime.upstairs_last_filter_change') %}
{% set changed_ts = as_timestamp(as_datetime(raw), default=none) %}
{% if changed_ts is not none %}
{{ [((as_timestamp(now()) - changed_ts) / 86400) | int(0), 0] | max }}
{% else %}
0
{% endif %}
- name: "Downstairs AC Filter Average Days Between Changes"
unique_id: downstairs_ac_filter_average_days_between_changes
unit_of_measurement: d
state_class: measurement
icon: mdi:calendar-range
availability: >-
{% set raw = states('input_text.downstairs_filter_change_history') %}
{{ raw not in ['unknown', 'unavailable', 'none', ''] and '||' in raw }}
state: >-
{% set raw = states('input_text.downstairs_filter_change_history') %}
{% if raw in ['unknown', 'unavailable', 'none', ''] %}
unknown
{% else %}
{% set entries = raw.split('||') %}
{% set ns = namespace(previous_ts=none, total_days=0, sample_count=0) %}
{% for entry in entries %}
{% set dt = as_datetime(entry | default('', true) | trim, default=none) %}
{% set ts = as_timestamp(dt, default=none) if dt is not none else none %}
{% if ts is not none %}
{% if ns.previous_ts is not none and ns.previous_ts > ts %}
{% set ns.total_days = ns.total_days + ((ns.previous_ts - ts) / 86400) %}
{% set ns.sample_count = ns.sample_count + 1 %}
{% endif %}
{% set ns.previous_ts = ts %}
{% endif %}
{% endfor %}
{% if ns.sample_count > 0 %}
{{ (ns.total_days / ns.sample_count) | round(1) }}
{% else %}
0
{% endif %}
{% endif %}
- name: "Upstairs AC Filter Average Days Between Changes"
unique_id: upstairs_ac_filter_average_days_between_changes
unit_of_measurement: d
state_class: measurement
icon: mdi:calendar-range
availability: >-
{% set raw = states('input_text.upstairs_filter_change_history') %}
{{ raw not in ['unknown', 'unavailable', 'none', ''] and '||' in raw }}
state: >-
{% set raw = states('input_text.upstairs_filter_change_history') %}
{% if raw in ['unknown', 'unavailable', 'none', ''] %}
unknown
{% else %}
{% set entries = raw.split('||') %}
{% set ns = namespace(previous_ts=none, total_days=0, sample_count=0) %}
{% for entry in entries %}
{% set dt = as_datetime(entry | default('', true) | trim, default=none) %}
{% set ts = as_timestamp(dt, default=none) if dt is not none else none %}
{% if ts is not none %}
{% if ns.previous_ts is not none and ns.previous_ts > ts %}
{% set ns.total_days = ns.total_days + ((ns.previous_ts - ts) / 86400) %}
{% set ns.sample_count = ns.sample_count + 1 %}
{% endif %}
{% set ns.previous_ts = ts %}
{% endif %}
{% endfor %}
{% if ns.sample_count > 0 %}
{{ (ns.total_days / ns.sample_count) | round(1) }}
{% else %}
0
{% endif %}
{% endif %}
- name: "Downstairs AC Filter Change Count"
unique_id: downstairs_ac_filter_change_count
unit_of_measurement: changes
state_class: total
icon: mdi:counter
state: "{{ states('counter.downstairs_filter_change_count') | int(0) }}"
- name: "Upstairs AC Filter Change Count"
unique_id: upstairs_ac_filter_change_count
unit_of_measurement: changes
state_class: total
icon: mdi:counter
state: "{{ states('counter.upstairs_filter_change_count') | int(0) }}"
counter:
downstairs_filter_change_count:
name: Downstairs Filter Change Count
initial: 1
step: 1
icon: mdi:counter
upstairs_filter_change_count:
name: Upstairs Filter Change Count
initial: 1
step: 1
icon: mdi:counter
input_datetime:
downstairs_last_filter_change:
name: Downstairs Last Filter Change
@ -135,6 +276,17 @@ input_datetime:
has_time: true
initial: "2026-06-28 00:00:00"
input_text:
downstairs_filter_change_history:
name: Downstairs Filter Change History
max: 255
icon: mdi:history
upstairs_filter_change_history:
name: Upstairs Filter Change History
max: 255
icon: mdi:history
# ---------------------------------------------------------------------------
# Integration sensors tally runtime based on compressor state
# ---------------------------------------------------------------------------
@ -165,11 +317,24 @@ script:
reset_downstairs_filter:
alias: Reset Downstairs Filter
mode: queued
variables:
occurred_at: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
previous_change: "{{ states('input_datetime.downstairs_last_filter_change') }}"
next_history: >-
{% set current = states('input_text.downstairs_filter_change_history') | default('', true) | string %}
{% set ns = namespace(items=[occurred_at]) %}
{% for raw in current.split('||') + [previous_change] %}
{% set value = raw | trim %}
{% if value and value not in ['unknown', 'unavailable', 'none'] and value not in ns.items and (ns.items | count) < 10 %}
{% set ns.items = ns.items + [value] %}
{% endif %}
{% endfor %}
{{ ns.items | join('||') }}
sequence:
- service: input_datetime.set_datetime
data:
entity_id: input_datetime.downstairs_last_filter_change
datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
datetime: "{{ occurred_at }}"
- service: input_datetime.set_datetime
data:
entity_id: input_datetime.downstairs_filter_snooze_until
@ -177,15 +342,36 @@ script:
- service: utility_meter.reset
target:
entity_id: sensor.downstairs_ac_runtime_since_last_filter_change
- service: counter.increment
target:
entity_id: counter.downstairs_filter_change_count
- service: input_text.set_value
target:
entity_id: input_text.downstairs_filter_change_history
data:
value: "{{ next_history | truncate(255, true, '') }}"
reset_upstairs_filter:
alias: Reset Upstairs Filter
mode: queued
variables:
occurred_at: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
previous_change: "{{ states('input_datetime.upstairs_last_filter_change') }}"
next_history: >-
{% set current = states('input_text.upstairs_filter_change_history') | default('', true) | string %}
{% set ns = namespace(items=[occurred_at]) %}
{% for raw in current.split('||') + [previous_change] %}
{% set value = raw | trim %}
{% if value and value not in ['unknown', 'unavailable', 'none'] and value not in ns.items and (ns.items | count) < 10 %}
{% set ns.items = ns.items + [value] %}
{% endif %}
{% endfor %}
{{ ns.items | join('||') }}
sequence:
- service: input_datetime.set_datetime
data:
entity_id: input_datetime.upstairs_last_filter_change
datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
datetime: "{{ occurred_at }}"
- service: input_datetime.set_datetime
data:
entity_id: input_datetime.upstairs_filter_snooze_until
@ -193,6 +379,14 @@ script:
- service: utility_meter.reset
target:
entity_id: sensor.upstairs_ac_runtime_since_last_filter_change
- service: counter.increment
target:
entity_id: counter.upstairs_filter_change_count
- service: input_text.set_value
target:
entity_id: input_text.upstairs_filter_change_history
data:
value: "{{ next_history | truncate(255, true, '') }}"
reset_hvac_condenser_lines_cleaned:
alias: Reset HVAC Condenser Lines Cleaned
@ -226,13 +420,47 @@ script:
### There are also some automations in the POWERWALL.yaml package when the grid is down.
##############################################################################
automation:
- alias: HVAC Filter NFC Stickers
id: hvac_filter_nfc_stickers
description: Log the unit whose bound HVAC filter NFC sticker was scanned
mode: single
max_exceeded: silent
trigger:
- platform: tag
tag_id: fcf8bb30-2887-406b-8f9d-b6048e6ac196
id: upstairs
- platform: tag
tag_id: 356ba9c3-312b-433a-975f-3f46a09ab33e
id: downstairs
action:
- choose:
- conditions:
- condition: trigger
id: upstairs
sequence:
- service: script.reset_upstairs_filter
- service: script.send_to_logbook
data:
topic: "MAINTENANCE"
message: "Upstairs HVAC filter change logged from NFC sticker."
- conditions:
- condition: trigger
id: downstairs
sequence:
- service: script.reset_downstairs_filter
- service: script.send_to_logbook
data:
topic: "MAINTENANCE"
message: "Downstairs HVAC filter change logged from NFC sticker."
- delay: "00:00:10"
- alias: Notify Climate Filter Change Due
id: notify_climate_filter_change_due
description: Notify when HVAC filter runtime exceeds the location threshold or age reaches 9 months
trigger:
- platform: numeric_state
entity_id: sensor.downstairs_ac_runtime_since_last_filter_change
above: 350
above: 550
id: downstairs_runtime
- platform: time
at: "09:00:00"
@ -251,7 +479,7 @@ automation:
last_filter_entity: "input_datetime.{{ location }}_last_filter_change"
snooze_entity: "input_datetime.{{ location }}_filter_snooze_until"
runtime_hours: "{{ states(runtime_sensor) | float(0) }}"
runtime_threshold_hours: "{{ 450 if location == 'upstairs' else 350 }}"
runtime_threshold_hours: "{{ 450 if location == 'upstairs' else 550 }}"
last_filter_ts: "{{ as_timestamp(states(last_filter_entity), 0) }}"
filter_age_days: "{{ ((as_timestamp(now()) - (last_filter_ts | float(0))) / 86400) | round(0) }}"
due_reason: >-

@ -24,11 +24,13 @@
# Notes: Tapple is now served by `games_hub` on `/tapple/`; do not keep a standalone `tapple` container switch in the monitored group.
# Notes: Teslamate and crystalsoftwashsolutions are live services and should remain in the monitored group when their discovery switches are present.
# Notes: Treat telemetry reconnects from unavailable/unknown to a concrete stopped state as actionable outages.
# Notes: An empty discovered container inventory is telemetry degradation, not a healthy zero-container state.
# Notes: Host APT maintenance rolls up independently from container telemetry so pending or stale host reports cannot remain buried on the Docker view.
# Notes: Treat stuck `restarting` and `created` states as down so monitored containers dispatch remediation.
# Notes: Infra Info was removed; BearClaw Admin is the planning snapshot surface.
# Notes: codex_appliance moved to a dedicated VM; keep the standard codex_appliance switches and retire the legacy hashed discovery entity when it disappears.
# Notes: codex_appliance moved to a dedicated VM; retain only its current Portainer discovery switch.
# Notes: Paige's Bookshelf is a live monitored service and should remain in the group when its discovery switch is present.
# Notes: Panel Notes currently exposes both `switch.panel_notes_container` and `switch.panel_notes_container_2`; keep the discovered live switches aligned until the duplicate is removed.
# Notes: Panel Notes and OneNote Indexer use their current `_2` discovery switches after stale registry cleanup.
# Notes: Retired repair cleanup clears stale legacy codex_appliance and Dozzle repair IDs.
######################################################################
@ -120,34 +122,20 @@ switch:
name: Docker Monitored Containers
unique_id: docker_monitored_containers
entities:
- switch.cloudflared_kch_container
- switch.cloudflared_kch_container_2
- switch.cloudflared_wp_container
- switch.cloudflared_wp_container_2
- switch.college_budget_app_container
- switch.college_budget_app_container_2
- switch.cruise_tracker_container
- switch.cruise_tracker_container_2
- switch.crystalsoftwashsolutions_container
- switch.dashy_container
- switch.dashy_container_2
- switch.docker_socket_proxy_container
- switch.docker_socket_proxy_container_2
- switch.codex_appliance_container
- switch.codex_appliance_container_2
- switch.duplicati_container
- switch.duplicati_container_2
- switch.esphome_container
- switch.esphome_container_2
- switch.foodie_tracker_container
- switch.foodie_tracker_container_2
- switch.frigate_container
- switch.frigate_container_2
- switch.games_hub_container
- switch.games_hub_container_2
- switch.home_assistant_container
- switch.home_assistant_container_2
- switch.imposter_container
- switch.imposter_container_2
- switch.justin_internship_crm_container
- switch.justin_internship_crm_enricher_container
@ -155,65 +143,39 @@ switch:
- switch.jobs_backend_container
- switch.jobs_db_container
- switch.jobs_frontend_container
- switch.kingcrafthomes_container
- switch.kingcrafthomes_container_2
- switch.lmediaservices_container
- switch.lmediaservices_container_2
- switch.mariadb_backup_container
- switch.mariadb_backup_container_2
- switch.mariadb_container
- switch.mariadb_container_2
- switch.matter_server_container
- switch.matter_server_container_2
- switch.mqtt_container
- switch.mqtt_container_2
- switch.nebula_sync_container
- switch.nebula_sync_container_2
- switch.onenote_indexer_container
- switch.onenote_indexer_container_2
- switch.onenote_indexer_stacey_container
- switch.paiges_bookshelf_container
- switch.panel_notes_container
- switch.panel_notes_container_2
- switch.pihole_container
- switch.pihole_container_2
- switch.pihole_secondary_container
- switch.pihole_secondary_container_2
- switch.poker_tracker_container
- switch.poker_tracker_container_2
- switch.portainer_agent_container
- switch.portainer_agent_container_2
- switch.portainer_container
- switch.portainer_container_2
- switch.postgres_webhooks_backup_container
- switch.postgres_webhooks_engine_container
- switch.postgres_webhooks_engine_container_2
- switch.rc_price_checker_container
- switch.rc_price_checker_container_2
- switch.redis_webhooks_engine_container
- switch.redis_webhooks_engine_container_2
- switch.rvtools_ppt_web_container
- switch.rvtools_ppt_web_container_2
- switch.teslamate_backup_container
- switch.teslamate_container
- switch.teslamate_database_container
- switch.teslamate_grafana_container
- switch.tugtainer_agent_container
- switch.tugtainer_agent_container_2
- switch.tugtainer_container
- switch.tugtainer_container_2
- switch.tugtainer_socket_proxy_container
- switch.tugtainer_socket_proxy_container_2
- switch.unifi_container
- switch.unifi_container_2
- switch.webhooks_engine_container
- switch.webhooks_engine_container_2
- switch.wordpress_db_backup_container
- switch.wordpress_db_container
- switch.wordpress_db_container_2
- switch.wordpress_wp_container
- switch.wordpress_wp_container_2
- switch.wyze_bridge_container
- switch.wyze_bridge_container_2
@ -620,6 +582,29 @@ template:
{% endif %}
- binary_sensor:
- name: "Docker Host Maintenance Attention"
unique_id: docker_host_maintenance_attention
device_class: problem
icon: mdi:package-up
state: >-
{% set entities = [
'sensor.docker_10_apt_status',
'sensor.docker_13_apt_status',
'sensor.docker_14_apt_status',
'sensor.docker_17_apt_status',
'sensor.docker_69_apt_status'
] %}
{% set ns = namespace(attention=false) %}
{% for entity_id in entities %}
{% set status = states(entity_id) | upper %}
{% if status in ['UNKNOWN', 'UNAVAILABLE', 'NONE', ''] or
'PENDING' in status or 'REBOOT REQ' in status or
status.startswith('ERROR') %}
{% set ns.attention = true %}
{% endif %}
{% endfor %}
{{ ns.attention }}
- name: "Docker Container Telemetry Degraded"
unique_id: docker_container_telemetry_degraded
device_class: problem
@ -628,7 +613,7 @@ template:
{% set total = states('sensor.docker_monitored_container_count') | int(0) %}
{% set unavailable = states('sensor.docker_monitored_unavailable_count') | int(0) %}
{% set threshold = [3, ((total * 0.6) | round(0, 'ceil') | int(0))] | max %}
{{ total > 0 and unavailable >= threshold }}
{{ total == 0 or unavailable >= threshold }}
- name: "Docker Container Alerts Snoozed"
unique_id: docker_container_alerts_snoozed

@ -27,8 +27,10 @@
# 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: Bear Stone and website outages dispatch Joanna quietly; no direct phone alerts are sent.
# Notes: Dashboard aggregate issue count is live-only; its underlying degraded sensors retain actionable history.
# Notes: Dashboard aggregate issue count includes compact BearClaw scheduled-job warning/error health.
# Notes: Docker host maintenance is counted separately from container runtime health so pending packages and stale host reports surface on Systems.
# Notes: Home Water rollup includes Rheem faults/telemetry loss plus active Phyn alerts and valve closure.
######################################################################
@ -188,6 +190,7 @@ template:
icon: mdi:counter
state: >-
{% set ids = [
'binary_sensor.bear_stone',
'binary_sensor.vcloudinfo_com',
'binary_sensor.ipmer_com',
'binary_sensor.fordst_com',
@ -206,6 +209,7 @@ template:
attributes:
monitored_entities: >-
{{ [
'binary_sensor.bear_stone',
'binary_sensor.vcloudinfo_com',
'binary_sensor.ipmer_com',
'binary_sensor.fordst_com',
@ -213,6 +217,7 @@ template:
] }}
down_entities: >-
{% set ids = [
'binary_sensor.bear_stone',
'binary_sensor.vcloudinfo_com',
'binary_sensor.ipmer_com',
'binary_sensor.fordst_com',
@ -239,6 +244,8 @@ template:
'binary_sensor.infra_compute_degraded',
'binary_sensor.infra_storage_degraded',
'binary_sensor.infra_external_monitoring_degraded',
'binary_sensor.docker_host_maintenance_attention',
'binary_sensor.hvac_maintenance_due',
'binary_sensor.home_water_problem',
'binary_sensor.kiosk_devices_need_attention'
] %}
@ -253,6 +260,8 @@ template:
'binary_sensor.infra_compute_degraded',
'binary_sensor.infra_storage_degraded',
'binary_sensor.infra_external_monitoring_degraded',
'binary_sensor.docker_host_maintenance_attention',
'binary_sensor.hvac_maintenance_due',
'binary_sensor.home_water_problem',
'binary_sensor.kiosk_devices_need_attention'
] %}
@ -423,19 +432,13 @@ template:
state: >-
{% set dns_state = states('sensor.infra_nebula_sync_dns_consistency') | lower %}
{% set portainer_known = [
expand('binary_sensor.nebula_sync_status') | count > 0,
expand('binary_sensor.nebula_sync_status_2') | count > 0,
expand('sensor.nebula_sync_state') | count > 0,
expand('sensor.nebula_sync_state_2') | count > 0,
expand('switch.nebula_sync_container') | count > 0,
expand('switch.nebula_sync_container_2') | count > 0
] | select('equalto', true) | list | count > 0 %}
{% set portainer_ok = [
is_state('binary_sensor.nebula_sync_status', 'on'),
is_state('binary_sensor.nebula_sync_status_2', 'on'),
(states('sensor.nebula_sync_state') | lower) == 'running',
(states('sensor.nebula_sync_state_2') | lower) == 'running',
is_state('switch.nebula_sync_container', 'on'),
is_state('switch.nebula_sync_container_2', 'on')
] | select('equalto', true) | list | count > 0 %}
{{ dns_state != 'ok' or (portainer_known and not portainer_ok) }}
@ -449,12 +452,9 @@ template:
secondary_fqdn: "{{ state_attr('sensor.infra_nebula_sync_dns_consistency', 'secondary_fqdn') }}"
primary_reverse: "{{ state_attr('sensor.infra_nebula_sync_dns_consistency', 'primary_reverse') }}"
secondary_reverse: "{{ state_attr('sensor.infra_nebula_sync_dns_consistency', 'secondary_reverse') }}"
nebula_status: "{{ states('binary_sensor.nebula_sync_status') }}"
nebula_status_2: "{{ states('binary_sensor.nebula_sync_status_2') }}"
nebula_state: "{{ states('sensor.nebula_sync_state') }}"
nebula_state_2: "{{ states('sensor.nebula_sync_state_2') }}"
pihole_secondary_status: "{{ states('binary_sensor.pihole_secondary_status') }}"
pihole_secondary_status_2: "{{ states('binary_sensor.pihole_secondary_status_2') }}"
nebula_status: "{{ states('binary_sensor.nebula_sync_status_2') }}"
nebula_state: "{{ states('sensor.nebula_sync_state_2') }}"
pihole_secondary_status: "{{ states('binary_sensor.pihole_secondary_status_2') }}"
- name: "Infra Pihole IoT DNS Degraded"
unique_id: infra_pihole_iot_dns_degraded
@ -617,12 +617,16 @@ automation:
entity_ids:
- binary_sensor.infra_website_degraded
- sensor.infra_website_down_count
- binary_sensor.bear_stone
- sensor.bear_stone
- binary_sensor.vcloudinfo_com
- sensor.vcloudinfo_com
- sensor.wordpress_wp_state_2
- switch.wordpress_wp_container_2
diagnostics: >-
down_entities={{ down_summary }};
bear_stone_sensor={{ states('sensor.bear_stone') }};
bear_stone_binary={{ states('binary_sensor.bear_stone') }};
vcloudinfo_sensor={{ states('sensor.vcloudinfo_com') }};
vcloudinfo_binary={{ states('binary_sensor.vcloudinfo_com') }};
wordpress_state={{ states('sensor.wordpress_wp_state_2') }};
@ -630,6 +634,8 @@ automation:
cloudflared_wp={{ states('switch.cloudflared_wp_container_2') }}.
request: >-
Investigate and resolve the monitored website outage. For
Bear Stone, start with public HTTPS reachability and the
Home Assistant/Cloudflare path. For
vcloudinfo.com, start with public HTTPS reachability,
wordpress_wp, wordpress_db, and cloudflared_wp telemetry.
Verify public HTTP 200 recovery before closing out. Do not
@ -726,12 +732,9 @@ automation:
dns_state: "{{ states('sensor.infra_nebula_sync_dns_consistency') }}"
previous_band: "{{ states('input_text.infra_nebula_sync_health_band') | lower }}"
degraded: "{{ is_state('binary_sensor.infra_nebula_sync_degraded', 'on') }}"
nebula_status: "{{ states('binary_sensor.nebula_sync_status') }}"
nebula_status_alt: "{{ states('binary_sensor.nebula_sync_status_2') }}"
nebula_state: "{{ states('sensor.nebula_sync_state') }}"
nebula_state_alt: "{{ states('sensor.nebula_sync_state_2') }}"
pihole_secondary_status: "{{ states('binary_sensor.pihole_secondary_status') }}"
pihole_secondary_status_alt: "{{ states('binary_sensor.pihole_secondary_status_2') }}"
nebula_status: "{{ states('binary_sensor.nebula_sync_status_2') }}"
nebula_state: "{{ states('sensor.nebula_sync_state_2') }}"
pihole_secondary_status: "{{ states('binary_sensor.pihole_secondary_status_2') }}"
action:
- choose:
- conditions: "{{ degraded and previous_band != 'warning' }}"
@ -748,11 +751,8 @@ automation:
entity_ids:
- sensor.infra_nebula_sync_dns_consistency
- binary_sensor.infra_nebula_sync_degraded
- binary_sensor.nebula_sync_status
- binary_sensor.nebula_sync_status_2
- sensor.nebula_sync_state
- sensor.nebula_sync_state_2
- binary_sensor.pihole_secondary_status
- binary_sensor.pihole_secondary_status_2
diagnostics: >-
issue_id={{ issue_id }},
@ -766,11 +766,8 @@ automation:
primary_reverse={{ state_attr('sensor.infra_nebula_sync_dns_consistency', 'primary_reverse') }},
secondary_reverse={{ state_attr('sensor.infra_nebula_sync_dns_consistency', 'secondary_reverse') }},
nebula_status={{ nebula_status }},
nebula_status_2={{ nebula_status_alt }},
nebula_state={{ nebula_state }},
nebula_state_2={{ nebula_state_alt }},
pihole_secondary_status={{ pihole_secondary_status }},
pihole_secondary_status_2={{ pihole_secondary_status_alt }},
primary_dns=192.168.10.10,
backup_dns=192.168.10.14
request: >-

@ -4,11 +4,13 @@
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Maintenance Logging - webhook ingest + graphable HA history
# Related Issue: 1816
# Stores water softener salt maintenance events in recorder-backed helpers.
# -------------------------------------------------------------------
# Video: https://youtu.be/_OqTv9i-jAs
# Blog: https://www.vcloudinfo.com/2026/07/home-assistant-water-softener-salt-automation.html
# Notes: Webhook id is bearclaw_maintenance_log_v1; payloads can come from agents, NFC tags, QR codes, or dashboard buttons.
# Notes: Webhook id is bearclaw_maintenance_log_v1; internal maintenance_log events reuse the same queued processor.
# Notes: The water-softener NFC tag uses a short single-run guard to suppress duplicate phone scans.
# Notes: Duplicate event_id values are ignored to prevent double-count totals.
# Notes: Recent event history string format is "YYYY-MM-DD HH:MM:SS|amount|note||...".
# Notes: Long-term stats live on template sensors; helpers are internal state for restore/calculation.
@ -147,21 +149,58 @@ template:
state: "{{ states('input_number.water_softener_salt_total_added_lb') | float(0) | round(1) }}"
automation:
- alias: "Maintenance Log - Water Softener Salt NFC"
id: water_softener_salt_nfc_refill
description: Log one 80 lb salt refill when the bound water-softener NFC sticker is scanned
mode: single
max_exceeded: silent
trigger:
- platform: tag
tag_id: 21d5898a49e64903911f1dbaac931cb4
action:
- event: maintenance_log
event_data:
item_key: water_softener_salt
action: add
amount_value: 80
amount_unit: lb
event_id: "nfc_water_softener_salt_{{ trigger.event.context.id }}"
occurred_at: "{{ now().isoformat() }}"
actor: home_assistant_nfc
raw_text: "Water softener salt refill NFC sticker scanned."
parse_confidence: deterministic
- delay: "00:00:10"
- alias: "Maintenance Log - Joanna Webhook Ingest"
id: 1c9fba4f-fef5-4da9-82d4-4049deff17cf
description: Process webhook and internal maintenance events through one deduplicated recorder-backed path
mode: queued
max: 30
trigger:
- platform: webhook
id: webhook
webhook_id: bearclaw_maintenance_log_v1
allowed_methods:
- POST
- PUT
local_only: false
- platform: event
id: internal_event
event_type: maintenance_log
variables:
payload: "{{ trigger.json if trigger.json is mapping else dict() }}"
payload: >-
{% if trigger.id == 'webhook' and trigger.json is mapping %}
{{ trigger.json }}
{% elif trigger.id == 'internal_event' and trigger.event.data is mapping %}
{{ trigger.event.data }}
{% else %}
{{ dict() }}
{% endif %}
source_item: "{{ payload.item_key | default('', true) | string | trim | lower }}"
item_key: >-
{% set aliases = {

@ -5,10 +5,10 @@
# -------------------------------------------------------------------
# Tugtainer Updates - Container update notifications via webhook
# Related Issue: 1561
# Receives Apprise JSON payloads from Tugtainer and posts HA alerts.
# Receives Apprise JSON payloads from Tugtainer and posts outcome alerts.
# -------------------------------------------------------------------
# Notes: Expects JSON with title/message/type from the Tugtainer template.
# Notes: Creates persistent notifications and stamps last-update time.
# Notes: Available-only reports stay silent in HA; Updated, Failed, and Rolled-back reports remain visible.
# Notes: Fires update events independently when a report has both Available and Updated sections.
# Notes: Fires `tugtainer_home_assistant_core_updated` when `### Updated:` includes Home Assistant.
# Notes: Every Available report dispatches Joanna; Tugtainer owns report scheduling and de-duplication.
@ -78,7 +78,7 @@ automation:
- alias: "Tugtainer Update Report"
id: tugtainer_update_report
description: "Receive Tugtainer reports, post persistent alerts, and dispatch failed updates."
description: "Receive Tugtainer reports, suppress Available-only alerts, and dispatch update work."
mode: queued
trigger:
- platform: webhook
@ -99,6 +99,8 @@ automation:
{% set sections = message | regex_findall('(?is)###\\s*updated:\\s*(.*?)(?:\\n###\\s|$)') %}
{{ sections[0] if sections | count > 0 else '' }}
has_updated_section: "{{ updated_section | trim != '' }}"
show_persistent_notification: >-
{{ not has_available_section or has_updated_section or has_failure_section }}
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 '' }}
@ -140,10 +142,14 @@ automation:
entity_id: input_datetime.tugtainer_last_update
data:
datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
- service: persistent_notification.create
data:
title: "{{ title }}"
message: "{{ full_message }}"
- if:
- condition: template
value_template: "{{ show_persistent_notification }}"
then:
- service: persistent_notification.create
data:
title: "{{ title }}"
message: "{{ full_message }}"
- if:
- condition: template
value_template: "{{ has_available_section }}"

@ -75,6 +75,7 @@ exclude:
- binary_sensor.carlo_maxpro_*
- binary_sensor.stacey_iphone11_*
- switch.*_container
- switch.*_container_*
- sensor.*_stack_type
- sensor.*_stack_containers_count
- binary_sensor.*_stack_status
@ -213,203 +214,17 @@ exclude:
- timer.kitchen_kiosk_detail_display
- timer.kitchen_kiosk_radar_display
# 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
- input_datetime.docker_container_alerts_snooze_until
- sensor.docker_monitored_container_count
- sensor.docker_monitored_switch_inventory
- sensor.docker_monitored_unavailable_count
- switch.docker_monitored_containers
- binary_sensor.carlo_hass_status
- binary_sensor.cloudflared_kch_status
- binary_sensor.cloudflared_kch_status_2
- binary_sensor.cloudflared_wp_status
- binary_sensor.cloudflared_wp_status_2
- binary_sensor.codex_appliance_status
- binary_sensor.codex_appliance_status_2
- binary_sensor.codex_appliance_status_3
- binary_sensor.codex_update_helper_d17_status
- binary_sensor.college_budget_app_status
- binary_sensor.college_budget_app_status_2
- binary_sensor.cruise_tracker_status
- binary_sensor.cruise_tracker_status_2
- binary_sensor.crystalsoftwashsolutions_status
- binary_sensor.dashy_status
- binary_sensor.dashy_status_2
- binary_sensor.docker_files_codex_appliance_run_6404b6f209c6_status
- binary_sensor.docker_files_codex_appliance_run_9c89dd69eada_status
- binary_sensor.docker_files_codex_appliance_run_f05ee34fde70_status
- binary_sensor.docker_socket_proxy_status
- binary_sensor.docker_socket_proxy_status_2
- binary_sensor.docker17_status
- binary_sensor.docker2_status
- binary_sensor.docker69_status
- binary_sensor.duplicati_status
- binary_sensor.duplicati_status_2
- binary_sensor.esphome_status
- binary_sensor.esphome_status_2
- binary_sensor.foodie_tracker_status
- binary_sensor.foodie_tracker_status_2
- binary_sensor.frigate_status
- binary_sensor.frigate_status_2
- binary_sensor.games_hub_status
- binary_sensor.games_hub_status_2
- binary_sensor.home_assistant_status
- binary_sensor.home_assistant_status_2
- binary_sensor.imposter_status
- binary_sensor.imposter_status_2
- binary_sensor.kingcrafthomes_status
- binary_sensor.kingcrafthomes_status_2
- binary_sensor.lmediaservices_status
- binary_sensor.lmediaservices_status_2
- binary_sensor.mariadb_backup_status
- binary_sensor.mariadb_backup_status_2
- binary_sensor.mariadb_status
- binary_sensor.mariadb_status_2
- binary_sensor.matter_server_status
- binary_sensor.matter_server_status_2
- binary_sensor.mqtt_status
- binary_sensor.mqtt_status_2
- binary_sensor.nebula_sync_status
# Current retained Portainer telemetry is live-only; aggregate Docker history remains recorded.
- binary_sensor.nebula_sync_status_2
- binary_sensor.onenote_indexer_status
- binary_sensor.onenote_indexer_status_2
- binary_sensor.paiges_bookshelf_status
- binary_sensor.panel_notes_status
- binary_sensor.panel_notes_status_2
- binary_sensor.pihole_secondary_status
- binary_sensor.pihole_secondary_status_2
- binary_sensor.pihole_status
- binary_sensor.pihole_status_2
- binary_sensor.poker_tracker_status
- binary_sensor.poker_tracker_status_2
- binary_sensor.portainer_agent_status
- binary_sensor.portainer_agent_status_2
- binary_sensor.portainer_status
- binary_sensor.portainer_status_2
- binary_sensor.postgres_webhooks_backup_status
- binary_sensor.postgres_webhooks_engine_status
- binary_sensor.postgres_webhooks_engine_status_2
- binary_sensor.rc_price_checker_status
- binary_sensor.rc_price_checker_status_2
- binary_sensor.redis_webhooks_engine_status
- binary_sensor.redis_webhooks_engine_status_2
- binary_sensor.rvtools_ppt_web_status
- binary_sensor.rvtools_ppt_web_status_2
- binary_sensor.teslamate_backup_status
- binary_sensor.teslamate_database_status
- binary_sensor.teslamate_grafana_status
- binary_sensor.teslamate_status
- binary_sensor.tugtainer_agent_status
- binary_sensor.tugtainer_agent_status_2
- binary_sensor.tugtainer_socket_proxy_status
- binary_sensor.tugtainer_socket_proxy_status_2
- binary_sensor.tugtainer_status
- binary_sensor.tugtainer_status_2
- binary_sensor.unifi_status
- binary_sensor.unifi_status_2
- binary_sensor.webhooks_engine_status
- binary_sensor.webhooks_engine_status_2
- binary_sensor.wordpress_db_backup_status
- binary_sensor.wordpress_db_status
- binary_sensor.wordpress_db_status_2
- binary_sensor.wordpress_wp_status
- binary_sensor.wordpress_wp_status_2
- binary_sensor.wyze_bridge_status
- binary_sensor.wyze_bridge_status_2
- sensor.cloudflared_kch_state
- sensor.cloudflared_kch_state_2
- sensor.cloudflared_wp_state
- sensor.cloudflared_wp_state_2
- sensor.codex_appliance_state
- sensor.codex_appliance_state_2
- sensor.codex_update_helper_d17_state
- sensor.codex_update_helper_d69_state
- sensor.college_budget_app_state
- sensor.college_budget_app_state_2
- sensor.cruise_tracker_state
- sensor.cruise_tracker_state_2
- sensor.crystalsoftwashsolutions_state
- sensor.dashy_state
- sensor.dashy_state_2
- sensor.docker_files_codex_appliance_run_6404b6f209c6_state
- sensor.docker_files_codex_appliance_run_9c89dd69eada_state
- sensor.docker_files_codex_appliance_run_f05ee34fde70_state
- sensor.docker_socket_proxy_state
- sensor.docker_socket_proxy_state_2
- sensor.duplicati_state
- sensor.duplicati_state_2
- sensor.esphome_state
- sensor.esphome_state_2
- sensor.foodie_tracker_state
- sensor.foodie_tracker_state_2
- sensor.frigate_state
- sensor.frigate_state_2
- sensor.games_hub_state
- sensor.games_hub_state_2
- sensor.home_assistant_state
- sensor.home_assistant_state_2
- sensor.imposter_state
- sensor.imposter_state_2
- sensor.kingcrafthomes_state
- sensor.kingcrafthomes_state_2
- sensor.lmediaservices_state
- sensor.lmediaservices_state_2
- sensor.mariadb_backup_state
- sensor.mariadb_backup_state_2
- sensor.mariadb_state
- sensor.mariadb_state_2
- sensor.matter_server_state
- sensor.matter_server_state_2
- sensor.mqtt_state
- sensor.mqtt_state_2
- sensor.nebula_sync_state
- sensor.nebula_sync_state_2
- sensor.onenote_indexer_state
- sensor.onenote_indexer_state_2
- sensor.paiges_bookshelf_state
- sensor.panel_notes_state
- sensor.panel_notes_state_2
- sensor.pihole_secondary_state
- sensor.pihole_secondary_state_2
- sensor.pihole_state
- sensor.pihole_state_2
- sensor.poker_tracker_state
- sensor.poker_tracker_state_2
- sensor.portainer_agent_state
- sensor.portainer_agent_state_2
- sensor.portainer_state
- sensor.portainer_state_2
- sensor.postgres_webhooks_backup_state
- sensor.postgres_webhooks_engine_state
- sensor.postgres_webhooks_engine_state_2
- sensor.rc_price_checker_state
- sensor.rc_price_checker_state_2
- sensor.redis_webhooks_engine_state
- sensor.redis_webhooks_engine_state_2
- sensor.rvtools_ppt_web_state
- sensor.rvtools_ppt_web_state_2
- sensor.teslamate_backup_state
- sensor.teslamate_database_state
- sensor.teslamate_grafana_state
- sensor.teslamate_state
- sensor.tugtainer_agent_state
- sensor.tugtainer_agent_state_2
- sensor.tugtainer_socket_proxy_state
- sensor.tugtainer_socket_proxy_state_2
- sensor.tugtainer_state
- sensor.tugtainer_state_2
- sensor.unifi_state
- sensor.unifi_state_2
- sensor.webhooks_engine_state
- sensor.webhooks_engine_state_2
- sensor.wordpress_db_backup_state
- sensor.wordpress_db_state
- sensor.wordpress_db_state_2
- sensor.wordpress_wp_state
- sensor.portainer_agent_cpu_usage_total_2
- sensor.portainer_agent_memory_usage_percentage_2
- sensor.portainer_cpu_usage_total_2
- sensor.portainer_memory_usage_percentage_2
- sensor.wordpress_wp_state_2
- sensor.wyze_bridge_state
- sensor.wyze_bridge_state_2
# END generated Portainer recorder/logbook noise exclusions
- binary_sensor.bear_stone
- binary_sensor.onenote_indexer_job_failed

Loading…
Cancel
Save

Powered by TurnKey Linux.