Compare commits

...

15 commits
v0.0.3 ... main

Author SHA1 Message Date
2f80b5db95
go mod tidy
All checks were successful
Build Docker image / build (push) Successful in 1m18s
Build Golang packages / release (push) Has been skipped
2024-08-14 23:19:54 +02:00
bd66af32fa
Update Go 1.22 -> 1.23
All checks were successful
Build Docker image / build (push) Successful in 1m16s
Build Golang packages / release (push) Successful in 2m8s
2024-08-14 19:32:31 +02:00
f2b74acfe9
Go dependency upgrade
All checks were successful
Build Golang packages / release (push) Has been skipped
Build Docker image / build (push) Successful in 2m15s
2024-08-10 23:45:05 +02:00
2e2205d938
Add ARG TARGETOS TARGETARCH to fix multi-platform builds. 2024-08-10 23:44:45 +02:00
022ed0e03e
Add TODOs 2024-08-10 23:44:13 +02:00
95e10766f0
ESPHome example add voltage sags and swells processing.
All checks were successful
Build Docker image / build (push) Successful in 1m16s
Build Golang packages / release (push) Has been skipped
2024-07-28 16:12:34 +02:00
21c1e08d79
Add .gitignore for esphome. 2024-07-28 14:08:49 +02:00
07986ef0b4
Upgrade Go dependencies.
All checks were successful
Build Docker image / build (push) Successful in 1m3s
Build Golang packages / release (push) Has been skipped
2024-07-26 22:38:33 +02:00
39b231b780
Small fix: migration funcs don't have to be exported.
All checks were successful
Build Docker image / build (push) Successful in 1m6s
Build Golang packages / release (push) Has been skipped
2024-07-26 22:29:05 +02:00
b70c6bd8e2
Implement proper DB migrations. Add support for logging electricity sags and swells. Preliminary work on supporting BE and LU specific variables.
All checks were successful
Build Docker image / build (push) Successful in 1m2s
Build Golang packages / release (push) Successful in 2m14s
2024-07-26 22:02:47 +02:00
49aeda69e9
Build container golang:1.21 -> 1.22. 2024-07-26 21:51:24 +02:00
f06e67ecec
Migrate to proper DB migrations.
All checks were successful
Build Docker image / build (push) Successful in 1m8s
Build Golang packages / release (push) Has been skipped
2024-07-26 21:42:29 +02:00
2712984ba4
add dsmr docs link.
All checks were successful
Build Docker image / build (push) Successful in 1m11s
Build Golang packages / release (push) Has been skipped
2024-07-26 20:26:37 +02:00
67c7b608e9
Add EN + DE dashboards.
All checks were successful
Build Docker image / build (push) Successful in 1m9s
Build Golang packages / release (push) Has been skipped
2024-07-26 18:31:25 +02:00
2475d05553
Fix dashboard to support external use.
All checks were successful
Build Docker image / build (push) Successful in 1m14s
Build Golang packages / release (push) Has been skipped
2024-07-26 18:09:17 +02:00
11 changed files with 2120 additions and 73 deletions

View file

@ -1,5 +1,5 @@
# Use buildx for multi-architecture support # Use buildx for multi-architecture support
FROM --platform=${BUILDPLATFORM} golang:1.21 AS builder FROM --platform=${BUILDPLATFORM} golang:1.23 AS builder
WORKDIR /app WORKDIR /app
@ -11,6 +11,9 @@ RUN go mod download
COPY . . COPY . .
# Build the application for the specified target architecture # Build the application for the specified target architecture
# https://www.docker.com/blog/faster-multi-platform-builds-dockerfile-cross-compilation-guide/
# Declare TARGETOS and TARGETARCH in the local scope so they can be used in the build stage.
ARG TARGETOS TARGETARCH
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -o p1-logger . RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -o p1-logger .
# Create a minimal runtime image # Create a minimal runtime image

5
esphome/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
# Gitignore settings for ESPHome
# This is an example and may include too much for your use-case.
# You can modify this file to suit your needs.
/.esphome/
/secrets.yaml

View file

@ -83,6 +83,7 @@ globals:
type: char[32] type: char[32]
restore_value: yes restore_value: yes
# https://esphome.io/components/sensor/dsmr.html
dsmr: dsmr:
uart_id: uart_dsmr uart_id: uart_dsmr
id: dsmr_instance id: dsmr_instance
@ -176,6 +177,30 @@ sensor:
name: "Long Electricity Failures" name: "Long Electricity Failures"
icon: mdi:alert icon: mdi:alert
id: electricity_long_failures id: electricity_long_failures
electricity_sags_l1:
name: "Voltage sags L1"
icon: mdi:alert
id: electricity_sags_l1
electricity_sags_l2:
name: "Voltage sags L1"
icon: mdi:alert
id: electricity_sags_l2
electricity_sags_l3:
name: "Voltage sags L1"
icon: mdi:alert
id: electricity_sags_l3
electricity_swells_l1:
name: "Voltage swells L1"
icon: mdi:alert
id: electricity_swells_l1
electricity_swells_l2:
name: "Voltage swells L1"
icon: mdi:alert
id: electricity_swells_l2
electricity_swells_l3:
name: "Voltage swells L1"
icon: mdi:alert
id: electricity_swells_l3
voltage_l1: voltage_l1:
name: "Voltage Phase 1" name: "Voltage Phase 1"
id: voltage_l1 id: voltage_l1

19
go.mod
View file

