Initial commit: Hermes Agent Skills collection
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
# HA Proactive Health Audit Methodology
|
||||
|
||||
Systematic health assessment of a Home Assistant instance — not tied to a specific outage, but used to find improvement opportunities, dead integrations, and hidden issues. Complements the reactive "Bulk Entity Audit" section in the SKILL.md (which diagnoses a *known* outage). This procedure finds problems the user doesn't know about yet.
|
||||
|
||||
Based on a 2026-07-12 audit session that reduced 28.5% unavailable entities to a prioritized action list.
|
||||
|
||||
## Audit Procedure (6 Steps)
|
||||
|
||||
### Step 1: Instance Metadata
|
||||
|
||||
```bash
|
||||
curl -s "$HA_URL/api/config" -H "Authorization: Bearer $TOKEN" | python3 -c "
|
||||
import sys,json
|
||||
d=json.load(sys.stdin)
|
||||
print(f'Version: {d.get(\"version\",\"?\")}')
|
||||
print(f'State: {d.get(\"state\",\"?\")}')
|
||||
comps=d.get('components',[])
|
||||
print(f'Components: {len(comps)}')
|
||||
"
|
||||
```
|
||||
|
||||
Records HA version, running state, and component count. Useful for tracking growth over time and confirming the instance is responsive.
|
||||
|
||||
### Step 2: Entity Health Statistics
|
||||
|
||||
```python
|
||||
import json
|
||||
from collections import Counter
|
||||
|
||||
data = json.loads(open('/tmp/ha_states.json','rb').read().decode('utf-8','replace'))
|
||||
total = len(data)
|
||||
unavail = [e for e in data if e['state'] == 'unavailable']
|
||||
unknown = [e for e in data if e['state'] == 'unknown']
|
||||
|
||||
print(f'Total entities: {total}')
|
||||
print(f'Unavailable: {len(unavail)} ({len(unavail)/total*100:.1f}%)')
|
||||
print(f'Unknown: {len(unknown)} ({len(unknown)/total*100:.1f}%)')
|
||||
|
||||
# Domain distribution
|
||||
domains = Counter(e['entity_id'].split('.')[0] for e in data)
|
||||
print(f'Domains (top 15): {domains.most_common(15)}')
|
||||
```
|
||||
|
||||
**Benchmark**: <5% unavailable is healthy. 10-20% suggests stale integrations. >25% indicates systemic issues (dead integrations, network migration remnants, or a recent outage that didn't fully recover).
|
||||
|
||||
### Step 3: Group Unavailable Entities by Source
|
||||
|
||||
This is the key diagnostic step — group unavailable entities by their integration/device prefix to identify which integrations are responsible for the bulk of unavailability.
|
||||
|
||||
```python
|
||||
groups = Counter()
|
||||
for e in unavail:
|
||||
eid = e['entity_id']
|
||||
parts = eid.replace('.', '_').split('_')
|
||||
key = '_'.join(parts[:3]) # e.g. "sensor_spoolman_spool"
|
||||
groups[key] += 1
|
||||
|
||||
for g, c in groups.most_common(20):
|
||||
print(f' {g}: {c}')
|
||||
```
|
||||
|
||||
**Interpretation**: Groups with 50+ unavailable entities indicate an entire integration is down, not individual device failures. Common patterns:
|
||||
- `sensor_spoolman_*` → Spoolman server offline
|
||||
- `*_hame_energy` → HAME Energy integration dead (often replaced by Modbus)
|
||||
- `select_x40_master` / `select_staubsauger_oben` → Vacuum "feature-not-supported" entities (normal, but bloat)
|
||||
- `*_double_take` → Face recognition dependent on Frigate (cascade failure)
|
||||
|
||||
### Step 4: Config Entry State Audit
|
||||
|
||||
```bash
|
||||
curl -s "$HA_URL/api/config/config_entries/entry" -H "Authorization: Bearer $TOKEN" | python3 -c "
|
||||
import sys,json
|
||||
from collections import Counter
|
||||
data=json.load(sys.stdin)
|
||||
states=Counter(e.get('state','?') for e in data)
|
||||
print(f'Config entry states: {dict(states)}')
|
||||
for state in ['setup_retry','setup_error']:
|
||||
entries=[e for e in data if e.get('state')==state]
|
||||
for e in entries:
|
||||
print(f' {e.get(\"domain\")} | {e.get(\"title\")} | {e.get(\"source\")}')
|
||||
"
|
||||
```
|
||||
|
||||
**States**:
|
||||
| State | Meaning | Action |
|
||||
|-------|---------|--------|
|
||||
| `loaded` | Working | None |
|
||||
| `setup_retry` | Discovered but can't connect | Check device/network, reconfigure |
|
||||
| `setup_error` | Configuration failed | Check credentials, re-setup |
|
||||
| `not_loaded` | Disabled/ignored | Clean up if no longer needed |
|
||||
|
||||
**Common findings**:
|
||||
- Stale IPs from network migrations (e.g. `192.168.100.x` titles when network is now `10.0.x`)
|
||||
- `not_loaded` entries from abandoned integrations (Nest, DLNA, UPnP, HomeKit bridges)
|
||||
- `setup_retry` from VLAN-boundary mDNS failures (see SKILL.md "VLAN/Network Segmentation Diagnosis")
|
||||
|
||||
### Step 5: Automation Status
|
||||
|
||||
```python
|
||||
autos = [e for e in data if e['entity_id'].startswith('automation.')]
|
||||
on_a = sum(1 for a in autos if a['state'] == 'on')
|
||||
off_a = sum(1 for a in autos if a['state'] == 'off')
|
||||
print(f'Automations: {len(autos)} total, {on_a} on, {off_a} off')
|
||||
|
||||
# List disabled ones with friendly names
|
||||
for a in autos:
|
||||
if a['state'] == 'off':
|
||||
name = a.get('attributes',{}).get('friendly_name', a['entity_id'])
|
||||
print(f' {a["entity_id"]}: {name}')
|
||||
```
|
||||
|
||||
Disabled automations are often forgotten after debugging. Check if they were disabled intentionally or left off after a fix was applied but never re-enabled. **Especially check automations that were recently fixed** — the fix may have been applied but the automation never reactivated.
|
||||
|
||||
### Step 6: Staleness Detection
|
||||
|
||||
```python
|
||||
import datetime
|
||||
now = datetime.datetime.now(datetime.timezone.utc)
|
||||
stale = []
|
||||
for e in data:
|
||||
lc = e.get('last_changed', '')
|
||||
if not lc:
|
||||
continue
|
||||
try:
|
||||
dt = datetime.datetime.fromisoformat(lc.replace('Z', '+00:00'))
|
||||
age = (now - dt).total_seconds() / 3600
|
||||
if age > 24 and e['state'] not in ('unavailable', 'unknown'):
|
||||
stale.append((e['entity_id'], e['state'], round(age)))
|
||||
except:
|
||||
pass
|
||||
stale.sort(key=lambda x: x[2], reverse=True)
|
||||
print(f'Stale entities (>24h, not unavail/unknown): {len(stale)}')
|
||||
```
|
||||
|
||||
Large stale counts (hundreds) often correlate with `update.*` entities that haven't been polled since last restart, or sensors from integrations that silently stopped publishing. Cross-reference with Step 3 groups.
|
||||
|
||||
## Synthesis: Prioritized Improvement List
|
||||
|
||||
Combine all 6 steps into a ranked table:
|
||||
|
||||
| Priority | Issue | Entities Affected | Effort | Rationale |
|
||||
|----------|-------|--------------------|--------|-----------|
|
||||
| 1 | Dead integration removal (HAME, Spoolman, Kia) | 194 | S | Largest reduction in unavailable count |
|
||||
| 2 | Frigate recovery chain | 13+ | M | Security cascade dependency |
|
||||
| 3 | Reactivate disabled automations | 7 | S | Immediate functional gain |
|
||||
| 4 | Network migration cleanup | 10+ | S | Stale IPs blocking reconnection |
|
||||
| 5 | Entity bloat reduction | 245+ | M | Disable unsupported vacuum entities |
|
||||
|
||||
**Scoring criteria** (adapted from `ideate` skill):
|
||||
- **Impact** (30%): How many entities/users are affected?
|
||||
- **Feasibility** (25%): Can it be done without breaking production?
|
||||
- **Strategic fit** (20%): Does it align with the user's direction (e.g. Marstek replacing HAME)?
|
||||
- **Effort** (15% inversely weighted): S < 30 min, M < 2h, L > 2h
|
||||
- **Compound potential** (10%): Does fixing this unlock other improvements?
|
||||
|
||||
## Audit Results Snapshot (2026-07-12)
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| HA Version | 2026.7.1 |
|
||||
| Total entities | 2,282 |
|
||||
| Unavailable | 650 (28.5%) |
|
||||
| Unknown | 197 (8.6%) |
|
||||
| Automations | 35 (25 on, 7 off) |
|
||||
| Config entries | 152 loaded, 5 setup_retry, 13 not_loaded, 1 setup_error |
|
||||
| Stale (>24h) | 680 |
|
||||
|
||||
### Top Unavailable Groups
|
||||
| Group | Count | Root Cause |
|
||||
|-------|-------|------------|
|
||||
| `sensor_spoolman_spool` | 101 | Spoolman server down |
|
||||
| `select_x40_master` | 85 | Vacuum feature-not-supported (normal bloat) |
|
||||
| `select_staubsauger_oben` | 48 | Same |
|
||||
| `text/sensor/number/switch_hame_energy` | 74 | HAME Energy completely dead (replaced by Marstek Modbus) |
|
||||
| `sensor_double_take` | 13 | Frigate dependency (cascade) |
|
||||
| `light_tradfri_*` | 16+ | Stale IP 192.168.100.60 (pre-migration) |
|
||||
|
||||
### Setup Retry Entries
|
||||
| Domain | Title | Source |
|
||||
|--------|-------|--------|
|
||||
| lifx | Kinderzimmer 1 | dhcp |
|
||||
| lifx | Kinderzimmer 2 | dhcp |
|
||||
| tradfri | 192.168.100.60 | user |
|
||||
| frigate | 10.0.30.104:5000 | user |
|
||||
| ollama | http://10.0.30.98:11434 | user |
|
||||
|
||||
### Disabled Automations
|
||||
| Automation | Friendly Name | Likely Reason |
|
||||
|------------|---------------|---------------|
|
||||
| `automation.warmwasserbereitung_an` | DHW onetimecharge | Disabled during tuning |
|
||||
| `automation.dhw_onetimecharge_when_dhw_charging_active` | DHW onetimecharge when DHW Charging active | Related to above |
|
||||
| `automation.tibber_charge_battery` | Tibber Charge Battery | Disabled during testing |
|
||||
| `automation.vogelabwehr_1/2/3` | Vogelabwehr 1/2/3 | Seasonal? |
|
||||
| `automation.emhass_batterie_kaskadensteuerung_sma_marstek` | EMHASS Kaskadensteuerung | Fixed but not reactivated! |
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **Error log endpoint may 404 on some HA versions.** `/api/error_log` returned 404 on HA 2026.7.1. Try `/api/error/all` or check via SSH instead. Don't treat the 404 as "no errors" — it means the endpoint isn't available, not that the log is empty.
|
||||
- **Vacuum "feature-not-supported" entities inflate the unavailable count dramatically.** Two Dreame vacuums contributed ~250 unavailable entities. These are normal (the integration creates entities for all possible features, unsupported ones stay unavailable). Don't try to "fix" them — consider disabling them in the entity registry to reduce noise.
|
||||
- **`update.*` entities dominate the stale list.** Most stale entities are `update.*` sensors that only change when an update is available. This is normal behavior, not a defect. Filter them out when assessing real staleness.
|
||||
- **Config entry titles can be stale.** A `setup_retry` TRADFRI entry titled "192.168.100.60" doesn't mean the integration is trying that IP — the title is cosmetic. Check the actual `data` field in `.storage/core.config_entries` for the real configured address.
|
||||
Reference in New Issue
Block a user