grafana: dashboard bundle + dev-stack integration
Adds a self-contained omni-pca/grafana/ bundle (InfluxDB v2 + Grafana with pre-provisioned datasource and dashboard) plus dev-stack wiring so iterating against the mock or real panel is one docker-compose-up. The dashboard has four rows plus an insights row: System health AC, battery, trouble, 24h event count Security area arming state, recent events table, zone trips Climate thermostat temperatures, HVAC mode Activity event rate by type, top toggled units Insights active zone bypasses, button press log, event distribution Color-coded event_type tags persist across panels (alarms red, restores green, batteries orange, etc.); explicit no-purple palette per CLAUDE.md. The bundle is portable: any HA install can use it by running grafana/ docker compose up -d and pasting ha-snippet.yaml into configuration.yaml. For the dev stack, dev/docker-compose.yml mounts the same provisioning files so dev and prod stay in lockstep. Verified end-to-end against the real Our House.pca panel (192.168.1.9): the dashboard fills with live zone trips, X-10 unit toggles, and push-event traffic within 30s of HA bootup.
This commit is contained in:
parent
9726ee36bb
commit
4ba8c2043e
@ -70,6 +70,49 @@ OMNI_PCA_FIXTURE_KEY=0xC1A280B2 # or --pca-key on the command line
|
||||
buttons, programs, model byte, and firmware version from the file —
|
||||
everything the HA integration reads at discovery time.
|
||||
|
||||
## Time-series & dashboards
|
||||
|
||||
`docker compose up -d` also brings up **InfluxDB v2** (port 8086) and
|
||||
**Grafana** (port 3000). Open Grafana at <http://localhost:3000>
|
||||
(login: `admin` / `$GRAFANA_PASSWORD` from `.env`) — the **Omni Pro II
|
||||
— Panel Overview** dashboard loads automatically, pre-provisioned from
|
||||
[`../grafana/`](../grafana/), the shipping bundle.
|
||||
|
||||
To wire HA → InfluxDB, append this block to `ha-config/configuration.yaml`
|
||||
(the directory is gitignored because it contains HA auth/state; the
|
||||
block lives in `../grafana/ha-snippet.yaml` for production users):
|
||||
|
||||
```yaml
|
||||
influxdb:
|
||||
api_version: 2
|
||||
host: influxdb
|
||||
port: 8086
|
||||
ssl: false
|
||||
verify_ssl: false
|
||||
token: dev-token-omnipca-9472-fixed-for-dev-stack
|
||||
organization: omni-pca
|
||||
bucket: ha
|
||||
precision: s
|
||||
tags_attributes: [event_type, event_class]
|
||||
include:
|
||||
domains: [alarm_control_panel, binary_sensor, climate, event, light, sensor, switch]
|
||||
entity_globs: ["*omni*"]
|
||||
```
|
||||
|
||||
Restart HA (`docker compose restart homeassistant`) after editing.
|
||||
Within 30 seconds, panels start populating with live data.
|
||||
|
||||
The dashboard JSON in `../grafana/provisioning/dashboards/` is the
|
||||
source of truth; edits in the Grafana UI don't persist (provisioned
|
||||
dashboards are read-only). Iterate by editing the JSON and running
|
||||
`docker compose restart grafana` — the provisioner picks up changes
|
||||
within ~30s.
|
||||
|
||||
To exercise dashboard panels against the mock, trigger HA actions
|
||||
(arm an area, toggle a light): the mock pushes the resulting
|
||||
`SystemEvent` back to HA, which ships it to InfluxDB, which Grafana
|
||||
queries. Each step takes <1s.
|
||||
|
||||
## Notes
|
||||
|
||||
- The HA container mounts `../custom_components/omni_pca/` read-only, so
|
||||
|
||||
BIN
dev/artifacts/screenshots/2026-05-17/grafana-iter-final.png
Normal file
BIN
dev/artifacts/screenshots/2026-05-17/grafana-iter-final.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 259 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 281 KiB |
@ -102,6 +102,74 @@ services:
|
||||
pip install --quiet --no-deps --upgrade /opt/omni-pca-src
|
||||
exec /init
|
||||
|
||||
# InfluxDB v2 + Grafana stack — kept inline rather than `extends:`-ing
|
||||
# ../grafana/docker-compose.yml so this file stays self-contained and
|
||||
# the named volumes get scoped to this compose project. The bundle
|
||||
# compose stays the canonical ship-to-users version; we share its
|
||||
# provisioning files via the volume mount on the grafana service.
|
||||
influxdb:
|
||||
image: influxdb:2.7-alpine
|
||||
container_name: omni-pca-dev-influxdb
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
DOCKER_INFLUXDB_INIT_MODE: setup
|
||||
DOCKER_INFLUXDB_INIT_USERNAME: ${INFLUX_USERNAME:-admin}
|
||||
DOCKER_INFLUXDB_INIT_PASSWORD: ${INFLUX_PASSWORD}
|
||||
DOCKER_INFLUXDB_INIT_ORG: omni-pca
|
||||
DOCKER_INFLUXDB_INIT_BUCKET: ha
|
||||
DOCKER_INFLUXDB_INIT_ADMIN_TOKEN: ${INFLUX_TOKEN}
|
||||
DOCKER_INFLUXDB_INIT_RETENTION: 30d
|
||||
volumes:
|
||||
- influxdb-data:/var/lib/influxdb2
|
||||
- influxdb-config:/etc/influxdb2
|
||||
ports:
|
||||
- "8086:8086"
|
||||
networks:
|
||||
- default
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8086/health"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:11.4.0
|
||||
container_name: omni-pca-dev-grafana
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
influxdb:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD}
|
||||
GF_AUTH_ANONYMOUS_ENABLED: "false"
|
||||
GF_USERS_ALLOW_SIGN_UP: "false"
|
||||
GF_LOG_LEVEL: warn
|
||||
INFLUX_URL: http://influxdb:8086
|
||||
INFLUX_TOKEN: ${INFLUX_TOKEN}
|
||||
volumes:
|
||||
- grafana-data:/var/lib/grafana
|
||||
- ../grafana/provisioning:/etc/grafana/provisioning:ro
|
||||
ports:
|
||||
- "3000:3000"
|
||||
networks:
|
||||
- default
|
||||
- caddy
|
||||
labels:
|
||||
caddy: grafana-omni.juliet.warehack.ing
|
||||
caddy.reverse_proxy: "{{upstreams 3000}}"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3000/api/health || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
start_period: 15s
|
||||
|
||||
volumes:
|
||||
influxdb-data:
|
||||
influxdb-config:
|
||||
grafana-data:
|
||||
|
||||
networks:
|
||||
caddy:
|
||||
external: true
|
||||
|
||||
19
grafana/.env.example
Normal file
19
grafana/.env.example
Normal file
@ -0,0 +1,19 @@
|
||||
# Copy to .env and fill in. Both files in this directory load .env
|
||||
# automatically via docker compose; ./env.example is committed, .env
|
||||
# is gitignored.
|
||||
|
||||
# InfluxDB v2 admin user (created on first boot).
|
||||
INFLUX_USERNAME=admin
|
||||
INFLUX_PASSWORD=change-me-strong-password-here
|
||||
|
||||
# Admin token used by Home Assistant (writes) and Grafana (reads).
|
||||
# Generate one with: openssl rand -hex 32
|
||||
INFLUX_TOKEN=replace-with-a-real-token-from-openssl-rand-hex-32
|
||||
|
||||
# Grafana admin password (UI login as "admin"/this value).
|
||||
GRAFANA_PASSWORD=change-me-too
|
||||
|
||||
# Public hostnames if you're putting either service behind a reverse
|
||||
# proxy. Leave blank for localhost-only access.
|
||||
INFLUX_PUBLIC_HOST=
|
||||
GRAFANA_PUBLIC_HOST=
|
||||
129
grafana/README.md
Normal file
129
grafana/README.md
Normal file
@ -0,0 +1,129 @@
|
||||
# Grafana dashboard for omni_pca
|
||||
|
||||
InfluxDB v2 + Grafana stack pre-provisioned to visualise an HAI/Leviton
|
||||
Omni Pro II panel via the `omni_pca` Home Assistant integration.
|
||||
Drop-in for any existing HA install — no integration changes required.
|
||||
|
||||