@ -1,15 +1,22 @@
module git.hollander.online/energy/p1-logger module git.hollander.online/energy/p1-logger
go 1.21 go 1.23
require ( require (
github.com/eclipse/paho.mqtt.golang v1.4.3 github.com/eclipse/paho.mqtt.golang v1.5.0
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/pressly/goose/v3 v3.21.1
) )
require ( require (
github.com/gorilla/websocket v1.5.0 // indirect github.com/mfridman/interpolate v0.0.2 // indirect
github.com/joho/godotenv v1.5.1 github.com/sethvargo/go-retry v0.3.0 // indirect
golang.org/x/net v0.8.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/sync v0.1.0 // indirect )
require (
github.com/gorilla/websocket v1.5.3 // indirect
github.com/joho/godotenv v1.5.1
golang.org/x/net v0.28.0 // indirect
golang.org/x/sync v0.8.0 // indirect
) )

60
go.sum
View file

@ -1,12 +1,56 @@
github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/eclipse/paho.mqtt.golang v1.5.0 h1:EH+bUVJNgttidWFkLLVKaQPGmkTUfQQqjOsyvMGvD6o=
github.com/eclipse/paho.mqtt.golang v1.5.0/go.mod h1:du/2qNQVqJf/Sqs4MEL77kR8QTqANF7XU7Fk0aOTAgk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pressly/goose/v3 v3.21.1 h1:5SSAKKWej8LVVzNLuT6KIvP1eFDuPvxa+B6H0w78buQ=
github.com/pressly/goose/v3 v3.21.1/go.mod h1:sqthmzV8PitchEkjecFJII//l43dLOCzfWh8pHEe+vE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk=
modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/sqlite v1.29.6 h1:0lOXGrycJPptfHDuohfYgNqoe4hu+gYuN/pKgY5XjS4=
modernc.org/sqlite v1.29.6/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

902
grafana/P1-Messdaten.json Normal file
View file

@ -0,0 +1,902 @@
{
"__inputs": [
{
"name": "DS_P1",
"label": "p1",
"description": "",
"type": "datasource",
"pluginId": "grafana-postgresql-datasource",
"pluginName": "PostgreSQL"
}
],
"__elements": {},
"__requires": [
{
"type": "grafana",
"id": "grafana",
"name": "Grafana",
"version": "11.1.1"
},
{
"type": "datasource",
"id": "grafana-postgresql-datasource",
"name": "PostgreSQL",
"version": "1.0.0"
},
{
"type": "panel",
"id": "timeseries",
"name": "Time series",
"version": ""
}
],
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": null,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "watt"
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 12,
"x": 0,
"y": 0
},
"id": 8,
"options": {
"legend": {
"calcs": [
"min",
"max",
"mean",
"stdDev",
"last"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"editorMode": "code",
"format": "table",
"rawQuery": true,
"rawSql": "-- SELECT time_bucket('$__interval', timestamp) AS time, \n-- delivery_l1 - returning_l1 AS \"Vermogen L1\", \n-- delivery_l2 - returning_l2 AS \"Vermogen L2\", \n-- delivery_l3 - returning_l3 AS \"Vermogen L3\",\n-- delivery_all - returning_all AS \"Vermogen totaal\"\n-- FROM p1\n-- WHERE timestamp >= $__timeFrom()::timestamptz AND timestamp < $__timeTo()::timestamptz\n\nWITH RECURSIVE DateRange AS (\n SELECT generate_series($__timeFrom()::timestamp, $__timeTo()::timestamp, '$__interval') AS timestamp\n),\nLastValues AS (\n SELECT\n dr.timestamp,\n (SELECT delivery_l1 FROM p1 WHERE timestamp <= dr.timestamp AND delivery_l1 IS NOT NULL ORDER BY timestamp DESC LIMIT 1) AS delivery_l1,\n (SELECT delivery_l2 FROM p1 WHERE timestamp <= dr.timestamp AND delivery_l2 IS NOT NULL ORDER BY timestamp DESC LIMIT 1) AS delivery_l2,\n (SELECT delivery_l3 FROM p1 WHERE timestamp <= dr.timestamp AND delivery_l3 IS NOT NULL ORDER BY timestamp DESC LIMIT 1) AS delivery_l3,\n (SELECT delivery_all FROM p1 WHERE timestamp <= dr.timestamp AND delivery_all IS NOT NULL ORDER BY timestamp DESC LIMIT 1) AS delivery_all,\n (SELECT returning_l1 FROM p1 WHERE timestamp <= dr.timestamp AND returning_l1 IS NOT NULL ORDER BY timestamp DESC LIMIT 1) AS returning_l1,\n (SELECT returning_l2 FROM p1 WHERE timestamp <= dr.timestamp AND returning_l2 IS NOT NULL ORDER BY timestamp DESC LIMIT 1) AS returning_l2,\n (SELECT returning_l3 FROM p1 WHERE timestamp <= dr.timestamp AND returning_l3 IS NOT NULL ORDER BY timestamp DESC LIMIT 1) AS returning_l3,\n (SELECT returning_all FROM p1 WHERE timestamp <= dr.timestamp AND returning_all IS NOT NULL ORDER BY timestamp DESC LIMIT 1) AS returning_all\n FROM DateRange dr\n)\nSELECT\n timestamp,\n COALESCE(delivery_l1, 0) - COALESCE(returning_l1, 0) AS \"Leistung L1\",\n COALESCE(delivery_l2, 0) - COALESCE(returning_l2, 0) AS \"Leistung L2\",\n COALESCE(delivery_l3, 0) - COALESCE(returning_l3, 0) AS \"Leistung L3\",\n COALESCE(delivery_all, 0) - COALESCE(returning_all, 0) AS \"Gesamtleistung\"\nFROM LastValues\nORDER BY timestamp;\n",
"refId": "A",
"sql": {
"columns": [
{
"parameters": [],
"type": "function"
}
],
"groupBy": [
{
"property": {
"type": "string"
},
"type": "groupBy"
}
],
"limit": 50
},
"table": "p1"
}
],
"title": "Aktueller Import oder Export",
"type": "timeseries"
},
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "watth"
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 6,
"x": 12,
"y": 0
},
"id": 4,
"options": {
"legend": {
"calcs": [
"firstNotNull",
"lastNotNull",
"range"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"editorMode": "code",
"format": "table",
"rawQuery": true,
"rawSql": "-- SELECT time_bucket('$__interval', timestamp) AS time, \n-- delivered_tariff1+delivered_tariff2 AS \"Import totaal\", \n-- returned_tariff1+returned_tariff2 AS \"Export totaal\", \n-- delivered_tariff1 AS \"Import telwerk 1\", \n-- delivered_tariff2 AS \"Import telwerk 2\", \n-- returned_tariff1 AS \"Export telwerk 1\", \n-- returned_tariff2 AS \"Export telwerk 2\"\n-- FROM p1\n-- WHERE timestamp >= $__timeFrom()::timestamptz AND timestamp < $__timeTo()::timestamptz\n\nWITH LastKnownValues AS (\n SELECT\n (SELECT delivered_tariff1 FROM p1 WHERE delivered_tariff1 IS NOT NULL AND timestamp < $__timeFrom()::timestamptz ORDER BY timestamp DESC LIMIT 1) AS last_delivered_tariff1,\n (SELECT delivered_tariff2 FROM p1 WHERE delivered_tariff2 IS NOT NULL AND timestamp < $__timeFrom()::timestamptz ORDER BY timestamp DESC LIMIT 1) AS last_delivered_tariff2,\n (SELECT returned_tariff1 FROM p1 WHERE returned_tariff1 IS NOT NULL AND timestamp < $__timeFrom()::timestamptz ORDER BY timestamp DESC LIMIT 1) AS last_returned_tariff1,\n (SELECT returned_tariff2 FROM p1 WHERE returned_tariff2 IS NOT NULL AND timestamp < $__timeFrom()::timestamptz ORDER BY timestamp DESC LIMIT 1) AS last_returned_tariff2\n),\nMainQuery AS (\n SELECT\n time_bucket('$__interval', timestamp) AS time,\n delivered_tariff1,\n delivered_tariff2,\n returned_tariff1,\n returned_tariff2\n FROM\n p1\n WHERE\n timestamp >= $__timeFrom()::timestamptz AND timestamp < $__timeTo()::timestamptz\n)\nSELECT\n mq.time,\n (COALESCE(mq.delivered_tariff1, lv.last_delivered_tariff1) + COALESCE(mq.delivered_tariff2, lv.last_delivered_tariff2)) AS \"Import totaal\",\n (COALESCE(mq.returned_tariff1, lv.last_returned_tariff1) + COALESCE(mq.returned_tariff2, lv.last_returned_tariff2)) AS \"Export totaal\",\n COALESCE(mq.delivered_tariff1, lv.last_delivered_tariff1) AS \"Import Zählwerk 1\",\n COALESCE(mq.delivered_tariff2, lv.last_delivered_tariff2) AS \"Import Zählwerk 2\",\n COALESCE(mq.returned_tariff1, lv.last_returned_tariff1) AS \"Export Zählwerk 1\",\n COALESCE(mq.returned_tariff2, lv.last_returned_tariff2) AS \"Export Zählwerk 2\"\nFROM\n MainQuery mq,\n LastKnownValues lv\n",
"refId": "A",
"sql": {
"columns": [
{
"parameters": [],
"type": "function"
}
],
"groupBy": [
{
"property": {
"type": "string"
},
"type": "groupBy"
}
],
"limit": 50
},
"table": "p1"
}
],
"title": "Zählerstände Strom",
"type": "timeseries"
},
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"description": " Erster + Letzter = seit 0, Bereich = aktueller Zeitraum",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "watth"
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 6,
"x": 18,
"y": 0
},
"id": 10,
"options": {
"legend": {
"calcs": [
"firstNotNull",
"lastNotNull",
"range"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"editorMode": "code",
"format": "table",
"rawQuery": true,
"rawSql": "-- SELECT time_bucket('$__interval', timestamp) AS time, \n-- delivered_tariff1-returned_tariff1 AS \"Telwerk 1\", \n-- delivered_tariff2-returned_tariff2 AS \"Telwerk 2\", \n-- delivered_tariff1+delivered_tariff2-returned_tariff1-returned_tariff2 AS \"Totaal\"\n-- FROM p1\n-- WHERE timestamp >= $__timeFrom()::timestamptz AND timestamp < $__timeTo()::timestamptz\n\nWITH LastKnownValues AS (\n SELECT\n (SELECT delivered_tariff1 FROM p1 WHERE delivered_tariff1 IS NOT NULL AND timestamp < $__timeFrom()::timestamptz ORDER BY timestamp DESC LIMIT 1) AS last_delivered_tariff1,\n (SELECT delivered_tariff2 FROM p1 WHERE delivered_tariff2 IS NOT NULL AND timestamp < $__timeFrom()::timestamptz ORDER BY timestamp DESC LIMIT 1) AS last_delivered_tariff2,\n (SELECT returned_tariff1 FROM p1 WHERE returned_tariff1 IS NOT NULL AND timestamp < $__timeFrom()::timestamptz ORDER BY timestamp DESC LIMIT 1) AS last_returned_tariff1,\n (SELECT returned_tariff2 FROM p1 WHERE returned_tariff2 IS NOT NULL AND timestamp < $__timeFrom()::timestamptz ORDER BY timestamp DESC LIMIT 1) AS last_returned_tariff2\n),\nMainQuery AS (\n SELECT\n time_bucket('$__interval', timestamp) AS time,\n COALESCE(delivered_tariff1, (SELECT last_delivered_tariff1 FROM LastKnownValues)) AS delivered_tariff1,\n COALESCE(delivered_tariff2, (SELECT last_delivered_tariff2 FROM LastKnownValues)) AS delivered_tariff2,\n COALESCE(returned_tariff1, (SELECT last_returned_tariff1 FROM LastKnownValues)) AS returned_tariff1,\n COALESCE(returned_tariff2, (SELECT last_returned_tariff2 FROM LastKnownValues)) AS returned_tariff2\n FROM\n p1\n WHERE\n timestamp >= $__timeFrom()::timestamptz AND timestamp < $__timeTo()::timestamptz\n)\nSELECT\n time,\n (delivered_tariff1 - returned_tariff1) AS \"Zählwerk 1\",\n (delivered_tariff2 - returned_tariff2) AS \"Zählwerk 2\",\n (delivered_tariff1 + delivered_tariff2 - returned_tariff1 - returned_tariff2) AS \"Gesamt\"\nFROM\n MainQuery\n",
"refId": "A",
"sql": {
"columns": [
{
"parameters": [],
"type": "function"
}
],
"groupBy": [
{
"property": {
"type": "string"
},
"type": "groupBy"
}
],
"limit": 50
},
"table": "p1"
}
],
"title": "Saldiert Zählerstände",
"type": "timeseries"
},
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"description": "Min: 216,2V. Max: 253V. (-6% bis +10%)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "volt"
},
"overrides": []
},
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 10
},
"id": 3,
"options": {
"legend": {
"calcs": [
"logmin",
"max",
"mean",
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"editorMode": "code",
"format": "table",
"rawQuery": true,
"rawSql": "SELECT time_bucket('$__interval', timestamp) AS time, \nvoltage_l1 AS \"Spannung L1\", \nvoltage_l2 AS \"Spannung L2\", \nvoltage_l3 AS \"Spannung L3\"\nFROM p1\nWHERE timestamp >= $__timeFrom()::timestamptz AND timestamp < $__timeTo()::timestamptz",
"refId": "A",
"sql": {
"columns": [
{
"parameters": [],
"type": "function"
}
],
"groupBy": [
{
"property": {
"type": "string"
},
"type": "groupBy"
}
],
"limit": 50
},
"table": "p1"
}
],
"title": "Spannung",
"type": "timeseries"
},
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "amp"
},
"overrides": []
},
"gridPos": {
"h": 9,
"w": 12,
"x": 12,
"y": 10
},
"id": 6,
"options": {
"legend": {
"calcs": [
"min",
"max",
"mean",
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"editorMode": "code",
"format": "table",
"rawQuery": true,
"rawSql": "SELECT time_bucket('$__interval', timestamp) AS time, \ncurrent_l1 AS \"Leistung L1\", \ncurrent_l2 AS \"Leistung L2\", \ncurrent_l3 AS \"Leistung L3\"\nFROM p1\nWHERE timestamp >= $__timeFrom()::timestamptz AND timestamp < $__timeTo()::timestamptz",
"refId": "A",
"sql": {
"columns": [
{
"parameters": [],
"type": "function"
}
],
"groupBy": [
{
"property": {
"type": "string"
},
"type": "groupBy"
}
],
"limit": 50
},
"table": "p1"
}
],
"title": "Leistung",
"type": "timeseries"
},
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 19
},
"id": 7,
"options": {
"legend": {
"calcs": [
"firstNotNull",
"lastNotNull",
"range"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"editorMode": "code",
"format": "table",
"rawQuery": true,
"rawSql": "SELECT time_bucket('$__interval', timestamp) AS time, \nfailures AS \"Stromausfall\", \nlong_failures AS \"Längerer Stromausfall\" \nFROM p1\nWHERE timestamp >= $__timeFrom()::timestamptz AND timestamp < $__timeTo()::timestamptz",
"refId": "A",
"sql": {
"columns": [
{
"parameters": [],
"type": "function"
}
],
"groupBy": [
{
"property": {
"type": "string"
},
"type": "groupBy"
}
],
"limit": 50
}
}
],
"title": "Stromausfall",
"type": "timeseries"
},
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"description": "1L = 1 dm³, also 1kL = 1m³\nBereich = Verbrauch im ausgewählten Zeitraum",
"fieldConfig": {
"defaults": {
"color": {
"fixedColor": "yellow",
"mode": "fixed"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 50,
"gradientMode": "opacity",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "stepAfter",
"lineStyle": {
"fill": "solid"
},
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"fieldMinMax": false,
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "litre"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 19
},
"id": 5,
"options": {
"legend": {
"calcs": [
"firstNotNull",
"lastNotNull",
"range"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"editorMode": "code",
"format": "table",
"rawQuery": true,
"rawSql": "SELECT time_bucket('$__interval', timestamp) AS time, \ngas AS \"Gasverbruik\"\nFROM p1\nWHERE timestamp >= $__timeFrom()::timestamptz AND timestamp < $__timeTo()::timestamptz AND gas IS NOT NULL",
"refId": "A",
"sql": {
"columns": [
{
"parameters": [],
"type": "function"
}
],
"groupBy": [
{
"property": {
"type": "string"
},
"type": "groupBy"
}
],
"limit": 50
}
}
],
"title": "Gasverbrauch",
"type": "timeseries"
}
],
"refresh": "5s",
"schemaVersion": 39,
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now/d",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "P1-Messdaten",
"uid": "cf296851-06f4-4479-9d5e-2bf85b56f69d",
"version": 3,
"weekStart": ""
}

