Files
bmspy/docs/grafana-ups.md
T
tim e32d3a8e22 Add a Grafana dashboard generator for the three UPS devices
tools/generate_grafana_ups.py writes grafana-ups.json, which shows the JBD network UPS, the River 2 desktop UPS, and the River 3 NAS UPS in three columns. Git ignores the generated file.

Each EcoFlow column shows one time-remaining panel in hours, for the direction in use. The River 2 histories add pack current and remaining capacity. The River 3 reports neither, so its histories add battery power and charge.
2026-10-10 11:56:29 +02:00

5.5 KiB

Three-UPS Grafana dashboard

The repository contains the generator for the dashboard, and Git ignores the generated file. From the repository root, write grafana-ups.json:

python tools/generate_grafana_ups.py

Import grafana-ups.json into Grafana. It contains the Grafana v2 dashboard model directly. Properties such as title, elements, and layout are at the JSON root, not inside the supplied export's spec wrapper. It uses Flux queries and requires an InfluxDB data source configured for Flux, not SQL or InfluxQL. Select that source in the InfluxDB (Flux) control. The Bucket control starts at ups. Enter the bucket name without quotes. Queries use from(bucket: "${bucket:raw}") to prevent Grafana from adding a second pair of quotes.

The dashboard uses three columns within a 24-column, 24-row grid. This matches the original dashboard's grid height. Grafana can rearrange panels on small screens. The original grafana-network-ups.json is unchanged.

Device names

The exporter must write these ups tag values:

Tag Display label Device Column
network Network JBD serial BMS Left
desktop Desktop EcoFlow River 2 Middle
nas NAS EcoFlow River 3 Right

The tags are server-assigned names, not device model selectors. Display labels do not change the stored tags. If your server uses different names, update DEVICES in the generator and regenerate the dashboard. For example:

uv run --extra ecoflow-bt bmspy-server --socket /tmp/bmspy-ups.sock \
  -d network:/dev/ttyUSB0 \
  -d 'desktop@RIVER2_MAC/RIVER2_SERIAL/YOUR_USER_ID' \
  -d 'nas@RIVER3_MAC/RIVER3_SERIAL/YOUR_USER_ID'

Set INFLUXDB_V2_URL, INFLUXDB_V2_ORG, and INFLUXDB_V2_TOKEN. In another terminal, export all three devices:

uv run --extra influxdb bmspy-influxdb --socket /tmp/bmspy-ups.sock --bucket ups

Do not add --ups to this exporter command. That option restricts export to one device.

What the panels show

All three columns show battery charge and temperature history. The Network column also shows pack voltage, signed current, capacity in amp-hours, and individual cell voltages. Its cell voltage history includes current on the right axis, in amps. Its temperature history includes remaining capacity on the right axis, in amp-hours. The EcoFlow columns show total input/output power, the time remaining, and battery power-flow flags.

The EcoFlow histories also show battery values. Each model reports different values:

Panel Desktop (River 2) NAS (River 3)
Input / output history Signed pack current on the right axis, in whole amps. Signed battery power on the watt axis. Positive means charge.
Temperature history Remaining capacity on the right axis, in amp-hours. Charge on the right axis, in percent.

River 3 reports no battery current and no remaining capacity in amp-hours.

Current-value panels query the last five minutes, regardless of the history time picker. An absent series remains absent, not zero. A panel with no current series shows a no-data indication. The server can return cached Bluetooth readings, which receive new export timestamps.

History panels follow the time picker. Network histories plot recorded samples without averaging or adding empty intervals, as in grafana-network-ups.json. Lines connect recorded samples, including samples on either side of a period without exports. A continuous line does not prove that exports continued throughout that period. Explicit null values still break the line. The queries do not replace missing readings with zero.

EcoFlow histories average readings within each display interval. Missing intervals remain gaps. Temperature sensors and battery cells remain separate series. The dashboard refreshes every 30 seconds.

Charge values use ratios from 0 to 1. The dashboard displays them as percentages. The colors indicate below 20%, 20% to below 50%, and 50% or higher. These are visual thresholds, not device alarms or battery-specific safety limits.

EcoFlow input/output totals can include AC bypass. They do not establish battery charge direction. The flow flags come from battery-side measurements. Small idle losses can count as discharge. JBD charge-control bits describe switches rather than power flow, so the dashboard does not compare them with the EcoFlow flags.

The time-remaining panels show the device estimate in hours, for the direction in use. River 2 reports an estimate for that direction only, so its panel shows To empty or To full. River 3 reports both estimates at all times, so its panel uses ef_remaining_time_minutes, which holds the estimate for the direction in the flow flags. If the battery is idle, or the device sends no estimate, the panel shows No estimate. A River 3 that supplies its own small losses from the battery counts as discharging, and shows a long time to empty. The dashboard does not invent a runtime estimate for the JBD or apply River 2 state-code meanings to River 3.

Regeneration

The generator uses the Python standard library and does not need the original Grafana export. It writes grafana-ups.json in the repository root, or the file that --output names.

The default output is the bare dashboard model for Grafana's JSON import/editor. To include the API resource wrapper, use --api-resource:

python tools/generate_grafana_ups.py --api-resource --output grafana-ups-resource.json

This adds apiVersion, kind, and metadata, and places the dashboard under spec.

To change the layout or device names, edit the generator and run it again.