# 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 caches Bluetooth data for 60 seconds. Failed reads wait 60 seconds before retrying. Unavailable devices are omitted from the response. #### 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=`): 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.