signalk-grafana
v1.2.1
Published
Managed Grafana with auto-provisioned QuestDB dashboards for Signal K
Maintainers
Readme
signalk-grafana
Managed Grafana with auto-provisioned QuestDB and Signal K datasources for Signal K.
Runs Grafana in a container (via signalk-container), automatically connects it to signalk-questdb via a shared container network, and provisions both datasources.
Features
- Zero-config Grafana -- container managed automatically, no manual setup
- Auto-provisioned datasources -- QuestDB (PostgreSQL) and Signal K datasources configured automatically
- Shared container network -- Grafana and QuestDB communicate on a private Podman/Docker network
- Table auto-discovery -- QuestDB supports
information_schema, Grafana query builder works - Anonymous access -- view dashboards without login (configurable)
- Live reachability -- config panel shows whether Grafana can actually reach your Signal K server
- One-click update -- check for new Grafana versions and update from the config panel
- Password management -- set admin password from Signal K config panel
- Config panel -- Grafana status with direct link, settings, all in Admin UI
- Backup & restore integration -- exposes
/api/full-export/{db,dashboards,provisioning}endpoints for signalk-backup to pull a consistent SQLite checkpoint plus dashboard JSONs and provisioning YAMLs. On plugin start, if a kopia restore left a stagedgrafana.dbon disk but the live one is missing, it's automatically copied into place before Grafana starts -- dashboards and datasources come back without any manual step.
How It Works
- Plugin creates a Podman/Docker network (
sk-network) - Starts Grafana container on the network
- Auto-provisions QuestDB datasource (connects via container DNS
sk-signalk-questdb:8812) - Auto-provisions Signal K datasource (connects via
host.containers.internal) - Sets admin password on every startup to match config
QuestDB must also be on sk-network -- set Container network to sk-network in the QuestDB plugin config.
Example Queries
Create dashboards in Grafana using the QuestDB datasource with raw SQL. QuestDB uses SAMPLE BY for time bucketing:
Speed Over Ground (knots):
SELECT ts AS "time", avg(value) * 1.94384 AS "SOG"
FROM signalk
WHERE path = 'navigation.speedOverGround'
AND context = 'self'
AND ts >= $__timeFrom() AND ts <= $__timeTo()
SAMPLE BY 10sWind Speed and Angle:
SELECT ts AS "time",
avg(value) * 1.94384 AS "AWS"
FROM signalk
WHERE path = 'environment.wind.speedApparent'
AND context = 'self'
AND ts >= $__timeFrom() AND ts <= $__timeTo()
SAMPLE BY 10sBattery Voltage:
SELECT ts AS "time", avg(value) AS "Voltage"
FROM signalk
WHERE path LIKE 'electrical.batteries.%.voltage'
AND context = 'self'
AND ts >= $__timeFrom() AND ts <= $__timeTo()
SAMPLE BY 10sEngine RPM (rev/s to RPM):
SELECT ts AS "time", avg(value) * 60 AS "RPM"
FROM signalk
WHERE path LIKE 'propulsion.%.revolutions'
AND context = 'self'
AND ts >= $__timeFrom() AND ts <= $__timeTo()
SAMPLE BY 10sTemperature (Kelvin to Celsius):
SELECT ts AS "time", avg(value) - 273.15 AS "Temp"
FROM signalk
WHERE path = 'environment.water.temperature'
AND context = 'self'
AND ts >= $__timeFrom() AND ts <= $__timeTo()
SAMPLE BY 10sUnit Conversions
Signal K stores values in SI units. Common conversions for Grafana:
| Conversion | Formula |
| ------------------ | --------------------- |
| m/s to knots | value * 1.94384 |
| radians to degrees | value * 57.2958 |
| Kelvin to Celsius | value - 273.15 |
| Pascals to hPa | value / 100 |
| rev/s to RPM | value * 60 |
| Pascals to PSI | value * 0.000145038 |
Grafana Macros
Use these Grafana PostgreSQL macros in your queries:
| Macro | Expands to |
| --------------- | ---------------------------- |
| $__timeFrom() | Start of selected time range |
| $__timeTo() | End of selected time range |
QuestDB's SAMPLE BY handles time bucketing (e.g., SAMPLE BY 10s, SAMPLE BY 1m, SAMPLE BY 1h).
Configuration
| Setting | Default | Description |
| --------------------- | ----------------- | ------------------------------------------------------------ |
| Grafana port | 3001 | Host port for Grafana UI |
| Image version | latest | Grafana Docker image tag |
| Admin password | admin | Grafana admin password (applied on every start) |
| Anonymous access | true | Allow viewing without login |
| Signal K URL override | auto | Auto-detected; set to override (use http:// or https://) |
| QuestDB container | signalk-questdb | Container name (without sk- prefix) |
| PostgreSQL port | 8812 | QuestDB PG wire port |
| Network name | sk-network | Shared container network name |
| Bind to 0.0.0.0 | false | Expose Grafana outside localhost |
| Sub-path | empty | Set to /grafana/ when running behind a reverse proxy |
| Auto-request token | true | On secured Signal K servers, request a device-access token |
Secured Signal K servers
When Signal K security is enabled, the plugin drives the standard SK device-access-request flow on startup so the Grafana Signal K datasource can read paths and history from the secured server.
- Plugin POSTs
/signalk/v1/access/requestswithclientId: signalk-grafana,permissions: readwrite. - Plugin status shows
Awaiting Signal K token approval — see Security → Access Requests. - Approve in Signal K Admin → Security → Access Requests.
- The plugin caches the JWT to
${dataDir}/signalk-token(mode 0600), injects it into the Grafana datasource provisioning, and recreates the Grafana container so the new credentials take effect.
Disable with requestSignalkToken: false if you prefer to paste a token into the Grafana datasource UI manually.
What works and what doesn't on secured SK
| Use case | Works on secured SK? |
| ---------------------------------------------------------------------------------- | -------------------- |
| Explore → Signal K → pick a path | ✅ |
| Historical range queries (from/to are explicit times, or now-1h to now-1m) | ✅ |
| /api/health and datasource Test button | ✅ |
| Live-updating panels (range now-X to now that streams values in real time) | ❌ |
The live-update gap is in the upstream tkurki-signalk-datasource plugin: it opens its WebSocket from the browser where Grafana's secret store is not accessible by design, so the upgrade request goes out without an Authorization header and Signal K rejects it. HTTP queries are unaffected because Grafana's datasource proxy injects the bearer token server-side for those. Tracked at tkurki/signalk-grafana-datasource#12 — until that lands, use the QuestDB datasource for live-updating panels (the intended architecture: SK → questdb → Grafana, with the SK datasource reserved for ad-hoc Explore and historical queries).
Requirements
- Node.js >= 22.16 (needs
node:sqlitebackup()for the SQLite checkpoint endpoint) - signalk-container plugin
- signalk-questdb plugin (with network set to
sk-network) - Signal K server
License
MIT
