diff --git a/software/edge/app/main.py b/software/edge/app/main.py index f8a5393..b92db5b 100644 --- a/software/edge/app/main.py +++ b/software/edge/app/main.py @@ -4,6 +4,7 @@ from contextlib import asynccontextmanager from fastapi import BackgroundTasks, FastAPI, HTTPException +from fastapi.middleware.cors import CORSMiddleware from app import db_client, poller, session_manager from app.config import get_settings @@ -46,6 +47,15 @@ async def lifespan(app: FastAPI): app = FastAPI(title="Edge Service", lifespan=lifespan, docs_url="/") +# Allow Grafana dashboard to call the Swagger endpoints from the browser. +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + async def high_frequency_polling_loop(): """The main data collection loop that runs in the background.""" diff --git a/software/edge/scripts/setup-replication.sh b/software/edge/scripts/setup-replication.sh index c4607a0..32be498 100755 --- a/software/edge/scripts/setup-replication.sh +++ b/software/edge/scripts/setup-replication.sh @@ -31,13 +31,20 @@ fi echo "Remote Org ID: $REMOTE_ORG_ID" -# Delete existing remote if it exists +# Delete existing replication with our name if it exists +echo "Cleaning up existing replication (if any)..." +EXISTING_REPL_ID=$(influx replication list --host "$INFLUX_HOST" --token "$INFLUX_TOKEN" --org "$INFLUX_ORG" 2>/dev/null | awk 'NR>1 && $2=="edge-to-ground" {print $1; exit}') +if [ -n "$EXISTING_REPL_ID" ]; then + echo "Deleting existing replication ID: $EXISTING_REPL_ID" + influx replication delete --id "$EXISTING_REPL_ID" --host "$INFLUX_HOST" --token "$INFLUX_TOKEN" 2>/dev/null || true +fi + +# Delete existing remote with our name if it exists echo "Cleaning up existing remote connection (if any)..." -EXISTING_REMOTE_ID=$(influx remote list --host $INFLUX_HOST --token $INFLUX_TOKEN --org $INFLUX_ORG --json 2>/dev/null | awk -F'"' '/"id":/ {print $4; exit}') +EXISTING_REMOTE_ID=$(influx remote list --host "$INFLUX_HOST" --token "$INFLUX_TOKEN" --org "$INFLUX_ORG" 2>/dev/null | awk 'NR>1 && $2=="ground-station" {print $1; exit}') if [ -n "$EXISTING_REMOTE_ID" ]; then echo "Deleting existing remote ID: $EXISTING_REMOTE_ID" - influx remote delete --id "$EXISTING_REMOTE_ID" \ - --host "$INFLUX_HOST" --token "$INFLUX_TOKEN" 2>/dev/null || true + influx remote delete --id "$EXISTING_REMOTE_ID" --host "$INFLUX_HOST" --token "$INFLUX_TOKEN" 2>/dev/null || true fi # Create remote connection @@ -49,13 +56,12 @@ influx remote create \ --remote-org-id "$REMOTE_ORG_ID" \ --org "$INFLUX_ORG" \ --host "$INFLUX_HOST" \ - --token "$INFLUX_TOKEN" - -# Get remote ID -REMOTE_ID=$(influx remote list --host $INFLUX_HOST --token $INFLUX_TOKEN --org $INFLUX_ORG --json | awk -F'"' '/"id":/ {print $4; exit}') + --token "$INFLUX_TOKEN" >/dev/null +# Fetch remote id from list output (table format) +REMOTE_ID=$(influx remote list --host "$INFLUX_HOST" --token "$INFLUX_TOKEN" --org "$INFLUX_ORG" 2>/dev/null | awk 'NR>1 && $2=="ground-station" {print $1; exit}') # Get bucket ID -BUCKET_ID=$(influx bucket list --host $INFLUX_HOST --token $INFLUX_TOKEN --org $INFLUX_ORG --name $INFLUX_BUCKET --json | awk -F'"' '/"id":/ {print $4; exit}') +BUCKET_ID=$(influx bucket list --host "$INFLUX_HOST" --token "$INFLUX_TOKEN" --org "$INFLUX_ORG" --name "$INFLUX_BUCKET" 2>/dev/null | awk 'NR>1 {print $1; exit}') if [ -z "$REMOTE_ID" ] || [ -z "$BUCKET_ID" ]; then echo "Error: Could not get remote ID or bucket ID" @@ -65,15 +71,6 @@ fi echo "Remote ID: $REMOTE_ID" echo "Bucket ID: $BUCKET_ID" -# Delete existing replication if it exists -echo "Cleaning up existing replication (if any)..." -EXISTING_REPL_ID=$(influx replication list --host $INFLUX_HOST --token $INFLUX_TOKEN --org $INFLUX_ORG --json 2>/dev/null | awk -F'"' '/"id":/ {print $4; exit}') -if [ -n "$EXISTING_REPL_ID" ]; then - echo "Deleting existing replication ID: $EXISTING_REPL_ID" - influx replication delete --id "$EXISTING_REPL_ID" \ - --host "$INFLUX_HOST" --token "$INFLUX_TOKEN" 2>/dev/null || true -fi - # Create replication echo "Creating replication stream..." influx replication create \ @@ -90,5 +87,5 @@ echo "=========================================" echo "Replication setup complete!" echo "=========================================" echo "" -influx replication list --host $INFLUX_HOST --token $INFLUX_TOKEN --org $INFLUX_ORG +influx replication list --host "$INFLUX_HOST" --token "$INFLUX_TOKEN" --org "$INFLUX_ORG" echo "" diff --git a/software/ground/docker-compose.yml b/software/ground/docker-compose.yml index 57cb33f..ffa34ee 100644 --- a/software/ground/docker-compose.yml +++ b/software/ground/docker-compose.yml @@ -34,7 +34,7 @@ services: - GF_SERVER_ROOT_URL=http://ground.local/ - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USER:-admin} - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123} - - GF_INSTALL_PLUGINS=grafana-clock-panel + - GF_INSTALL_PLUGINS=grafana-clock-panel,volkovlabs-form-panel - GF_PATHS_PROVISIONING=/etc/grafana/provisioning - DATABASE__ORG=${DATABASE__ORG} - DATABASE__BUCKET=${DATABASE__BUCKET} diff --git a/software/ground/grafana/provisioning/dashboards/cellmeter-dashboard.json b/software/ground/grafana/provisioning/dashboards/cellmeter-dashboard.json index ec16ffd..55d5d31 100644 --- a/software/ground/grafana/provisioning/dashboards/cellmeter-dashboard.json +++ b/software/ground/grafana/provisioning/dashboards/cellmeter-dashboard.json @@ -37,7 +37,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "fieldConfig": { "defaults": { @@ -101,7 +101,10 @@ "id": 2, "options": { "legend": { - "calcs": ["last", "mean"], + "calcs": [ + "last", + "mean" + ], "displayMode": "table", "placement": "bottom", "showLegend": true @@ -115,7 +118,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"state_metrics\")\n |> filter(fn: (r) => r._field == \"rsrp\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", "refId": "A" @@ -127,7 +130,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "fieldConfig": { "defaults": { @@ -189,7 +192,10 @@ "id": 3, "options": { "legend": { - "calcs": ["last", "mean"], + "calcs": [ + "last", + "mean" + ], "displayMode": "table", "placement": "bottom", "showLegend": true @@ -203,7 +209,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"state_metrics\")\n |> filter(fn: (r) => r._field == \"rsrq\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", "refId": "A" @@ -215,7 +221,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "fieldConfig": { "defaults": { @@ -277,7 +283,10 @@ "id": 4, "options": { "legend": { - "calcs": ["last", "mean"], + "calcs": [ + "last", + "mean" + ], "displayMode": "table", "placement": "bottom", "showLegend": true @@ -291,7 +300,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"state_metrics\")\n |> filter(fn: (r) => r._field == \"sinr\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", "refId": "A" @@ -316,7 +325,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "fieldConfig": { "defaults": { @@ -423,7 +432,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"state_metrics\")\n |> filter(fn: (r) => r._field == \"latitude\" or r._field == \"longitude\")\n |> pivot(rowKey:[\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")", "refId": "A" @@ -435,7 +444,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "fieldConfig": { "defaults": { @@ -522,7 +531,11 @@ "id": 7, "options": { "legend": { - "calcs": ["last", "mean", "max"], + "calcs": [ + "last", + "mean", + "max" + ], "displayMode": "table", "placement": "bottom", "showLegend": true @@ -536,7 +549,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"state_metrics\")\n |> filter(fn: (r) => r._field == \"gps_altitude\" or r._field == \"baro_relative_altitude\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", "refId": "A" @@ -561,7 +574,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "fieldConfig": { "defaults": { @@ -623,7 +636,11 @@ "id": 9, "options": { "legend": { - "calcs": ["last", "mean", "max"], + "calcs": [ + "last", + "mean", + "max" + ], "displayMode": "table", "placement": "bottom", "showLegend": true @@ -637,7 +654,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"state_metrics\")\n |> filter(fn: (r) => r._field == \"speed_kmh\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", "refId": "A" @@ -649,7 +666,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "fieldConfig": { "defaults": { @@ -693,7 +710,9 @@ "orientation": "auto", "reduceOptions": { "values": false, - "calcs": ["lastNotNull"], + "calcs": [ + "lastNotNull" + ], "fields": "" }, "showThresholdLabels": false, @@ -705,7 +724,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"state_metrics\")\n |> filter(fn: (r) => r._field == \"satellites\")\n |> last()", "refId": "A" @@ -717,7 +736,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "fieldConfig": { "defaults": { @@ -728,7 +747,7 @@ "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", - "axisLabel": "Temperature (°C)", + "axisLabel": "Temperature (\u00b0C)", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", @@ -804,7 +823,10 @@ "id": 11, "options": { "legend": { - "calcs": ["last", "mean"], + "calcs": [ + "last", + "mean" + ], "displayMode": "table", "placement": "bottom", "showLegend": true @@ -818,7 +840,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"state_metrics\")\n |> filter(fn: (r) => r._field == \"modem_temperature\" or r._field == \"temperature_celsius\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", "refId": "A" @@ -830,7 +852,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "fieldConfig": { "defaults": { @@ -892,7 +914,10 @@ "id": 12, "options": { "legend": { - "calcs": ["last", "mean"], + "calcs": [ + "last", + "mean" + ], "displayMode": "table", "placement": "bottom", "showLegend": true @@ -906,7 +931,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"state_metrics\")\n |> filter(fn: (r) => r._field == \"pressure_hpa\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", "refId": "A" @@ -918,7 +943,7 @@ { "collapsed": false, "gridPos": { - "h": 1, + "h": 10, "w": 24, "x": 0, "y": 29 @@ -931,7 +956,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "fieldConfig": { "defaults": { @@ -1032,7 +1057,11 @@ "id": 14, "options": { "legend": { - "calcs": ["last", "mean", "max"], + "calcs": [ + "last", + "mean", + "max" + ], "displayMode": "table", "placement": "bottom", "showLegend": true @@ -1046,7 +1075,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"performance_benchmarks\")\n |> filter(fn: (r) => r._field == \"upload_mbps\" or r._field == \"download_mbps\")", "refId": "A" @@ -1058,7 +1087,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "fieldConfig": { "defaults": { @@ -1112,7 +1141,7 @@ "overrides": [] }, "gridPos": { - "h": 8, + "h": 10, "w": 12, "x": 12, "y": 30 @@ -1120,7 +1149,11 @@ "id": 15, "options": { "legend": { - "calcs": ["last", "mean", "max"], + "calcs": [ + "last", + "mean", + "max" + ], "displayMode": "table", "placement": "bottom", "showLegend": true @@ -1134,9 +1167,9 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, - "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"performance_benchmarks\")\n |> filter(fn: (r) => r._field == \"ping_rtt_avg_ms\" or r._field == \"jitter_ms\")", + "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"performance_benchmarks\")\n |> filter(fn: (r) => r._field == \"rtt_avg_ms\" or r._field == \"jitter_ms\")", "refId": "A" } ], @@ -1146,7 +1179,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "fieldConfig": { "defaults": { @@ -1210,7 +1243,7 @@ "overrides": [] }, "gridPos": { - "h": 8, + "h": 10, "w": 12, "x": 0, "y": 38 @@ -1218,7 +1251,11 @@ "id": 16, "options": { "legend": { - "calcs": ["last", "mean", "max"], + "calcs": [ + "last", + "mean", + "max" + ], "displayMode": "table", "placement": "bottom", "showLegend": true @@ -1232,9 +1269,9 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, - "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"performance_benchmarks\")\n |> filter(fn: (r) => r._field == \"ping_packet_loss_pct\")", + "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"performance_benchmarks\")\n |> filter(fn: (r) => r._field == \"packet_loss_pct\")", "refId": "A" } ], @@ -1244,7 +1281,7 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, "fieldConfig": { "defaults": { @@ -1280,7 +1317,9 @@ "percentChangeColorMode": "standard", "reduceOptions": { "values": false, - "calcs": ["lastNotNull"], + "calcs": [ + "lastNotNull" + ], "fields": "" }, "showPercentChange": false, @@ -1292,21 +1331,685 @@ { "datasource": { "type": "influxdb", - "uid": "${DS_INFLUXDB}" + "uid": "InfluxDB" }, - "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"state_metrics\")\n |> filter(fn: (r) => r._field == \"cell_id\" or r._field == \"network_type\" or r._field == \"operator\")\n |> last()\n |> group()", + "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"state_metrics\")\n |> last()\n |> keep(columns:[\"_time\",\"cell_id\",\"network_type\",\"operator\",\"tracking_area_code\",\"frequency_channel\",\"frequency_band\",\"physical_cell_id\"])\n |> group(columns: [])", "refId": "A" } ], "title": "Network Info", "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 46 + }, + "id": 18, + "panels": [], + "title": "Session Controls", + "type": "row" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 47 + }, + "id": 19, + "options": { + "buttonGroup": { + "orientation": "center", + "size": "lg" + }, + "confirmModal": { + "body": "Please confirm to update changed values", + "cancel": "Cancel", + "columns": { + "include": [ + "name", + "oldValue", + "newValue" + ], + "name": "Label", + "newValue": "New Value", + "oldValue": "Old Value" + }, + "confirm": "Confirm", + "elementDisplayMode": "modified", + "title": "Confirm update request" + }, + "elementValueChanged": "", + "elements": [ + { + "id": "auto_benchmarks", + "labelWidth": 10, + "title": "Auto benchmarks", + "tooltip": "Enable automatic benchmarks during the session", + "type": "boolean", + "uid": "26f28d0c-5bff-44a0-bc0a-696fd994414d", + "unit": "", + "value": false + } + ], + "initial": { + "code": "context.panel.enableSubmit();", + "contentType": "application/json", + "getPayload": "return {}", + "highlight": false, + "highlightColor": "red", + "method": "-", + "payload": {}, + "payloadMode": "all", + "url": "" + }, + "layout": { + "orientation": "horizontal", + "padding": 10, + "sectionVariant": "default", + "variant": "single" + }, + "reset": { + "backgroundColor": "", + "foregroundColor": "", + "icon": "process", + "text": "Reset", + "variant": "hidden" + }, + "resetAction": { + "code": "", + "confirm": false, + "getPayload": "return {}", + "mode": "initial", + "payload": {} + }, + "saveDefault": { + "icon": "save", + "text": "Save Default", + "variant": "hidden" + }, + "submit": { + "backgroundColor": "", + "foregroundColor": "", + "icon": "play", + "text": "Start Session", + "variant": "primary" + }, + "sync": false, + "update": { + "code": "if (context.panel.response?.ok) { context.grafana.notifySuccess(['Session', 'Started']); } else { context.grafana.notifyError(['Session', 'Start failed']); }\ncontext.panel.enableSubmit();", + "confirm": false, + "contentType": "application/json", + "getPayload": "const auto = context.panel.elements.find((el) => el.id === 'auto_benchmarks');\nreturn { auto_benchmarks: Boolean(auto?.value) };", + "header": [], + "method": "POST", + "payload": {}, + "payloadMode": "custom", + "url": "${edge_api_base:raw}/sessions/start" + }, + "updateEnabled": "manual" + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "refId": "A" + } + ], + "title": "Start Session", + "type": "volkovlabs-form-panel" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 47 + }, + "id": 20, + "options": { + "buttonGroup": { + "orientation": "center", + "size": "lg" + }, + "confirmModal": { + "body": "Please confirm to update changed values", + "cancel": "Cancel", + "columns": { + "include": [ + "name", + "oldValue", + "newValue" + ], + "name": "Label", + "newValue": "New Value", + "oldValue": "Old Value" + }, + "confirm": "Confirm", + "elementDisplayMode": "modified", + "title": "Confirm update request" + }, + "elementValueChanged": "", + "elements": [ + { + "hidden": true, + "id": "placeholder", + "labelWidth": 10, + "title": "Placeholder", + "tooltip": "", + "type": "string", + "uid": "b83f5f22-f2d4-45e8-8e08-6dc3117a5f4c", + "unit": "", + "value": "" + } + ], + "initial": { + "code": "context.panel.enableSubmit();", + "contentType": "application/json", + "getPayload": "return {}", + "highlight": false, + "highlightColor": "red", + "method": "-", + "payload": {}, + "payloadMode": "all", + "url": "" + }, + "layout": { + "orientation": "horizontal", + "padding": 10, + "sectionVariant": "default", + "variant": "single" + }, + "reset": { + "backgroundColor": "", + "foregroundColor": "", + "icon": "process", + "text": "Reset", + "variant": "hidden" + }, + "resetAction": { + "code": "", + "confirm": false, + "getPayload": "return {}", + "mode": "initial", + "payload": {} + }, + "saveDefault": { + "icon": "save", + "text": "Save Default", + "variant": "hidden" + }, + "submit": { + "backgroundColor": "", + "foregroundColor": "", + "icon": "stop", + "text": "End Session", + "variant": "secondary" + }, + "sync": false, + "update": { + "code": "if (context.panel.response?.ok) { context.grafana.notifySuccess(['Session', 'Ended']); } else { context.grafana.notifyError(['Session', 'End failed']); }", + "confirm": false, + "contentType": "application/json", + "getPayload": "return {}", + "header": [], + "method": "POST", + "payload": {}, + "payloadMode": "custom", + "url": "${edge_api_base:raw}/sessions/end" + }, + "updateEnabled": "manual" + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "refId": "A" + } + ], + "title": "End Session", + "type": "volkovlabs-form-panel" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 47 + }, + "id": 21, + "options": { + "buttonGroup": { + "orientation": "center", + "size": "lg" + }, + "confirmModal": { + "body": "Please confirm to update changed values", + "cancel": "Cancel", + "columns": { + "include": [ + "name", + "oldValue", + "newValue" + ], + "name": "Label", + "newValue": "New Value", + "oldValue": "Old Value" + }, + "confirm": "Confirm", + "elementDisplayMode": "modified", + "title": "Confirm update request" + }, + "elementValueChanged": "", + "elements": [ + { + "hidden": true, + "id": "placeholder", + "labelWidth": 10, + "title": "Placeholder", + "tooltip": "", + "type": "string", + "uid": "bbbdab14-7fcf-4f6d-8ddb-8d5a1a53b897", + "unit": "", + "value": "" + } + ], + "initial": { + "code": "context.panel.enableSubmit();", + "contentType": "application/json", + "getPayload": "return {}", + "highlight": false, + "highlightColor": "red", + "method": "-", + "payload": {}, + "payloadMode": "all", + "url": "" + }, + "layout": { + "orientation": "horizontal", + "padding": 10, + "sectionVariant": "default", + "variant": "single" + }, + "reset": { + "backgroundColor": "", + "foregroundColor": "", + "icon": "process", + "text": "Reset", + "variant": "hidden" + }, + "resetAction": { + "code": "", + "confirm": false, + "getPayload": "return {}", + "mode": "initial", + "payload": {} + }, + "saveDefault": { + "icon": "save", + "text": "Save Default", + "variant": "hidden" + }, + "submit": { + "backgroundColor": "", + "foregroundColor": "", + "icon": "bolt", + "text": "Run Benchmarks", + "variant": "primary" + }, + "sync": false, + "update": { + "code": "if (context.panel.response?.ok) { context.grafana.notifySuccess(['Benchmarks', 'Triggered']); } else { context.grafana.notifyError(['Benchmarks', 'Trigger failed']); }", + "confirm": true, + "contentType": "application/json", + "getPayload": "return {}", + "header": [], + "method": "POST", + "payload": {}, + "payloadMode": "custom", + "url": "${edge_api_base:raw}/benchmarks/run" + }, + "updateEnabled": "manual" + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "refId": "A" + } + ], + "title": "Run Manual Benchmarks", + "type": "volkovlabs-form-panel" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 14, + "w": 24, + "x": 0, + "y": 54 + }, + "id": 22, + "options": { + "buttonGroup": { + "orientation": "center", + "size": "md" + }, + "confirmModal": { + "body": "Please confirm to update changed values", + "cancel": "Cancel", + "columns": { + "include": [ + "name", + "oldValue", + "newValue" + ], + "name": "Label", + "newValue": "New Value", + "oldValue": "Old Value" + }, + "confirm": "Confirm", + "elementDisplayMode": "modified", + "title": "Confirm update request" + }, + "elementValueChanged": "", + "elements": [ + { + "hidden": false, + "id": "message", + "labelWidth": 10, + "title": "Message", + "tooltip": "", + "type": "disabled", + "uid": "8674cdc5-8db5-4b0a-ba9b-7c1f29ade986", + "unit": "", + "value": "" + }, + { + "hidden": false, + "id": "session_id", + "labelWidth": 10, + "title": "Session ID", + "tooltip": "", + "type": "disabled", + "uid": "326828f1-9166-4f12-af4c-97224e858e77", + "unit": "", + "value": "" + }, + { + "hidden": false, + "id": "iccid", + "labelWidth": 10, + "title": "ICCID", + "tooltip": "", + "type": "disabled", + "uid": "bbe0478f-0769-4089-a34f-e8a7f7cca606", + "unit": "", + "value": "" + }, + { + "hidden": false, + "id": "benchmark_in_progress", + "labelWidth": 10, + "title": "Benchmarks Running", + "tooltip": "", + "type": "disabled", + "uid": "4bc72cb0-ff04-45a8-a2c3-762d50802a7c", + "unit": "", + "value": "" + }, + { + "hidden": false, + "id": "auto_benchmarks", + "labelWidth": 10, + "title": "Auto Benchmarks", + "tooltip": "", + "type": "disabled", + "uid": "ba08bcc1-b3b6-403b-9ae2-c3a7768e437d", + "unit": "", + "value": "" + }, + { + "backgroundColor": "", + "buttonLabel": "Refresh", + "customCode": "await context.panel.initialRequest();", + "foregroundColor": "", + "id": "refresh", + "labelWidth": 10, + "section": "", + "show": "form", + "size": "sm", + "title": "", + "tooltip": "", + "type": "button", + "uid": "5064ade5-9a9f-404e-b38e-4515dc6da902", + "unit": "", + "value": "", + "variant": "secondary", + "disabled": false + } + ], + "initial": { + "code": "", + "contentType": "application/json", + "getPayload": "return {}", + "highlight": false, + "highlightColor": "red", + "method": "GET", + "payload": {}, + "payloadMode": "all", + "url": "${edge_api_base:raw}/sessions/status" + }, + "layout": { + "orientation": "horizontal", + "padding": 10, + "sectionVariant": "default", + "variant": "single" + }, + "reset": { + "backgroundColor": "", + "foregroundColor": "", + "icon": "process", + "text": "Reset", + "variant": "hidden" + }, + "resetAction": { + "code": "", + "confirm": false, + "getPayload": "return {}", + "mode": "initial", + "payload": {} + }, + "saveDefault": { + "icon": "save", + "text": "Save Default", + "variant": "hidden" + }, + "submit": { + "backgroundColor": "", + "foregroundColor": "", + "icon": "", + "text": "", + "variant": "hidden" + }, + "sync": true, + "update": { + "code": "", + "confirm": false, + "contentType": "application/json", + "getPayload": "return {}", + "header": [], + "method": "-", + "payload": {}, + "payloadMode": "all", + "url": "" + }, + "updateEnabled": "manual" + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "refId": "A" + } + ], + "title": "Session Status", + "type": "volkovlabs-form-panel" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 68 + }, + "id": 23, + "panels": [], + "title": "Ground Wi-Fi", + "type": "row" + }, + { + "datasource": { + "type": "influxdb", + "uid": "InfluxDB" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": -75 + }, + { + "color": "green", + "value": -60 + } + ] + }, + "unit": "dBm", + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 20, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "axisPlacement": "auto", + "axisLabel": "RSSI (dBm)", + "axisCenteredZero": false, + "axisBorderShow": false, + "barAlignment": 0, + "gradientMode": "none", + "pointSize": 4, + "thresholdsStyle": { + "mode": "off" + }, + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 69 + }, + "id": 24, + "options": { + "legend": { + "calcs": [ + "last" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "InfluxDB" + }, + "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"ground_wifi\")\n |> filter(fn: (r) => r._field == \"rssi_dbm\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", + "refId": "A" + } + ], + "title": "Ground Wi-Fi RSSI", + "type": "timeseries" } ], "refresh": "5s", "schemaVersion": 39, - "tags": ["cellmeter", "drone", "telemetry"], + "tags": [ + "cellmeter", + "drone", + "telemetry" + ], "templating": { - "list": [] + "list": [ + { + "name": "edge_api_base", + "type": "constant", + "label": "Edge API", + "query": "http://10.0.2.1:8000", + "current": { + "text": "http://10.0.2.1:8000", + "value": "http://10.0.2.1:8000" + }, + "options": [ + { + "text": "http://10.0.2.1:8000", + "value": "http://10.0.2.1:8000", + "selected": true + } + ], + "hide": 0 + } + ] }, "time": { "from": "now-6h", @@ -1318,4 +2021,4 @@ "uid": "cellmeter-main", "version": 0, "weekStart": "" -} +} \ No newline at end of file diff --git a/software/ground/grafana/provisioning/dashboards/old-grafana.json b/software/ground/grafana/provisioning/dashboards/old-grafana.json new file mode 100644 index 0000000..ec16ffd --- /dev/null +++ b/software/ground/grafana/provisioning/dashboards/old-grafana.json @@ -0,0 +1,1321 @@ +{ + "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": 1, + "id": null, + "links": [], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "panels": [], + "title": "Radio Signal Quality", + "type": "row" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "RSRP (dBm)", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": -40, + "min": -120, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "dBm" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 1 + }, + "id": 2, + "options": { + "legend": { + "calcs": ["last", "mean"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"state_metrics\")\n |> filter(fn: (r) => r._field == \"rsrp\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", + "refId": "A" + } + ], + "title": "RSRP - Signal Strength", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "RSRQ (dB)", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "dB" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 1 + }, + "id": 3, + "options": { + "legend": { + "calcs": ["last", "mean"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"state_metrics\")\n |> filter(fn: (r) => r._field == \"rsrq\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", + "refId": "A" + } + ], + "title": "RSRQ - Signal Quality", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "SINR (dB)", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "dB" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 1 + }, + "id": 4, + "options": { + "legend": { + "calcs": ["last", "mean"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"state_metrics\")\n |> filter(fn: (r) => r._field == \"sinr\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", + "refId": "A" + } + ], + "title": "SINR - Signal to Noise Ratio", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 5, + "panels": [], + "title": "GPS & Altitude", + "type": "row" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 10 + }, + "id": 6, + "options": { + "basemap": { + "config": {}, + "name": "Layer 0", + "type": "default" + }, + "controls": { + "mouseWheelZoom": true, + "showAttribution": true, + "showDebug": false, + "showMeasure": false, + "showScale": false, + "showZoom": true + }, + "layers": [ + { + "config": { + "showLegend": true, + "style": { + "color": { + "fixed": "dark-green" + }, + "opacity": 0.4, + "rotation": { + "fixed": 0, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 5, + "max": 15, + "min": 2 + }, + "symbol": { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed" + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 0, + "textAlign": "center", + "textBaseline": "middle" + } + } + }, + "location": { + "mode": "coords", + "latitude": "latitude", + "longitude": "longitude" + }, + "name": "GPS Track", + "tooltip": true, + "type": "markers" + } + ], + "tooltip": { + "mode": "details" + }, + "view": { + "allLayers": true, + "id": "coords", + "lat": 49.195, + "lon": 16.606, + "zoom": 12 + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"state_metrics\")\n |> filter(fn: (r) => r._field == \"latitude\" or r._field == \"longitude\")\n |> pivot(rowKey:[\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")", + "refId": "A" + } + ], + "title": "GPS Track", + "type": "geomap" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Altitude (m)", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "m" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "baro_relative_altitude" + }, + "properties": [ + { + "id": "displayName", + "value": "Barometric Altitude (relative)" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "gps_altitude" + }, + "properties": [ + { + "id": "displayName", + "value": "GPS Altitude (MSL)" + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 10 + }, + "id": 7, + "options": { + "legend": { + "calcs": ["last", "mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"state_metrics\")\n |> filter(fn: (r) => r._field == \"gps_altitude\" or r._field == \"baro_relative_altitude\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", + "refId": "A" + } + ], + "title": "Altitude", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 20 + }, + "id": 8, + "panels": [], + "title": "Speed & Environmental", + "type": "row" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Speed (km/h)", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "kmh" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 21 + }, + "id": 9, + "options": { + "legend": { + "calcs": ["last", "mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"state_metrics\")\n |> filter(fn: (r) => r._field == \"speed_kmh\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", + "refId": "A" + } + ], + "title": "Ground Speed", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 30, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 6 + }, + { + "color": "green", + "value": 10 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 6, + "y": 21 + }, + "id": 10, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "values": false, + "calcs": ["lastNotNull"], + "fields": "" + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"state_metrics\")\n |> filter(fn: (r) => r._field == \"satellites\")\n |> last()", + "refId": "A" + } + ], + "title": "GPS Satellites", + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Temperature (°C)", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "celsius" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "modem_temperature" + }, + "properties": [ + { + "id": "displayName", + "value": "Modem Temperature" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "temperature_celsius" + }, + "properties": [ + { + "id": "displayName", + "value": "Air Temperature" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 21 + }, + "id": 11, + "options": { + "legend": { + "calcs": ["last", "mean"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"state_metrics\")\n |> filter(fn: (r) => r._field == \"modem_temperature\" or r._field == \"temperature_celsius\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", + "refId": "A" + } + ], + "title": "Temperature", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Pressure (hPa)", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "pressurehpa" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 21 + }, + "id": 12, + "options": { + "legend": { + "calcs": ["last", "mean"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"state_metrics\")\n |> filter(fn: (r) => r._field == \"pressure_hpa\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)", + "refId": "A" + } + ], + "title": "Atmospheric Pressure", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 29 + }, + "id": 13, + "panels": [], + "title": "Network Performance", + "type": "row" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Throughput (Mbps)", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "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 + } + ] + }, + "unit": "Mbits" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "upload_mbps" + }, + "properties": [ + { + "id": "displayName", + "value": "Upload" + }, + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "download_mbps" + }, + "properties": [ + { + "id": "displayName", + "value": "Download" + }, + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 30 + }, + "id": 14, + "options": { + "legend": { + "calcs": ["last", "mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"performance_benchmarks\")\n |> filter(fn: (r) => r._field == \"upload_mbps\" or r._field == \"download_mbps\")", + "refId": "A" + } + ], + "title": "Throughput (Upload/Download)", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Latency (ms)", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "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 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 30 + }, + "id": 15, + "options": { + "legend": { + "calcs": ["last", "mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"performance_benchmarks\")\n |> filter(fn: (r) => r._field == \"ping_rtt_avg_ms\" or r._field == \"jitter_ms\")", + "refId": "A" + } + ], + "title": "Latency & Jitter", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Packet Loss (%)", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 1 + }, + { + "color": "red", + "value": 5 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 38 + }, + "id": 16, + "options": { + "legend": { + "calcs": ["last", "mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"performance_benchmarks\")\n |> filter(fn: (r) => r._field == \"ping_packet_loss_pct\")", + "refId": "A" + } + ], + "title": "Packet Loss", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + }, + "unit": "string" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 38 + }, + "id": 17, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "values": false, + "calcs": ["lastNotNull"], + "fields": "" + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "query": "from(bucket: \"metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"state_metrics\")\n |> filter(fn: (r) => r._field == \"cell_id\" or r._field == \"network_type\" or r._field == \"operator\")\n |> last()\n |> group()", + "refId": "A" + } + ], + "title": "Network Info", + "type": "stat" + } + ], + "refresh": "5s", + "schemaVersion": 39, + "tags": ["cellmeter", "drone", "telemetry"], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Cellmeter - Drone Telemetry", + "uid": "cellmeter-main", + "version": 0, + "weekStart": "" +} diff --git a/software/ground/push_ground_wifi_rssi.sh b/software/ground/push_ground_wifi_rssi.sh new file mode 100644 index 0000000..47d66b2 --- /dev/null +++ b/software/ground/push_ground_wifi_rssi.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Collect Wi-Fi RSSI and push to InfluxDB. +# Usage: push_ground_wifi_rssi.sh [iface] + +set -uo pipefail # <-- -e removed so iw failures don't kill cron +PATH=/usr/sbin:/usr/bin:/bin + +IFACE=${1:-wlan1} +ORG="cellmeter-org" +BUCKET="metrics" +HOST="http://localhost:8086" + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +cd "$SCRIPT_DIR" + +ENV_FILE="$SCRIPT_DIR/.env" +if [ -f "$ENV_FILE" ]; then + set -a + . "$ENV_FILE" + set +a +fi + +TOKEN="${DATABASE__TOKEN:-REPLACE_WITH_GROUND_INFLUX_TOKEN}" + +LOG=/tmp/wifi_rssi_debug.log + +# Try RSSI from link (STA mode) +RSSI=$(/usr/sbin/iw dev "$IFACE" link 2>&1 | /usr/bin/awk '/signal:/ {print $2}') || true + +# Try station dump (AP mode) +if [ -z "${RSSI:-}" ]; then + RSSI=$(/usr/sbin/iw dev "$IFACE" station dump 2>&1 | /usr/bin/awk '/signal:/ {print $2; exit}') || true +fi + +echo "$(date -Iseconds) iface=$IFACE rssi=${RSSI:-nil}" >> "$LOG" + +# If still nothing, exit cleanly +[ -z "${RSSI:-}" ] && exit 0 + +DATA="ground_wifi,host=ground,iface=${IFACE} rssi_dbm=${RSSI}" + +HTTP_CODE=$(/usr/bin/curl -s -o /tmp/wifi_rssi_curl.out -w "%{http_code}" \ + -X POST "${HOST}/api/v2/write?org=${ORG}&bucket=${BUCKET}&precision=s" \ + -H "Authorization: Token ${TOKEN}" \ + --data-binary "$DATA") + +echo "$(date -Iseconds) http_code=$HTTP_CODE data=$DATA" >> "$LOG"