diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 18ace52..5692539 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -3,19 +3,19 @@ name: Docker Build and Test on: push: paths: - - 'grafana/**' - - '.github/workflows/workflow.yml' + - "grafana/**" + - ".github/workflows/workflow.yml" pull_request: paths: - - 'grafana/**' - - '.github/workflows/workflow.yml' + - "grafana/**" + - ".github/workflows/workflow.yml" jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -49,3 +49,74 @@ jobs: run: | cd grafana docker compose down -v + + deploy: + runs-on: ubuntu-latest + needs: build + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + + steps: + - uses: actions/checkout@v4 + + - name: Deploy dashboards to Grafana Cloud + env: + GRAFANA_URL: https://softwarecsc65.grafana.net + GRAFANA_TOKEN: ${{ secrets.GRAFANA_SERVICE_ACCOUNT_TOKEN }} + run: | + datasources=$(curl -sf \ + -H "Authorization: Bearer $GRAFANA_TOKEN" \ + "$GRAFANA_URL/api/datasources") || { echo "ERROR: failed to fetch datasources from Grafana Cloud"; exit 1; } + + for file in grafana/dashboards/*.json; do + echo "Deploying $file..." + dashboard=$(jq 'del(.id)' "$file") + + # For each datasource listed in __inputs, look up its uid on Grafana + # Cloud by matching on pluginId, datasource type, or name. Build an + # inputs array for the import payload (required when __inputs is + # present) and rewrite hardcoded panel UIDs at the same time. + resolved_inputs='[]' + while IFS= read -r input_json; do + plugin_id=$(echo "$input_json" | jq -r '.pluginId') + local_uid=$(echo "$input_json" | jq -r '.label') + input_name=$(echo "$input_json" | jq -r '.name') + + cloud_uid=$(echo "$datasources" | jq -r --arg t "$plugin_id" --arg n "$local_uid" \ + '.[] | select(.type == $t or .type == $n or .name == $n) | .uid' | head -1) + + if [ -z "$cloud_uid" ]; then + echo "ERROR: no datasource with type '$plugin_id' or name '$local_uid' found in Grafana Cloud" + exit 1 + fi + + resolved_inputs=$(echo "$resolved_inputs" | jq \ + --arg name "$input_name" \ + --arg plugin_id "$plugin_id" \ + --arg uid "$cloud_uid" \ + '. + [{"name": $name, "type": "datasource", "pluginId": $plugin_id, "value": $uid}]') + + dashboard=$(echo "$dashboard" | jq \ + --arg plugin_id "$plugin_id" \ + --arg local_uid "$local_uid" \ + --arg cloud_uid "$cloud_uid" \ + 'walk(if type == "object" and has("uid") and (.type == $plugin_id or .type == $local_uid or .uid == $local_uid) then .uid = $cloud_uid else . end)') + done < <(echo "$dashboard" | jq -c '.__inputs[]?') + + payload=$(jq -n \ + --argjson dashboard "$dashboard" \ + --argjson inputs "$resolved_inputs" \ + '{dashboard: $dashboard, overwrite: true, folderUid: "", inputs: $inputs}') + + response=$(curl -s -o /tmp/gf_response.json -w "%{http_code}" \ + -X POST "$GRAFANA_URL/api/dashboards/import" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $GRAFANA_TOKEN" \ + -d "$payload") + + if [ "$response" != "200" ]; then + echo "Failed to deploy $file (HTTP $response)" + cat /tmp/gf_response.json + exit 1 + fi + echo "Successfully deployed $file" + done diff --git a/grafana/.env.example b/grafana/.env.example index 9f11bcf..beddbeb 100644 --- a/grafana/.env.example +++ b/grafana/.env.example @@ -1,5 +1,8 @@ -MQTT_PASSWORD=yourpassword MQTT_USERNAME=yourusername +MQTT_PASSWORD=yourpassword +MQTT_HOST=yourhost + POSTGRES_USERNAME=yourusername POSTGRES_PASSWORD=yourpassword -POSTGRES_HOST=yourhost \ No newline at end of file +POSTGRES_HOST=yourhost +POSTGRES_SSLMODE=disable diff --git a/grafana/dashboards/Das Telemetry Base Dashboard (History)-1769897898291.json b/grafana/dashboards/Das Telemetry Base Dashboard (History)-1769897898291.json index c0b8ca0..7e9fb27 100644 --- a/grafana/dashboards/Das Telemetry Base Dashboard (History)-1769897898291.json +++ b/grafana/dashboards/Das Telemetry Base Dashboard (History)-1769897898291.json @@ -1,4 +1,22 @@ { + "__inputs": [ + { + "name": "DS_GRAFANA-POSTGRESQL-DATASOURCE", + "label": "grafana-postgresql-datasource", + "description": "", + "type": "datasource", + "pluginId": "postgres", + "pluginName": "PostgreSQL" + } + ], + "__requires": [ + { + "type": "datasource", + "id": "postgres", + "name": "PostgreSQL", + "version": "1.0.0" + } + ], "annotations": { "list": [ { @@ -35,6 +53,32 @@ "type": "row" }, { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "grafana-postgresql-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, "gridPos": { "h": 8, "w": 6, @@ -42,17 +86,53 @@ "y": 1 }, "id": 3, - "libraryPanel": { - "name": "Pack State Of Charge", - "uid": "efagvhn0selfkc" + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": true, + "showThresholdMarkers": true, + "sizing": "auto" }, + "pluginVersion": "12.3.1", + "targets": [ + { + "dataset": "tsdb", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "grafana-postgresql-datasource" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT \"timestamp\", \"PackStateOfCharge\"\nFROM telemetry_packet\nWHERE $__timeFilter(\"timestamp\")\nORDER BY \"timestamp\" ASC", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [], + "limit": 50 + } + } + ], "title": "Pack State Of Charge", - "type": "library-panel-ref" + "type": "gauge" }, { "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "fieldConfig": { "defaults": { @@ -136,12 +216,12 @@ "dataset": "tsdb", "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT \"Timestamp\", \"PackCurrent\", \"PackVoltage\", \"PackStateOfCharge\", \"PackAmphours\", \"RegenBraking\" \nFROM telemetry_packet\nWHERE\n $__timeFilter(\"Timestamp\")\nORDER BY \"Timestamp\" ASC", + "rawSql": "SELECT \"timestamp\", \"PackCurrent\", \"PackVoltage\", \"PackStateOfCharge\", \"PackAmphours\", \"RegenBraking\" \nFROM telemetry_packet\nWHERE\n $__timeFilter(\"timestamp\")\nORDER BY \"timestamp\" ASC", "refId": "A", "sql": { "columns": [ @@ -168,7 +248,7 @@ { "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "fieldConfig": { "defaults": { @@ -223,12 +303,12 @@ "dataset": "tsdb", "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT \"Timestamp\", \"EnergyConsumed\" \nFROM lap\nWHERE\n $__timeFilter(\"Timestamp\")\nORDER BY \"Timestamp\" ASC", + "rawSql": "SELECT \"timestamp\", \"EnergyConsumed\" \nFROM lap\nWHERE\n $__timeFilter(\"timestamp\")\nORDER BY \"timestamp\" ASC", "refId": "A", "sql": { "columns": [ @@ -255,7 +335,7 @@ { "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "fieldConfig": { "defaults": { @@ -310,12 +390,12 @@ "dataset": "tsdb", "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT\n \"Timestamp\" AS time,\n \"TotalPowerIn\"\nFROM lap\nWHERE\n $__timeFilter(\"Timestamp\")\nORDER BY \"Timestamp\" ASC", + "rawSql": "SELECT\n \"timestamp\" AS time,\n \"TotalPowerIn\"\nFROM lap\nWHERE\n $__timeFilter(\"timestamp\")\nORDER BY \"timestamp\" ASC", "refId": "A", "sql": { "columns": [ @@ -342,7 +422,7 @@ { "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "fieldConfig": { "defaults": { @@ -397,12 +477,12 @@ "dataset": "tsdb", "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT \"Timestamp\", \"AveragePackCurrent\"\nFROM lap \nWHERE\n $__timeFilter(\"Timestamp\")\nORDER BY \"Timestamp\" ASC", + "rawSql": "SELECT \"timestamp\", \"AveragePackCurrent\"\nFROM lap \nWHERE\n $__timeFilter(\"timestamp\")\nORDER BY \"timestamp\" ASC", "refId": "A", "sql": { "columns": [ @@ -429,7 +509,7 @@ { "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "fieldConfig": { "defaults": { @@ -484,12 +564,12 @@ "dataset": "tsdb", "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT \"TotalPowerOut\" \nFROM lap\nWHERE\n $__timeFilter(\"Timestamp\")\nORDER BY \"Timestamp\" ASC", + "rawSql": "SELECT \"TotalPowerOut\" \nFROM lap\nWHERE\n $__timeFilter(\"timestamp\")\nORDER BY \"timestamp\" ASC", "refId": "A", "sql": { "columns": [ @@ -516,7 +596,7 @@ { "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "fieldConfig": { "defaults": { @@ -571,12 +651,12 @@ "dataset": "tsdb", "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT \"Timestamp\", \"BatterySecondsRemaining\" \nFROM lap \nWHERE\n $__timeFilter(\"Timestamp\")\nORDER BY \"Timestamp\" ASC", + "rawSql": "SELECT \"timestamp\", \"BatterySecondsRemaining\" \nFROM lap \nWHERE\n $__timeFilter(\"timestamp\")\nORDER BY \"timestamp\" ASC", "refId": "A", "sql": { "columns": [ @@ -632,7 +712,7 @@ { "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "fieldConfig": { "defaults": { @@ -687,12 +767,12 @@ "dataset": "tsdb", "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT \"Timestamp\", \"AverageSpeed\" \nFROM lap\nWHERE\n $__timeFilter(\"Timestamp\")\nORDER BY \"Timestamp\" ASC", + "rawSql": "SELECT \"timestamp\", \"AverageSpeed\" \nFROM lap\nWHERE\n $__timeFilter(\"timestamp\")\nORDER BY \"timestamp\" ASC", "refId": "A", "sql": { "columns": [ @@ -719,7 +799,7 @@ { "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "fieldConfig": { "defaults": { @@ -775,12 +855,12 @@ "dataset": "tsdb", "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT \"Timestamp\", \"LapTime\" \nFROM lap \nWHERE\n $__timeFilter(\"Timestamp\")\nORDER BY \"Timestamp\" ASC", + "rawSql": "SELECT \"timestamp\", \"LapTime\" \nFROM lap \nWHERE\n $__timeFilter(\"timestamp\")\nORDER BY \"timestamp\" ASC", "refId": "A", "sql": { "columns": [ @@ -820,7 +900,7 @@ { "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "fieldConfig": { "defaults": { @@ -904,12 +984,12 @@ "dataset": "tsdb", "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT \"Timestamp\", \"Acceleration\", \"BrakeSwitchDigital\", \"LeftSignalStatus\", \"RightSignalStatus\" \nFROM telemetry_packet \nWHERE\n $__timeFilter(\"Timestamp\")\nORDER BY \"Timestamp\" ASC", + "rawSql": "SELECT \"timestamp\", \"Acceleration\", \"BrakeSwitchDigital\", \"LeftSignalStatus\", \"RightSignalStatus\" \nFROM telemetry_packet \nWHERE\n $__timeFilter(\"timestamp\")\nORDER BY \"timestamp\" ASC", "refId": "A", "sql": { "columns": [ @@ -936,7 +1016,7 @@ { "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "description": "Critical system errors or safety trips within the battery management system.", "fieldConfig": { @@ -1021,12 +1101,12 @@ "dataset": "tsdb", "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT \"ProximitySensor1\", \"Timestamp\", \"ProtectionTrip\", \"MalfunctionIndicatorActive\", \"ArrayContactorError\"\nFROM telemetry_packet \nWHERE\n $__timeFilter(\"Timestamp\")\nORDER BY \"Timestamp\" ASC", + "rawSql": "SELECT \"ProximitySensor1\", \"timestamp\", \"ProtectionTrip\", \"MalfunctionIndicatorActive\", \"ArrayContactorError\"\nFROM telemetry_packet \nWHERE\n $__timeFilter(\"timestamp\")\nORDER BY \"timestamp\" ASC", "refId": "A", "sql": { "columns": [ @@ -1053,7 +1133,7 @@ { "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "fieldConfig": { "defaults": { @@ -1108,12 +1188,12 @@ "dataset": "tsdb", "datasource": { "type": "grafana-postgresql-datasource", - "uid": "ffbv3rpe7yy2oc" + "uid": "grafana-postgresql-datasource" }, "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT \"Timestamp\", \"Distance\" \nFROM lap \nWHERE\n $__timeFilter(\"Timestamp\")\nORDER BY \"Timestamp\" ASC", + "rawSql": "SELECT \"timestamp\", \"Distance\" \nFROM lap \nWHERE\n $__timeFilter(\"timestamp\")\nORDER BY \"timestamp\" ASC", "refId": "A", "sql": { "columns": [ diff --git a/grafana/dashboards/Das Telemetry Base Dashboard-1763418239880.json b/grafana/dashboards/Das Telemetry Base Dashboard-1763418239880.json index 8ee4aa6..bf7167b 100644 --- a/grafana/dashboards/Das Telemetry Base Dashboard-1763418239880.json +++ b/grafana/dashboards/Das Telemetry Base Dashboard-1763418239880.json @@ -1,4 +1,22 @@ { + "__inputs": [ + { + "name": "DS_MQTT", + "label": "MQTT", + "description": "", + "type": "datasource", + "pluginId": "grafana-mqtt-datasource", + "pluginName": "MQTT" + } + ], + "__requires": [ + { + "type": "datasource", + "id": "grafana-mqtt-datasource", + "name": "MQTT", + "version": "1.0.0" + } + ], "annotations": { "list": [ { @@ -6,770 +24,39 @@ "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": 2, - "links": [], - "panels": [ - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 2, - "panels": [], - "title": "Range and Efficiency", - "type": "row" - }, - { - "datasource": { - "type": "grafana-mqtt-datasource", - "uid": "P51B197A508373975" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": 0 - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "__systemRef": "hideSeriesFrom", - "matcher": { - "id": "byNames", - "options": { - "mode": "exclude", - "names": [ - "FanSpeed" - ], - "prefix": "All except:", - "readOnly": true - } - }, - "properties": [] - } - ] - }, - "gridPos": { - "h": 8, - "w": 6, - "x": 0, - "y": 1 - }, - "id": 3, - "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": true, - "showThresholdMarkers": true, - "sizing": "auto" - }, - "pluginVersion": "12.3.1", - "targets": [ - { - "datasource": { - "type": "grafana-mqtt-datasource", - "uid": "P51B197A508373975" - }, - "refId": "A", - "topic": "packet" - } - ], - "title": "Pack State Of Charge", - "transformations": [ - { - "id": "extractFields", - "options": { - "delimiter": ",", - "format": "json", - "jsonPaths": [ - { - "path": "PackStateOfCharge" - } - ], - "keepTime": false, - "replace": true, - "source": "Battery" - } - } - ], - "type": "gauge" - }, - { - "datasource": { - "type": "grafana-mqtt-datasource", - "uid": "P51B197A508373975" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "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", - "showValues": false, - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": 0 - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "__systemRef": "hideSeriesFrom", - "matcher": { - "id": "byNames", - "options": { - "mode": "exclude", - "names": [ - "PackCurrent" - ], - "prefix": "All except:", - "readOnly": true - } - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "legend": false, - "tooltip": true, - "viz": true - } - } - ] - } - ] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 6, - "y": 1 - }, - "id": 1, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "12.3.1", - "targets": [ - { - "datasource": { - "type": "grafana-mqtt-datasource", - "uid": "P51B197A508373975" - }, - "refId": "A", - "topic": "packet" - } - ], - "title": "Packet (History)", - "transformations": [ - { - "id": "extractFields", - "options": { - "delimiter": ",", - "format": "json", - "jsonPaths": [ - { - "path": "PackVoltage" - }, - { - "path": "PackCurrent" - }, - { - "alias": "PackStateOfCharge (SOC)", - "path": "PackStateOfCharge" - }, - { - "alias": "PackAmphours (Ah)", - "path": "PackAmphours" - } - ], - "keepTime": false, - "replace": false, - "source": "Battery" - } - }, - { - "id": "extractFields", - "options": { - "delimiter": ",", - "format": "json", - "jsonPaths": [ - { - "path": "RegenBraking" - } - ], - "keepTime": false, - "replace": false, - "source": "B3" - } - } - ], - "type": "timeseries" - }, - { - "datasource": { - "type": "grafana-mqtt-datasource", - "uid": "P51B197A508373975" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": 0 - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "kwatth" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 6, - "x": 0, - "y": 9 - }, - "id": 11, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "center", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "12.3.1", - "targets": [ - { - "datasource": { - "type": "golioth-websocket-datasource", - "uid": "bf4fqic90e41se" - }, - "fields": [ - { - "jsonPath": "$.data.data", - "language": "jsonpath", - "name": "" - } - ], - "path": "", - "refId": "A", - "topic": "lapdata", - "withStreaming": true - } - ], - "title": "Energy Consumed", - "transformations": [ - { - "id": "extractFields", - "options": { - "delimiter": ",", - "format": "json", - "jsonPaths": [ - { - "path": "energyConsumed" - } - ], - "replace": true, - "source": "data" - } - } - ], - "type": "stat" - }, - { - "datasource": { - "type": "grafana-mqtt-datasource", - "uid": "P51B197A508373975" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": 0 - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "kwatth" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 6, - "y": 9 - }, - "id": 13, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "center", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "12.3.1", - "targets": [ - { - "datasource": { - "type": "golioth-websocket-datasource", - "uid": "bf4fqic90e41se" - }, - "fields": [ - { - "jsonPath": "$.data.data", - "language": "jsonpath", - "name": "" - } - ], - "path": "", - "refId": "A", - "topic": "lapdata", - "withStreaming": true - } - ], - "title": "Total Power In", - "transformations": [ - { - "id": "extractFields", - "options": { - "delimiter": ",", - "format": "json", - "jsonPaths": [ - { - "path": "totalPowerIn" - } - ], - "replace": true, - "source": "data" - } - } - ], - "type": "stat" - }, - { - "datasource": { - "type": "grafana-mqtt-datasource", - "uid": "P51B197A508373975" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": 0 - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "amph" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 7, - "x": 12, - "y": 9 - }, - "id": 10, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "center", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "12.3.1", - "targets": [ - { - "datasource": { - "type": "golioth-websocket-datasource", - "uid": "bf4fqic90e41se" - }, - "fields": [ - { - "jsonPath": "$.data.data", - "language": "jsonpath", - "name": "" - } - ], - "path": "", - "refId": "A", - "topic": "lapdata", - "withStreaming": true - } - ], - "title": "Average Pack Current", - "transformations": [ - { - "id": "extractFields", - "options": { - "delimiter": ",", - "format": "json", - "jsonPaths": [ - { - "path": "averagePackCurrent" - } - ], - "replace": true, - "source": "data" - } - } - ], - "type": "stat" - }, - { - "datasource": { - "type": "grafana-mqtt-datasource", - "uid": "P51B197A508373975" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": 0 - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "kwatt" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 6, - "y": 13 - }, - "id": 14, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "center", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "12.3.1", - "targets": [ - { - "datasource": { - "type": "golioth-websocket-datasource", - "uid": "bf4fqic90e41se" - }, - "fields": [ - { - "jsonPath": "$.data.data", - "language": "jsonpath", - "name": "" - } - ], - "path": "", - "refId": "A", - "topic": "lapdata", - "withStreaming": true - } - ], - "title": "Total Power Out", - "transformations": [ - { - "id": "extractFields", - "options": { - "delimiter": ",", - "format": "json", - "jsonPaths": [ - { - "path": "totalPowerOut" - } - ], - "replace": true, - "source": "data" - } - } - ], - "type": "stat" - }, - { - "datasource": { - "type": "grafana-mqtt-datasource", - "uid": "P51B197A508373975" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": 0 - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 17 - }, - "id": 12, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "center", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "12.3.1", - "targets": [ - { - "datasource": { - "type": "golioth-websocket-datasource", - "uid": "bf4fqic90e41se" - }, - "fields": [ - { - "jsonPath": "$.data.data", - "language": "jsonpath", - "name": "" - } - ], - "path": "", - "refId": "A", - "topic": "lapdata", - "withStreaming": true - } - ], - "title": "Battery Seconds Remaining", - "transformations": [ - { - "id": "extractFields", - "options": { - "delimiter": ",", - "format": "json", - "jsonPaths": [ - { - "path": "batterySecondsRemaining" - } - ], - "replace": true, - "source": "data" - } - } - ], - "type": "stat" - }, + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 2, + "links": [], + "panels": [ { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, - "y": 24 + "y": 0 }, - "id": 16, + "id": 2, "panels": [], - "title": "Speed & Performance", + "title": "Range and Efficiency", "type": "row" }, { "datasource": { "type": "grafana-mqtt-datasource", - "uid": "P51B197A508373975" + "uid": "grafana-mqtt-datasource" }, - "description": "", "fieldConfig": { "defaults": { "color": { @@ -788,24 +75,37 @@ "value": 80 } ] - }, - "unit": "velocitykmh" + } }, - "overrides": [] + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "FanSpeed" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [] + } + ] }, "gridPos": { - "h": 7, + "h": 8, "w": 6, "x": 0, - "y": 25 + "y": 1 }, - "id": 17, + "id": 3, "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "center", + "minVizHeight": 75, + "minVizWidth": 75, "orientation": "auto", - "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" @@ -813,31 +113,22 @@ "fields": "", "values": false }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "showThresholdLabels": true, + "showThresholdMarkers": true, + "sizing": "auto" }, "pluginVersion": "12.3.1", "targets": [ { "datasource": { - "type": "golioth-websocket-datasource", - "uid": "bf4fqic90e41se" + "type": "grafana-mqtt-datasource", + "uid": "grafana-mqtt-datasource" }, - "fields": [ - { - "jsonPath": "$.data.data", - "language": "jsonpath", - "name": "" - } - ], - "path": "", "refId": "A", - "topic": "lapdata", - "withStreaming": true + "topic": "packet" } ], - "title": "Average Speed", + "title": "Pack State Of Charge", "transformations": [ { "id": "extractFields", @@ -846,28 +137,61 @@ "format": "json", "jsonPaths": [ { - "path": "averageSpeed" + "path": "PackStateOfCharge" } ], + "keepTime": false, "replace": true, - "source": "data" + "source": "Battery" } } ], - "type": "stat" + "type": "gauge" }, { "datasource": { "type": "grafana-mqtt-datasource", - "uid": "P51B197A508373975" + "uid": "grafana-mqtt-datasource" }, - "description": "", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, - "fieldMinMax": false, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, "mappings": [], "thresholds": { "mode": "absolute", @@ -881,56 +205,67 @@ "value": 80 } ] - }, - "unit": "dtdurations" + } }, - "overrides": [] + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "PackCurrent" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": true, + "viz": true + } + } + ] + } + ] }, "gridPos": { - "h": 7, - "w": 6, + "h": 8, + "w": 12, "x": 6, - "y": 25 + "y": 1 }, - "id": 18, + "id": 1, "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "center", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } }, "pluginVersion": "12.3.1", "targets": [ { "datasource": { - "type": "golioth-websocket-datasource", - "uid": "bf4fqic90e41se" + "type": "grafana-mqtt-datasource", + "uid": "grafana-mqtt-datasource" }, - "fields": [ - { - "jsonPath": "$.data.data", - "language": "jsonpath", - "name": "" - } - ], - "path": "", "refId": "A", - "topic": "lapdata", - "withStreaming": true + "topic": "packet" } ], - "title": "Lap Time", + "title": "Packet (History)", "transformations": [ { "id": "extractFields", @@ -939,16 +274,55 @@ "format": "json", "jsonPaths": [ { - "path": "lapTime" + "path": "PackVoltage" + }, + { + "path": "PackCurrent" + }, + { + "alias": "PackStateOfCharge (SOC)", + "path": "PackStateOfCharge" + }, + { + "alias": "PackAmphours (Ah)", + "path": "PackAmphours" + } + ], + "keepTime": false, + "replace": false, + "source": "Battery" + } + }, + { + "id": "extractFields", + "options": { + "delimiter": ",", + "format": "json", + "jsonPaths": [ + { + "path": "RegenBraking" } ], "keepTime": false, - "replace": true, - "source": "data" + "replace": false, + "source": "B3" } } ], - "type": "stat" + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 24 + }, + "id": 16, + "panels": [], + "title": "Speed & Performance", + "type": "row" }, { "collapsed": false, @@ -966,7 +340,7 @@ { "datasource": { "type": "grafana-mqtt-datasource", - "uid": "P51B197A508373975" + "uid": "grafana-mqtt-datasource" }, "fieldConfig": { "defaults": { @@ -1074,7 +448,7 @@ { "datasource": { "type": "grafana-mqtt-datasource", - "uid": "P51B197A508373975" + "uid": "grafana-mqtt-datasource" }, "refId": "A", "topic": "packet" @@ -1112,7 +486,7 @@ { "datasource": { "type": "grafana-mqtt-datasource", - "uid": "P51B197A508373975" + "uid": "grafana-mqtt-datasource" }, "description": "Critical system errors or safety trips within the battery management system.", "fieldConfig": { @@ -1221,7 +595,7 @@ { "datasource": { "type": "grafana-mqtt-datasource", - "uid": "P51B197A508373975" + "uid": "grafana-mqtt-datasource" }, "refId": "A", "topic": "packet" @@ -1275,7 +649,7 @@ "format": "json", "jsonPaths": [ { - "path": "Error" + "path": "ArrayContactorError" } ], "source": "Contactor" @@ -1283,97 +657,6 @@ } ], "type": "timeseries" - }, - { - "datasource": { - "type": "grafana-mqtt-datasource", - "uid": "P51B197A508373975" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": 0 - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "lengthkm" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 41 - }, - "id": 15, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "center", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "12.3.1", - "targets": [ - { - "datasource": { - "type": "golioth-websocket-datasource", - "uid": "bf4fqic90e41se" - }, - "fields": [ - { - "jsonPath": "$.data.data", - "language": "jsonpath", - "name": "" - } - ], - "path": "", - "refId": "A", - "topic": "lapdata", - "withStreaming": true - } - ], - "title": "Total Distance", - "transformations": [ - { - "id": "extractFields", - "options": { - "delimiter": ",", - "format": "json", - "jsonPaths": [ - { - "path": "distance" - } - ], - "replace": true, - "source": "data" - } - } - ], - "type": "stat" } ], "preload": false, @@ -1391,5 +674,5 @@ "timezone": "browser", "title": "Das Telemetry Base Dashboard", "uid": "40c53014-a3c2-47e8-8c8b-3ce15a6ecc61", - "version": 14 -} \ No newline at end of file + "version": 16 +} diff --git a/grafana/docker-compose.yml b/grafana/docker-compose.yml index bede113..4a3120e 100644 --- a/grafana/docker-compose.yml +++ b/grafana/docker-compose.yml @@ -13,9 +13,16 @@ services: - GF_SECURITY_ADMIN_USER=admin - GF_SECURITY_ADMIN_PASSWORD=admin - GF_INSTALL_PLUGINS=grafana-mqtt-datasource + # local dev only — do not use in production + - GF_AUTH_ANONYMOUS_ENABLED=true + - GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer + - GF_SECURITY_ALLOW_EMBEDDING=true + - MQTT_HOST=${MQTT_HOST} - MQTT_USERNAME=${MQTT_USERNAME} - MQTT_PASSWORD=${MQTT_PASSWORD} - POSTGRES_USERNAME=${POSTGRES_USERNAME} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_HOST=${POSTGRES_HOST} + - POSTGRES_SSLMODE=${POSTGRES_SSLMODE} volumes: grafana-storage: diff --git a/grafana/provisioning/datasources/datasources.yaml b/grafana/provisioning/datasources/datasources.yaml index 270d717..be4d407 100644 --- a/grafana/provisioning/datasources/datasources.yaml +++ b/grafana/provisioning/datasources/datasources.yaml @@ -3,6 +3,7 @@ apiVersion: 1 datasources: # configruation setup viewable here: https://github.com/grafana/mqtt-datasource/blob/main/src/ConfigEditor.tsx - name: MQTT + uid: grafana-mqtt-datasource type: grafana-mqtt-datasource editable: true isDefault: true @@ -10,22 +11,23 @@ datasources: password: ${MQTT_PASSWORD} jsonData: username: ${MQTT_USERNAME} - uri: host.docker.internal:1883 + uri: ${MQTT_HOST} - name: grafana-postgresql-datasource + uid: grafana-postgresql-datasource type: postgres url: ${POSTGRES_HOST} - database: tsdb + database: postgres user: ${POSTGRES_USERNAME} secureJsonData: password: ${POSTGRES_PASSWORD} jsonData: - sslmode: "require" - postgresVersion: 9.3 - timescaledb: true + sslmode: "${POSTGRES_SSLMODE}" + postgresVersion: 1500 + timescaledb: false minInterval: 1m - maxOpenConns: 100 - maxIdleConns: 100 + maxOpenConns: 10 + maxIdleConns: 10 maxLifetime: 14400 editable: true # - name: Postgres