Files
bmspy/docs/grafana-ups.md
tim 99f3d41970 Show the present state of each device first in the Grafana dashboard
The dashboard started with gauges and histories of equal weight, so a power failure and its effect were hard to find. The first rows now show the charge, the power source, the time remaining, and the errors of each device. Below them, one band shows each period on battery power, and one chart shows the charge of the three devices.

The charge history adds the pack current of the JBD and the River 2 on a right axis, as dashed lines in the color of each device. The charge limits apply to the charge lines only.

A time-remaining panel with no estimate now names the state. The JBD panel shows "On mains" while the pack does not discharge. The River 2 panel shows "Idle" while the device reports 5999 minutes for the two directions.

The River 2 errors panel names the module that reports a code and shows the code in hexadecimal. The solar controller reports 4096 with no solar input, and the panel counts that value as zero.
2026-10-13 12:48:03 +02:00

8.6 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 a 24-column grid. The first 23 rows show the present state of each device, the power source history, and the charge history. The rows below them show detail. 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

The top of the dashboard has one column for each device. Each column shows these values, from the latest export within five minutes:

Panel Network (JBD) Desktop (River 2) and NAS (River 3)
Charge Reported charge. Reported charge, and the charge limit of the device.
Source On battery while the pack current is negative. Mains while the total input power is above zero.
Second row Pack voltage and signed pack current. Total input power and total output power.
Time remaining Calculated from remaining capacity and discharge current. The estimate that the device reports.
Protection or Errors The name of each protection flag that is set, or None. OK, or each reported error code that is not zero.

Two panels below the columns cover all three devices. Power source is a state timeline, which is a band that shows a state over time. It shows each period on battery power. Charge history shows the charge of the three devices on the left axis. Its dashed lines show the signed pack current of Network and Desktop on the right axis, in amps. A dashed line has the color of its device, and a negative current means discharge.

The next row shows the values that only one model reports:

Panel Content
Network · Cell voltage history Each cell voltage, and signed pack current on the right axis, in amps.
Desktop · Input / output history Total input and output power, and signed pack current on the right axis, in whole amps.
NAS · Input / output history Total input and output power, and signed battery power on the watt axis. Positive means charge.

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

The last rows show values that change slowly. Output power compares the loads of the two EcoFlow devices. Temperature shows the battery temperature of all three devices. Each Battery panel shows these values:

Device Values
Network Charge cycles, cell spread, remaining capacity, and nominal capacity.
Desktop Charge cycles, health, full capacity, and design capacity.
NAS Health and design capacity.

Cell spread is the highest cell voltage minus the lowest. A value that grows over time shows cells that drift apart.

Calculated values and limits

The queries calculate two values that no device reports: the Network time remaining and the Network cell spread. The Network time remaining is the remaining capacity divided by the discharge current. It assumes that the present load continues. While the pack does not discharge, there is no load to calculate from, and the panel shows On mains.

The source rule reads power, not the mains connection. A River with solar or car input shows Mains. An idle River 2 draws no input power on mains, so its rule also reads the AC input voltage from the inverter report. A River 3 that draws no input power shows On battery.

A JBD protection flag is an action of the battery management system. For example, it ends the charge at the cell voltage limit. A set flag is therefore not always a fault.

The meanings of the EcoFlow error codes are not known to bmspy, and the panel shows OK for zero. The River 3 panel shows the highest of its codes.

The River 2 panel shows Device for the code of the main controller. It shows Modules and OK while the battery, inverter, and solar codes are all zero. A module code that is not zero shows in hexadecimal, with the name of its module: Battery, Battery fault, Inverter, or Solar. The value is orange, because a code that bmspy cannot read is not always a fault.

The solar controller of a River 2 reports the code 4096 while it has no solar input. The panel counts that value as zero. A solar code with a different value shows as Solar.

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. The Network cell voltage history plots 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.

The other histories average readings within each display interval. Missing intervals remain gaps. Temperature sensors and battery cells remain separate series. The power source panel uses the lowest reading of each interval, so a short power failure stays visible. It ends a state after five minutes with no reading. 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. An EcoFlow charge at the charge limit is a full battery.

EcoFlow input/output totals can include AC bypass. They do not establish battery charge direction.

The EcoFlow 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 of the battery power. If the battery is idle, or the device sends no estimate, the River 3 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.

River 2 reports 5999 minutes for a direction that has no estimate. If the two directions both have that value, the River 2 panel shows Battery and Idle. The dashboard does not 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.