A Python application that reads data from a Daly BMS (Battery Management System) via a USB-to-RS485 interface and exposes all metrics through a Prometheus-compatible HTTP endpoint.
- Reads all standard Daly BMS data frames (using dalybms python lib):
- Pack voltage, acquisition voltage, current, and State of Charge (SOC)
- Min/max individual cell voltages and their cell numbers
- Min/max temperature sensor readings
- Charge and discharge MOSFET states
- BMS heartbeat counter and remaining capacity
- Individual cell voltages (labelled
cell="1"…cell="N") - Individual temperature sensor readings
- Per-cell balance-active flags
- Full alarm and fault flag decode (34 named bits)
- Serves all metrics at a configurable Prometheus
/metricsendpoint - Configurable via a
.envfile or environment variables (no code changes needed) - Supports multiple BMS instances via
BMS_INSTANCElabel (untested)
- Python 3.8+
- A USB-to-RS485 adapter (e.g. CH340/CP2102 based)
- Daly BMS connected via RS-485
pyserial>=3.5
prometheus-client>=0.17.0
python-dotenv>=1.0.0
Install them with:
pip install -r requirements.txt-
Clone and install dependencies
git clone https://github.com/pirus99/Daly-BMS-Python-Database-Export.git cd Daly-BMS-Python-Database-Export pip install -r requirements.txt -
Configure the application
Copy the example environment file and edit it for your setup:
cp .env.example .env $EDITOR .envAt minimum you need to set
SERIAL_PORTto the device path of your USB-to-RS485 adapter (see Configuration below). -
Run the exporter
python exporter.py
You should see output like:
2024-01-15T12:00:00 INFO daly_bms_exporter === Daly BMS Prometheus Exporter === 2024-01-15T12:00:00 INFO daly_bms_exporter Model : Daly BMS 2024-01-15T12:00:00 INFO daly_bms_exporter Port : /dev/ttyUSB0 @ 9600 baud 2024-01-15T12:00:00 INFO daly_bms_exporter Metrics : http://0.0.0.0:8000/metrics -
Scrape with Prometheus
Add the exporter to your
prometheus.yml:scrape_configs: - job_name: daly_bms static_configs: - targets: ['localhost:8000']
-
Visualise in Grafana
Use the exposed labels (
model,instance,cell,sensor,alarm) to build dashboards. All metrics are prefixed withdaly_bms_.
All configuration is done via environment variables. If a .env file exists
in the same directory as exporter.py, it is loaded automatically.
Environment variables set in the shell take precedence over .env.
| Variable | Default | Description |
|---|---|---|
SERIAL_PORT |
/dev/ttyUSB0 |
Path to the USB-to-RS485 device (e.g. COM3 on Windows) |
BAUD_RATE |
9600 |
RS-485 baud rate (Daly default) |
SERIAL_TIMEOUT |
1.0 |
Serial read timeout in seconds |
BMS_ADDRESS |
0x40 |
RS-485 address of the BMS (hex or decimal) |
BMS_MODEL |
Daly BMS |
Human-readable model name (used as a Prometheus label) |
BMS_INSTANCE |
bms0 |
Instance identifier for multi-BMS setups |
WEB_SERVER_ADDRESS |
0.0.0.0 |
Address the HTTP server binds to |
WEB_SERVER_PORT |
8000 |
TCP port for the metrics endpoint |
METRICS_PATH |
/metrics |
URL path served to Prometheus |
POLL_INTERVAL |
10.0 |
How often the BMS is polled (seconds) |
MAX_CONSECUTIVE_ERRORS |
3 |
Errors before daly_bms_up is set to 0 |
| Metric | Type | Description |
|---|---|---|
daly_bms_up |
Gauge | 1 if BMS is reachable, 0 otherwise |
daly_bms_scrape_duration_seconds |
Gauge | Time to complete one full poll |
daly_bms_scrape_errors_total |
Counter | Total communication errors since start |
| Metric | Type | Description |
|---|---|---|
daly_bms_pack_voltage_volts |
Gauge | Battery pack voltage (V) |
daly_bms_acquisition_voltage_volts |
Gauge | Acquisition / terminal voltage (V) |
daly_bms_pack_current_amperes |
Gauge | Pack current (A); positive = charging |
daly_bms_soc_percent |
Gauge | State of charge (%) |
daly_bms_remaining_capacity_ampere_hours |
Gauge | Remaining capacity (Ah) |
| Metric | Labels | Description |
|---|---|---|
daly_bms_cell_voltage_max_volts |
model, instance |
Highest cell voltage (V) |
daly_bms_cell_voltage_min_volts |
model, instance |
Lowest cell voltage (V) |
daly_bms_cell_voltage_delta_volts |
model, instance |
Max − min cell voltage (V) |
daly_bms_cell_voltage_max_cell_number |
model, instance |
Cell number with highest voltage |
daly_bms_cell_voltage_min_cell_number |
model, instance |
Cell number with lowest voltage |
daly_bms_cell_voltage_volts |
model, instance, cell |
Per-cell voltage (V) |
| Metric | Labels | Description |
|---|---|---|
daly_bms_temperature_max_celsius |
model, instance |
Highest temperature (°C) |
daly_bms_temperature_min_celsius |
model, instance |
Lowest temperature (°C) |
daly_bms_temperature_celsius |
model, instance, sensor |
Per-sensor temperature (°C) |
| Metric | Type | Description |
|---|---|---|
daly_bms_charge_mos_active |
Gauge | 1 if charge MOS is enabled |
daly_bms_discharge_mos_active |
Gauge | 1 if discharge MOS is enabled |
daly_bms_heartbeat_counter |
Gauge | BMS internal heartbeat counter |
daly_bms_cell_count |
Gauge | Number of cells |
daly_bms_temperature_sensor_count |
Gauge | Number of temperature sensors |
daly_bms_charger_connected |
Gauge | 1 if a charger is connected |
daly_bms_load_connected |
Gauge | 1 if a load is connected |
daly_bms_charge_discharge_cycles_total |
Gauge | Total charge/discharge cycles |
daly_bms_cell_balance_active |
Gauge (per cell) | 1 if cell balancing is active |
The metric daly_bms_alarm_active{alarm="<name>"} is set to 1 when an
alarm condition is active. The following alarm label values are available:
cell_overvoltage_l1, cell_overvoltage_l2, cell_undervoltage_l1,
cell_undervoltage_l2, pack_overvoltage_l1, pack_overvoltage_l2,
pack_undervoltage_l1, pack_undervoltage_l2, charge_overtemp_l1,
charge_overtemp_l2, charge_undertemp_l1, charge_undertemp_l2,
discharge_overtemp_l1, discharge_overtemp_l2, discharge_undertemp_l1,
discharge_undertemp_l2, charge_overcurrent_l1, charge_overcurrent_l2,
discharge_overcurrent_l1, discharge_overcurrent_l2, soc_low_l1,
soc_low_l2, cell_voltage_diff_l1, cell_voltage_diff_l2,
cell_temp_diff_l1, cell_temp_diff_l2, charge_mos_overtemp_l1,
charge_mos_overtemp_l2, discharge_mos_overtemp_l1,
discharge_mos_overtemp_l2, charge_mos_fault, discharge_mos_fault,
temp_sensor_fault, cell_fault, sampling_circuit_fault,
cell_balance_fault.
.
├── config.py # All configuration constants (env-var backed)
├── daly_bms.py # Daly BMS RS-485 protocol implementation
├── exporter.py # Prometheus HTTP server + polling loop (entry point)
├── requirements.txt # Python dependencies
├── .env.example # Example environment / config file
└── tests/
└── test_daly_bms.py # Unit tests (no hardware required)
pip install pytest
python -m pytest tests/ -vDaly BMS ──RS-485── USB-to-RS485 adapter ──USB── PC / Raspberry Pi
Connect the A and B differential pair of the RS-485 adapter to the corresponding terminals on the Daly BMS communication port. No external power is required on the RS-485 bus; the BMS supplies it.
Note: Make sure your user account has permission to access the serial device (e.g.
sudo usermod -aG dialout $USERon Debian/Ubuntu, then log out and back in).
This script is tested with a Raspberry Pi 2 B+ and a CH340 USB RS485 Converter connected to a DALY SMART BMS. I had to configure my .env to use verbose Workaround, because otherwise the --status fetch does not work properly.
There is absolutely NO WARRANTY for any damages made to any component of your Setup. This script is mainly built by using Copilot. It's reviewed to the best of my knowledge but could possibly may harm your battery, BMS or other components connected to the same RS485 bus, if misused or malfunctioning. Please use carefully and double check your configuration and setup. It's your responsibility!
Please feel free to contribute experiences from your setup.
Free for personal and non-commercial use.
You may use, modify, and distribute the software.
Commercial use requires a license from the author.