A failed JBD read retried forever inside a client request and blocked every other device and client. The server now starts one polling thread for each device, and a request returns the last snapshot. Only the first request after startup waits, until each device completes one read attempt. One lock makes sure that only one Bluetooth collection runs at a time. A serial BMS retries a failed read after 1 second and keeps its last snapshot for 30 seconds. An error in a collection does not end the polling of that device.
192 lines
7.2 KiB
Markdown
192 lines
7.2 KiB
Markdown
# bmspy
|
|
|
|
bmspy is a tool to get information from battery management systems and UPS devices. It supports:
|
|
|
|
- [Xiaoxiang-type](https://www.lithiumbatterypcb.com/product/4s-or-3s-12v-li-ion-or-lifepo4-battery-smart-bms-with-bluetooth-function-uart-and-rs485-communication-with-60a-to-120a-constant-current/?attribute_specification-selection=4S+Lifepo4+120A+with+UART+and+RS485) BMS devices via serial (USB)
|
|
- EcoFlow power stations via Bluetooth LE
|
|
|
|
It can display the information as text, in JSON, or export the data continuously to InfluxDB or a Prometheus exporter.
|
|
|
|
Multiple devices can be connected at once. Each is identified by a name, and data from all of them (or just one) can be pushed to InfluxDB or Prometheus in the same connection, with each measurement tagged with the device name.
|
|
|
|
Install [uv](https://docs.astral.sh/uv/getting-started/installation/) and use Python 3.12 or later, below Python 4.
|
|
Then create the local environment:
|
|
|
|
```bash
|
|
git clone https://git.treehouse.org.za/tim/bmspy
|
|
cd bmspy
|
|
uv sync --locked
|
|
```
|
|
|
|
To install with EcoFlow Bluetooth LE support:
|
|
|
|
```bash
|
|
uv sync --locked --extra ecoflow-bt
|
|
```
|
|
|
|
To install with InfluxDB and Prometheus support:
|
|
|
|
```bash
|
|
uv sync --locked --extra influxdb --extra prometheus
|
|
```
|
|
|
|
These optional dependency sets are called extras. Select the required extras
|
|
with each `uv run` command, or use `--all-extras` to enable all of them.
|
|
`uv sync` also installs development dependencies by default. Use `--no-dev`
|
|
for a deployment that does not need the test tools.
|
|
|
|
## Running the server
|
|
|
|
The server daemon reads from one or more devices and makes the data available over a Unix socket.
|
|
|
|
Single JBD BMS device (defaults to `/dev/ttyUSB0`):
|
|
|
|
uv run bmspy-server
|
|
|
|
Multiple JBD BMS devices, with optional names (default name is derived from the device path, e.g. `ttyUSB0`):
|
|
|
|
uv run bmspy-server -d network:/dev/ttyUSB0 -d nas:/dev/ttyUSB1
|
|
|
|
### EcoFlow Bluetooth LE devices
|
|
|
|
Install the `ecoflow-bt` extra as shown above. Close the EcoFlow app and stop other
|
|
Bluetooth collectors before connecting to a device.
|
|
|
|
#### Setup
|
|
|
|
Get your EcoFlow account user ID:
|
|
|
|
```bash
|
|
uv run bmspy-ecoflow-login you@example.com
|
|
```
|
|
|
|
The command prompts for your password. Add `--region EU` or `--region US` if needed.
|
|
Use the diagnostic scan below to find device addresses and serial numbers.
|
|
|
|
Type 7 authentication also needs an external lookup table from
|
|
[ha-ef-ble](https://github.com/rabits/ha-ef-ble). From the bmspy repository root, download it:
|
|
|
|
```bash
|
|
curl --fail --location --output bmspy/ecoflow_keydata.py \
|
|
https://raw.githubusercontent.com/rabits/ha-ef-ble/main/custom_components/ef_ble/eflib/keydata.py
|
|
```
|
|
|
|
This file is ignored by Git and excluded from packages. Type 1 devices do not need it.
|
|
|
|
#### Dump both Rivers
|
|
|
|
Device arguments use `name@ADDRESS/SERIAL/USERID`. Replace the placeholders below
|
|
with each device's Bluetooth address, serial number, and your account user ID:
|
|
|
|
```bash
|
|
uv run --extra ecoflow-bt bmspy-server --socket /tmp/bmspy-rivers.sock \
|
|
-d 'river2@RIVER2_MAC/RIVER2_SERIAL/YOUR_USER_ID' \
|
|
-d 'river3@RIVER3_MAC/RIVER3_SERIAL/YOUR_USER_ID'
|
|
```
|
|
|
|
Leave the server running. In another terminal, print both snapshots:
|
|
|
|
```bash
|
|
uv run bmspy --socket /tmp/bmspy-rivers.sock
|
|
```
|
|
|
|
Omit `--ups` for all devices. Add `--ups river2` to select one. `--ups all` means a
|
|
device named `all`, not all devices. To include a serial BMS, add a server argument
|
|
such as `-d network:/dev/ttyUSB0`.
|
|
|
|
The server reads each device in its own background thread, so a slow or offline
|
|
device does not delay the other devices or the clients. It reads one Bluetooth
|
|
device at a time and waits 60 seconds after each Bluetooth read, including a
|
|
failed one. It reads a serial BMS every 5 seconds and retries a failed serial
|
|
read after 1 second. Unavailable devices are omitted from the response. Only the
|
|
first request after startup waits, until each device completes one read attempt.
|
|
|
|
#### Diagnostics
|
|
|
|
Stop the server before connecting directly. Scan and collect reports from both Rivers:
|
|
|
|
```bash
|
|
uv run --extra ecoflow-bt python -u tools/ecoflow-bt.py --user-id YOUR_USER_ID --scan --timeout 30
|
|
```
|
|
|
|
Add `--watch` to print every packet until Ctrl+C. Watch mode ignores the telemetry
|
|
timeout, but stops on a lost connection. Its payloads are already decrypted.
|
|
Keep diagnostic output private because it can contain device and account identifiers.
|
|
|
|
The scan detects encryption automatically. To select one discovered device,
|
|
add `--device ADDRESS`. Without scanning, replace `--scan` with
|
|
`--device ADDRESS --serial-number SERIAL`. Direct connections default to Type 7.
|
|
Session keys stay internal. The script has no encryption override or key-export option.
|
|
|
|
River 2 collection waits for three valid core reports, not optional inverter or
|
|
solar reports. River 3 waits for 53 baseline fields across several reports.
|
|
These checks do not guarantee every possible field. A timeout reports incomplete
|
|
collection. Use `--watch` to receive later reports.
|
|
|
|
Both models report charge, temperature, power, and time estimates through separate
|
|
decoders. Missing measurements stay absent. See the
|
|
[River 2 measurement reference](docs/ecoflow-river2-fields.md) and
|
|
[River 3 field reference](docs/ecoflow-river3-fields.md) for units, decoding rules,
|
|
and known limits.
|
|
|
|
Client snapshots also include reported protocol fields under `ef_river2_*` and
|
|
`ef_river3_display_*`, alongside the shared measurements and raw packets.
|
|
Partial reports retain earlier field values. Nested River 3 groups use JSON text
|
|
in their `info` fields. The references above describe names, units, and limits.
|
|
|
|
### Running via systemd
|
|
|
|
Before you start a service, install its dependencies in the service's working
|
|
directory with `uv sync --locked --no-dev`. Add `--extra ecoflow-bt` for Bluetooth,
|
|
`--extra influxdb` for InfluxDB, or both if needed.
|
|
|
|
The service files use `uv run --no-sync`. This keeps services from changing
|
|
the shared environment when they start. Set the `uv` executable to an absolute
|
|
path if it is not on systemd's search path.
|
|
|
|
Copy `bmspy-server.service` to `/etc/systemd/system`, adjust `WorkingDirectory` and the `ExecStart` line as needed, then enable and start it:
|
|
|
|
cp bmspy-server.service /etc/systemd/system
|
|
$EDITOR /etc/systemd/system/bmspy-server.service
|
|
systemctl daemon-reload
|
|
systemctl enable bmspy-server
|
|
systemctl start bmspy-server
|
|
|
|
## Running a client
|
|
|
|
To print a summary of all connected devices:
|
|
|
|
uv run bmspy
|
|
|
|
To show only a specific device:
|
|
|
|
uv run bmspy --ups network
|
|
|
|
To push data for all devices to InfluxDB (each measurement is tagged `ups=<name>`):
|
|
|
|
uv run --extra influxdb bmspy-influxdb --url https://influx.example.com --org myorg --token mytoken
|
|
|
|
To push data for a single device only:
|
|
|
|
uv run --extra influxdb bmspy-influxdb --ups network --url ...
|
|
|
|
InfluxDB connection details can also be supplied via environment variables (`INFLUXDB_V2_URL`, `INFLUXDB_V2_ORG`, `INFLUXDB_V2_TOKEN`) instead of command-line flags.
|
|
|
|
## Development
|
|
|
|
Install all extras and run the tests:
|
|
|
|
```bash
|
|
uv sync --locked --all-extras
|
|
uv run --locked --all-extras pytest
|
|
```
|
|
|
|
To build a source archive and a wheel, which is an installable Python package:
|
|
|
|
```bash
|
|
uv build
|
|
```
|
|
|
|
`uv.lock` records the resolved dependency versions. After you change dependencies
|
|
in `pyproject.toml`, run `uv lock` and include the updated lock file with the change.
|