From a5ac054f5776b2faea0ad89e1158e2fa63eec3e7 Mon Sep 17 00:00:00 2001 From: Ignace Date: Sun, 30 Aug 2026 15:04:40 +0200 Subject: [PATCH] nicer graphs i hope --- README.md | 5 ++- config/grafana/dashboards/hue.json | 65 ++++++++++++++++++++++-------- hue_collector/collector.py | 6 +++ tests/test_collector.py | 2 + 4 files changed, 59 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 65008b3..e3bedb1 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,9 @@ with a provisioned dashboard. ## Metrics -- For every room and zone: available lights, shining lights, percentages shining, - off, and unavailable, plus average brightness of the shining lights. +- For every room and zone: available, shining, off, and unavailable light counts; + percentages shining, off, and unavailable; plus average brightness of the shining + lights. - For every temperature and illuminance sensor: availability and its latest reading, labelled with its room and zones. diff --git a/config/grafana/dashboards/hue.json b/config/grafana/dashboards/hue.json index 424b2c4..cfe38f6 100644 --- a/config/grafana/dashboards/hue.json +++ b/config/grafana/dashboards/hue.json @@ -4,46 +4,77 @@ "graphTooltip": 1, "panels": [ { - "type": "timeseries", "title": "Lights shining", "id": 1, - "gridPos": {"h": 8, "w": 12, "x": 0, "y": 0}, - "fieldConfig": {"defaults": {"unit": "percent", "min": 0, "max": 100}, "overrides": []}, + "type": "timeseries", "title": "$group_type: $group", "id": 1, + "repeat": "group", "repeatDirection": "v", + "gridPos": {"h": 10, "w": 24, "x": 0, "y": 0}, + "fieldConfig": { + "defaults": { + "decimals": 0, + "min": 0, + "custom": {"drawStyle": "bars", "barAlignment": 0, "fillOpacity": 80, "lineWidth": 1, "stacking": {"mode": "normal", "group": "lights"}} + }, + "overrides": [ + {"matcher": {"id": "byName", "options": "Unavailable"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "dark-red"}}]}, + {"matcher": {"id": "byName", "options": "Off"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "dark-gray"}}]}, + {"matcher": {"id": "byName", "options": "On"}, "properties": [{"id": "color", "value": {"mode": "fixed", "fixedColor": "yellow"}}]}, + {"matcher": {"id": "byName", "options": "Intensity"}, "properties": [ + {"id": "unit", "value": "percent"}, {"id": "max", "value": 100}, + {"id": "custom.axisPlacement", "value": "right"}, {"id": "custom.drawStyle", "value": "line"}, + {"id": "custom.fillOpacity", "value": 0}, {"id": "custom.lineWidth", "value": 3}, + {"id": "custom.stacking", "value": {"mode": "none", "group": "intensity"}}, + {"id": "color", "value": {"mode": "fixed", "fixedColor": "green"}} + ]} + ] + }, "targets": [ - {"expr": "hue_group_lights_shining_percent", "legendFormat": "shining — {{group_type}}: {{group}}", "refId": "A"}, - {"expr": "hue_group_lights_off_percent", "legendFormat": "off — {{group_type}}: {{group}}", "refId": "B"}, - {"expr": "hue_group_lights_unavailable_percent", "legendFormat": "unavailable — {{group_type}}: {{group}}", "refId": "C"} + {"expr": "hue_group_lights_unavailable{group_type=\"$group_type\", group=~\"$group\"}", "legendFormat": "Unavailable", "refId": "A"}, + {"expr": "hue_group_lights_off{group_type=\"$group_type\", group=~\"$group\"}", "legendFormat": "Off", "refId": "B"}, + {"expr": "hue_group_lights_shining{group_type=\"$group_type\", group=~\"$group\"}", "legendFormat": "On", "refId": "C"}, + {"expr": "hue_group_average_brightness_percent{group_type=\"$group_type\", group=~\"$group\"}", "legendFormat": "Intensity", "refId": "D"} ] }, - { - "type": "timeseries", "title": "Average intensity of shining lights", "id": 2, - "gridPos": {"h": 8, "w": 12, "x": 12, "y": 0}, - "fieldConfig": {"defaults": {"unit": "percent", "min": 0, "max": 100}, "overrides": []}, - "targets": [{"expr": "hue_group_average_brightness_percent", "legendFormat": "{{group_type}}: {{group}}", "refId": "A"}] - }, { "type": "timeseries", "title": "Temperature", "id": 3, - "gridPos": {"h": 8, "w": 12, "x": 0, "y": 8}, + "gridPos": {"h": 8, "w": 12, "x": 0, "y": 10}, "fieldConfig": {"defaults": {"unit": "celsius"}, "overrides": []}, "targets": [{"expr": "hue_temperature_celsius", "legendFormat": "{{sensor}} ({{room}})", "refId": "A"}] }, { "type": "timeseries", "title": "Light intensity", "id": 4, - "gridPos": {"h": 8, "w": 12, "x": 12, "y": 8}, + "gridPos": {"h": 8, "w": 12, "x": 12, "y": 10}, "fieldConfig": {"defaults": {"unit": "lux"}, "overrides": []}, "targets": [{"expr": "hue_light_level_lux", "legendFormat": "{{sensor}} ({{room}})", "refId": "A"}] }, { "type": "table", "title": "Unavailable sensors", "id": 5, - "gridPos": {"h": 7, "w": 24, "x": 0, "y": 16}, + "gridPos": {"h": 7, "w": 24, "x": 0, "y": 18}, "targets": [{"expr": "hue_sensor_available == 0", "format": "table", "instant": true, "refId": "A"}] } ], "refresh": "30s", "schemaVersion": 42, "tags": ["hue"], - "templating": {"list": []}, + "templating": {"list": [ + { + "name": "group_type", "label": "Show", "type": "custom", + "query": "room,zone", "options": [ + {"selected": true, "text": "room", "value": "room"}, + {"selected": false, "text": "zone", "value": "zone"} + ], + "current": {"selected": true, "text": "room", "value": "room"} + }, + { + "name": "group", "label": "Room / zone", "type": "query", + "datasource": {"type": "prometheus", "uid": "victoriametrics"}, + "definition": "label_values(hue_group_lights_available{group_type=\"$group_type\"}, group)", + "query": {"query": "label_values(hue_group_lights_available{group_type=\"$group_type\"}, group)", "refId": "variable-group"}, + "refresh": 1, "sort": 1, "multi": true, "includeAll": true, + "allValue": ".+", "current": {"selected": true, "text": "All", "value": "$__all"} + } + ]}, "time": {"from": "now-24h", "to": "now"}, "timezone": "browser", "title": "Philips Hue", "uid": "philips-hue-local", - "version": 1 + "version": 2 } diff --git a/hue_collector/collector.py b/hue_collector/collector.py index ffc93b5..acf927a 100644 --- a/hue_collector/collector.py +++ b/hue_collector/collector.py @@ -92,6 +92,10 @@ class Collector: "# TYPE hue_group_lights_available gauge", "# HELP hue_group_lights_shining Number of connected lights that are on.", "# TYPE hue_group_lights_shining gauge", + "# HELP hue_group_lights_off Number of connected lights that are off.", + "# TYPE hue_group_lights_off gauge", + "# HELP hue_group_lights_unavailable Number of configured lights that are disconnected.", + "# TYPE hue_group_lights_unavailable gauge", "# HELP hue_group_lights_shining_percent Percentage of connected lights that are on.", "# TYPE hue_group_lights_shining_percent gauge", "# HELP hue_group_lights_off_percent Percentage of connected lights that are off.", @@ -120,6 +124,8 @@ class Collector: labels = {"group_type": kind, "group": group.get("metadata", {}).get("name", group["id"]), "group_id": group["id"]} lines.append(_sample("hue_group_lights_available", len(lights), **labels)) lines.append(_sample("hue_group_lights_shining", len(shining), **labels)) + lines.append(_sample("hue_group_lights_off", off, **labels)) + lines.append(_sample("hue_group_lights_unavailable", unavailable, **labels)) lines.append(_sample("hue_group_lights_shining_percent", 100 * len(shining) / len(lights) if lights else 0, **labels)) lines.append(_sample("hue_group_lights_off_percent", 100 * off / len(lights) if lights else 0, **labels)) lines.append(_sample("hue_group_lights_unavailable_percent", 100 * unavailable / len(all_lights) if all_lights else 0, **labels)) diff --git a/tests/test_collector.py b/tests/test_collector.py index 33ab082..208656e 100644 --- a/tests/test_collector.py +++ b/tests/test_collector.py @@ -35,6 +35,8 @@ class CollectorTests(unittest.TestCase): def test_group_counts_only_connected_lights(self): self.assertIn('hue_group_lights_available{group="Office",group_id="r1",group_type="room"} 1', self.metrics) + self.assertIn('hue_group_lights_off{group="Office",group_id="r1",group_type="room"} 0', self.metrics) + self.assertIn('hue_group_lights_unavailable{group="Office",group_id="r1",group_type="room"} 1', self.metrics) self.assertIn('hue_group_lights_shining_percent{group="Office",group_id="r1",group_type="room"} 100.0', self.metrics) self.assertIn('hue_group_lights_off_percent{group="Office",group_id="r1",group_type="room"} 0.0', self.metrics) self.assertIn('hue_group_lights_unavailable_percent{group="Office",group_id="r1",group_type="room"} 50.0', self.metrics)