View file

@ -0,0 +1,902 @@
{
"__inputs": [
{
"name": "DS_P1",
"label": "p1",
"description": "",
"type": "datasource",
"pluginId": "grafana-postgresql-datasource",
"pluginName": "PostgreSQL"
}
],
"__elements": {},
"__requires": [
{
"type": "grafana",
"id": "grafana",
"name": "Grafana",
"version": "11.1.1"
},
{
"type": "datasource",
"id": "grafana-postgresql-datasource",
"name": "PostgreSQL",
"version": "1.0.0"
},
{
"type": "panel",
"id": "timeseries",
"name": "Time series",
"version": ""
}
],
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": null,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "watt"
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 12,
"x": 0,
"y": 0
},
"id": 8,
"options": {
"legend": {
"calcs": [
"min",
"max",
"mean",
"stdDev",
"last"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"editorMode": "code",
"format": "table",
"rawQuery": true,
"rawSql": "-- SELECT time_bucket('$__interval', timestamp) AS time, \n-- delivery_l1 - returning_l1 AS \"Power L1\", \n-- delivery_l2 - returning_l2 AS \"Power L2\", \n-- delivery_l3 - returning_l3 AS \"Power L3\",\n-- delivery_all - returning_all AS \"Power total\"\n-- FROM p1\n-- WHERE timestamp >= $__timeFrom()::timestamptz AND timestamp < $__timeTo()::timestamptz\n\nWITH RECURSIVE DateRange AS (\n SELECT generate_series($__timeFrom()::timestamp, $__timeTo()::timestamp, '$__interval') AS timestamp\n),\nLastValues AS (\n SELECT\n dr.timestamp,\n (SELECT delivery_l1 FROM p1 WHERE timestamp <= dr.timestamp AND delivery_l1 IS NOT NULL ORDER BY timestamp DESC LIMIT 1) AS delivery_l1,\n (SELECT delivery_l2 FROM p1 WHERE timestamp <= dr.timestamp AND delivery_l2 IS NOT NULL ORDER BY timestamp DESC LIMIT 1) AS delivery_l2,\n (SELECT delivery_l3 FROM p1 WHERE timestamp <= dr.timestamp AND delivery_l3 IS NOT NULL ORDER BY timestamp DESC LIMIT 1) AS delivery_l3,\n (SELECT delivery_all FROM p1 WHERE timestamp <= dr.timestamp AND delivery_all IS NOT NULL ORDER BY timestamp DESC LIMIT 1) AS delivery_all,\n (SELECT returning_l1 FROM p1 WHERE timestamp <= dr.timestamp AND returning_l1 IS NOT NULL ORDER BY timestamp DESC LIMIT 1) AS returning_l1,\n (SELECT returning_l2 FROM p1 WHERE timestamp <= dr.timestamp AND returning_l2 IS NOT NULL ORDER BY timestamp DESC LIMIT 1) AS returning_l2,\n (SELECT returning_l3 FROM p1 WHERE timestamp <= dr.timestamp AND returning_l3 IS NOT NULL ORDER BY timestamp DESC LIMIT 1) AS returning_l3,\n (SELECT returning_all FROM p1 WHERE timestamp <= dr.timestamp AND returning_all IS NOT NULL ORDER BY timestamp DESC LIMIT 1) AS returning_all\n FROM DateRange dr\n)\nSELECT\n timestamp,\n COALESCE(delivery_l1, 0) - COALESCE(returning_l1, 0) AS \"Power L1\",\n COALESCE(delivery_l2, 0) - COALESCE(returning_l2, 0) AS \"Power L2\",\n COALESCE(delivery_l3, 0) - COALESCE(returning_l3, 0) AS \"Power L3\",\n COALESCE(delivery_all, 0) - COALESCE(returning_all, 0) AS \"Power total\"\nFROM LastValues\nORDER BY timestamp;\n",
"refId": "A",
"sql": {
"columns": [
{
"parameters": [],
"type": "function"
}
],
"groupBy": [
{
"property": {
"type": "string"
},
"type": "groupBy"
}
],
"limit": 50
},
"table": "p1"
}
],
"title": "Current import or export",
"type": "timeseries"
},
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "watth"
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 6,
"x": 12,
"y": 0
},
"id": 4,
"options": {
"legend": {
"calcs": [
"firstNotNull",
"lastNotNull",
"range"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"editorMode": "code",
"format": "table",
"rawQuery": true,
"rawSql": "-- SELECT time_bucket('$__interval', timestamp) AS time, \n-- delivered_tariff1+delivered_tariff2 AS \"Import total\", \n-- returned_tariff1+returned_tariff2 AS \"Export total\", \n-- delivered_tariff1 AS \"Import register 1\", \n-- delivered_tariff2 AS \"Import register 2\", \n-- returned_tariff1 AS \"Export register 1\", \n-- returned_tariff2 AS \"Export register 2\"\n-- FROM p1\n-- WHERE timestamp >= $__timeFrom()::timestamptz AND timestamp < $__timeTo()::timestamptz\n\nWITH LastKnownValues AS (\n SELECT\n (SELECT delivered_tariff1 FROM p1 WHERE delivered_tariff1 IS NOT NULL AND timestamp < $__timeFrom()::timestamptz ORDER BY timestamp DESC LIMIT 1) AS last_delivered_tariff1,\n (SELECT delivered_tariff2 FROM p1 WHERE delivered_tariff2 IS NOT NULL AND timestamp < $__timeFrom()::timestamptz ORDER BY timestamp DESC LIMIT 1) AS last_delivered_tariff2,\n (SELECT returned_tariff1 FROM p1 WHERE returned_tariff1 IS NOT NULL AND timestamp < $__timeFrom()::timestamptz ORDER BY timestamp DESC LIMIT 1) AS last_returned_tariff1,\n (SELECT returned_tariff2 FROM p1 WHERE returned_tariff2 IS NOT NULL AND timestamp < $__timeFrom()::timestamptz ORDER BY timestamp DESC LIMIT 1) AS last_returned_tariff2\n),\nMainQuery AS (\n SELECT\n time_bucket('$__interval', timestamp) AS time,\n delivered_tariff1,\n delivered_tariff2,\n returned_tariff1,\n returned_tariff2\n FROM\n p1\n WHERE\n timestamp >= $__timeFrom()::timestamptz AND timestamp < $__timeTo()::timestamptz\n)\nSELECT\n mq.time,\n (COALESCE(mq.delivered_tariff1, lv.last_delivered_tariff1) + COALESCE(mq.delivered_tariff2, lv.last_delivered_tariff2)) AS \"Import total\",\n (COALESCE(mq.returned_tariff1, lv.last_returned_tariff1) + COALESCE(mq.returned_tariff2, lv.last_returned_tariff2)) AS \"Export total\",\n COALESCE(mq.delivered_tariff1, lv.last_delivered_tariff1) AS \"Import register 1\",\n COALESCE(mq.delivered_tariff2, lv.last_delivered_tariff2) AS \"Import register 2\",\n COALESCE(mq.returned_tariff1, lv.last_returned_tariff1) AS \"Export register 1\",\n COALESCE(mq.returned_tariff2, lv.last_returned_tariff2) AS \"Export register 2\"\nFROM\n MainQuery mq,\n LastKnownValues lv\n",
"refId": "A",
"sql": {
"columns": [
{
"parameters": [],
"type": "function"
}
],
"groupBy": [
{
"property": {
"type": "string"
},
"type": "groupBy"
}
],
"limit": 50
},
"table": "p1"
}
],
"title": "Meter readings electricity",
"type": "timeseries"
},
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"description": "First + Last = since 0, Range = current time window.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "watth"
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 6,
"x": 18,
"y": 0
},
"id": 10,
"options": {
"legend": {
"calcs": [
"firstNotNull",
"lastNotNull",
"range"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"editorMode": "code",
"format": "table",
"rawQuery": true,
"rawSql": "-- SELECT time_bucket('$__interval', timestamp) AS time, \n-- delivered_tariff1-returned_tariff1 AS \"Register 1\", \n-- delivered_tariff2-returned_tariff2 AS \"Register 2\", \n-- delivered_tariff1+delivered_tariff2-returned_tariff1-returned_tariff2 AS \"Total\"\n-- FROM p1\n-- WHERE timestamp >= $__timeFrom()::timestamptz AND timestamp < $__timeTo()::timestamptz\n\nWITH LastKnownValues AS (\n SELECT\n (SELECT delivered_tariff1 FROM p1 WHERE delivered_tariff1 IS NOT NULL AND timestamp < $__timeFrom()::timestamptz ORDER BY timestamp DESC LIMIT 1) AS last_delivered_tariff1,\n (SELECT delivered_tariff2 FROM p1 WHERE delivered_tariff2 IS NOT NULL AND timestamp < $__timeFrom()::timestamptz ORDER BY timestamp DESC LIMIT 1) AS last_delivered_tariff2,\n (SELECT returned_tariff1 FROM p1 WHERE returned_tariff1 IS NOT NULL AND timestamp < $__timeFrom()::timestamptz ORDER BY timestamp DESC LIMIT 1) AS last_returned_tariff1,\n (SELECT returned_tariff2 FROM p1 WHERE returned_tariff2 IS NOT NULL AND timestamp < $__timeFrom()::timestamptz ORDER BY timestamp DESC LIMIT 1) AS last_returned_tariff2\n),\nMainQuery AS (\n SELECT\n time_bucket('$__interval', timestamp) AS time,\n COALESCE(delivered_tariff1, (SELECT last_delivered_tariff1 FROM LastKnownValues)) AS delivered_tariff1,\n COALESCE(delivered_tariff2, (SELECT last_delivered_tariff2 FROM LastKnownValues)) AS delivered_tariff2,\n COALESCE(returned_tariff1, (SELECT last_returned_tariff1 FROM LastKnownValues)) AS returned_tariff1,\n COALESCE(returned_tariff2, (SELECT last_returned_tariff2 FROM LastKnownValues)) AS returned_tariff2\n FROM\n p1\n WHERE\n timestamp >= $__timeFrom()::timestamptz AND timestamp < $__timeTo()::timestamptz\n)\nSELECT\n time,\n (delivered_tariff1 - returned_tariff1) AS \"Register 1\",\n (delivered_tariff2 - returned_tariff2) AS \"Register 2\",\n (delivered_tariff1 + delivered_tariff2 - returned_tariff1 - returned_tariff2) AS \"Total\"\nFROM\n MainQuery\n",
"refId": "A",
"sql": {
"columns": [
{
"parameters": [],
"type": "function"
}
],
"groupBy": [
{
"property": {
"type": "string"
},
"type": "groupBy"
}
],
"limit": 50
},
"table": "p1"
}
],
"title": "Meter readings (net metered)",
"type": "timeseries"
},
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"description": "Min: 216,2V. Max: 253V. (-6% to +10%)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "volt"
},
"overrides": []
},
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 10
},
"id": 3,
"options": {
"legend": {
"calcs": [
"logmin",
"max",
"mean",
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"editorMode": "code",
"format": "table",
"rawQuery": true,
"rawSql": "SELECT time_bucket('$__interval', timestamp) AS time, \nvoltage_l1 AS \"Voltage L1\", \nvoltage_l2 AS \"Voltage L2\", \nvoltage_l3 AS \"Voltage L3\"\nFROM p1\nWHERE timestamp >= $__timeFrom()::timestamptz AND timestamp < $__timeTo()::timestamptz",
"refId": "A",
"sql": {
"columns": [
{
"parameters": [],
"type": "function"
}
],
"groupBy": [
{
"property": {
"type": "string"
},
"type": "groupBy"
}
],
"limit": 50
},
"table": "p1"
}
],
"title": "Voltage",
"type": "timeseries"
},
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "amp"
},
"overrides": []
},
"gridPos": {
"h": 9,
"w": 12,
"x": 12,
"y": 10
},
"id": 6,
"options": {
"legend": {
"calcs": [
"min",
"max",
"mean",
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"editorMode": "code",
"format": "table",
"rawQuery": true,
"rawSql": "SELECT time_bucket('$__interval', timestamp) AS time, \ncurrent_l1 AS \"Current L1\", \ncurrent_l2 AS \"Current L2\", \ncurrent_l3 AS \"Current L3\"\nFROM p1\nWHERE timestamp >= $__timeFrom()::timestamptz AND timestamp < $__timeTo()::timestamptz",
"refId": "A",
"sql": {
"columns": [
{
"parameters": [],
"type": "function"
}
],
"groupBy": [
{
"property": {
"type": "string"
},
"type": "groupBy"
}
],
"limit": 50
},
"table": "p1"
}
],
"title": "Current",
"type": "timeseries"
},
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 19
},
"id": 7,
"options": {
"legend": {
"calcs": [
"firstNotNull",
"lastNotNull",
"range"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"editorMode": "code",
"format": "table",
"rawQuery": true,
"rawSql": "SELECT time_bucket('$__interval', timestamp) AS time, \nfailures AS \"Power outages\", \nlong_failures AS \"Long power outages\" \nFROM p1\nWHERE timestamp >= $__timeFrom()::timestamptz AND timestamp < $__timeTo()::timestamptz",
"refId": "A",
"sql": {
"columns": [
{
"parameters": [],
"type": "function"
}
],
"groupBy": [
{
"property": {
"type": "string"
},
"type": "groupBy"
}
],
"limit": 50
}
}
],
"title": "Power outages",
"type": "timeseries"
},
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"description": "1L = 1 dm^3, so 1kL = 1m^3. \nRange = consumption in selected time window.",
"fieldConfig": {
"defaults": {
"color": {
"fixedColor": "yellow",
"mode": "fixed"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 50,
"gradientMode": "opacity",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "stepAfter",
"lineStyle": {
"fill": "solid"
},
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"fieldMinMax": false,
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "litre"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 19
},
"id": 5,
"options": {
"legend": {
"calcs": [
"firstNotNull",
"lastNotNull",
"range"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "${DS_P1}"
},
"editorMode": "code",
"format": "table",
"rawQuery": true,
"rawSql": "SELECT time_bucket('$__interval', timestamp) AS time, \ngas AS \"Gas consumption\"\nFROM p1\nWHERE timestamp >= $__timeFrom()::timestamptz AND timestamp < $__timeTo()::timestamptz AND gas IS NOT NULL",
"refId": "A",
"sql": {
"columns": [
{
"parameters": [],
"type": "function"
}
],
"groupBy": [
{
"property": {
"type": "string"
},
"type": "groupBy"
}
],
"limit": 50
}
}
],
"title": "Gas consumption",
"type": "timeseries"
}
],
"refresh": "5s",
"schemaVersion": 39,
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now/d",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "P1 meter readings",
"uid": "cf296851-06f4-4479-9d5e-2bf85b56f69c",
"version": 2,
"weekStart": ""
}

