mirror of
https://github.com/CCOSTAN/Home-AssistantConfig.git
synced 2026-04-28 03:02:07 +00:00
Enhance Home Assistant YAML Dry Verifier and related automations
- Added a mandatory resolution policy to the YAML verifier documentation, emphasizing the need for immediate resolution of findings. - Introduced a new `CENTRAL_SCRIPT` finding type to identify scripts defined in `config/packages` but called from multiple YAML files. - Updated the verifier script to collect and report on central script usage, including recommendations for moving definitions to appropriate locations. - Refactored various automations to utilize the new `script.joanna_dispatch` for improved context handling and remediation requests. - Enhanced existing automations with additional conditions and variables for better control and monitoring of actions. - Updated README files to reflect new features and improvements across packages.
This commit is contained in:
@@ -7,6 +7,13 @@ description: "Verify Home Assistant YAML for DRY and efficiency issues by detect
|
||||
|
||||
Use this skill to lint Home Assistant YAML for repeat logic before or after edits, then refactor repeated blocks into reusable helpers.
|
||||
|
||||
## Mandatory Resolution Policy
|
||||
|
||||
- If the verifier reports findings for files touched in the current task, do not stop at reporting.
|
||||
- Resolve the findings in the same task by refactoring YAML to remove duplication.
|
||||
- Re-run the verifier after refactoring and iterate until targeted findings are cleared.
|
||||
- If a finding cannot be safely resolved, explicitly document the blocker and the smallest safe follow-up.
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Run the verifier script on the file(s) you edited.
|
||||
@@ -38,6 +45,7 @@ python codex_skills/homeassistant-yaml-dry-verifier/scripts/verify_ha_yaml_dry.p
|
||||
- `FULL_BLOCK`: repeated full trigger/condition/action/sequence blocks.
|
||||
- `ENTRY`: repeated individual entries inside those blocks.
|
||||
- `INTRA`: duplicate entries inside a single block.
|
||||
- `CENTRAL_SCRIPT`: script is defined in `config/packages` but called from 2+ YAML files.
|
||||
|
||||
4. Refactor with intent:
|
||||
- Repeated actions/sequence: move to a reusable `script.*`, pass variables.
|
||||
@@ -48,6 +56,12 @@ python codex_skills/homeassistant-yaml-dry-verifier/scripts/verify_ha_yaml_dry.p
|
||||
- Re-run this verifier.
|
||||
- Run Home Assistant config validation before reload/restart.
|
||||
|
||||
6. Enforce closure:
|
||||
- Treat unresolved `FULL_BLOCK`/`ENTRY` findings in touched files as incomplete work unless a blocker is documented.
|
||||
- Prefer consolidating duplicated automation triggers/conditions/actions into shared logic or a single branching automation.
|
||||
- Treat unresolved `CENTRAL_SCRIPT` findings in touched scope as incomplete unless documented as deferred-with-blocker.
|
||||
- Move shared package scripts to `config/script/<script_id>.yaml` when they are used cross-file.
|
||||
|
||||
## Dashboard Designer Integration
|
||||
|
||||
When dashboard or automation work includes YAML edits beyond card layout, use this verifier after generation to catch duplicated logic that may have been introduced during fast refactors.
|
||||
@@ -58,7 +72,13 @@ Always report:
|
||||
- Total files scanned.
|
||||
- Parse errors (if any).
|
||||
- Duplicate groups by kind (`trigger`, `condition`, `action`, `sequence`).
|
||||
- Central script placement findings (`CENTRAL_SCRIPT`) with definition + caller files.
|
||||
- Concrete refactor recommendation per group.
|
||||
- Resolution status for each finding (`resolved`, `deferred-with-blocker`).
|
||||
|
||||
Strict behavior:
|
||||
- `--strict` returns non-zero for any reported finding (`FULL_BLOCK`, `ENTRY`, `INTRA`, `CENTRAL_SCRIPT`).
|
||||
- Without `--strict`, findings are reported but exit remains zero unless parse errors occur.
|
||||
|
||||
## References
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
@@ -82,6 +83,13 @@ class ParseError:
|
||||
error: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CentralScriptFinding:
|
||||
script_id: str
|
||||
definition_files: tuple[str, ...]
|
||||
caller_files: tuple[str, ...]
|
||||
|
||||
|
||||
def _discover_yaml_files(paths: Iterable[str]) -> list[Path]:
|
||||
found: set[Path] = set()
|
||||
for raw in paths:
|
||||
@@ -270,6 +278,41 @@ def _render_occurrences(occurrences: list[Occurrence], max_rows: int = 6) -> str
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _normalize_path(path: str) -> str:
|
||||
return path.replace("\\", "/").lower()
|
||||
|
||||
|
||||
def _infer_script_id(candidate: Candidate) -> str | None:
|
||||
if candidate.kind != "script":
|
||||
return None
|
||||
marker = ".script."
|
||||
if marker in candidate.path:
|
||||
return candidate.path.split(marker, 1)[1]
|
||||
if "/config/script/" in _normalize_path(candidate.file_path):
|
||||
if candidate.path.startswith("$."):
|
||||
return candidate.path[2:]
|
||||
match = re.match(r"^\$doc\[\d+\]\.(.+)$", candidate.path)
|
||||
if match:
|
||||
return match.group(1)
|
||||
return None
|
||||
|
||||
|
||||
def _collect_script_service_calls(node: Any, script_ids: set[str]) -> None:
|
||||
if isinstance(node, dict):
|
||||
for key, value in node.items():
|
||||
if key in {"service", "action"} and isinstance(value, str):
|
||||
service_name = value.strip()
|
||||
if service_name.startswith("script."):
|
||||
script_id = service_name.split(".", 1)[1].strip()
|
||||
if script_id:
|
||||
script_ids.add(script_id)
|
||||
_collect_script_service_calls(value, script_ids)
|
||||
return
|
||||
if isinstance(node, list):
|
||||
for item in node:
|
||||
_collect_script_service_calls(item, script_ids)
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
ap = argparse.ArgumentParser(description="Detect duplicated Home Assistant YAML structures.")
|
||||
ap.add_argument("paths", nargs="+", help="YAML file(s) or directory path(s) to scan")
|
||||
@@ -289,6 +332,7 @@ def main(argv: list[str]) -> int:
|
||||
|
||||
parse_errors: list[ParseError] = []
|
||||
candidates: list[Candidate] = []
|
||||
script_calls_by_id: dict[str, set[str]] = defaultdict(set)
|
||||
|
||||
for path in files:
|
||||
try:
|
||||
@@ -297,8 +341,12 @@ def main(argv: list[str]) -> int:
|
||||
parse_errors.append(ParseError(file_path=str(path), error=str(exc)))
|
||||
continue
|
||||
|
||||
script_calls_in_file: set[str] = set()
|
||||
for doc_idx, doc in enumerate(docs):
|
||||
candidates.extend(_extract_candidates_from_doc(doc, str(path), doc_idx))
|
||||
_collect_script_service_calls(doc, script_calls_in_file)
|
||||
for script_id in script_calls_in_file:
|
||||
script_calls_by_id[script_id].add(str(path))
|
||||
|
||||
full_index: dict[tuple[str, str, str], list[Occurrence]] = defaultdict(list)
|
||||
entry_index: dict[tuple[str, str, str], list[Occurrence]] = defaultdict(list)
|
||||
@@ -354,6 +402,32 @@ def main(argv: list[str]) -> int:
|
||||
full_groups = _filter_groups(full_index)
|
||||
entry_groups = _filter_groups(entry_index)
|
||||
intra_duplicate_notes = sorted(set(intra_duplicate_notes))
|
||||
script_definitions_by_id: dict[str, set[str]] = defaultdict(set)
|
||||
|
||||
for candidate in candidates:
|
||||
script_id = _infer_script_id(candidate)
|
||||
if script_id:
|
||||
script_definitions_by_id[script_id].add(candidate.file_path)
|
||||
|
||||
central_script_findings: list[CentralScriptFinding] = []
|
||||
for script_id, definition_files in script_definitions_by_id.items():
|
||||
normalized_definitions = {_normalize_path(path): path for path in definition_files}
|
||||
if not any("/config/packages/" in n for n in normalized_definitions):
|
||||
continue
|
||||
if any("/config/script/" in n for n in normalized_definitions):
|
||||
continue
|
||||
caller_files = sorted(script_calls_by_id.get(script_id, set()))
|
||||
if len(caller_files) < 2:
|
||||
continue
|
||||
central_script_findings.append(
|
||||
CentralScriptFinding(
|
||||
script_id=script_id,
|
||||
definition_files=tuple(sorted(definition_files)),
|
||||
caller_files=tuple(caller_files),
|
||||
)
|
||||
)
|
||||
|
||||
central_script_findings.sort(key=lambda item: (-len(item.caller_files), item.script_id))
|
||||
|
||||
print(f"Scanned files: {len(files)}")
|
||||
print(f"Parsed candidates: {len(candidates)}")
|
||||
@@ -361,6 +435,7 @@ def main(argv: list[str]) -> int:
|
||||
print(f"Duplicate full-block groups: {len(full_groups)}")
|
||||
print(f"Duplicate entry groups: {len(entry_groups)}")
|
||||
print(f"Intra-block duplicates: {len(intra_duplicate_notes)}")
|
||||
print(f"Central-script findings: {len(central_script_findings)}")
|
||||
|
||||
if parse_errors:
|
||||
print("\nParse errors:")
|
||||
@@ -386,8 +461,28 @@ def main(argv: list[str]) -> int:
|
||||
for idx, note in enumerate(intra_duplicate_notes[: args.max_groups], start=1):
|
||||
print(f"{idx}. {note}")
|
||||
|
||||
duplicate_count = len(full_groups) + len(entry_groups) + len(intra_duplicate_notes)
|
||||
if args.strict and duplicate_count > 0:
|
||||
if central_script_findings:
|
||||
print("\nCENTRAL_SCRIPT findings:")
|
||||
for idx, finding in enumerate(central_script_findings[: args.max_groups], start=1):
|
||||
print(
|
||||
f"{idx}. script.{finding.script_id} is package-defined and called from "
|
||||
f"{len(finding.caller_files)} files"
|
||||
)
|
||||
for definition_file in finding.definition_files:
|
||||
print(f" - definition: {definition_file}")
|
||||
for caller_file in finding.caller_files[:6]:
|
||||
print(f" - caller: {caller_file}")
|
||||
if len(finding.caller_files) > 6:
|
||||
print(f" - ... {len(finding.caller_files) - 6} more callers")
|
||||
print(f" suggestion: Move definition to config/script/{finding.script_id}.yaml")
|
||||
|
||||
finding_count = (
|
||||
len(full_groups)
|
||||
+ len(entry_groups)
|
||||
+ len(intra_duplicate_notes)
|
||||
+ len(central_script_findings)
|
||||
)
|
||||
if args.strict and finding_count > 0:
|
||||
return 1
|
||||
if parse_errors:
|
||||
return 2
|
||||
|
||||
@@ -48,9 +48,10 @@ Live collection of plug-and-play Home Assistant packages. Each YAML file in this
|
||||
| [docker_infrastructure.yaml](docker_infrastructure.yaml) | Docker host patching telemetry (docker_10/14/17/69) + host-side auto-reboots + container-down Repairs alerts, with degraded-telemetry guardrails when Portainer data drops. | `sensor.docker_*_apt_status`, `binary_sensor.docker_container_telemetry_degraded`, `repairs.create`, `repairs.remove` |
|
||||
| [infrastructure_observability.yaml](infrastructure_observability.yaml) | Normalized WAN/DNS/backup/domain/cert health sensors used by the Infrastructure Home + Website Health dashboards. | `binary_sensor.infra_*`, `sensor.infra_*`, `script.send_to_logbook` |
|
||||
| [onenote_indexer.yaml](onenote_indexer.yaml) | OneNote indexer health/status monitoring for Joanna, failure-repair automation, and a daily duplicate-delete maintenance request. | `sensor.onenote_indexer_last_job_status`, `binary_sensor.onenote_indexer_last_job_successful` |
|
||||
| [mqtt_status.yaml](mqtt_status.yaml) | Command-line MQTT broker reachability probe with Spook Repairs escalation and Joanna troubleshooting dispatch on outage. | `binary_sensor.mqtt_status_raw`, `binary_sensor.mqtt_broker_problem`, `repairs.create`, `rest_command.bearclaw_command` |
|
||||
| [mariadb.yaml](mariadb.yaml) | MariaDB recorder health and capacity SQL sensors. | `sensor.mariadb_status`, `sensor.database_size` |
|
||||
| [tugtainer_updates.yaml](tugtainer_updates.yaml) | Tugtainer container update notifications via webhook + persistent alerts. | `persistent_notification.create`, `input_datetime.tugtainer_last_update` |
|
||||
| [bearclaw.yaml](bearclaw.yaml) | Joanna/BearClaw bridge automations that forward Telegram commands to codex_appliance and relay replies back. | `rest_command.bearclaw_*`, `automation.bearclaw_*`, webhook relay |
|
||||
| [bearclaw.yaml](bearclaw.yaml) | Joanna/BearClaw bridge automations that forward Telegram commands to codex_appliance and relay replies back, with Telegram user/chat allowlist enforcement via secrets CSV. | `rest_command.bearclaw_*`, `automation.bearclaw_*`, webhook relay |
|
||||
| [telegram_bot.yaml](telegram_bot.yaml) | Telegram script wrappers used by BearClaw and other ops flows (UI integration remains the source for bot config). | `script.joanna_send_telegram`, `telegram_bot.send_message` |
|
||||
| [phynplus.yaml](phynplus.yaml) | Phyn shutoff automations with push + Activity feed + Repairs issues for leak events. | `valve.phyn_shutoff_valve`, `binary_sensor.phyn_leak_test_running`, `repairs.create` |
|
||||
| [water_delivery.yaml](water_delivery.yaml) | ReadyRefresh delivery date helper with night-before + garage door Alexa reminders. | `input_datetime.water_delivery_date`, `notify.alexa_media_garage` |
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
# Notes: Keep BearClaw transport + bridge logic centralized in this package.
|
||||
# Notes: Most BearClaw decision logic runs in docker_17/codex_appliance (server.js).
|
||||
# Notes: GitHub capture behavior (issue creation/labels/research flow) belongs in codex_appliance, not HA YAML.
|
||||
# Notes: Shared script helper `script.joanna_dispatch` lives in config/script/joanna_dispatch.yaml.
|
||||
# Notes: Telegram inline button callbacks are handled here and mapped to BearClaw commands.
|
||||
# Notes: Inbound Telegram handling enforces user_id + chat_id allowlists from secrets CSV values.
|
||||
######################################################################
|
||||
|
||||
rest_command:
|
||||
@@ -40,7 +42,6 @@ rest_command:
|
||||
"wake": {{ wake | default(false) | tojson }},
|
||||
"source": "home_assistant"
|
||||
}
|
||||
|
||||
automation:
|
||||
- id: bearclaw_telegram_bear_command
|
||||
alias: BearClaw Telegram Bear Command
|
||||
@@ -51,9 +52,21 @@ automation:
|
||||
event_type: telegram_command
|
||||
event_data:
|
||||
command: /bear
|
||||
variables:
|
||||
allowed_user_ids_csv: !secret bearclaw_allowed_telegram_user_ids
|
||||
allowed_chat_ids_csv: !secret bearclaw_allowed_telegram_chat_ids
|
||||
condition:
|
||||
- condition: template
|
||||
value_template: "{{ trigger.event.data.user_id is defined }}"
|
||||
- condition: template
|
||||
value_template: "{{ trigger.event.data.chat_id is defined }}"
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{% set allowed_users = (allowed_user_ids_csv | default('', true) | string).split(',') | map('trim') | reject('equalto', '') | list %}
|
||||
{% set allowed_chats = (allowed_chat_ids_csv | default('', true) | string).split(',') | map('trim') | reject('equalto', '') | list %}
|
||||
{% set incoming_user = trigger.event.data.user_id | default('') | string | trim %}
|
||||
{% set incoming_chat = trigger.event.data.chat_id | default('') | string | trim %}
|
||||
{{ allowed_users | count > 0 and allowed_chats | count > 0 and incoming_user in allowed_users and incoming_chat in allowed_chats }}
|
||||
action:
|
||||
- variables:
|
||||
command_text: "{{ (trigger.event.data.args | default([])) | join(' ') | trim }}"
|
||||
@@ -85,9 +98,21 @@ automation:
|
||||
trigger:
|
||||
- platform: event
|
||||
event_type: telegram_callback
|
||||
variables:
|
||||
allowed_user_ids_csv: !secret bearclaw_allowed_telegram_user_ids
|
||||
allowed_chat_ids_csv: !secret bearclaw_allowed_telegram_chat_ids
|
||||
condition:
|
||||
- condition: template
|
||||
value_template: "{{ trigger.event.data.user_id is defined }}"
|
||||
- condition: template
|
||||
value_template: "{{ trigger.event.data.chat_id is defined }}"
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{% set allowed_users = (allowed_user_ids_csv | default('', true) | string).split(',') | map('trim') | reject('equalto', '') | list %}
|
||||
{% set allowed_chats = (allowed_chat_ids_csv | default('', true) | string).split(',') | map('trim') | reject('equalto', '') | list %}
|
||||
{% set incoming_user = trigger.event.data.user_id | default('') | string | trim %}
|
||||
{% set incoming_chat = trigger.event.data.chat_id | default('') | string | trim %}
|
||||
{{ allowed_users | count > 0 and allowed_chats | count > 0 and incoming_user in allowed_users and incoming_chat in allowed_chats }}
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{% set cb = trigger.event.data.data | default('') %}
|
||||
@@ -192,9 +217,21 @@ automation:
|
||||
trigger:
|
||||
- platform: event
|
||||
event_type: telegram_text
|
||||
variables:
|
||||
allowed_user_ids_csv: !secret bearclaw_allowed_telegram_user_ids
|
||||
allowed_chat_ids_csv: !secret bearclaw_allowed_telegram_chat_ids
|
||||
condition:
|
||||
- condition: template
|
||||
value_template: "{{ trigger.event.data.user_id is defined }}"
|
||||
- condition: template
|
||||
value_template: "{{ trigger.event.data.chat_id is defined }}"
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{% set allowed_users = (allowed_user_ids_csv | default('', true) | string).split(',') | map('trim') | reject('equalto', '') | list %}
|
||||
{% set allowed_chats = (allowed_chat_ids_csv | default('', true) | string).split(',') | map('trim') | reject('equalto', '') | list %}
|
||||
{% set incoming_user = trigger.event.data.user_id | default('') | string | trim %}
|
||||
{% set incoming_chat = trigger.event.data.chat_id | default('') | string | trim %}
|
||||
{{ allowed_users | count > 0 and allowed_chats | count > 0 and incoming_user in allowed_users and incoming_chat in allowed_chats }}
|
||||
- condition: template
|
||||
value_template: "{{ (trigger.event.data.text | default('') | trim) != '' }}"
|
||||
- condition: template
|
||||
|
||||
@@ -165,6 +165,7 @@ script:
|
||||
data:
|
||||
entity_id: climate.downstairs
|
||||
temperature: 80
|
||||
|
||||
- conditions:
|
||||
- condition: and
|
||||
conditions:
|
||||
@@ -223,6 +224,34 @@ script:
|
||||
entity_id: climate.downstairs
|
||||
temperature: 80
|
||||
|
||||
set_downstairs_daytime_target:
|
||||
alias: Set Downstairs Daytime Target
|
||||
mode: single
|
||||
sequence:
|
||||
- service: script.set_downstairs_target_temp_based_on_conditions
|
||||
|
||||
set_downstairs_hvac_cool:
|
||||
alias: Set Downstairs HVAC Cool
|
||||
mode: single
|
||||
sequence:
|
||||
- service: climate.set_hvac_mode
|
||||
data:
|
||||
entity_id: climate.downstairs
|
||||
hvac_mode: cool
|
||||
|
||||
set_upstairs_cool_82:
|
||||
alias: Set Upstairs Cool 82
|
||||
mode: single
|
||||
sequence:
|
||||
- service: climate.set_hvac_mode
|
||||
data:
|
||||
entity_id: climate.upstairs
|
||||
hvac_mode: cool
|
||||
- service: climate.set_temperature
|
||||
data:
|
||||
entity_id: climate.upstairs
|
||||
temperature: 82
|
||||
|
||||
##############################################################################
|
||||
### AUTOMATIONS - Thermostat schedules, guardrails, and presence/weather logic
|
||||
### Some shutoff automations are also in the ALARM.yaml package when windows/doors are left open.
|
||||
@@ -299,9 +328,8 @@ automation:
|
||||
attribute: temperature
|
||||
below: 76
|
||||
condition:
|
||||
- condition: state
|
||||
entity_id: binary_sensor.powerwall_grid_status
|
||||
state: 'on'
|
||||
- condition: template
|
||||
value_template: "{{ is_state('binary_sensor.powerwall_grid_status', 'on') }}"
|
||||
action:
|
||||
- delay: "00:03:00"
|
||||
- service: climate.set_temperature
|
||||
@@ -352,9 +380,6 @@ automation:
|
||||
- platform: numeric_state
|
||||
entity_id: sensor.pirateweather_temperature
|
||||
above: 92
|
||||
- platform: state
|
||||
entity_id: group.family
|
||||
to: 'home'
|
||||
condition:
|
||||
- condition: and
|
||||
conditions:
|
||||
@@ -368,7 +393,7 @@ automation:
|
||||
entity_id: binary_sensor.powerwall_grid_status
|
||||
state: 'on'
|
||||
action:
|
||||
- service: script.set_downstairs_target_temp_based_on_conditions
|
||||
- service: script.set_downstairs_daytime_target
|
||||
|
||||
# Set thermostats to eco mode when everyone is away
|
||||
- alias: 'Set Thermostats to Eco When Away'
|
||||
@@ -401,10 +426,7 @@ automation:
|
||||
topic: "CLIMATE"
|
||||
message: "Skipping downstairs cool mode (outside temp <75F)."
|
||||
default:
|
||||
- service: climate.set_hvac_mode
|
||||
data:
|
||||
entity_id: climate.downstairs
|
||||
hvac_mode: cool
|
||||
- service: script.set_downstairs_hvac_cool
|
||||
- service: climate.set_temperature
|
||||
data:
|
||||
entity_id: climate.upstairs
|
||||
@@ -424,9 +446,8 @@ automation:
|
||||
- condition: state
|
||||
entity_id: group.family
|
||||
state: 'home'
|
||||
- condition: state
|
||||
entity_id: input_boolean.guest_mode
|
||||
state: 'off'
|
||||
- condition: template
|
||||
value_template: "{{ is_state('input_boolean.guest_mode', 'off') }}"
|
||||
- condition: state
|
||||
entity_id: binary_sensor.powerwall_grid_status
|
||||
state: 'on'
|
||||
@@ -452,18 +473,10 @@ automation:
|
||||
- platform: time
|
||||
at: "03:00:00"
|
||||
condition:
|
||||
- condition: state
|
||||
entity_id: binary_sensor.powerwall_grid_status
|
||||
state: 'on'
|
||||
- condition: template
|
||||
value_template: "{{ states('binary_sensor.powerwall_grid_status') == 'on' }}"
|
||||
action:
|
||||
- service: climate.set_hvac_mode
|
||||
data:
|
||||
entity_id: climate.upstairs
|
||||
hvac_mode: cool
|
||||
- service: climate.set_temperature
|
||||
data:
|
||||
entity_id: climate.upstairs
|
||||
temperature: 82
|
||||
- service: script.set_upstairs_cool_82
|
||||
|
||||
- alias: 'Humidity Control'
|
||||
id: AC_Humidity_Control
|
||||
@@ -485,9 +498,8 @@ automation:
|
||||
value_template: "{{ now().month in [10, 11, 12, 1, 2, 3] }}"
|
||||
- condition: template # Only run if AC is idle (prevents fighting other automations)
|
||||
value_template: "{{ state_attr('climate.downstairs', 'hvac_action') == 'idle' }}"
|
||||
- condition: state # Never run if the grid is down and running on powerwall.
|
||||
entity_id: binary_sensor.powerwall_grid_status
|
||||
state: 'on'
|
||||
- condition: template # Never run if the grid is down and running on powerwall.
|
||||
value_template: "{{ states('binary_sensor.powerwall_grid_status')|lower == 'on' }}"
|
||||
action:
|
||||
- choose:
|
||||
- conditions:
|
||||
|
||||
@@ -43,10 +43,11 @@ automation:
|
||||
- variables:
|
||||
broker_endpoint: "192.168.10.10:1883"
|
||||
mqtt_raw_state: "{{ states('binary_sensor.mqtt_status_raw') }}"
|
||||
issue_id: "mqtt_broker_unreachable"
|
||||
trigger_context: "HA automation mqtt_open_repair_on_failure (MQTT - Open Repair On Failure)"
|
||||
- service: repairs.create
|
||||
data:
|
||||
issue_id: "mqtt_broker_unreachable"
|
||||
issue_id: "{{ issue_id }}"
|
||||
title: "MQTT broker unreachable"
|
||||
severity: "warning"
|
||||
persistent: true
|
||||
@@ -60,16 +61,19 @@ automation:
|
||||
topic: "MQTT"
|
||||
message: >-
|
||||
MQTT broker appears down at {{ broker_endpoint }}. Spook repair opened and Joanna remediation requested.
|
||||
- service: rest_command.bearclaw_command
|
||||
- service: script.joanna_dispatch
|
||||
data:
|
||||
text: >-
|
||||
Trigger: {{ trigger_context }}.
|
||||
MQTT broker health probe failed for endpoint {{ broker_endpoint }}.
|
||||
Please troubleshoot and resolve if possible.
|
||||
Current HA raw probe state: {{ mqtt_raw_state }}.
|
||||
user: "carlo"
|
||||
trigger_context: "{{ trigger_context }}"
|
||||
source: "home_assistant_automation.mqtt_open_repair_on_failure"
|
||||
context: "{{ trigger_context }}"
|
||||
summary: "MQTT broker endpoint is unreachable"
|
||||
entity_ids:
|
||||
- "binary_sensor.mqtt_status_raw"
|
||||
- "binary_sensor.mqtt_broker_problem"
|
||||
diagnostics: >-
|
||||
issue_id={{ issue_id }},
|
||||
broker_endpoint={{ broker_endpoint }},
|
||||
mqtt_status_raw={{ mqtt_raw_state }}
|
||||
request: "Troubleshoot and resolve MQTT broker reachability if possible."
|
||||
|
||||
- id: mqtt_clear_repair_on_recovery
|
||||
alias: MQTT - Clear Repair On Recovery
|
||||
|
||||
@@ -127,16 +127,18 @@ automation:
|
||||
data:
|
||||
topic: "ONENOTE"
|
||||
message: "Requesting daily duplicate cleanup + delete reconciliation run from Joanna."
|
||||
- service: rest_command.bearclaw_command
|
||||
- service: script.joanna_dispatch
|
||||
data:
|
||||
text: >-
|
||||
Trigger: {{ trigger_context }}.
|
||||
Run OneNote indexer duplicate cleanup and apply deletions.
|
||||
Include delete reconciliation so removed source pages are removed from the index.
|
||||
Report pages/chunks and delete metrics before and after completion.
|
||||
user: "carlo"
|
||||
trigger_context: "{{ trigger_context }}"
|
||||
source: "home_assistant_automation.onenote_indexer_daily_delete_maintenance"
|
||||
context: "{{ trigger_context }}"
|
||||
summary: "Run daily OneNote indexer duplicate cleanup and deletion reconciliation"
|
||||
entity_ids:
|
||||
- "sensor.onenote_indexer_status_payload"
|
||||
- "sensor.onenote_indexer_last_job_status"
|
||||
diagnostics: "scheduled_time=03:15:00"
|
||||
request: >-
|
||||
Run OneNote duplicate cleanup and apply deletions.
|
||||
Include delete reconciliation and report pages/chunks and delete metrics before and after completion.
|
||||
|
||||
- id: onenote_indexer_failure_open_repair
|
||||
alias: OneNote Indexer - Open Repair On Failure
|
||||
@@ -153,10 +155,11 @@ automation:
|
||||
run_id: "{{ state_attr('sensor.onenote_indexer_last_job_status', 'last_run_id') | default('n/a') }}"
|
||||
last_error: "{{ state_attr('sensor.onenote_indexer_last_job_status', 'last_error') | default('n/a') }}"
|
||||
last_metrics: "{{ state_attr('sensor.onenote_indexer_last_job_status', 'last_metrics') | default({}) }}"
|
||||
issue_id: "onenote_indexer_job_failed"
|
||||
trigger_context: "HA automation onenote_indexer_failure_open_repair (OneNote Indexer - Open Repair On Failure)"
|
||||
- service: repairs.create
|
||||
data:
|
||||
issue_id: "onenote_indexer_job_failed"
|
||||
issue_id: "{{ issue_id }}"
|
||||
title: "OneNote indexer job failed"
|
||||
severity: "warning"
|
||||
persistent: true
|
||||
@@ -172,19 +175,21 @@ automation:
|
||||
topic: "ONENOTE"
|
||||
message: >-
|
||||
OneNote indexer failed (run {{ run_id }}). Spook repair opened and Joanna remediation requested.
|
||||
- service: rest_command.bearclaw_command
|
||||
- service: script.joanna_dispatch
|
||||
data:
|
||||
text: >-
|
||||
Trigger: {{ trigger_context }}.
|
||||
OneNote indexer containerhealth alert from Home Assistant.
|
||||
Please troubleshoot and resolve if possible.
|
||||
trigger_context: "{{ trigger_context }}"
|
||||
source: "home_assistant_automation.onenote_indexer_failure_open_repair"
|
||||
summary: "OneNote indexer job reported failure"
|
||||
entity_ids:
|
||||
- "binary_sensor.onenote_indexer_job_failed"
|
||||
- "sensor.onenote_indexer_last_job_status"
|
||||
diagnostics: >-
|
||||
issue_id={{ issue_id }},
|
||||
last_status={{ last_status }},
|
||||
last_run_id={{ run_id }},
|
||||
last_error={{ last_error }},
|
||||
last_metrics={{ last_metrics }}.
|
||||
user: "carlo"
|
||||
source: "home_assistant_automation.onenote_indexer_failure_open_repair"
|
||||
context: "{{ trigger_context }}"
|
||||
last_metrics={{ last_metrics }}
|
||||
request: "Troubleshoot and resolve the indexer failure if possible."
|
||||
|
||||
- id: onenote_indexer_failure_clear_repair
|
||||
alias: OneNote Indexer - Clear Repair On Recovery
|
||||
|
||||
@@ -41,6 +41,18 @@
|
||||
######################################################################
|
||||
|
||||
#-------------------------------------------
|
||||
script:
|
||||
powerwall_turn_off_nonessential_lights:
|
||||
alias: "Powerwall - Turn Off Non-Essential Lights"
|
||||
sequence:
|
||||
- service: homeassistant.turn_off
|
||||
target:
|
||||
entity_id:
|
||||
- group.interior_lights
|
||||
- group.exterior_lights
|
||||
- switch.kitchen_accent_2
|
||||
- switch.master_bathroom_accent_2
|
||||
|
||||
automation:
|
||||
- alias: Notify if Grid is down
|
||||
id: 56a32121-5725-4510-a1fa-10f69a5c82ef
|
||||
@@ -78,87 +90,61 @@ automation:
|
||||
to: 'off'
|
||||
|
||||
action:
|
||||
- service: homeassistant.turn_off
|
||||
entity_id:
|
||||
- group.interior_lights
|
||||
- group.exterior_lights
|
||||
- switch.kitchen_accent_2
|
||||
- switch.master_bathroom_accent_2
|
||||
- service: script.notify_engine
|
||||
data:
|
||||
title: "Electrical Grid Status {{ (trigger.to_state.state)|replace('True', 'up')|replace('False', 'down') }}."
|
||||
value1: "Taking actions to turning off the House Lights to preserve Battery Power."
|
||||
who: 'family'
|
||||
group: 'information'
|
||||
- repeat:
|
||||
count: 3
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ repeat.index == 2 }}"
|
||||
sequence:
|
||||
- delay:
|
||||
minutes: 1
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ repeat.index == 3 }}"
|
||||
sequence:
|
||||
- delay:
|
||||
minutes: 3
|
||||
- service: script.powerwall_turn_off_nonessential_lights
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ repeat.index == 1 }}"
|
||||
sequence:
|
||||
- service: script.notify_engine
|
||||
data:
|
||||
title: "Electrical Grid Status {{ (trigger.to_state.state)|replace('True', 'up')|replace('False', 'down') }}."
|
||||
value1: "Taking actions to turning off the House Lights to preserve Battery Power."
|
||||
who: 'family'
|
||||
group: 'information'
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ repeat.index == 2 }}"
|
||||
sequence:
|
||||
- service: script.speech_engine
|
||||
data:
|
||||
value1: "Because of the Power Outage, the Lights will be recycled for 3 minutes. Lights may turn on and off during this time."
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ repeat.index == 3 }}"
|
||||
sequence:
|
||||
- service: script.speech_engine
|
||||
data:
|
||||
value1: "Automatic light recycling has been completed. Any abnormalities will have to be addressed in the Hue App most likely. "
|
||||
|
||||
- delay:
|
||||
minutes: 1
|
||||
- service: homeassistant.turn_off
|
||||
entity_id:
|
||||
- group.interior_lights
|
||||
- group.exterior_lights
|
||||
- switch.kitchen_accent_2
|
||||
- switch.master_bathroom_accent_2
|
||||
|
||||
- service: script.speech_engine
|
||||
data:
|
||||
value1: "Because of the Power Outage, the Lights will be recycled for 3 minutes. Lights may turn on and off during this time."
|
||||
|
||||
- delay:
|
||||
minutes: 3
|
||||
- service: homeassistant.turn_off
|
||||
entity_id:
|
||||
- group.interior_lights
|
||||
- group.exterior_lights
|
||||
- switch.kitchen_accent_2
|
||||
- switch.master_bathroom_accent_2
|
||||
|
||||
- service: script.speech_engine
|
||||
data:
|
||||
value1: "Automatic light recycling has been completed. Any abnormalities will have to be addressed in the Hue App most likely. "
|
||||
|
||||
- alias: Powerwall Low Charge Monitoring with Grid Status
|
||||
- alias: "Powerwall Low Charge Monitoring and Recovery"
|
||||
id: fda6116b-b2a5-4198-a1ce-4cf4bb3254b2
|
||||
mode: single
|
||||
trigger:
|
||||
- platform: numeric_state
|
||||
- id: low_24h
|
||||
platform: numeric_state
|
||||
entity_id: sensor.powerwall_charge
|
||||
below: 60
|
||||
for:
|
||||
hours: 24
|
||||
condition:
|
||||
- condition: state
|
||||
entity_id: binary_sensor.powerwall_grid_status
|
||||
state: 'on'
|
||||
action:
|
||||
- service: script.send_to_logbook
|
||||
data:
|
||||
topic: "POWER"
|
||||
message: "Powerwall charge below 60% for 24h (current: {{ states('sensor.powerwall_charge') }}%)."
|
||||
|
||||
- service: repairs.create
|
||||
data:
|
||||
issue_id: "powerwall_low_charge_60_24h"
|
||||
title: "Powerwall charge low for 24h"
|
||||
severity: "warning"
|
||||
persistent: true
|
||||
description: >-
|
||||
Powerwall has been below 60% for 24 hours while the grid is online.
|
||||
|
||||
Current charge: {{ states('sensor.powerwall_charge') }}%.
|
||||
|
||||
- service: script.notify_engine
|
||||
data:
|
||||
title: "Powerwall Low Charge Alert - Current Charge: {{ states('sensor.powerwall_charge') }}"
|
||||
value1: "The Powerwall has been below 50% charge for more than 24 hours while the grid is online. This may indicate an issue."
|
||||
who: 'parents'
|
||||
group: 'information'
|
||||
|
||||
- alias: "Powerwall Low Charge Resolved - Clear Repair Issue"
|
||||
id: 5fd1f0b3-0e64-4a4b-bd7a-9f5d5e6b8f90
|
||||
mode: single
|
||||
trigger:
|
||||
- platform: numeric_state
|
||||
- id: recovered
|
||||
platform: numeric_state
|
||||
entity_id: sensor.powerwall_charge
|
||||
above: 60
|
||||
for:
|
||||
@@ -168,14 +154,43 @@ automation:
|
||||
entity_id: binary_sensor.powerwall_grid_status
|
||||
state: 'on'
|
||||
action:
|
||||
- service: repairs.remove
|
||||
continue_on_error: true
|
||||
data:
|
||||
issue_id: "powerwall_low_charge_60_24h"
|
||||
- service: script.send_to_logbook
|
||||
data:
|
||||
topic: "POWER"
|
||||
message: "Powerwall charge recovered above 60%. Cleared repair issue."
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: trigger
|
||||
id: low_24h
|
||||
sequence:
|
||||
- service: script.send_to_logbook
|
||||
data:
|
||||
topic: "POWER"
|
||||
message: "Powerwall charge below 60% for 24h (current: {{ states('sensor.powerwall_charge') }}%)."
|
||||
- service: repairs.create
|
||||
data:
|
||||
issue_id: "powerwall_low_charge_60_24h"
|
||||
title: "Powerwall charge low for 24h"
|
||||
severity: "warning"
|
||||
persistent: true
|
||||
description: >-
|
||||
Powerwall has been below 60% for 24 hours while the grid is online.
|
||||
|
||||
Current charge: {{ states('sensor.powerwall_charge') }}%.
|
||||
- service: script.notify_engine
|
||||
data:
|
||||
title: "Powerwall Low Charge Alert - Current Charge: {{ states('sensor.powerwall_charge') }}"
|
||||
value1: "The Powerwall has been below 50% charge for more than 24 hours while the grid is online. This may indicate an issue."
|
||||
who: 'parents'
|
||||
group: 'information'
|
||||
- conditions:
|
||||
- condition: trigger
|
||||
id: recovered
|
||||
sequence:
|
||||
- service: repairs.remove
|
||||
continue_on_error: true
|
||||
data:
|
||||
issue_id: "powerwall_low_charge_60_24h"
|
||||
- service: script.send_to_logbook
|
||||
data:
|
||||
topic: "POWER"
|
||||
message: "Powerwall charge recovered above 60%. Cleared repair issue."
|
||||
|
||||
- alias: "Shut down Docker hosts and camera PoE at 75% Powerwall"
|
||||
id: 25b3d3d8-92fa-454a-9f1c-6d3fd0f3af58
|
||||
@@ -201,48 +216,25 @@ automation:
|
||||
- switch.poe_garage_port_5_poe
|
||||
- switch.poe_garage_port_6_poe
|
||||
|
||||
- alias: "Powerwall outage - Rheem WH off at night"
|
||||
- alias: "Powerwall outage - Rheem WH mode control"
|
||||
id: d686f650-65ad-4cc6-8e27-8b5ee76b5338
|
||||
description: "During outages, turn off the water heater after sunset to protect battery"
|
||||
description: "During outages, switch Rheem mode by time-of-day and battery reserve"
|
||||
mode: single
|
||||
trigger:
|
||||
- platform: sun
|
||||
event: sunset
|
||||
- platform: state
|
||||
entity_id: binary_sensor.powerwall_grid_status
|
||||
to: 'off'
|
||||
for:
|
||||
minutes: 1
|
||||
condition:
|
||||
- condition: state
|
||||
entity_id: binary_sensor.powerwall_grid_status
|
||||
state: 'off'
|
||||
- condition: or
|
||||
conditions:
|
||||
- condition: sun
|
||||
after: sunset
|
||||
- condition: sun
|
||||
before: sunrise
|
||||
action:
|
||||
- service: water_heater.set_operation_mode
|
||||
target:
|
||||
entity_id: water_heater.rheem_wh
|
||||
data:
|
||||
state: off
|
||||
|
||||
- alias: "Powerwall outage - Rheem WH heat pump after sunrise and 50%"
|
||||
id: 7b6e8bb0-7d0c-4e63-89cf-ff6e7811b579
|
||||
description: "During outages, restore water heater to heat pump once battery is healthy during daytime"
|
||||
mode: single
|
||||
trigger:
|
||||
- platform: sun
|
||||
- id: sunrise
|
||||
platform: sun
|
||||
event: sunrise
|
||||
- platform: numeric_state
|
||||
- id: charge_above_50
|
||||
platform: numeric_state
|
||||
entity_id: sensor.powerwall_charge
|
||||
above: 50
|
||||
for:
|
||||
minutes: 5
|
||||
- platform: state
|
||||
- id: sunset
|
||||
platform: sun
|
||||
event: sunset
|
||||
- id: grid_off_1m
|
||||
platform: state
|
||||
entity_id: binary_sensor.powerwall_grid_status
|
||||
to: 'off'
|
||||
for:
|
||||
@@ -251,22 +243,42 @@ automation:
|
||||
- condition: state
|
||||
entity_id: binary_sensor.powerwall_grid_status
|
||||
state: 'off'
|
||||
- condition: numeric_state
|
||||
entity_id: sensor.powerwall_charge
|
||||
above: 50
|
||||
- condition: sun
|
||||
after: sunrise
|
||||
before: sunset
|
||||
action:
|
||||
- service: water_heater.set_operation_mode
|
||||
target:
|
||||
entity_id: water_heater.rheem_wh
|
||||
data:
|
||||
state: heat_pump
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ trigger.id in ['sunset', 'grid_off_1m'] }}"
|
||||
- condition: or
|
||||
conditions:
|
||||
- condition: sun
|
||||
after: sunset
|
||||
- condition: sun
|
||||
before: sunrise
|
||||
sequence:
|
||||
- service: water_heater.set_operation_mode
|
||||
target:
|
||||
entity_id: water_heater.rheem_wh
|
||||
data:
|
||||
state: off
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ trigger.id in ['sunrise', 'charge_above_50', 'grid_off_1m'] }}"
|
||||
- condition: numeric_state
|
||||
entity_id: sensor.powerwall_charge
|
||||
above: 50
|
||||
- condition: sun
|
||||
after: sunrise
|
||||
before: sunset
|
||||
sequence:
|
||||
- service: water_heater.set_operation_mode
|
||||
target:
|
||||
entity_id: water_heater.rheem_wh
|
||||
data:
|
||||
state: heat_pump
|
||||
|
||||
- alias: "Notify to restore PoE ports when grid returns"
|
||||
- alias: "Restore PoE ports when grid returns"
|
||||
id: 1ae8b5c5-8627-4a44-8c8a-5bf8ca5e1bf5
|
||||
description: "Prompt to turn PoE ports back on after outage shutdown steps"
|
||||
description: "Turn camera PoE ports back on after grid has been stable"
|
||||
mode: single
|
||||
trigger:
|
||||
- platform: state
|
||||
@@ -274,7 +286,11 @@ automation:
|
||||
from: 'off'
|
||||
to: 'on'
|
||||
for:
|
||||
minutes: 10
|
||||
minutes: 60
|
||||
- platform: numeric_state
|
||||
entity_id: sensor.powerwall_charge
|
||||
above: 90
|
||||
|
||||
condition:
|
||||
- condition: or
|
||||
conditions:
|
||||
@@ -290,11 +306,21 @@ automation:
|
||||
- condition: state
|
||||
entity_id: switch.poe_garage_port_6_poe
|
||||
state: 'off'
|
||||
- condition: numeric_state
|
||||
entity_id: sensor.powerwall_charge
|
||||
above: 90
|
||||
action:
|
||||
- service: switch.turn_on
|
||||
target:
|
||||
entity_id:
|
||||
- switch.poe_garage_port_3_poe
|
||||
- switch.poe_garage_port_4_poe
|
||||
- switch.poe_garage_port_5_poe
|
||||
- switch.poe_garage_port_6_poe
|
||||
- service: script.notify_engine
|
||||
data:
|
||||
title: "Grid restored - turn PoE ports back on"
|
||||
value1: "Power is back. Remember to re-enable PoE ports 3-6 if cameras stayed offline."
|
||||
title: "Grid restored - PoE ports re-enabled"
|
||||
value1: "Power is stable. Camera PoE ports 3-6 were turned back on automatically."
|
||||
who: 'family'
|
||||
group: 'information'
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
#-------------------------------------------
|
||||
# @CCOSTAN
|
||||
######################################################################
|
||||
# @CCOSTAN - Follow Me on X
|
||||
# For more info visit https://www.vcloudinfo.com/click-here
|
||||
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
|
||||
# Wireless AP Alerts - Unifi client monitoring and repair triggers.
|
||||
#-------------------------------------------
|
||||
# -------------------------------------------------------------------
|
||||
# Wireless AP Alerts - Unifi client monitoring and repair triggers
|
||||
# Opens/clears Spook repairs and dispatches Joanna when APs stay at 0 clients.
|
||||
# -------------------------------------------------------------------
|
||||
# Notes: Discussion context: https://github.com/CCOSTAN/Home-AssistantConfig/issues/1534
|
||||
# Notes: Joanna remediation requests default to investigate + recommend (no auto reset/power-cycle).
|
||||
######################################################################
|
||||
## Alert when APs have zero clients; create repair issues.
|
||||
######################################################################
|
||||
# Discussion: https://github.com/CCOSTAN/Home-AssistantConfig/issues/1534
|
||||
|
||||
automation:
|
||||
- id: unifi_ap_no_clients_repair_combined
|
||||
alias: "Unifi AP Create Repair Issue after 5m of 0 Clients"
|
||||
@@ -50,6 +53,11 @@ automation:
|
||||
issue_id: >
|
||||
{{ ap_name | lower }}_ap_no_clients
|
||||
|
||||
client_sensor: "{{ trigger.entity_id }}"
|
||||
client_count: "{{ states(trigger.entity_id) }}"
|
||||
uptime_value: "{{ states(uptime_sensor) }}"
|
||||
trigger_context: "HA automation unifi_ap_no_clients_repair_combined (Unifi AP Create Repair Issue after 5m of 0 Clients)"
|
||||
|
||||
action:
|
||||
- service: repairs.create
|
||||
data:
|
||||
@@ -59,10 +67,33 @@ automation:
|
||||
title: "{{ ap_name }} AP has 0 Wi-Fi Clients"
|
||||
description: >
|
||||
The {{ ap_name }} Unifi AP has reported 0 connected clients for
|
||||
at least 5 minutes.
|
||||
Current uptime: {{ states(uptime_sensor) }}.
|
||||
at least 5 minutes.
|
||||
Current uptime: {{ uptime_value }}.
|
||||
View and manage this AP here:
|
||||
https://unifi.ui.com
|
||||
- service: script.send_to_logbook
|
||||
data:
|
||||
topic: "WIRELESS"
|
||||
message: >-
|
||||
{{ ap_name }} AP has 0 clients for 5 minutes. Repair {{ issue_id }} opened and Joanna investigation requested.
|
||||
- service: script.joanna_dispatch
|
||||
data:
|
||||
trigger_context: "{{ trigger_context }}"
|
||||
source: "home_assistant_automation.unifi_ap_no_clients_repair_combined"
|
||||
summary: "{{ ap_name }} Unifi AP reported 0 clients for 5 minutes"
|
||||
entity_ids:
|
||||
- "{{ client_sensor }}"
|
||||
- "{{ uptime_sensor }}"
|
||||
diagnostics: >-
|
||||
issue_id={{ issue_id }},
|
||||
ap_name={{ ap_name }},
|
||||
client_sensor={{ client_sensor }},
|
||||
client_count={{ client_count }},
|
||||
uptime_sensor={{ uptime_sensor }},
|
||||
uptime={{ uptime_value }}
|
||||
request: >-
|
||||
Investigate Unifi controller and AP health, then recommend remediation.
|
||||
Do not run automated reset or power-cycle actions unless explicitly requested.
|
||||
|
||||
- id: unifi_ap_no_clients_repair_resolved_combined
|
||||
alias: "Unifi AP Resolve Repair Issue When Clients Return"
|
||||
@@ -91,5 +122,10 @@ automation:
|
||||
|
||||
action:
|
||||
- service: repairs.remove
|
||||
continue_on_error: true
|
||||
data:
|
||||
issue_id: "{{ issue_id }}"
|
||||
- service: script.send_to_logbook
|
||||
data:
|
||||
topic: "WIRELESS"
|
||||
message: "{{ ap_name }} AP clients recovered above 0. Repair {{ issue_id }} cleared."
|
||||
|
||||
@@ -29,10 +29,27 @@ Reusable scripts that other automations call for notifications, lighting, and sa
|
||||
| --- | --- |
|
||||
| [notify_engine.yaml](notify_engine.yaml) | Single entrypoint for rich push notifications. |
|
||||
| [send_to_logbook.yaml](send_to_logbook.yaml) | Generic `logbook.log` helper for Activity feed entries (Issue #1550). |
|
||||
| [joanna_dispatch.yaml](joanna_dispatch.yaml) | Shared BearClaw/Joanna dispatch schema for automation remediation requests. |
|
||||
| [speech_engine.yaml](speech_engine.yaml) | TTS/announcement orchestration with templated speech. |
|
||||
| [monthly_color_scene.yaml](monthly_color_scene.yaml) | Seasonal lighting scenes used across automations. |
|
||||
| [interior_off.yaml](interior_off.yaml) | One-call <20>all interior lights off<66> helper. |
|
||||
|
||||
### Joanna + BearClaw automated resolution flow
|
||||
`script.joanna_dispatch` is the shared handoff contract from Home Assistant automations to BearClaw/Joanna.
|
||||
|
||||
Why we use it:
|
||||
- Keeps one message schema for remediation context (`trigger_context`, `source`, `summary`, `entity_ids`, `diagnostics`, `request`).
|
||||
- Avoids repeating direct `rest_command.bearclaw_command` payload formatting in multiple packages.
|
||||
- Makes resolution-trigger automations easier to review, update, and audit.
|
||||
|
||||
Current automations that kick off automated resolutions (via `script.joanna_dispatch`):
|
||||
| Automation ID | Alias | File |
|
||||
| --- | --- | --- |
|
||||
| `mqtt_open_repair_on_failure` | MQTT - Open Repair On Failure | [../packages/mqtt_status.yaml](../packages/mqtt_status.yaml) |
|
||||
| `onenote_indexer_daily_delete_maintenance` | OneNote Indexer - Daily Delete Maintenance Request | [../packages/onenote_indexer.yaml](../packages/onenote_indexer.yaml) |
|
||||
| `onenote_indexer_failure_open_repair` | OneNote Indexer - Open Repair On Failure | [../packages/onenote_indexer.yaml](../packages/onenote_indexer.yaml) |
|
||||
| `unifi_ap_no_clients_repair_combined` | Unifi AP Create Repair Issue after 5m of 0 Clients | [../packages/wireless.yaml](../packages/wireless.yaml) |
|
||||
|
||||
### Tips
|
||||
- Keep scripts generic<69>route data via `data:`/`variables:` and reuse everywhere.
|
||||
- If you copy a script, rename any `alias` and `id` fields to avoid duplicates.
|
||||
|
||||
66
config/script/joanna_dispatch.yaml
Normal file
66
config/script/joanna_dispatch.yaml
Normal file
@@ -0,0 +1,66 @@
|
||||
######################################################################
|
||||
# @CCOSTAN - Follow Me on X
|
||||
# For more info visit https://www.vcloudinfo.com/click-here
|
||||
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
|
||||
# -------------------------------------------------------------------
|
||||
# Joanna Dispatch - Shared BearClaw dispatch helper for automations
|
||||
# Normalizes remediation context and forwards requests via bearclaw_command.
|
||||
# -------------------------------------------------------------------
|
||||
# Notes: Keep this helper generic so package automations can reuse one schema.
|
||||
# Notes: Source defaults to home_assistant_automation.unknown when omitted.
|
||||
######################################################################
|
||||
|
||||
joanna_dispatch:
|
||||
alias: Joanna Dispatch
|
||||
description: Standardized Joanna/BearClaw dispatch helper for automation-triggered requests.
|
||||
mode: queued
|
||||
fields:
|
||||
trigger_context:
|
||||
description: Automation and trigger context string.
|
||||
source:
|
||||
description: Source identifier sent to BearClaw.
|
||||
summary:
|
||||
description: Short summary of the condition requiring Joanna.
|
||||
request:
|
||||
description: What Joanna should do.
|
||||
entity_ids:
|
||||
description: Relevant entity IDs (list or comma-delimited string).
|
||||
diagnostics:
|
||||
description: Extra troubleshooting context.
|
||||
user:
|
||||
description: BearClaw user identity.
|
||||
sequence:
|
||||
- variables:
|
||||
normalized_context: "{{ trigger_context | default('HA automation', true) }}"
|
||||
normalized_source: "{{ source | default('home_assistant_automation.unknown', true) }}"
|
||||
normalized_summary: "{{ summary | default('Home Assistant remediation request', true) }}"
|
||||
normalized_request: >-
|
||||
{{ request | default('Investigate and recommend remediation. Do not run automated resets or power-cycles unless explicitly requested.', true) }}
|
||||
normalized_user: "{{ user | default('carlo', true) }}"
|
||||
normalized_entity_ids: >-
|
||||
{% if entity_ids is sequence and entity_ids is not string %}
|
||||
{{ entity_ids | map('string') | join(', ') }}
|
||||
{% elif entity_ids is string and entity_ids | trim != '' %}
|
||||
{{ entity_ids | trim }}
|
||||
{% else %}
|
||||
n/a
|
||||
{% endif %}
|
||||
normalized_diagnostics: >-
|
||||
{% if diagnostics is string %}
|
||||
{{ diagnostics | trim if diagnostics | trim != '' else 'n/a' }}
|
||||
{% elif diagnostics is mapping or (diagnostics is sequence and diagnostics is not string) %}
|
||||
{{ diagnostics | tojson }}
|
||||
{% else %}
|
||||
n/a
|
||||
{% endif %}
|
||||
- service: rest_command.bearclaw_command
|
||||
data:
|
||||
text: >-
|
||||
Trigger: {{ normalized_context }}.
|
||||
Summary: {{ normalized_summary }}.
|
||||
Entity IDs: {{ normalized_entity_ids }}.
|
||||
Diagnostics: {{ normalized_diagnostics }}.
|
||||
Request: {{ normalized_request }}
|
||||
user: "{{ normalized_user }}"
|
||||
source: "{{ normalized_source }}"
|
||||
context: "{{ normalized_context }}"
|
||||
Reference in New Issue
Block a user