Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f48730a40 | ||
|
|
c23d256ced | ||
|
|
45024f80c3 | ||
|
|
403f83a187 | ||
|
|
c8f2cbde4a | ||
|
|
5629bc9b4b | ||
|
|
4f54635b33 | ||
|
|
523a56bd52 | ||
|
|
758fc92e36 | ||
|
|
528dab5100 | ||
|
|
6266043d11 | ||
|
|
8931b653d5 | ||
|
|
09f3276b31 |
@@ -0,0 +1,28 @@
|
||||
# pfv-bms CI [#784 #778] — fleet standard per Discourse "CI/CD: Gitea Actions".
|
||||
# Runs on dev (pre-release gate) and release (final gate before live pull).
|
||||
name: ci
|
||||
on:
|
||||
push:
|
||||
branches: [dev, release]
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ultix
|
||||
container:
|
||||
image: node:20-bookworm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: yamllint
|
||||
run: |
|
||||
apt-get update -qq >/dev/null && apt-get install -qq -y python3-pip >/dev/null && pip install --quiet --break-system-packages yamllint
|
||||
yamllint -c .yamllint .
|
||||
- name: secret scan
|
||||
run: |
|
||||
if grep -rInE "BEGIN (RSA |OPENSSH |EC |DSA )?PRIVATE KEY|aws_secret_access_key *=|AKIA[0-9A-Z]{16}" --exclude-dir=.git .; then
|
||||
echo "::error::secret material committed"; exit 1
|
||||
else
|
||||
echo "secret scan clean"
|
||||
fi
|
||||
- name: sdlc regression tests
|
||||
run: |
|
||||
chmod +x tests/test_sdlc.sh
|
||||
bash tests/test_sdlc.sh
|
||||
@@ -0,0 +1,22 @@
|
||||
# HA-config-aware yamllint [#784]: keep the checks that catch real bugs
|
||||
# (duplicates, structure), relax the stylistic ones HA templates trip.
|
||||
extends: default
|
||||
ignore: |
|
||||
docs/
|
||||
secrets.yaml
|
||||
*.db
|
||||
rules:
|
||||
line-length: disable
|
||||
comments: disable
|
||||
comments-indentation: disable
|
||||
document-start: disable
|
||||
truthy:
|
||||
check-keys: false
|
||||
indentation:
|
||||
spaces: consistent
|
||||
indent-sequences: consistent
|
||||
key-duplicates: enable
|
||||
new-line-at-end-of-file: disable
|
||||
trailing-spaces: disable
|
||||
empty-lines:
|
||||
max-end: 2
|
||||
@@ -134,16 +134,23 @@ apps.
|
||||
|
||||
## 🚀 Deployment pipeline
|
||||
|
||||
**Branch model (2026-09-04):** `dev` = WIP integration — the pipeline
|
||||
IGNORES it. `release` = what the box runs — pushing release IS the deploy.
|
||||
`master` is frozen (pre-branch-model history). A PR process with AI +
|
||||
human reviewers will gate dev → release (founder, upcoming).
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
PUSH[📝 git push] --> HOOK[gitea webhook<br/>fast path]
|
||||
PUSH --> SHA[sha-watch<br/>safety net · 300 s poll]
|
||||
PUSH[📝 push to dev] --> NOOP[🚫 pipeline ignores dev]
|
||||
REL[🚀 merge dev → release<br/>and push] --> HOOK[gitea webhook<br/>fast path]
|
||||
REL --> SHA[sha-watch<br/>release sha · 300 s poll]
|
||||
HOOK & SHA --> CHECK{changed files?}
|
||||
CHECK -->|packages/**| RESTART[♻️ core restart<br/>webhook triggers need it]
|
||||
CHECK -->|configuration.yaml,<br/>packages/**| RESTART[♻️ core restart<br/>webhook triggers need it]
|
||||
CHECK -->|other YAML| RELOAD[⚡ reload only<br/>~5 min to live]
|
||||
```
|
||||
|
||||
Gate before pushing `packages/**`: `ha core check` must pass on-box.
|
||||
Gate before pushing `release` (and after any `packages/**` change on
|
||||
`dev`): `ha core check` must pass on-box.
|
||||
Measured end-to-end: 4 m 52 s commit → live.
|
||||
|
||||
## 🗺️ Repo map
|
||||
|
||||
+124
-12
@@ -4,7 +4,7 @@
|
||||
- trigger: state
|
||||
entity_id:
|
||||
- sensor.tsys1_ups_status
|
||||
- sensor.tsys9_ups_status
|
||||
- sensor.tsys1_triplite_status
|
||||
conditions:
|
||||
- condition: template
|
||||
value_template: '{{ ''On Battery'' in states(trigger.entity_id) }}'
|
||||
@@ -28,6 +28,7 @@
|
||||
- trigger: numeric_state
|
||||
entity_id:
|
||||
- sensor.tsys1_ups_battery_charge
|
||||
- sensor.tsys1_triplite_battery_charge
|
||||
below: 40
|
||||
conditions:
|
||||
- condition: template
|
||||
@@ -52,7 +53,7 @@
|
||||
- trigger: state
|
||||
entity_id:
|
||||
- sensor.tsys1_ups_status
|
||||
- sensor.tsys9_ups_status
|
||||
- sensor.tsys1_triplite_status
|
||||
to: unavailable
|
||||
for: 00:05:00
|
||||
actions:
|
||||
@@ -97,7 +98,7 @@
|
||||
- trigger: state
|
||||
entity_id:
|
||||
- sensor.tsys1_ups_status
|
||||
- sensor.tsys9_ups_status
|
||||
- sensor.tsys1_triplite_status
|
||||
conditions:
|
||||
- condition: template
|
||||
value_template: "{{ trigger.from_state is not none and 'On Battery' in trigger.from_state.state\n
|
||||
@@ -246,7 +247,7 @@
|
||||
url: /lovelace-pfv-server-room/temps
|
||||
message: '{{ trigger.entity_id }} climbing ~10F/15min — check cooling
|
||||
before the 104F threshold. (rate-of-change early warning)'
|
||||
# Deploy pipeline [#734]: gitea repo webhook (master pushes) -> forced pull
|
||||
# Deploy pipeline [#734]: gitea repo webhook (release pushes) -> forced pull
|
||||
# -> minimal reload. Core restarts ONLY when configuration.yaml or a
|
||||
# packages platform-sensor file changed; everything else reloads with no
|
||||
# bounce. The git_pull add-on's auto_restart is disabled - this webhook
|
||||
@@ -254,7 +255,7 @@
|
||||
- id: pfv_deploy_git_webhook
|
||||
alias: PFV deploy - git push to pull + minimal reload
|
||||
description: >-
|
||||
Gitea webhook on master pushes. Forces a git pull now, then
|
||||
Gitea webhook on release pushes. Forces a git pull now, then
|
||||
homeassistant.reload_all unless files that require a core restart
|
||||
changed. Replaces the git_pull add-on timer auto-restart [#734].
|
||||
mode: queued
|
||||
@@ -269,9 +270,9 @@
|
||||
actions:
|
||||
- if:
|
||||
- condition: template
|
||||
value_template: "{{ trigger.json.ref | default('') != 'refs/heads/master' }}"
|
||||
value_template: "{{ trigger.json.ref | default('') != 'refs/heads/release' }}"
|
||||
then:
|
||||
- stop: "deploy webhook: non-master ref {{ trigger.json.ref | default('') }} ignored"
|
||||
- stop: "deploy webhook: non-release ref {{ trigger.json.ref | default('') }} ignored"
|
||||
- variables:
|
||||
changed_files: >-
|
||||
{{ (trigger.json.commits | default([]) | map(attribute='added') | flatten | list)
|
||||
@@ -337,7 +338,7 @@
|
||||
data:
|
||||
title: PFV deploy complete
|
||||
message: "Pulled + reloaded YAML at {{ now().strftime('%H:%M:%S') }} - no core restart. Files: {{ changed_files | join(', ') | truncate(120) }}"
|
||||
# Deploy pipeline safety net [#734]: polls gitea master HEAD (rest sensor
|
||||
# Deploy pipeline safety net [#734]: polls gitea release HEAD (rest sensor
|
||||
# in packages/deploy_pipeline.yaml) and applies pulls reload-only. The
|
||||
# gitea webhook is the fast path once gitea's SSRF allowlist permits
|
||||
# pfv-bms.knel.net; until then this net catches every push within 5 min
|
||||
@@ -346,7 +347,7 @@
|
||||
- id: pfv_deploy_sha_watch
|
||||
alias: PFV deploy - gitea sha watch safety net
|
||||
description: >-
|
||||
Master HEAD sha changed -> pull + CORE RESTART. Interim deploy
|
||||
Release HEAD sha changed -> pull + CORE RESTART. Interim deploy
|
||||
trigger while gitea webhook delivery is blocked by its SSRF host
|
||||
allowlist [#734]. Restart (not reload) is REQUIRED: automation
|
||||
reload loses webhook-trigger registration - found live 2026-09-03
|
||||
@@ -354,12 +355,12 @@
|
||||
scan ingest, this pipeline's own fast path) silently stop firing
|
||||
while the endpoint still returns HTTP 200. Core restart
|
||||
re-registers them. Accepted trade-off: ~1 min HA outage per
|
||||
master deploy.
|
||||
release deploy.
|
||||
mode: single
|
||||
max_exceeded: silent
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id: sensor.pfv_deploy_gitea_master_head
|
||||
entity_id: sensor.pfv_deploy_gitea_release_head
|
||||
conditions:
|
||||
- condition: template
|
||||
value_template: >-
|
||||
@@ -383,7 +384,7 @@
|
||||
data:
|
||||
title: PFV deploy (sha watch)
|
||||
message: >-
|
||||
New master HEAD {{ states('sensor.pfv_deploy_gitea_master_head')[:7] }}
|
||||
New release HEAD {{ states('sensor.pfv_deploy_gitea_release_head')[:7] }}
|
||||
pulled. Restarting HA core (webhook re-registration) - back in
|
||||
~1 min.
|
||||
- action: homeassistant.restart
|
||||
@@ -525,3 +526,114 @@
|
||||
back to {{ states(trigger.entity_id) }}°F and holding — was
|
||||
{{ trigger.from_state.state }}°F when the alert cleared (tier fire line {{ fire }}°F).
|
||||
mode: single
|
||||
- id: pfv_watchdog_stale_entities
|
||||
alias: PFV watchdog - stale entity auto-heal
|
||||
description: >
|
||||
Detects silent poller wedges (2026-09-04: snmp tsys9 went 12h
|
||||
undetected; NUT APC 3h) and auto-heals config-entry platforms via
|
||||
homeassistant.reload_config_entry — no restart needed. Escalates to
|
||||
notify when reload does not recover. Auto core restart ONLY while
|
||||
input_boolean.pfv_watchdog_auto_restart is ON and entities stay
|
||||
stale — restart costs ~1 min downtime and re-registers webhooks
|
||||
(#345, #789). v1 notify repeats each cycle while stale (visible
|
||||
alarm); throttle in v2.
|
||||
mode: single
|
||||
max_exceeded: silent
|
||||
triggers:
|
||||
- trigger: time_pattern
|
||||
minutes: /5
|
||||
variables:
|
||||
checks:
|
||||
- [sensor.pfv_tsys1_rack_temperature, 20]
|
||||
- [sensor.pfv_tsys1_drive_temperature, 30]
|
||||
- [sensor.pfv_tsys3_cpu_temperature, 20]
|
||||
- [sensor.pfv_tsys4_ambient_temperature, 20]
|
||||
- [sensor.pfv_tsys6_cpu_temperature, 20]
|
||||
- [sensor.pfv_tsys6_drac_ambient, 30]
|
||||
- [sensor.pfv_tsys7_cpu_temperature, 20]
|
||||
- [sensor.pfv_tsys7_drac_inlet, 30]
|
||||
- [sensor.pfv_tsys9_cpu_temperature, 20]
|
||||
- [sensor.pfv_tsys9_drive_temperature, 30]
|
||||
- [sensor.tsys1_ups_status, 15]
|
||||
- [sensor.tsys1_triplite_status, 15]
|
||||
- [sensor.pfv_deploy_gitea_release_head, 30]
|
||||
actions:
|
||||
- variables:
|
||||
stale_list: >-
|
||||
{% set ns = namespace(stale=[]) %}
|
||||
{% for pair in checks %}
|
||||
{% set s = states[pair[0]] %}
|
||||
{% if s is not none and s.last_updated is defined
|
||||
and (now() - s.last_updated).total_seconds() > pair[1] * 60 %}
|
||||
{% set ns.stale = ns.stale + [pair[0]] %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{{ ns.stale }}
|
||||
- condition: template
|
||||
value_template: "{{ stale_list | length > 0 }}"
|
||||
- repeat:
|
||||
for_each: "{{ stale_list }}"
|
||||
sequence:
|
||||
- action: homeassistant.reload_config_entry
|
||||
data:
|
||||
entity_id: "{{ repeat.item }}"
|
||||
- delay:
|
||||
hours: 0
|
||||
minutes: 2
|
||||
seconds: 0
|
||||
- variables:
|
||||
still_stale: >-
|
||||
{% set ns = namespace(stale=[]) %}
|
||||
{% for pair in checks %}
|
||||
{% set s = states[pair[0]] %}
|
||||
{% if s is not none and s.last_updated is defined
|
||||
and (now() - s.last_updated).total_seconds() > pair[1] * 60 %}
|
||||
{% set ns.stale = ns.stale + [pair[0]] %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{{ ns.stale }}
|
||||
- action: notify.send_message
|
||||
target:
|
||||
entity_id:
|
||||
- notify.ultix_sidecar_new
|
||||
- notify.allthes_ipad
|
||||
data:
|
||||
title: WATCHDOG
|
||||
clickAction: /lovelace-pfv-server-room/temps
|
||||
url: /lovelace-pfv-server-room/temps
|
||||
message: >-
|
||||
Stale after auto-reload: {{ still_stale | join(', ') }}.
|
||||
Auto-restart is {{ 'ON' if is_state('input_boolean.pfv_watchdog_auto_restart','on')
|
||||
else 'OFF (arm it on the watchdog switch if you want unattended restarts)' }}.
|
||||
- if:
|
||||
- condition: template
|
||||
value_template: "{{ is_state('input_boolean.pfv_watchdog_auto_restart','on') }}"
|
||||
then:
|
||||
- delay:
|
||||
hours: 0
|
||||
minutes: 30
|
||||
seconds: 0
|
||||
- variables:
|
||||
hard_stale: >-
|
||||
{% set ns = namespace(stale=[]) %}
|
||||
{% for pair in checks %}
|
||||
{% set s = states[pair[0]] %}
|
||||
{% if s is not none and s.last_updated is defined
|
||||
and (now() - s.last_updated).total_seconds() > pair[1] * 60 %}
|
||||
{% set ns.stale = ns.stale + [pair[0]] %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{{ ns.stale }}
|
||||
- if:
|
||||
- condition: template
|
||||
value_template: "{{ hard_stale | length > 0 }}"
|
||||
then:
|
||||
- action: notify.send_message
|
||||
target:
|
||||
entity_id:
|
||||
- notify.ultix_sidecar_new
|
||||
- notify.allthes_ipad
|
||||
data:
|
||||
title: WATCHDOG — restarting core
|
||||
message: "Still stale after reload + 30 min: {{ hard_stale | join(', ') }}. Restarting HA core now."
|
||||
- action: homeassistant.restart
|
||||
|
||||
@@ -20,6 +20,21 @@ prometheus:
|
||||
lovelace:
|
||||
mode: storage
|
||||
dashboards:
|
||||
pfv-overview:
|
||||
mode: yaml
|
||||
title: Ops Overview
|
||||
filename: dashboards/overview.yaml
|
||||
icon: mdi:view-dashboard-outline
|
||||
pfv-power:
|
||||
mode: yaml
|
||||
title: Power
|
||||
filename: dashboards/power.yaml
|
||||
icon: mdi:flash
|
||||
pfv-thermal:
|
||||
mode: yaml
|
||||
title: Thermal
|
||||
filename: dashboards/thermal.yaml
|
||||
icon: mdi:thermometer-lines
|
||||
pfv-server-room:
|
||||
mode: yaml
|
||||
title: Server Room
|
||||
@@ -34,3 +49,10 @@ lovelace:
|
||||
title: STLP
|
||||
filename: dashboards/stlp.yaml
|
||||
icon: mdi:home
|
||||
|
||||
# Watchdog arm switch (#789): auto core restart on unresolved staleness.
|
||||
# Default OFF — reload_config_entry heals config-entry wedges without it.
|
||||
input_boolean:
|
||||
pfv_watchdog_auto_restart:
|
||||
name: Watchdog auto core restart
|
||||
icon: mdi:restart-alert
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
title: Ops Overview
|
||||
views:
|
||||
- title: Overview
|
||||
type: sections
|
||||
sections:
|
||||
- type: grid
|
||||
cards:
|
||||
- type: markdown
|
||||
content: >
|
||||
## Ops Overview
|
||||
|
||||
One screen: is anything on fire? Details live in
|
||||
[Power](/lovelace-pfv-power), [Thermal](/lovelace-pfv-thermal),
|
||||
[Server Room](/lovelace-pfv-server-room) and
|
||||
[STLP](/lovelace-pfv-stlp).
|
||||
- type: grid
|
||||
cards:
|
||||
- type: heading
|
||||
heading: Room & cooling
|
||||
- type: gauge
|
||||
entity: sensor.pfv_tsys1_rack_temperature
|
||||
name: Server room
|
||||
min: 50
|
||||
max: 110
|
||||
severity:
|
||||
green: 60
|
||||
yellow: 85
|
||||
red: 95
|
||||
- type: entities
|
||||
entities:
|
||||
- sensor.pfv_garage_temperature
|
||||
- sensor.pfv_artroom_temperature
|
||||
- type: grid
|
||||
cards:
|
||||
- type: heading
|
||||
heading: Power
|
||||
- type: gauge
|
||||
entity: sensor.tsys1_ups_battery_charge
|
||||
name: APC charge
|
||||
min: 0
|
||||
max: 100
|
||||
severity:
|
||||
green: 60
|
||||
yellow: 40
|
||||
red: 20
|
||||
- type: gauge
|
||||
entity: sensor.tsys1_triplite_battery_charge
|
||||
name: Tripp Lite charge
|
||||
min: 0
|
||||
max: 100
|
||||
severity:
|
||||
green: 60
|
||||
yellow: 40
|
||||
red: 20
|
||||
- type: entities
|
||||
entities:
|
||||
- sensor.tsys1_ups_status
|
||||
- sensor.tsys1_triplite_status
|
||||
- sensor.pfv_ups_runtime_minutes
|
||||
- sensor.pfv_tripp_lite_runtime_minutes
|
||||
- type: grid
|
||||
cards:
|
||||
- type: heading
|
||||
heading: Alerts & self-healing
|
||||
- type: entities
|
||||
entities:
|
||||
- automation.pfv_plant_ups_on_battery
|
||||
- automation.pfv_plant_ups_battery_low
|
||||
- automation.pfv_plant_ups_communication_lost
|
||||
- automation.pfv_plant_high_temperature
|
||||
- automation.pfv_watchdog_stale_entity_auto_heal
|
||||
- input_boolean.pfv_watchdog_auto_restart
|
||||
- type: grid
|
||||
cards:
|
||||
- type: markdown
|
||||
content: >
|
||||
**Pipeline:** deploy sensor tracks `release` on KNEL/KNELBMS.
|
||||
CI runs on every dev/release push (Actions tab in gitea).
|
||||
Out-of-band paging: Uptime Kuma (Cloudron).
|
||||
@@ -0,0 +1,114 @@
|
||||
title: Power
|
||||
views:
|
||||
- title: UPS detail
|
||||
type: sections
|
||||
sections:
|
||||
- type: grid
|
||||
cards:
|
||||
- type: heading
|
||||
heading: APC Smart-UPS C 1500 (battery-side USB HID)
|
||||
- type: gauge
|
||||
entity: sensor.tsys1_ups_battery_charge
|
||||
name: Charge
|
||||
min: 0
|
||||
max: 100
|
||||
severity:
|
||||
green: 60
|
||||
yellow: 40
|
||||
red: 20
|
||||
- type: gauge
|
||||
entity: sensor.pfv_ups_runtime_minutes
|
||||
name: Runtime (min)
|
||||
min: 0
|
||||
max: 60
|
||||
severity:
|
||||
green: 25
|
||||
yellow: 15
|
||||
red: 5
|
||||
- type: grid
|
||||
cards:
|
||||
- type: entities
|
||||
title: APC status
|
||||
entities:
|
||||
- sensor.tsys1_ups_status
|
||||
- sensor.tsys1_ups_status_data
|
||||
- sensor.tsys1_ups_battery_charge
|
||||
- sensor.pfv_ups_runtime_minutes
|
||||
- sensor.tsys1_ups_battery_runtime
|
||||
- sensor.tsys1_ups_battery_voltage
|
||||
- sensor.tsys1_ups_nominal_battery_voltage
|
||||
- sensor.tsys1_ups_low_battery_setpoint
|
||||
- sensor.tsys1_ups_beeper_status
|
||||
- type: markdown
|
||||
content: >
|
||||
Serial AS1213210423 · mfr 2012-03 · fw UPS 02.2 · 24 V
|
||||
nominal. USB HID exposes battery-side metrics only — no
|
||||
input/output voltage or watts on this unit.
|
||||
- type: grid
|
||||
cards:
|
||||
- type: heading
|
||||
heading: Tripp Lite 1500 VA (moved from tsys9, #372)
|
||||
- type: gauge
|
||||
entity: sensor.tsys1_triplite_battery_charge
|
||||
name: Charge
|
||||
min: 0
|
||||
max: 100
|
||||
severity:
|
||||
green: 60
|
||||
yellow: 40
|
||||
red: 20
|
||||
- type: gauge
|
||||
entity: sensor.tsys1_triplite_load
|
||||
name: Load %
|
||||
min: 0
|
||||
max: 100
|
||||
severity:
|
||||
green: 70
|
||||
yellow: 85
|
||||
red: 95
|
||||
- type: grid
|
||||
cards:
|
||||
- type: entities
|
||||
title: Tripp Lite status
|
||||
entities:
|
||||
- sensor.tsys1_triplite_status
|
||||
- sensor.tsys1_triplite_status_data
|
||||
- sensor.tsys1_triplite_battery_charge
|
||||
- sensor.tsys1_triplite_apparent_power
|
||||
- sensor.tsys1_triplite_output_current
|
||||
- sensor.tsys1_triplite_load
|
||||
- sensor.pfv_tripp_lite_runtime_minutes
|
||||
- sensor.tsys1_triplite_battery_runtime
|
||||
- sensor.tsys1_triplite_input_voltage
|
||||
- sensor.tsys1_triplite_output_voltage
|
||||
- sensor.tsys1_triplite_nominal_power
|
||||
- title: Trends
|
||||
type: sections
|
||||
sections:
|
||||
- type: grid
|
||||
cards:
|
||||
- type: history-graph
|
||||
title: UPS charge (7 days)
|
||||
entities:
|
||||
- sensor.tsys1_ups_battery_charge
|
||||
- sensor.tsys1_triplite_battery_charge
|
||||
hours_to_show: 168
|
||||
- type: grid
|
||||
cards:
|
||||
- type: history-graph
|
||||
title: Tripp Lite apparent power (24h)
|
||||
entities:
|
||||
- sensor.tsys1_triplite_apparent_power
|
||||
hours_to_show: 24
|
||||
- type: history-graph
|
||||
title: UPS load % (24h)
|
||||
entities:
|
||||
- sensor.tsys1_triplite_load
|
||||
hours_to_show: 24
|
||||
- type: grid
|
||||
cards:
|
||||
- type: markdown
|
||||
content: >
|
||||
**Planned sections** (land with their data sources):
|
||||
Emporia CT rental-bank feed (#623), whole-house kWh vs
|
||||
solar production (#760), garage PDU circuits (#733).
|
||||
@@ -103,6 +103,46 @@ views:
|
||||
fw UPS 02.2 · PbAc, 24 V nominal · usbhid-ups via pfv-tsys1
|
||||
(NUT :3493). This unit reports no input/output voltage over
|
||||
USB HID — battery-side metrics only.
|
||||
- type: grid
|
||||
cards:
|
||||
- type: gauge
|
||||
entity: sensor.tsys1_triplite_battery_charge
|
||||
name: Tripp Lite charge
|
||||
min: 0
|
||||
max: 100
|
||||
severity:
|
||||
green: 60
|
||||
yellow: 40
|
||||
red: 20
|
||||
- type: gauge
|
||||
entity: sensor.tsys1_triplite_load
|
||||
name: Tripp Lite load %
|
||||
min: 0
|
||||
max: 100
|
||||
severity:
|
||||
green: 70
|
||||
yellow: 85
|
||||
red: 95
|
||||
- type: grid
|
||||
cards:
|
||||
- type: entities
|
||||
title: Tripp Lite status
|
||||
entities:
|
||||
- sensor.tsys1_triplite_status
|
||||
- sensor.tsys1_triplite_status_data
|
||||
- sensor.tsys1_triplite_battery_charge
|
||||
- sensor.tsys1_triplite_apparent_power
|
||||
- sensor.tsys1_triplite_output_current
|
||||
- sensor.tsys1_triplite_load
|
||||
- sensor.tsys1_triplite_input_voltage
|
||||
- sensor.tsys1_triplite_output_voltage
|
||||
- sensor.tsys1_triplite_battery_runtime
|
||||
- type: markdown
|
||||
content: >
|
||||
**Tripp Lite UPS** · usbhid-ups via pfv-tsys1 (NUT :3493);
|
||||
moved from tsys9 2026-09-04 (#372). Carries ~46% load
|
||||
(~414 VA apparent / 1500 VA nominal) — mind runtime
|
||||
before adding gear.
|
||||
- type: grid
|
||||
cards:
|
||||
- type: entities
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
title: Thermal
|
||||
views:
|
||||
- title: Rooms
|
||||
type: sections
|
||||
sections:
|
||||
- type: grid
|
||||
cards:
|
||||
- type: gauge
|
||||
entity: sensor.pfv_tsys1_rack_temperature
|
||||
name: Server room
|
||||
min: 50
|
||||
max: 110
|
||||
severity:
|
||||
green: 60
|
||||
yellow: 85
|
||||
red: 95
|
||||
- type: history-graph
|
||||
title: Server room (24h)
|
||||
entities:
|
||||
- sensor.pfv_tsys1_rack_temperature
|
||||
hours_to_show: 24
|
||||
- type: grid
|
||||
cards:
|
||||
- type: gauge
|
||||
entity: sensor.pfv_garage_temperature
|
||||
name: Garage
|
||||
min: 30
|
||||
max: 110
|
||||
severity:
|
||||
green: 40
|
||||
yellow: 90
|
||||
red: 100
|
||||
- type: history-graph
|
||||
title: Garage (24h)
|
||||
entities:
|
||||
- sensor.pfv_garage_temperature
|
||||
hours_to_show: 24
|
||||
- type: grid
|
||||
cards:
|
||||
- type: gauge
|
||||
entity: sensor.pfv_artroom_temperature
|
||||
name: Art room
|
||||
min: 40
|
||||
max: 110
|
||||
severity:
|
||||
green: 60
|
||||
yellow: 85
|
||||
red: 95
|
||||
- type: history-graph
|
||||
title: Art room (24h)
|
||||
entities:
|
||||
- sensor.pfv_artroom_temperature
|
||||
hours_to_show: 24
|
||||
- type: grid
|
||||
cards:
|
||||
- type: markdown
|
||||
content: >
|
||||
**Minisplit** — climate entity lands with the Carrier dongle
|
||||
commissioning (#629). Manual setpoint tonight: 62°F, with the
|
||||
oscillating fan improving distribution. Alert tiers:
|
||||
ambient 104°F · drives 140°F · NVMe 150°F · PCH 160°F ·
|
||||
GPU 185°F.
|
||||
- title: Racks & silicon
|
||||
type: sections
|
||||
sections:
|
||||
- type: grid
|
||||
cards:
|
||||
- type: entities
|
||||
title: R3 · PowerEdge (DRAC)
|
||||
entities:
|
||||
- sensor.pfv_tsys6_cpu_temperature
|
||||
- sensor.pfv_tsys6_drac_ambient
|
||||
- sensor.pfv_tsys7_cpu_temperature
|
||||
- sensor.pfv_tsys7_drac_inlet
|
||||
- sensor.pfv_tsys7_drac_exhaust
|
||||
- sensor.pfv_tsys7_drac_cpu1
|
||||
- sensor.pfv_tsys7_drac_cpu2
|
||||
- type: grid
|
||||
cards:
|
||||
- type: entities
|
||||
title: R3 · Pis + Jetson
|
||||
entities:
|
||||
- sensor.pfv_svrpi_temperature
|
||||
- sensor.pfv_subopi3_temperature
|
||||
- sensor.pfv_subopi_dev_3_temperature
|
||||
- sensor.pfv_subopi_dev_4_temperature
|
||||
- sensor.pfv_jetson_temperature
|
||||
- sensor.pfv_jetson_gpu_temperature
|
||||
- type: grid
|
||||
cards:
|
||||
- type: entities
|
||||
title: R5 · Laptops
|
||||
entities:
|
||||
- sensor.pfv_tsys3_cpu_temperature
|
||||
- sensor.pfv_tsys3_gpu_temperature
|
||||
- sensor.pfv_tsys3_nvme_temperature
|
||||
- sensor.pfv_tsys5_nvme_temperature
|
||||
- type: grid
|
||||
cards:
|
||||
- type: entities
|
||||
title: R5 · Desktops / OptiPlex-class
|
||||
entities:
|
||||
- sensor.pfv_tsys1_rack_temperature
|
||||
- sensor.pfv_tsys4_ambient_temperature
|
||||
- sensor.pfv_tsys1_cpu_temperature
|
||||
- sensor.pfv_tsys4_cpu_temperature
|
||||
- sensor.pfv_tsys9_cpu_temperature
|
||||
- sensor.pfv_tsys9_pch_temperature
|
||||
- type: grid
|
||||
cards:
|
||||
- type: history-graph
|
||||
title: CPU temperatures (24h)
|
||||
entities:
|
||||
- sensor.pfv_tsys1_cpu_temperature
|
||||
- sensor.pfv_tsys3_cpu_temperature
|
||||
- sensor.pfv_tsys6_cpu_temperature
|
||||
- sensor.pfv_tsys7_cpu_temperature
|
||||
- sensor.pfv_tsys9_cpu_temperature
|
||||
hours_to_show: 24
|
||||
@@ -1,4 +1,4 @@
|
||||
# Deploy pipeline [#734]: gitea master-HEAD sha watcher. Feeds the
|
||||
# Deploy pipeline [#734]: gitea release-branch sha watcher. Feeds the
|
||||
# sha-watch deploy automation (reload-only apply). The gitea webhook
|
||||
# (gitea hook #10 -> pfv_deploy_git_webhook automation) is the fast path
|
||||
# with full restart mapping, but gitea cannot deliver webhooks to
|
||||
@@ -6,15 +6,15 @@
|
||||
# (founder action via Cloudron file manager); this watcher is the
|
||||
# always-works safety net.
|
||||
rest:
|
||||
- resource: https://git.knownelement.com/api/v1/repos/KNEL/pfv-bms/commits?limit=1
|
||||
- resource: https://git.knownelement.com/api/v1/repos/KNEL/KNELBMS/commits?sha=release&limit=1
|
||||
headers:
|
||||
Authorization: !secret gitea_auth_header
|
||||
User-Agent: pfv-bms-deploy-watch
|
||||
scan_interval: 300
|
||||
verify_ssl: true
|
||||
sensor:
|
||||
- name: PFV deploy gitea master head
|
||||
unique_id: pfv_deploy_gitea_master_head
|
||||
- name: PFV deploy gitea release head
|
||||
unique_id: pfv_deploy_gitea_release_head
|
||||
value_template: "{{ value_json[0].sha if value_json is list and value_json | length > 0 else (value_json | string | truncate(90)) }}"
|
||||
|
||||
# E2E self-deploy proof commit [#734] - if you can read this in /config,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
# PR gate). The roster is fetched live from the gitea API — NO git
|
||||
# mechanics, NO submodule (git_pull add-on lacks submodule support),
|
||||
# NO local copy: access-roster remains the single source of truth.
|
||||
# Reader/door contract (webhook JSON + unlock endpoint): KNEL/doorman README
|
||||
#
|
||||
# UNLOCK PATH (founder-approved 2026-09-03): valid badge + the
|
||||
# doorman_unlock_enabled input_boolean ON -> rest_command fires the
|
||||
|
||||
@@ -800,6 +800,13 @@ template:
|
||||
state: >-
|
||||
{% set v = states('sensor.tsys1_ups_battery_runtime') %}
|
||||
{{ (v | float / 60) | round(0) if is_number(v) else none }}
|
||||
- name: "PFV Tripp Lite runtime minutes"
|
||||
unique_id: pfv_triplite_runtime_minutes
|
||||
device_class: duration
|
||||
unit_of_measurement: "min"
|
||||
state: >-
|
||||
{% set v = states('sensor.tsys1_triplite_battery_runtime') %}
|
||||
{{ (v | float / 60) | round(0) if is_number(v) else none }}
|
||||
- name: "PFV tsys1 drive temperature"
|
||||
unique_id: pfv_tsys1_drive_temperature
|
||||
device_class: temperature
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# intentionally empty — scenes are authored in the HA UI if ever needed
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# intentionally empty — scripts are authored in the HA UI if ever needed
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
# SDLC regression tests (#778) — run in CI (see .gitea/workflows/ci.yml).
|
||||
# Each test guards a rule that exists because of a real incident or ruling.
|
||||
set -uo pipefail
|
||||
cd "$(dirname "$0")/.." || exit 1
|
||||
|
||||
FAIL=0
|
||||
t() {
|
||||
# $1 = label, $2 = shell command string
|
||||
if eval "$2"; then
|
||||
echo " OK $1"
|
||||
else
|
||||
echo " FAIL $1"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
echo "=== alert twins (every alert ships a recovery twin) ==="
|
||||
t "utility power restored twin" \
|
||||
"grep -q 'id: pfv_plant_ups_power_restored' automations.yaml"
|
||||
t "temperature recovered twin" \
|
||||
"grep -q 'id: pfv_plant_temperature_recovered' automations.yaml"
|
||||
|
||||
echo "=== alert deep links (taps land on dashboards, #344) ==="
|
||||
ALERTS="POWER OUTAGE|UPS BATTERY LOW|UPS COMMS LOST|UPS RUNTIME LOW|Power restored|HIGH TEMPERATURE|SENSOR WATCHDOG|TEMP RISING FAST|TEMPERATURE RECOVERED"
|
||||
DL_OK=1
|
||||
IFS='|' read -ra ALERT_ARR <<< "$ALERTS"
|
||||
for a in "${ALERT_ARR[@]}"; do
|
||||
if ! grep -A2 "title: $a$" automations.yaml | grep -q "clickAction:"; then
|
||||
echo " (missing deep link: $a)"
|
||||
DL_OK=0
|
||||
fi
|
||||
done
|
||||
t "all alerts deep-linked" "[ \"$DL_OK\" -eq 1 ]"
|
||||
|
||||
echo "=== watchdog coverage (#789) ==="
|
||||
t "watchdog watches the deploy sensor" \
|
||||
"grep -q 'sensor.pfv_deploy_gitea_release_head, 30' automations.yaml"
|
||||
t "watchdog automation exists" \
|
||||
"grep -q 'id: pfv_watchdog_stale_entities' automations.yaml"
|
||||
|
||||
echo "=== ghost entities (dead references never return) ==="
|
||||
t "no tsys9_ups ghost refs" \
|
||||
"! grep -rq 'tsys9_ups' automations.yaml packages/ dashboards/"
|
||||
|
||||
echo "=== secrets ==="
|
||||
t "no private keys in tree" \
|
||||
"! grep -rInE 'BEGIN (RSA |OPENSSH |EC |DSA )?PRIVATE KEY' --exclude-dir=.git --exclude-dir=docs ."
|
||||
|
||||
echo "=== SDLC SUITE: $FAIL failures ==="
|
||||
exit "$FAIL"
|
||||
@@ -0,0 +1,2 @@
|
||||
# intentional placeholder: themes dir must exist for the
|
||||
# !include_dir_merge_named in configuration.yaml [#784 gardening]
|
||||
Reference in New Issue
Block a user