View file

@ -1,4 +1,35 @@
{ {
"__inputs": [
{
"name": "DS_P1",
"label": "p1",
"description": "",
"type": "datasource",
"pluginId": "grafana-postgresql-datasource",
"pluginName": "PostgreSQL"
}
],
"__elements": {},
"__requires": [
{
"type": "grafana",
"id": "grafana",
"name": "Grafana",
"version": "11.1.1"
},
{
"type": "datasource",
"id": "grafana-postgresql-datasource",
"name": "PostgreSQL",
"version": "1.0.0"
},
{
"type": "panel",
"id": "timeseries",
"name": "Time series",
"version": ""
}
],
"annotations": { "annotations": {
"list": [ "list": [
{ {
@ -18,14 +49,14 @@
"editable": true, "editable": true,
"fiscalYearStartMonth": 0, "fiscalYearStartMonth": 0,
"graphTooltip": 0, "graphTooltip": 0,
"id": 4, "id": null,
"links": [], "links": [],
"liveNow": false, "liveNow": false,
"panels": [ "panels": [
{ {
"datasource": { "datasource": {
"type": "grafana-postgresql-datasource", "type": "grafana-postgresql-datasource",
"uid": "e2a4c5ce-511e-4a7d-acb7-fcbb79b230bf" "uid": "${DS_P1}"
}, },
"fieldConfig": { "fieldConfig": {
"defaults": { "defaults": {
@ -110,8 +141,8 @@
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
"type": "postgres", "type": "grafana-postgresql-datasource",
"uid": "e2a4c5ce-511e-4a7d-acb7-fcbb79b230bf" "uid": "${DS_P1}"
}, },
"editorMode": "code", "editorMode": "code",
"format": "table", "format": "table",
@ -144,7 +175,7 @@
{ {
"datasource": { "datasource": {
"type": "grafana-postgresql-datasource", "type": "grafana-postgresql-datasource",
"uid": "e2a4c5ce-511e-4a7d-acb7-fcbb79b230bf" "uid": "${DS_P1}"
}, },
"fieldConfig": { "fieldConfig": {
"defaults": { "defaults": {
@ -227,8 +258,8 @@
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
"type": "postgres", "type": "grafana-postgresql-datasource",
"uid": "e2a4c5ce-511e-4a7d-acb7-fcbb79b230bf" "uid": "${DS_P1}"
}, },
"editorMode": "code", "editorMode": "code",
"format": "table", "format": "table",
@ -261,7 +292,7 @@
{ {
"datasource": { "datasource": {
"type": "grafana-postgresql-datasource", "type": "grafana-postgresql-datasource",
"uid": "e2a4c5ce-511e-4a7d-acb7-fcbb79b230bf" "uid": "${DS_P1}"
}, },
"description": "First + Last = sinds 0, Range = huidige tijdsperiode.", "description": "First + Last = sinds 0, Range = huidige tijdsperiode.",
"fieldConfig": { "fieldConfig": {
@ -345,8 +376,8 @@
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
"type": "postgres", "type": "grafana-postgresql-datasource",
"uid": "e2a4c5ce-511e-4a7d-acb7-fcbb79b230bf" "uid": "${DS_P1}"
}, },
"editorMode": "code", "editorMode": "code",
"format": "table", "format": "table",
@ -378,8 +409,8 @@
}, },
{ {
"datasource": { "datasource": {
"type": "postgres", "type": "grafana-postgresql-datasource",
"uid": "e2a4c5ce-511e-4a7d-acb7-fcbb79b230bf" "uid": "${DS_P1}"
}, },
"description": "Min: 216,2V. Max: 253V. (-6% tot +10%)", "description": "Min: 216,2V. Max: 253V. (-6% tot +10%)",
"fieldConfig": { "fieldConfig": {
@ -464,8 +495,8 @@
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
"type": "postgres", "type": "grafana-postgresql-datasource",
"uid": "e2a4c5ce-511e-4a7d-acb7-fcbb79b230bf" "uid": "${DS_P1}"
}, },
"editorMode": "code", "editorMode": "code",
"format": "table", "format": "table",
@ -497,8 +528,8 @@
}, },
{ {
"datasource": { "datasource": {
"type": "postgres", "type": "grafana-postgresql-datasource",
"uid": "e2a4c5ce-511e-4a7d-acb7-fcbb79b230bf" "uid": "${DS_P1}"
}, },
"description": "Hoofdaansluiting: 3*25A", "description": "Hoofdaansluiting: 3*25A",
"fieldConfig": { "fieldConfig": {
@ -583,8 +614,8 @@
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
"type": "postgres", "type": "grafana-postgresql-datasource",
"uid": "e2a4c5ce-511e-4a7d-acb7-fcbb79b230bf" "uid": "${DS_P1}"
}, },
"editorMode": "code", "editorMode": "code",
"format": "table", "format": "table",
@ -617,7 +648,7 @@
{ {
"datasource": { "datasource": {
"type": "grafana-postgresql-datasource", "type": "grafana-postgresql-datasource",
"uid": "e2a4c5ce-511e-4a7d-acb7-fcbb79b230bf" "uid": "${DS_P1}"
}, },
"fieldConfig": { "fieldConfig": {
"defaults": { "defaults": {
@ -699,8 +730,8 @@
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
"type": "postgres", "type": "grafana-postgresql-datasource",
"uid": "e2a4c5ce-511e-4a7d-acb7-fcbb79b230bf" "uid": "${DS_P1}"
}, },
"editorMode": "code", "editorMode": "code",
"format": "table", "format": "table",
@ -732,7 +763,7 @@
{ {
"datasource": { "datasource": {
"type": "grafana-postgresql-datasource", "type": "grafana-postgresql-datasource",
"uid": "e2a4c5ce-511e-4a7d-acb7-fcbb79b230bf" "uid": "${DS_P1}"
}, },
"description": "1L = 1 dm^3 dus 1kL = 1m^3. \nRange = verbruik in geselecteerde periode.", "description": "1L = 1 dm^3 dus 1kL = 1m^3. \nRange = verbruik in geselecteerde periode.",
"fieldConfig": { "fieldConfig": {
@ -821,8 +852,8 @@
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
"type": "postgres", "type": "grafana-postgresql-datasource",
"uid": "e2a4c5ce-511e-4a7d-acb7-fcbb79b230bf" "uid": "${DS_P1}"
}, },
"editorMode": "code", "editorMode": "code",
"format": "table", "format": "table",
@ -859,8 +890,8 @@
"list": [] "list": []
}, },
"time": { "time": {
"from": "now-2d/d", "from": "now/d",
"to": "now-2d/d" "to": "now"
}, },
"timepicker": {}, "timepicker": {},
"timezone": "", "timezone": "",

153
main.go
View file

@ -1,9 +1,13 @@
// TODO: Process p1/online status.
// TODO: Add health check endpoint.
// TODO: Add multiple P1 monitoring capabilities
package main package main
import ( import (
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/fs"
"log" "log"
"log/slog" "log/slog"
"os" "os"
@ -14,6 +18,9 @@ import (
mqtt "github.com/eclipse/paho.mqtt.golang" mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/joho/godotenv" "github.com/joho/godotenv"
_ "github.com/lib/pq" _ "github.com/lib/pq"
"github.com/pressly/goose/v3"
"git.hollander.online/energy/p1-logger/migrations"
) )
// Payload struct // Payload struct
@ -93,6 +100,12 @@ type Payload struct {
R *int `json:"r"` // Returning / exporting (W) R *int `json:"r"` // Returning / exporting (W)
F *int `json:"f"` // Failure (counter) F *int `json:"f"` // Failure (counter)
Fl *int `json:"fl"` // Failure long duration (counter) Fl *int `json:"fl"` // Failure long duration (counter)
Sa1 *int `json:"sa1"` // Number of voltage sags L1
Sa2 *int `json:"sa2"` // Number of voltage sags L2
Sa3 *int `json:"sa3"` // Number of voltage sags L3
Sw1 *int `json:"sw1"` // Number of voltage swells L1
Sw2 *int `json:"sw2"` // Number of voltage swells L1
Sw3 *int `json:"sw3"` // Number of voltage swells L1
G *int `json:"g"` // Gas meter reading (l) G *int `json:"g"` // Gas meter reading (l)
V1 int `json:"v1"` // Voltage L1 (V) V1 int `json:"v1"` // Voltage L1 (V)
V2 int `json:"v2"` // Voltage L2 (V) V2 int `json:"v2"` // Voltage L2 (V)
@ -106,6 +119,14 @@ type Payload struct {
R1 *int `json:"r1"` // Returning / exporting L1 (W) R1 *int `json:"r1"` // Returning / exporting L1 (W)
R2 *int `json:"r2"` // Returning / exporting L2 (W) R2 *int `json:"r2"` // Returning / exporting L2 (W)
R3 *int `json:"r3"` // Returning / exporting L3 (W) R3 *int `json:"r3"` // Returning / exporting L3 (W)
GBe *int `json:"gbe"` // Gas meter reading (l) (Belgium)
CAQDBe *int `json:"aeicad"` // Current Average Quarterly Demand for Peak Tariff (Belgium)
CMMQDBe *int `json:"cmmqd"` // Current Months Maximum Quarterly Demand for Peak Tarrif (Belgium)
TMMQDBe *int `json:"13mmqd"` // 13 Month Maximum Quarterly Demand for Peak Tarrif (Belgium).
DLu *int `json:"dlu"` // Energy Delivered (Luxembourg)
RLu *int `json:"rlu"` // Energy Returned (Luxembourg)
} }
type Config struct { type Config struct {
@ -281,8 +302,11 @@ func safeDerefInt(ptr *int) string {
} }
var prevDt1, prevDt2, prevRt1, prevRt2, prevG, prevF, prevFl int var prevDt1, prevDt2, prevRt1, prevRt2, prevG, prevF, prevFl int
var prevSa1, prevSa2, prevSa3, prevSw1, prevSw2, prevSw3 int
var prevD, prevR int var prevD, prevR int
var prevD1, prevD2, prevD3, prevR1, prevR2, prevR3 int var prevD1, prevD2, prevD3, prevR1, prevR2, prevR3 int
var prevGBe, prevCAQDBe, prevCMMQDBe, prevTMMQDBe int
var prevDLu, prevRLu int
func mqttMessageHandler(client mqtt.Client, msg mqtt.Message) { func mqttMessageHandler(client mqtt.Client, msg mqtt.Message) {
// Parse JSON payload // Parse JSON payload
@ -324,6 +348,22 @@ func mqttMessageHandler(client mqtt.Client, msg mqtt.Message) {
payload.Fl, tempChanged = updateFieldIfChanged(payload.Fl, &prevFl) payload.Fl, tempChanged = updateFieldIfChanged(payload.Fl, &prevFl)
changed = changed || tempChanged changed = changed || tempChanged
// Sags
payload.Sa1, tempChanged = updateFieldIfChanged(payload.Sa1, &prevSa1)
changed = changed || tempChanged
payload.Sa2, tempChanged = updateFieldIfChanged(payload.Sa2, &prevSa2)
changed = changed || tempChanged
payload.Sa3, tempChanged = updateFieldIfChanged(payload.Sa3, &prevSa3)
changed = changed || tempChanged
// Swells
payload.Sw1, tempChanged = updateFieldIfChanged(payload.Sw1, &prevSw1)
changed = changed || tempChanged
payload.Sw2, tempChanged = updateFieldIfChanged(payload.Sw2, &prevSw2)
changed = changed || tempChanged
payload.Sw3, tempChanged = updateFieldIfChanged(payload.Sw3, &prevSw3)
changed = changed || tempChanged
// Gas // Gas
payload.G, tempChanged = updateFieldIfChanged(payload.G, &prevG) payload.G, tempChanged = updateFieldIfChanged(payload.G, &prevG)
changed = changed || tempChanged changed = changed || tempChanged
@ -350,6 +390,22 @@ func mqttMessageHandler(client mqtt.Client, msg mqtt.Message) {
payload.R3, tempChanged = updateFieldIfChanged(payload.R3, &prevR3) payload.R3, tempChanged = updateFieldIfChanged(payload.R3, &prevR3)
changed = changed || tempChanged changed = changed || tempChanged
// Belgium
payload.GBe, tempChanged = updateFieldIfChanged(payload.GBe, &prevGBe)
changed = changed || tempChanged
payload.CAQDBe, tempChanged = updateFieldIfChanged(payload.CAQDBe, &prevCAQDBe)
changed = changed || tempChanged
payload.CMMQDBe, tempChanged = updateFieldIfChanged(payload.CMMQDBe, &prevCMMQDBe)
changed = changed || tempChanged
payload.TMMQDBe, tempChanged = updateFieldIfChanged(payload.TMMQDBe, &prevTMMQDBe)
changed = changed || tempChanged
// Luxembourg
payload.DLu, tempChanged = updateFieldIfChanged(payload.DLu, &prevDLu)
changed = changed || tempChanged
payload.RLu, tempChanged = updateFieldIfChanged(payload.RLu, &prevRLu)
changed = changed || tempChanged
// If any value has changed, log all the relevant values // If any value has changed, log all the relevant values
if changed { if changed {
logger.Debug("Values changed", logger.Debug("Values changed",
@ -363,6 +419,15 @@ func mqttMessageHandler(client mqtt.Client, msg mqtt.Message) {
"f", safeDerefInt(payload.F), "f", safeDerefInt(payload.F),
"fl", safeDerefInt(payload.Fl), "fl", safeDerefInt(payload.Fl),
"sa1", safeDerefInt(payload.Sa1),
"sa2", safeDerefInt(payload.Sa2),
"sa3", safeDerefInt(payload.Sa3),
"sw1", safeDerefInt(payload.Sw1),
"sw2", safeDerefInt(payload.Sw2),
"sw3", safeDerefInt(payload.Sw3),
"g", safeDerefInt(payload.G), "g", safeDerefInt(payload.G),
"d1", safeDerefInt(payload.D1), "d1", safeDerefInt(payload.D1),
@ -372,6 +437,14 @@ func mqttMessageHandler(client mqtt.Client, msg mqtt.Message) {
"r1", safeDerefInt(payload.R1), "r1", safeDerefInt(payload.R1),
"r2", safeDerefInt(payload.R2), "r2", safeDerefInt(payload.R2),
"r3", safeDerefInt(payload.R3), "r3", safeDerefInt(payload.R3),
"gbe", safeDerefInt(payload.GBe),
"caqdbe", safeDerefInt(payload.CAQDBe),
"cmmqdbe", safeDerefInt(payload.CMMQDBe),
"tmmqdbe", safeDerefInt(payload.TMMQDBe),
"dlu", safeDerefInt(payload.DLu),
"rlu", safeDerefInt(payload.RLu),
) )
} }
// Insert data into PostgreSQL // Insert data into PostgreSQL
@ -398,7 +471,17 @@ func mqttMessageHandler(client mqtt.Client, msg mqtt.Message) {
"d3", payload.D3, "d3", payload.D3,
"r1", payload.R1, "r1", payload.R1,
"r2", payload.R2, "r2", payload.R2,
"r3", payload.R3) "r3", payload.R3,
"gbe", payload.GBe,
"caqdbe", payload.CAQDBe,
"cmmqdbe", payload.CMMQDBe,
"tmmqdbe", payload.TMMQDBe,
"dlu", payload.DLu,
"rlu", payload.RLu,
)
if err != nil { if err != nil {
logger.Error("Error inserting data into PostgreSQL", "error", err) logger.Error("Error inserting data into PostgreSQL", "error", err)
} }
@ -435,12 +518,16 @@ func insertData(timestamp time.Time, payload Payload) error {
stmt := ` stmt := `
INSERT INTO p1 ( INSERT INTO p1 (
timestamp, delivered_tariff1, delivered_tariff2, returned_tariff1, returned_tariff2, timestamp, delivered_tariff1, delivered_tariff2, returned_tariff1, returned_tariff2,
delivery_all, returning_all, failures, long_failures, gas, delivery_all, returning_all,
failures, long_failures,
sags_l1, sags_l2, sags_l3,
swells_l1, swells_l2, swells_l3,
gas,
voltage_l1, voltage_l2, voltage_l3, voltage_l1, voltage_l2, voltage_l3,
current_l1, current_l2, current_l3, current_l1, current_l2, current_l3,
delivery_l1, delivery_l2, delivery_l3, delivery_l1, delivery_l2, delivery_l3,
returning_l1, returning_l2, returning_l3 returning_l1, returning_l2, returning_l3
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22) ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28)
` `
_, err := db.Exec( _, err := db.Exec(
stmt, stmt,
@ -449,6 +536,8 @@ func insertData(timestamp time.Time, payload Payload) error {
payload.Rt1, payload.Rt2, payload.Rt1, payload.Rt2,
payload.D, payload.R, payload.D, payload.R,
payload.F, payload.Fl, payload.F, payload.Fl,
payload.Sa1, payload.Sa2, payload.Sa3,
payload.Sw1, payload.Sw2, payload.Sw3,
payload.G, payload.G,
payload.V1, payload.V2, payload.V3, payload.V1, payload.V2, payload.V3,
payload.C1, payload.C2, payload.C3, payload.C1, payload.C2, payload.C3,
@ -471,36 +560,10 @@ func connectToPostgreSQL(pgConnStr string) error {
time.Sleep(5 * time.Second) // Retry after 5 seconds time.Sleep(5 * time.Second) // Retry after 5 seconds
} }
// Create table if not exists // Perform DB migrations
_, err = db.Exec(` err = migrateFS(db, migrations.FS, ".")
CREATE TABLE IF NOT EXISTS p1 (
timestamp TIMESTAMPTZ,
delivered_tariff1 INT,
delivered_tariff2 INT,
returned_tariff1 INT,
returned_tariff2 INT,
delivery_all INT,
returning_all INT,
failures INT,
long_failures INT,
gas INT,
voltage_l1 INT,
voltage_l2 INT,
voltage_l3 INT,
current_l1 INT,
current_l2 INT,
current_l3 INT,
delivery_l1 INT,
delivery_l2 INT,
delivery_l3 INT,
returning_l1 INT,
returning_l2 INT,
returning_l3 INT
);
-- CREATE UNIQUE INDEX IF NOT EXISTS timestamp_idx ON p1 (timestamp);
`)
if err != nil { if err != nil {
log.Fatal("Error creating table:", err) log.Fatal(err)
} }
if config.TimescaleDB { if config.TimescaleDB {
@ -516,3 +579,29 @@ func connectToPostgreSQL(pgConnStr string) error {
return nil return nil
} }
func migrate(db *sql.DB, dir string) error {
err := goose.SetDialect("postgres")
if err != nil {
return fmt.Errorf("migrate: %w", err)
}
err = goose.Up(db, dir)
if err != nil {
return fmt.Errorf("migrate: %w", err)
}
return nil
}
func migrateFS(db *sql.DB, migrationFS fs.FS, dir string) error {
// In case the dir is an empty string, they probably meant the current directory and goose wants a period for that.
if dir == "" {
dir = "."
}
goose.SetBaseFS(migrationFS)
defer func() {
// Ensure that we remove the FS on the off chance some other part of our app uses goose for migrations and doesn't want to use our FS.
goose.SetBaseFS(nil)
}()
return migrate(db, dir)
}

33
migrations/00001_p1.sql Normal file
View file

@ -0,0 +1,33 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE IF NOT EXISTS p1 (
timestamp TIMESTAMPTZ,
delivered_tariff1 INT,
delivered_tariff2 INT,
returned_tariff1 INT,
returned_tariff2 INT,
delivery_all INT,
returning_all INT,
failures INT,
long_failures INT,
sags_l1 INT,
sags_l2 INT,
sags_l3 INT,
swells_l1 INT,
swells_l2 INT,
swells_l3 INT,
gas INT,
voltage_l1 INT,
voltage_l2 INT,
voltage_l3 INT,
current_l1 INT,
current_l2 INT,
current_l3 INT,
delivery_l1 INT,
delivery_l2 INT,
delivery_l3 INT,
returning_l1 INT,
returning_l2 INT,
returning_l3 INT
);
-- +goose StatementEnd

6
migrations/fs.go Normal file
View file

@ -0,0 +1,6 @@
package migrations
import "embed"
//go:embed *.sql
var FS embed.FS