|
||||
|
||||
## What you get
|
||||
|
||||
One dashboard, four rows:
|
||||
|
||||
- **System health** — AC power, backup battery, system trouble, event count (24h).
|
||||
- **Security** — area arming state timeline, recent push-event log, zone trip timeline.
|
||||
- **Climate** — per-thermostat current temperatures + setpoints, HVAC mode timeline.
|
||||
- **Activity** — event rate by typed event class, unit brightness heatmap.
|
||||
|
||||
Data flows: HA entity state → HA's `influxdb:` integration → InfluxDB
|
||||
v2 bucket → Grafana Flux queries → dashboard panels.
|
||||
|
||||
## Quick start (~5 minutes)
|
||||
|
||||
```bash
|
||||
cd grafana/
|
||||
cp .env.example .env
|
||||
# Edit .env — set strong INFLUX_PASSWORD, INFLUX_TOKEN, GRAFANA_PASSWORD.
|
||||
# Generate the token with: openssl rand -hex 32
|
||||
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Wait ~30 seconds. InfluxDB does first-boot setup (creates the
|
||||
`omni-pca` org, `ha` bucket, admin token); Grafana then auto-provisions
|
||||
the InfluxDB datasource and the dashboard.
|
||||
|
||||
Then add the influxdb integration to your Home Assistant config:
|
||||
|
||||
```bash
|
||||
# Paste the contents of ha-snippet.yaml into your configuration.yaml.
|
||||
# Add `influxdb_token: <your INFLUX_TOKEN from .env>` to your secrets.yaml.
|
||||
# Restart HA.
|
||||
```
|
||||
|
||||
Within ~30 seconds you should see real-time data populating the
|
||||
dashboard at <http://localhost:3000> (login: `admin` / your
|
||||
`GRAFANA_PASSWORD`).
|
||||
|
||||
## Networking notes
|
||||
|
||||
The default `ha-snippet.yaml` assumes HA and InfluxDB sit on the same
|
||||
docker network and HA can reach `influxdb:8086` by container name.
|
||||
Three common variants:
|
||||
|
||||
| HA layout | `host:` value |
|
||||
|---|---|
|
||||
| Same compose stack as this bundle | `influxdb` |
|
||||
| HA on the host, InfluxDB in docker | `host.docker.internal` or your LAN IP |
|
||||
| Different machine entirely | the InfluxDB host's IP / FQDN |
|
||||
|
||||
If you put either service behind a reverse proxy with TLS, set `ssl:
|
||||
true` in the HA snippet and supply the public hostname.
|
||||
|
||||
## Iterating on the dashboard
|
||||
|
||||
The dashboard JSON at `provisioning/dashboards/omni-pro-ii.json` is
|
||||
loaded read-only by the provisioner. To change it:
|
||||
|
||||
1. Edit the JSON directly, then `docker compose restart grafana`
|
||||
(provisioner picks up changes within ~30s).
|
||||
2. Or use the Grafana UI to experiment, then **Dashboard settings →
|
||||
JSON Model → Save to file** and overwrite the file in this repo.
|
||||
|
||||
Provisioned dashboards can't be saved from the UI by design — this is
|
||||
intentional, so the file on disk stays the source of truth.
|
||||
|
||||
## Extending coverage
|
||||
|
||||
The bundle is scoped to the `omni_pca` entity surface via the
|
||||
`entity_globs: ["*omni*"]` filter in `ha-snippet.yaml`. Drop that
|
||||
filter (or add a second `include:` block) if you want to graph other
|
||||
HA entities alongside omni data — Grafana's datasource is general
|
||||
InfluxDB v2, nothing in the dashboard JSON hard-codes omni-specific
|
||||
field names beyond what you'd want to scope to anyway.
|
||||
|
||||
A few panel ideas not yet shipped:
|
||||
|
||||
- Alarm activation drill-down — filter the event log to
|
||||
`event_type == "alarm_activated"` and show the `alarm_type`
|
||||
(Burglary / Fire / Auxiliary / …) distribution.
|
||||
- Zone trip rate histogram — `binary_sensor` zone changes per zone
|
||||
per hour, useful for spotting flaky sensors.
|
||||
- Comm health — track integration coordinator state via the panel
|
||||
device's "Comm error" attribute.
|
||||
|
||||
## Files in this bundle
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `docker-compose.yml` | InfluxDB v2 + Grafana services |
|
||||
| `.env.example` | Required environment template |
|
||||
| `ha-snippet.yaml` | HA configuration.yaml additions |
|
||||
| `provisioning/datasources/influxdb.yml` | Auto-wires the datasource |
|
||||
| `provisioning/dashboards/dashboards.yml` | Provisioner config |
|
||||
| `provisioning/dashboards/omni-pro-ii.json` | The dashboard JSON |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"No data" in panels.** Most panels need either continuous state
|
||||
updates (climate, security) or push events (event-driven panels).
|
||||
Verify HA is shipping data:
|
||||
|
||||
```bash
|
||||
docker exec -it omni-pca-influxdb influx query \
|
||||
'from(bucket:"ha") |> range(start:-5m) |> limit(n:5)' \
|
||||
--token "$INFLUX_TOKEN" --org omni-pca
|
||||
```
|
||||
|
||||
If this returns rows, the pipeline is healthy and panels will fill in
|
||||
as the panel does interesting things. If it's empty, check HA logs for
|
||||
`[homeassistant.components.influxdb]` errors.
|
||||
|
||||
**Dashboard didn't auto-load.** Check `docker logs omni-pca-grafana
|
||||
2>&1 | grep -i provision` — provisioner errors show up there.
|
||||
|
||||
**Stat panels show duplicate values.** Your HA has multiple entities
|
||||
matching the regex (e.g. `omni_pro_ii_ac_power` AND
|
||||
`omni_pro_ii_ac_power_2` from prior integration reloads). Clean up the
|
||||
duplicates in HA's entity registry, or tighten the filter in the
|
||||
dashboard JSON.
|
||||
69
grafana/docker-compose.yml
Normal file
69
grafana/docker-compose.yml
Normal file
@ -0,0 +1,69 @@
|
||||
# Self-contained InfluxDB v2 + Grafana stack for the omni_pca
|
||||
# integration. Pre-provisioned with the InfluxDB datasource and the
|
||||
# "Omni Pro II — Panel Overview" dashboard.
|
||||
#
|
||||
# Usage:
|
||||
# cp .env.example .env && edit the secrets && docker compose up -d
|
||||
# open http://localhost:3000 (admin / $GRAFANA_PASSWORD)
|
||||
#
|
||||
# Then paste the contents of ha-snippet.yaml into your HA
|
||||
# configuration.yaml (and add `influxdb_token: $INFLUX_TOKEN` to
|
||||
# secrets.yaml). Restart HA. Within 30s the dashboard's panels start
|
||||
# filling in.
|
||||
|
||||
services:
|
||||
influxdb:
|
||||
image: influxdb:2.7-alpine
|
||||
container_name: omni-pca-influxdb
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
DOCKER_INFLUXDB_INIT_MODE: setup
|
||||
DOCKER_INFLUXDB_INIT_USERNAME: ${INFLUX_USERNAME:-admin}
|
||||
DOCKER_INFLUXDB_INIT_PASSWORD: ${INFLUX_PASSWORD}
|
||||
DOCKER_INFLUXDB_INIT_ORG: omni-pca
|
||||
DOCKER_INFLUXDB_INIT_BUCKET: ha
|
||||
DOCKER_INFLUXDB_INIT_ADMIN_TOKEN: ${INFLUX_TOKEN}
|
||||
DOCKER_INFLUXDB_INIT_RETENTION: 30d
|
||||
volumes:
|
||||
- influxdb-data:/var/lib/influxdb2
|
||||
- influxdb-config:/etc/influxdb2
|
||||
ports:
|
||||
- "8086:8086"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8086/health"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:11.4.0
|
||||
container_name: omni-pca-grafana
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
influxdb:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD}
|
||||
GF_AUTH_ANONYMOUS_ENABLED: "false"
|
||||
GF_USERS_ALLOW_SIGN_UP: "false"
|
||||
GF_LOG_LEVEL: warn
|
||||
# Consumed by ./provisioning/datasources/influxdb.yml
|
||||
INFLUX_URL: http://influxdb:8086
|
||||
INFLUX_TOKEN: ${INFLUX_TOKEN}
|
||||
volumes:
|
||||
- grafana-data:/var/lib/grafana
|
||||
- ./provisioning:/etc/grafana/provisioning:ro
|
||||
ports:
|
||||
- "3000:3000"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3000/api/health || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
start_period: 15s
|
||||
|
||||
volumes:
|
||||
influxdb-data:
|
||||
influxdb-config:
|
||||
grafana-data:
|
||||
51
grafana/ha-snippet.yaml
Normal file
51
grafana/ha-snippet.yaml
Normal file
@ -0,0 +1,51 @@
|
||||
# Paste this block into your Home Assistant configuration.yaml.
|
||||
#
|
||||
# Prerequisites:
|
||||
# 1. The grafana stack from this directory is running:
|
||||
# cd grafana/ && cp .env.example .env && docker compose up -d
|
||||
# 2. Your HA instance can reach the influxdb container on port 8086.
|
||||
# Common patterns:
|
||||
# - HA and InfluxDB on the same compose stack: use host=influxdb
|
||||
# - HA and InfluxDB on different hosts: use host=<your-influx-ip>
|
||||
# - HA on the host network, InfluxDB in docker: use
|
||||
# host=host.docker.internal or the host's LAN IP
|
||||
# 3. Add `influxdb_token: <your INFLUX_TOKEN from .env>` to your
|
||||
# secrets.yaml. Restart HA after editing both files.
|
||||
#
|
||||
# What this ships:
|
||||
# - All state changes from omni_pca entities (alarm_control_panel,
|
||||
# binary_sensor, climate, event, light, sensor, switch).
|
||||
# - Event entity attributes carried as fields, including the typed
|
||||
# event_class and event_data payload — so Flux queries can filter
|
||||
# by alarm_type, zone_index, etc.
|
||||
#
|
||||
# Adjust the entity_globs filter if you also want non-omni entities in
|
||||
# the dashboard, or tighten it further to scope by area / device.
|
||||
|
||||
influxdb:
|
||||
api_version: 2
|
||||
host: influxdb # change to match your network layout
|
||||
port: 8086
|
||||
ssl: false
|
||||
verify_ssl: false
|
||||
token: !secret influxdb_token
|
||||
organization: omni-pca
|
||||
bucket: ha
|
||||
precision: s
|
||||
|
||||
# Tag the typed event kind so Flux queries can filter by it cheaply.
|
||||
tags_attributes:
|
||||
- event_type
|
||||
- event_class
|
||||
|
||||
include:
|
||||
domains:
|
||||
- alarm_control_panel
|
||||
- binary_sensor
|
||||
- climate
|
||||
- event
|
||||
- light
|
||||
- sensor
|
||||
- switch
|
||||
entity_globs:
|
||||
- "*omni*" # scope to omni_pca entities only
|
||||
19
grafana/provisioning/dashboards/dashboards.yml
Normal file
19
grafana/provisioning/dashboards/dashboards.yml
Normal file
@ -0,0 +1,19 @@
|
||||
# Tells Grafana to scan /etc/grafana/provisioning/dashboards for
|
||||
# *.json dashboard files at boot. Picks up omni-pro-ii.json
|
||||
# automatically. Dashboards loaded this way are read-only in the UI;
|
||||
# the source of truth is the JSON in this directory.
|
||||
|
||||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: omni-pca
|
||||
orgId: 1
|
||||
folder: ''
|
||||
folderUid: ''
|
||||
type: file
|
||||
disableDeletion: false
|
||||
updateIntervalSeconds: 30
|
||||
allowUiUpdates: false
|
||||
options:
|
||||
path: /etc/grafana/provisioning/dashboards
|
||||
foldersFromFilesStructure: false
|
||||
682
grafana/provisioning/dashboards/omni-pro-ii.json
Normal file
682
grafana/provisioning/dashboards/omni-pro-ii.json
Normal file
@ -0,0 +1,682 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {"type": "grafana", "uid": "-- Grafana --"},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Live view of an HAI/Leviton Omni Pro II panel surfaced by the omni_pca Home Assistant integration. System health, security activity, climate trends, and the typed push-event stream — all sourced from InfluxDB writes shipped by HA's influxdb integration.",
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 1,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"liveNow": false,
|
||||
"panels": [
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {"h": 1, "w": 24, "x": 0, "y": 0},
|
||||
"id": 100,
|
||||
"panels": [],
|
||||
"title": "System health",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {"mode": "thresholds"},
|
||||
"mappings": [
|
||||
{"options": {"0": {"color": "red", "index": 0, "text": "LOST"}}, "type": "value"},
|
||||
{"options": {"1": {"color": "green", "index": 1, "text": "OK"}}, "type": "value"}
|
||||
],
|
||||
"thresholds": {"mode": "absolute", "steps": [{"color": "red"}, {"color": "green", "value": 1}]},
|
||||
"unit": "none"
|
||||
}
|
||||
},
|
||||
"gridPos": {"h": 5, "w": 6, "x": 0, "y": 1},
|
||||
"id": 101,
|
||||
"options": {
|
||||
"colorMode": "background",
|
||||
"graphMode": "none",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "11.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"query": "from(bucket: \"ha\")\n |> range(start: -24h)\n |> filter(fn: (r) => r.domain == \"binary_sensor\")\n |> filter(fn: (r) => r.entity_id =~ /ac_power/ and not (r.entity_id =~ /_[0-9]+$/))\n |> filter(fn: (r) => r._field == \"value\")\n |> last()\n |> group()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "AC power",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {"mode": "thresholds"},
|
||||
"mappings": [
|
||||
{"options": {"0": {"color": "green", "index": 0, "text": "OK"}}, "type": "value"},
|
||||
{"options": {"1": {"color": "red", "index": 1, "text": "LOW"}}, "type": "value"}
|
||||
],
|
||||
"thresholds": {"mode": "absolute", "steps": [{"color": "green"}, {"color": "red", "value": 1}]}
|
||||
}
|
||||
},
|
||||
"gridPos": {"h": 5, "w": 6, "x": 6, "y": 1},
|
||||
"id": 102,
|
||||
"options": {
|
||||
"colorMode": "background",
|
||||
"graphMode": "none",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "11.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"query": "from(bucket: \"ha\")\n |> range(start: -24h)\n |> filter(fn: (r) => r.domain == \"binary_sensor\")\n |> filter(fn: (r) => r.entity_id =~ /battery/ and not (r.entity_id =~ /_[0-9]+$/))\n |> filter(fn: (r) => r._field == \"value\")\n |> last()\n |> group()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Backup battery",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {"mode": "thresholds"},
|
||||
"mappings": [
|
||||
{"options": {"0": {"color": "green", "index": 0, "text": "Clear"}}, "type": "value"},
|
||||
{"options": {"1": {"color": "red", "index": 1, "text": "Trouble"}}, "type": "value"}
|
||||
],
|
||||
"thresholds": {"mode": "absolute", "steps": [{"color": "green"}, {"color": "red", "value": 1}]}
|
||||
}
|
||||
},
|
||||
"gridPos": {"h": 5, "w": 6, "x": 12, "y": 1},
|
||||
"id": 103,
|
||||
"options": {
|
||||
"colorMode": "background",
|
||||
"graphMode": "none",
|
||||
"reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "11.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"query": "from(bucket: \"ha\")\n |> range(start: -24h)\n |> filter(fn: (r) => r.domain == \"binary_sensor\")\n |> filter(fn: (r) => r.entity_id =~ /trouble/ and not (r.entity_id =~ /_[0-9]+$/))\n |> filter(fn: (r) => r._field == \"value\")\n |> last()\n |> group()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "System trouble",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"description": "Count of panel push events in the last 24 hours. Empty until the panel pushes its first event (the mock fires events when HA actions trigger panel state changes; a real panel pushes continuously).",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {"mode": "thresholds"},
|
||||
"thresholds": {"mode": "absolute", "steps": [{"color": "blue"}, {"color": "green", "value": 1}]},
|
||||
"unit": "short"
|
||||
}
|
||||
},
|
||||
"gridPos": {"h": 5, "w": 6, "x": 18, "y": 1},
|
||||
"id": 104,
|
||||
"options": {
|
||||
"colorMode": "background",
|
||||
"graphMode": "area",
|
||||
"reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "11.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"query": "from(bucket: \"ha\")\n |> range(start: -24h)\n |> filter(fn: (r) => r.domain == \"event\")\n |> filter(fn: (r) => r._field == \"state\")\n |> group()\n |> count()",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Events (24h)",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {"h": 1, "w": 24, "x": 0, "y": 6},
|
||||
"id": 200,
|
||||
"panels": [],
|
||||
"title": "Security",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"description": "Arming state per area. Disarmed = green, day = teal, night = blue, away = orange, vacation = magenta, triggered = red, arming/pending = yellow.",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {"mode": "thresholds"},
|
||||
"custom": {"fillOpacity": 80, "lineWidth": 0},
|
||||
"mappings": [
|
||||
{"options": {"disarmed": {"color": "#43aa8b", "text": "disarmed"}}, "type": "value"},
|
||||
{"options": {"armed_home": {"color": "#577590", "text": "armed home"}}, "type": "value"},
|
||||
{"options": {"armed_night": {"color": "#277da1", "text": "armed night"}}, "type": "value"},
|
||||
{"options": {"armed_away": {"color": "#f8961e", "text": "armed away"}}, "type": "value"},
|
||||
{"options": {"armed_vacation": {"color": "#a663cc", "text": "armed vacation"}}, "type": "value"},
|
||||
{"options": {"armed_custom_bypass": {"color": "#90be6d", "text": "armed custom"}}, "type": "value"},
|
||||
{"options": {"arming": {"color": "#f9c74f", "text": "arming"}}, "type": "value"},
|
||||
{"options": {"pending": {"color": "#f9c74f", "text": "pending"}}, "type": "value"},
|
||||
{"options": {"triggered": {"color": "#d62828", "text": "TRIGGERED"}}, "type": "value"}
|
||||
],
|
||||
"thresholds": {"mode": "absolute", "steps": [{"color": "#6c757d"}]}
|
||||
}
|
||||
},
|
||||
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 7},
|
||||
"id": 201,
|
||||
"options": {
|
||||
"legend": {"displayMode": "list", "placement": "bottom", "showLegend": true},
|
||||
"mergeValues": true,
|
||||
"rowHeight": 0.9,
|
||||
"showValue": "auto",
|
||||
"tooltip": {"mode": "single"}
|
||||
},
|
||||
"pluginVersion": "11.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"query": "from(bucket: \"ha\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r.domain == \"alarm_control_panel\")\n |> filter(fn: (r) => r._field == \"state\")\n |> filter(fn: (r) => not (r.entity_id =~ /_[0-9]+$/))\n |> keep(columns: [\"_time\", \"_value\", \"entity_id\"])\n |> group(columns: [\"entity_id\"])",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Area arming state",
|
||||
"type": "state-timeline"
|
||||
},
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"description": "Push events the panel sent in the selected window. Columns: time, typed event_type, object index (zone / unit / area / user), and new_state for state-changed events.",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {"mode": "thresholds"},
|
||||
"custom": {
|
||||
"align": "auto",
|
||||
"cellOptions": {"type": "auto"},
|
||||
"inspect": false
|
||||
},
|
||||
"thresholds": {"mode": "absolute", "steps": [{"color": "transparent"}]}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {"id": "byName", "options": "event_type"},
|
||||
"properties": [
|
||||
{"id": "custom.cellOptions", "value": {"type": "color-background", "mode": "basic"}},
|
||||
{"id": "mappings", "value": [
|
||||
{"options": {"alarm_activated": {"color": "#d62828", "text": "alarm_activated"}}, "type": "value"},
|
||||
{"options": {"alarm_cleared": {"color": "#43aa8b", "text": "alarm_cleared"}}, "type": "value"},
|
||||
{"options": {"ac_lost": {"color": "#d62828", "text": "ac_lost"}}, "type": "value"},
|
||||
{"options": {"ac_restored": {"color": "#43aa8b", "text": "ac_restored"}}, "type": "value"},
|
||||
{"options": {"battery_low": {"color": "#f8961e", "text": "battery_low"}}, "type": "value"},
|
||||
{"options": {"battery_restored": {"color": "#43aa8b", "text": "battery_restored"}}, "type": "value"},
|
||||
{"options": {"zone_state_changed": {"color": "#577590", "text": "zone_state_changed"}}, "type": "value"},
|
||||
{"options": {"unit_state_changed": {"color": "#90be6d", "text": "unit_state_changed"}}, "type": "value"},
|
||||
{"options": {"arming_changed": {"color": "#f9c74f", "text": "arming_changed"}}, "type": "value"},
|
||||
{"options": {"user_macro_button": {"color": "#277da1", "text": "user_macro_button"}}, "type": "value"},
|
||||
{"options": {"phone_line_dead": {"color": "#f8961e", "text": "phone_line_dead"}}, "type": "value"},
|
||||
{"options": {"phone_line_restored": {"color": "#43aa8b", "text": "phone_line_restored"}}, "type": "value"}
|
||||
]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {"id": "byName", "options": "_time"},
|
||||
"properties": [
|
||||
{"id": "custom.width", "value": 175},
|
||||
{"id": "displayName", "value": "time"}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 7},
|
||||
"id": 202,
|
||||
"options": {
|
||||
"cellHeight": "sm",
|
||||
"footer": {"countRows": false, "fields": "", "reducer": ["sum"], "show": false},
|
||||
"showHeader": true,
|
||||
"sortBy": [{"desc": true, "displayName": "time"}]
|
||||
},
|
||||
"pluginVersion": "11.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"query": "from(bucket: \"ha\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r.domain == \"event\")\n |> filter(fn: (r) => r._field == \"new_state\" or r._field == \"unit_index\" or r._field == \"zone_index\" or r._field == \"area_index\" or r._field == \"user_index\" or r._field == \"alarm_type\" or r._field == \"button_index\")\n |> pivot(rowKey: [\"_time\", \"event_type\"], columnKey: [\"_field\"], valueColumn: \"_value\")\n |> drop(columns: [\"_start\", \"_stop\", \"_measurement\", \"domain\", \"entity_id\", \"event_class\"])\n |> group()\n |> sort(columns: [\"_time\"], desc: true)\n |> limit(n: 50)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Recent panel events",
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"description": "Zone open/closed timeline. Painted segments = zone is_on (open / tripped).",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {"mode": "thresholds"},
|
||||
"custom": {"fillOpacity": 70, "lineWidth": 0},
|
||||
"mappings": [
|
||||
{"options": {"0": {"color": "green", "index": 0, "text": "secure"}}, "type": "value"},
|
||||
{"options": {"1": {"color": "orange", "index": 1, "text": "open"}}, "type": "value"}
|
||||
],
|
||||
"thresholds": {"mode": "absolute", "steps": [{"color": "green"}, {"color": "orange", "value": 1}]}
|
||||
}
|
||||
},
|
||||
"gridPos": {"h": 8, "w": 24, "x": 0, "y": 15},
|
||||
"id": 203,
|
||||
"options": {
|
||||
"legend": {"displayMode": "list", "placement": "bottom", "showLegend": false},
|
||||
"mergeValues": true,
|
||||
"rowHeight": 0.9,
|
||||
"showValue": "never",
|
||||
"tooltip": {"mode": "single"}
|
||||
},
|
||||
"pluginVersion": "11.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"query": "from(bucket: \"ha\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r.domain == \"binary_sensor\")\n |> filter(fn: (r) => not (r.entity_id =~ /ac_power|battery|trouble|bypass|_[0-9]+$/))\n |> filter(fn: (r) => r._field == \"value\")\n |> keep(columns: [\"_time\", \"_value\", \"entity_id\"])\n |> group(columns: [\"entity_id\"])",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Zone trip timeline",
|
||||
"type": "state-timeline"
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {"h": 1, "w": 24, "x": 0, "y": 23},
|
||||
"id": 300,
|
||||
"panels": [],
|
||||
"title": "Climate",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"description": "Current temperature per thermostat. Mock fixture values are raw panel format; a real panel reports °F.",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {"mode": "fixed", "fixedColor": "#f1faee"},
|
||||
"custom": {
|
||||
"axisPlacement": "auto",
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 8,
|
||||
"gradientMode": "opacity",
|
||||
"lineInterpolation": "stepBefore",
|
||||
"lineWidth": 2,
|
||||
"pointSize": 4,
|
||||
"showPoints": "auto",
|
||||
"spanNulls": true
|
||||
},
|
||||
"unit": "celsius"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {"id": "byFrameRefID", "options": "A"},
|
||||
"properties": [{"id": "color", "value": {"mode": "palette-classic-by-name"}}]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {"h": 9, "w": 16, "x": 0, "y": 24},
|
||||
"id": 301,
|
||||
"options": {
|
||||
"legend": {"calcs": ["mean", "lastNotNull"], "displayMode": "table", "placement": "right", "showLegend": true},
|
||||
"tooltip": {"mode": "multi", "sort": "desc"}
|
||||
},
|
||||
"pluginVersion": "11.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"query": "from(bucket: \"ha\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r.domain == \"climate\")\n |> filter(fn: (r) => not (r.entity_id =~ /_[0-9]+$/))\n |> filter(fn: (r) => r._field == \"current_temperature\")\n |> keep(columns: [\"_time\", \"_value\", \"entity_id\"])\n |> group(columns: [\"entity_id\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Thermostat temperatures",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"description": "HVAC system mode per thermostat over the selected window. Off = grey, Heat = orange, Cool = blue, Auto = green, Dry = teal, Fan only = yellow.",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {"mode": "thresholds"},
|
||||
"custom": {"fillOpacity": 80, "lineWidth": 0},
|
||||
"mappings": [
|
||||
{"options": {"off": {"color": "#adb5bd", "text": "off"}}, "type": "value"},
|
||||
{"options": {"heat": {"color": "#f3722c", "text": "heat"}}, "type": "value"},
|
||||
{"options": {"cool": {"color": "#277da1", "text": "cool"}}, "type": "value"},
|
||||
{"options": {"heat_cool":{"color": "#43aa8b", "text": "auto"}}, "type": "value"},
|
||||
{"options": {"auto": {"color": "#43aa8b", "text": "auto"}}, "type": "value"},
|
||||
{"options": {"dry": {"color": "#577590", "text": "dry"}}, "type": "value"},
|
||||
{"options": {"fan_only": {"color": "#f9c74f", "text": "fan only"}}, "type": "value"}
|
||||
],
|
||||
"thresholds": {"mode": "absolute", "steps": [{"color": "#adb5bd"}]}
|
||||
}
|
||||
},
|
||||
"gridPos": {"h": 9, "w": 8, "x": 16, "y": 24},
|
||||
"id": 302,
|
||||
"options": {
|
||||
"legend": {"displayMode": "list", "placement": "bottom", "showLegend": true},
|
||||
"mergeValues": true,
|
||||
"rowHeight": 0.9,
|
||||
"showValue": "auto",
|
||||
"tooltip": {"mode": "single"}
|
||||
},
|
||||
"pluginVersion": "11.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"query": "from(bucket: \"ha\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r.domain == \"climate\")\n |> filter(fn: (r) => r._field == \"state\")\n |> filter(fn: (r) => not (r.entity_id =~ /_[0-9]+$/))\n |> keep(columns: [\"_time\", \"_value\", \"entity_id\"])\n |> group(columns: [\"entity_id\"])",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "HVAC mode",
|
||||
"type": "state-timeline"
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {"h": 1, "w": 24, "x": 0, "y": 33},
|
||||
"id": 400,
|
||||
"panels": [],
|
||||
"title": "Activity",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"description": "Panel event rate, bucketed by event_type. Tracks zone state changes, button presses, alarm activation, AC/battery events, etc. Each event_type has its own color matching the events table.",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {"mode": "palette-classic"},
|
||||
"custom": {
|
||||
"drawStyle": "bars",
|
||||
"fillOpacity": 80,
|
||||
"lineWidth": 0,
|
||||
"showPoints": "never",
|
||||
"stacking": {"mode": "normal"}
|
||||
},
|
||||
"unit": "short"
|
||||
},
|
||||
"overrides": [
|
||||
{"matcher": {"id": "byName", "options": "alarm_activated"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#d62828"}}]},
|
||||
{"matcher": {"id": "byName", "options": "alarm_cleared"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#43aa8b"}}]},
|
||||
{"matcher": {"id": "byName", "options": "ac_lost"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#d62828"}}]},
|
||||
{"matcher": {"id": "byName", "options": "ac_restored"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#43aa8b"}}]},
|
||||
{"matcher": {"id": "byName", "options": "battery_low"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#f8961e"}}]},
|
||||
{"matcher": {"id": "byName", "options": "battery_restored"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#43aa8b"}}]},
|
||||
{"matcher": {"id": "byName", "options": "zone_state_changed"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#577590"}}]},
|
||||
{"matcher": {"id": "byName", "options": "unit_state_changed"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#90be6d"}}]},
|
||||
{"matcher": {"id": "byName", "options": "arming_changed"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#f9c74f"}}]},
|
||||
{"matcher": {"id": "byName", "options": "user_macro_button"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#277da1"}}]},
|
||||
{"matcher": {"id": "byName", "options": "phone_line_dead"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#f8961e"}}]},
|
||||
{"matcher": {"id": "byName", "options": "phone_line_restored"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#43aa8b"}}]}
|
||||
]
|
||||
},
|
||||
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 34},
|
||||
"id": 401,
|
||||
"options": {
|
||||
"legend": {"displayMode": "table", "placement": "right", "showLegend": true, "calcs": ["sum"]},
|
||||
"tooltip": {"mode": "multi", "sort": "desc"}
|
||||
},
|
||||
"pluginVersion": "11.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"query": "from(bucket: \"ha\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r.domain == \"event\")\n |> filter(fn: (r) => r._field == \"state\")\n |> group(columns: [\"event_type\"])\n |> aggregateWindow(every: v.windowPeriod, fn: count, createEmpty: true)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Event rate by type",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"description": "Top 15 most-toggled units in the selected window — bar length = number of state changes. Reveals which lights/relays get used most.",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {"mode": "thresholds"},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{"color": "#f9c74f", "value": null},
|
||||
{"color": "#f8961e", "value": 5},
|
||||
{"color": "#f3722c", "value": 15},
|
||||
{"color": "#d62828", "value": 30}
|
||||
]
|
||||
},
|
||||
"min": 0,
|
||||
"unit": "short"
|
||||
}
|
||||
},
|
||||
"gridPos": {"h": 10, "w": 12, "x": 12, "y": 34},
|
||||
"id": 402,
|
||||
"options": {
|
||||
"displayMode": "gradient",
|
||||
"valueMode": "color",
|
||||
"showUnfilled": true,
|
||||
"orientation": "horizontal",
|
||||
"reduceOptions": {"calcs": ["lastNotNull"], "fields": "/^_value$/", "values": true},
|
||||
"minVizHeight": 10,
|
||||
"minVizWidth": 0,
|
||||
"namePlacement": "left"
|
||||
},
|
||||
"pluginVersion": "11.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"query": "from(bucket: \"ha\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r.domain == \"light\")\n |> filter(fn: (r) => not (r.entity_id =~ /_[0-9]+$/))\n |> filter(fn: (r) => r._field == \"value\")\n |> toFloat()\n |> keep(columns: [\"_value\", \"entity_id\"])\n |> group(columns: [\"entity_id\"])\n |> count()\n |> group()\n |> sort(columns: [\"_value\"], desc: true)\n |> limit(n: 15)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Top toggled units (24h)",
|
||||
"type": "bargauge"
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {"h": 1, "w": 24, "x": 0, "y": 44},
|
||||
"id": 500,
|
||||
"panels": [],
|
||||
"title": "Insights",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"description": "Zones currently bypassed. Bypass = the panel ignores this zone for arming/alarm purposes. Empty when nothing is bypassed; rows accrue when a switch is flipped.",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {"mode": "thresholds"},
|
||||
"custom": {
|
||||
"align": "auto",
|
||||
"cellOptions": {"type": "auto"},
|
||||
"inspect": false
|
||||
},
|
||||
"thresholds": {"mode": "absolute", "steps": [{"color": "transparent"}]}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {"id": "byName", "options": "entity_id"},
|
||||
"properties": [
|
||||
{"id": "displayName", "value": "zone bypass switch"},
|
||||
{"id": "custom.cellOptions", "value": {"type": "color-text", "wrapText": false}},
|
||||
{"id": "color", "value": {"mode": "fixed", "fixedColor": "#f9c74f"}}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {"id": "byName", "options": "_time"},
|
||||
"properties": [
|
||||
{"id": "displayName", "value": "since"},
|
||||
{"id": "custom.width", "value": 175}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {"id": "byName", "options": "_value"},
|
||||
"properties": [{"id": "custom.hidden", "value": true}]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {"h": 8, "w": 8, "x": 0, "y": 45},
|
||||
"id": 501,
|
||||
"options": {
|
||||
"cellHeight": "sm",
|
||||
"footer": {"countRows": true, "fields": "", "reducer": ["sum"], "show": true},
|
||||
"showHeader": true,
|
||||
"sortBy": [{"desc": true, "displayName": "since"}]
|
||||
},
|
||||
"pluginVersion": "11.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"query": "from(bucket: \"ha\")\n |> range(start: -24h)\n |> filter(fn: (r) => r.domain == \"switch\")\n |> filter(fn: (r) => not (r.entity_id =~ /_[0-9]+$/))\n |> filter(fn: (r) => r._field == \"value\")\n |> last()\n |> filter(fn: (r) => r._value > 0.0)\n |> keep(columns: [\"_time\", \"_value\", \"entity_id\"])\n |> group()\n |> sort(columns: [\"_time\"], desc: true)",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Active zone bypasses",
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"description": "User-macro button press events from the panel. Each row = one press; button_index identifies which scene/macro fired.",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {"mode": "thresholds"},
|
||||
"custom": {
|
||||
"align": "auto",
|
||||
"cellOptions": {"type": "auto"},
|
||||
"inspect": false
|
||||
},
|
||||
"thresholds": {"mode": "absolute", "steps": [{"color": "transparent"}]}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {"id": "byName", "options": "button_index"},
|
||||
"properties": [
|
||||
{"id": "displayName", "value": "button #"},
|
||||
{"id": "custom.cellOptions", "value": {"type": "color-background", "mode": "basic"}},
|
||||
{"id": "color", "value": {"mode": "fixed", "fixedColor": "#277da1"}}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {"id": "byName", "options": "_time"},
|
||||
"properties": [
|
||||
{"id": "displayName", "value": "time"},
|
||||
{"id": "custom.width", "value": 175}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {"h": 8, "w": 8, "x": 8, "y": 45},
|
||||
"id": 502,
|
||||
"options": {
|
||||
"cellHeight": "sm",
|
||||
"footer": {"countRows": true, "fields": "", "reducer": ["sum"], "show": true},
|
||||
"showHeader": true,
|
||||
"sortBy": [{"desc": true, "displayName": "time"}]
|
||||
},
|
||||
"pluginVersion": "11.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"query": "from(bucket: \"ha\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r.domain == \"event\" and r.event_type == \"user_macro_button\")\n |> filter(fn: (r) => r._field == \"button_index\")\n |> keep(columns: [\"_time\", \"_value\"])\n |> group()\n |> sort(columns: [\"_time\"], desc: true)\n |> limit(n: 25)\n |> rename(columns: {_value: \"button_index\"})",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Button press log",
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"description": "Distribution of panel push events by typed kind across the selected window. Matches the colors used in the event rate and events table panels.",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {"mode": "palette-classic"},
|
||||
"custom": {
|
||||
"hideFrom": {"legend": false, "tooltip": false, "viz": false}
|
||||
},
|
||||
"mappings": []
|
||||
},
|
||||
"overrides": [
|
||||
{"matcher": {"id": "byName", "options": "alarm_activated"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#d62828"}}]},
|
||||
{"matcher": {"id": "byName", "options": "alarm_cleared"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#43aa8b"}}]},
|
||||
{"matcher": {"id": "byName", "options": "ac_lost"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#d62828"}}]},
|
||||
{"matcher": {"id": "byName", "options": "ac_restored"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#43aa8b"}}]},
|
||||
{"matcher": {"id": "byName", "options": "battery_low"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#f8961e"}}]},
|
||||
{"matcher": {"id": "byName", "options": "battery_restored"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#43aa8b"}}]},
|
||||
{"matcher": {"id": "byName", "options": "zone_state_changed"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#577590"}}]},
|
||||
{"matcher": {"id": "byName", "options": "unit_state_changed"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#90be6d"}}]},
|
||||
{"matcher": {"id": "byName", "options": "arming_changed"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#f9c74f"}}]},
|
||||
{"matcher": {"id": "byName", "options": "user_macro_button"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "#277da1"}}]}
|
||||
]
|
||||
},
|
||||
"gridPos": {"h": 8, "w": 8, "x": 16, "y": 45},
|
||||
"id": 503,
|
||||
"options": {
|
||||
"displayLabels": ["percent", "name"],
|
||||
"legend": {"displayMode": "table", "placement": "right", "showLegend": true, "values": ["value"]},
|
||||
"pieType": "donut",
|
||||
"reduceOptions": {"calcs": ["sum"], "fields": "", "values": false},
|
||||
"tooltip": {"mode": "single", "sort": "none"}
|
||||
},
|
||||
"pluginVersion": "11.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"query": "from(bucket: \"ha\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r.domain == \"event\" and r._field == \"state\")\n |> keep(columns: [\"_time\", \"_value\", \"event_type\"])\n |> group(columns: [\"event_type\"])\n |> count(column: \"_value\")\n |> map(fn: (r) => ({_time: now(), _value: r._value, event_type: r.event_type}))\n |> pivot(rowKey: [\"_time\"], columnKey: [\"event_type\"], valueColumn: \"_value\")",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Event distribution",
|
||||
"type": "piechart"
|
||||
}
|
||||
],
|
||||
"refresh": "30s",
|
||||
"schemaVersion": 39,
|
||||
"tags": ["omni-pca", "hai", "omni-pro-ii", "home-assistant"],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"current": {"selected": true, "text": "All", "value": "$__all"},
|
||||
"datasource": {"type": "influxdb", "uid": "InfluxDB"},
|
||||
"definition": "from(bucket: \"ha\") |> range(start: -7d) |> filter(fn: (r) => r.domain == \"event\" and r._field == \"state\") |> keep(columns: [\"event_type\"]) |> group() |> distinct(column: \"event_type\")",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": "Event type",
|
||||
"multi": true,
|
||||
"name": "event_type",
|
||||
"options": [],
|
||||
"query": "from(bucket: \"ha\") |> range(start: -7d) |> filter(fn: (r) => r.domain == \"event\" and r._field == \"state\") |> keep(columns: [\"event_type\"]) |> group() |> distinct(column: \"event_type\")",
|
||||
"refresh": 2,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 1,
|
||||
"type": "query"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {"from": "now-24h", "to": "now"},
|
||||
"timepicker": {
|
||||
"refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h"],
|
||||
"time_options": ["1h", "6h", "24h", "2d", "7d", "30d"]
|
||||
},
|
||||
"timezone": "browser",
|
||||
"title": "Omni Pro II — Panel Overview",
|
||||
"uid": "omni-pro-ii-overview",
|
||||
"version": 1,
|
||||
"weekStart": ""
|
||||
}
|
||||
21
grafana/provisioning/datasources/influxdb.yml
Normal file
21
grafana/provisioning/datasources/influxdb.yml
Normal file
@ -0,0 +1,21 @@
|
||||
# Auto-wires the InfluxDB v2 datasource at Grafana boot. Picks up
|
||||
# INFLUX_URL and INFLUX_TOKEN from the grafana container's environment
|
||||
# (set in docker-compose.yml from .env). No manual datasource config
|
||||
# needed.
|
||||
|
||||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: InfluxDB
|
||||
type: influxdb
|
||||
access: proxy
|
||||
url: ${INFLUX_URL}
|
||||
isDefault: true
|
||||
editable: false
|
||||
jsonData:
|
||||
version: Flux
|
||||
organization: omni-pca
|
||||
defaultBucket: ha
|
||||
tlsSkipVerify: true
|
||||
secureJsonData:
|
||||
token: ${INFLUX_TOKEN}
|
||||
Loading…
x
Reference in New Issue
Block a user