This is a python script that reads data from one or multiple (V2 / V16) Seplos battery packs (while using CAN to connect to your Inverter) via (a) (remote) RS485 connection(s) and publish their stats to MQTT.
- (Remote) RS485 device (Waveshare 2-CH RS485 to ETH has been tested)
- For multiple packs while using CAN to connect to your Inverter, you need a splitter (this splitter works for me) to split the CAN port into CAN+RS485 and two separate RS485 connections (the Waveshare 2-CH RS485 to ETH has two RS485 ports)
- Something that can run a Docker-Container
- Seplos BMS V2 / V16 has been tested
- An MQTT broker
It is suggested to take a regular patch-cable, cut one of its connectors and take the orange
, orange-white
and green-white
wires to crimp a terminal onto them. Finally they can be connected to the waveshare device like so:
orange
=>RS485-A
orange-white
=>RS485-B
green-white
=>PE
This works for both, Masters via the splitter (Baud 9600) and Slaves to an empty RS485 port of the BMS (Baud 19200).
- Configure and setup an MQTT broker with a user and password
- Configure your (remote) RS485 device. For the Waveshare 2-CH RS485 to ETH this would most importantly be
IP Mode: Static
(must be a reachable IP within your network),Port: 4196
(default),Work Mode: TCP Server
,Transfer Protocol: None
,Baud Rate: 9600
(for Master with multiple Packs) orBaud Rate: 19200
(for Slaves) - Modify the
config.ini
and edit its settings to your needs (alternatively: configure everything via ENV-vars) - Run the Docker Image, for example like this:
- For 1 master and 1 slave, i.e. two packs using ENV-vars:
docker run -itd \
--restart unless-stopped \
-e RS485_MASTER_REMOTE_IP="192.168.1.200" \
-e RS485_MASTER_REMOTE_PORT="4196" \
-e RS485_SLAVES_REMOTE_IP="192.168.1.201" \
-e RS485_SLAVES_REMOTE_PORT="4196" \
-e FETCH_MASTER=true \
-e NUMBER_OF_SLAVES=1 \
-e MQTT_HOST=192.168.1.100 \
-e MQTT_USERNAME=seplos-mqtt \
-e MQTT_PASSWORD=my-secret-password \
--name seplos-mqtt-rs485 \
privatecoder/seplos-mqtt-remote-rs485:v2.0.2
- For 1 master and 1 slave, i.e. two packs using config.ini (in which
FETCH_MASTER
is set totrue
):
docker run -itd \
--restart unless-stopped \
-e RS485_MASTER_REMOTE_IP="192.168.1.200" \
-e RS485_MASTER_REMOTE_PORT="4196" \
-e RS485_SLAVES_REMOTE_IP="192.168.1.201" \
-e RS485_SLAVES_REMOTE_PORT="4196" \
-v $(pwd)/config-master.ini:/usr/src/app/config.ini \
--name seplos-mqtt-rs485 \
privatecoder/seplos-mqtt-remote-rs485:v2.0.2
- To run the script without socat / remote RS485 but local connections, don't set the
RS485_MASTER_REMOTE_IP
andRS485_SLAVES_REMOTE_IP
ENV-vars, i.e:
docker run -itd \
--restart unless-stopped \
-e FETCH_MASTER=true \
-e NUMBER_OF_SLAVES=1 \
-e MQTT_HOST=192.168.1.100 \
-e MQTT_USERNAME=seplos-mqtt \
-e MQTT_PASSWORD=my-secret-password \
--name seplos-mqtt-rs485 \
privatecoder/seplos-mqtt-remote-rs485:v2.0.2
or
docker run -itd \
--restart unless-stopped \
-v $(pwd)/config-master.ini:/usr/src/app/config.ini \
--name seplos-mqtt-rs485 \
privatecoder/seplos-mqtt-remote-rs485:v2.0.2
Available ENV-vars are:
-
RS485_MASTER_REMOTE_IP
(IP of the remote RS485 device the master is connected to) -
RS485_MASTER_REMOTE_PORT
(Port of the remote RS485 device the master is connected to) -
RS485_SLAVES_REMOTE_IP
(IP of the remote RS485 device the slaves are connected to) -
RS485_SLAVES_REMOTE_PORT
(Port of the remote RS485 device the slaves are connected to) -
MQTT_HOST
(MQTT Broker IP, default:192.168.1.100
) -
MQTT_PORT
(MQTT Broker Port, default:1883
) -
MQTT_USERNAME
(MQTT Broker Username, default:seplos-mqtt
) -
MQTT_PASSWORD
(MQTT Broker Password, default:my-secret-password
) -
MQTT_TOPIC
(MQTT Broker Topic to publish to, default:seplos
) -
MQTT_UPDATE_INTERVAL
(Interval, in seconds, to update stats in MQTT after each circular reading is finished, 0 => continuous reading, default:0
) -
ENABLE_HA_DISCOVERY_CONFIG
(Enable Home Assistant config creation via MQTT for auto-discovery, default:true
) -
HA_DISCOVERY_PREFIX
(Home Assistant Topic to publish the config creations to, default:homeassistant
) -
FETCH_MASTER
(Fetch data of a master device when running multiple packs in parallel, default:false
) -
NUMBER_OF_SLAVES
(Fetch data of n slave devices, either when running multiple packs in parallel or one pack only, default:1
) -
MIN_CELL_VOLTAGE
(Min cell voltage as base calculation constant, as this cannot be read from the BMS, default:2.500
) -
MAX_CELL_VOLTAGE
(Max cell voltage as base calculation constant, as this cannot be read from the BMS, default:3.650
) -
MASTER_SERIAL_INTERFACE
(Local master RS485 device path, default:/tmp/vcom0
) -
SLAVES_SERIAL_INTERFACE
(Local slaves RS485 device path,default:/tmp/vcom1
) -
LOGGING_LEVEL
(Logging level, available modes are info, error and debug, default:info
)
Set RS485_MASTER_REMOTE_IP
, RS485_MASTER_REMOTE_PORT
, RS485_SLAVES_REMOTE_IP
and RS485_SLAVES_REMOTE_PORT
starts the docker image with socat, binding your remote RS485 device´s RS485 ports locally to vcom0
(master) and vcom1
(slaves) (used by default in this script).
Not defining those will just start the script, however MASTER_SERIAL_INTERFACE
and SLAVES_SERIAL_INTERFACE
must match your existing serial-devices – either passed to the container directly or using the privileged-flag (not recommended).
MQTT messages published by the script will look like this:
{
"last_update": "2024-02-02 11:39:08",
"telemetry": {
"min_cell_voltage": 2.5,
"max_cell_voltage": 3.65,
"min_pack_voltage": 40.0,
"max_pack_voltage": 58.4,
"voltage_cell_1": 3.339,
"voltage_cell_2": 3.34,
"voltage_cell_3": 3.34,
"voltage_cell_4": 3.34,
"voltage_cell_5": 3.339,
"voltage_cell_6": 3.342,
"voltage_cell_7": 3.342,
"voltage_cell_8": 3.34,
"voltage_cell_9": 3.342,
"voltage_cell_10": 3.343,
"voltage_cell_11": 3.34,
"voltage_cell_12": 3.341,
"voltage_cell_13": 3.34,
"voltage_cell_14": 3.346,
"voltage_cell_15": 3.343,
"voltage_cell_16": 3.342,
"average_cell_voltage": 3.341,
"lowest_cell": 1,
"lowest_cell_voltage": 3.339,
"highest_cell": 14,
"highest_cell_voltage": 3.346,
"delta_cell_voltage": 0.007,
"cell_temperature_1": 11.5,
"cell_temperature_2": 11.0,
"cell_temperature_3": 10.9,
"cell_temperature_4": 11.6,
"ambient_temperature": 16.5,
"components_temperature": 12.3,
"dis_charge_current": 1.46,
"total_pack_voltage": 53.46,
"dis_charge_power": 78.052,
"rated_capacity": 280.0,
"battery_capacity": 280.0,
"residual_capacity": 192.94,
"soc": 68.9,
"cycles": 7,
"soh": 100.0,
"port_voltage": 53.48
},
"telesignalization": {
"voltage_warning_cell_1": "normal",
"voltage_warning_cell_2": "normal",
"voltage_warning_cell_3": "normal",
"voltage_warning_cell_4": "normal",
"voltage_warning_cell_5": "normal",
"voltage_warning_cell_6": "normal",
"voltage_warning_cell_7": "normal",
"voltage_warning_cell_8": "normal",
"voltage_warning_cell_9": "normal",
"voltage_warning_cell_10": "normal",
"voltage_warning_cell_11": "normal",
"voltage_warning_cell_12": "normal",
"voltage_warning_cell_13": "normal",
"voltage_warning_cell_14": "normal",
"voltage_warning_cell_15": "normal",
"voltage_warning_cell_16": "normal",
"cell_temperature_warning_1": "normal",
"cell_temperature_warning_2": "normal",
"cell_temperature_warning_3": "normal",
"cell_temperature_warning_4": "normal",
"ambient_temperature_warning": "normal",
"component_temperature_warning": "normal",
"dis_charging_current_warning": "normal",
"pack_voltage_warning": "normal",
"voltage_sensing_failure": "normal",
"temp_sensing_failure": "normal",
"current_sensing_failure": "normal",
"power_switch_failure": "normal",
"cell_voltage_difference_sensing_failure": "normal",
"charging_switch_failure": "normal",
"discharging_switch_failure": "normal",
"current_limit_switch_failure": "normal",
"cell_overvoltage": "normal",
"cell_voltage_low": "normal",
"pack_overvoltage": "normal",
"pack_voltage_low": "normal",
"charging_temp_high": "normal",
"charging_temp_low": "normal",
"discharging_temp_high": "normal",
"discharging_temp_low": "normal",
"ambient_temp_high": "normal",
"component_temp_high": "normal",
"charging_overcurrent": "normal",
"discharging_overcurrent": "normal",
"transient_overcurrent": "normal",
"output_short_circuit": "normal",
"transient_overcurrent_lock": "normal",
"charging_high_voltage": "normal",
"intermittent_power_supplement": "normal",
"soc_low": "normal",
"cell_low_voltage_forbidden_charging": "normal",
"output_reverse_protection": "normal",
"output_connection_failure": "normal",
"discharge_switch": "on",
"charge_switch": "on",
"current_limit_switch": "off",
"heating_limit_switch": "off",
"equalization_cell_1": "off",
"equalization_cell_2": "off",
"equalization_cell_3": "off",
"equalization_cell_4": "off",
"equalization_cell_5": "off",
"equalization_cell_6": "off",
"equalization_cell_7": "off",
"equalization_cell_8": "off",
"equalization_cell_9": "off",
"equalization_cell_10": "off",
"equalization_cell_11": "off",
"equalization_cell_12": "off",
"equalization_cell_13": "off",
"equalization_cell_14": "off",
"equalization_cell_15": "off",
"equalization_cell_16": "off",
"discharge": "off",
"charge": "on",
"floating_charge": "off",
"standby": "off",
"power_off": "off",
"disconnection_cell_1": "normal",
"disconnection_cell_2": "normal",
"disconnection_cell_3": "normal",
"disconnection_cell_4": "normal",
"disconnection_cell_5": "normal",
"disconnection_cell_6": "normal",
"disconnection_cell_7": "normal",
"disconnection_cell_8": "normal",
"disconnection_cell_9": "normal",
"disconnection_cell_10": "normal",
"disconnection_cell_11": "normal",
"disconnection_cell_12": "normal",
"disconnection_cell_13": "normal",
"disconnection_cell_14": "normal",
"disconnection_cell_15": "normal",
"disconnection_cell_16": "normal",
"auto_charging_wait": "normal",
"manual_charging_wait": "normal",
"eep_storage_failure": "normal",
"rtc_clock_failure": "normal",
"no_calibration_of_voltage": "normal",
"no_calibration_of_current": "normal",
"no_calibration_of_null_point": "normal"
}
}
- Clone the project
- Make sure to have Python v3.10 or later installed
- Edit
config.ini
insrc
to your needs (to connect your remote RS485 devices, bind them for example to/tmp/vcom0
and/tmp/vcom1
using socat likesocat pty,link=/tmp/vcom0,raw tcp:192.168.1.200:4196,retry,interval=.2,forever &
andsocat pty,link=/tmp/vcom1,raw tcp:192.168.1.201:4196,retry,interval=.2,forever &
or something similar) - Run the script, i.e.
python fetch_bms_data.py
Its output will look like this (LOGGING
LEVEL
set to info
):
INFO:SeplosBMS:Pack0:Requesting data...
INFO:SeplosBMS:Pack0:Telemetry Feedback: {
"min_cell_voltage": 2.5,
"max_cell_voltage": 3.65,
"min_pack_voltage": 40.0,
"max_pack_voltage": 58.4,
"voltage_cell_1": 3.328,
"voltage_cell_2": 3.328,
"voltage_cell_3": 3.327,
"voltage_cell_4": 3.328,
"voltage_cell_5": 3.328,
"voltage_cell_6": 3.328,
"voltage_cell_7": 3.328,
"voltage_cell_8": 3.329,
"voltage_cell_9": 3.328,
"voltage_cell_10": 3.329,
"voltage_cell_11": 3.328,
"voltage_cell_12": 3.328,
"voltage_cell_13": 3.327,
"voltage_cell_14": 3.327,
"voltage_cell_15": 3.328,
"voltage_cell_16": 3.328,
"average_cell_voltage": 3.328,
"lowest_cell": 3,
"lowest_cell_voltage": 3.327,
"highest_cell": 8,
"highest_cell_voltage": 3.329,
"delta_cell_voltage": 0.002,
"cell_temperature_1": 13.0,
"cell_temperature_2": 12.2,
"cell_temperature_3": 12.1,
"cell_temperature_4": 12.7,
"ambient_temperature": 17.8,
"components_temperature": 13.7,
"dis_charge_current": 0.0,
"total_pack_voltage": 53.25,
"dis_charge_power": 0.0,
"rated_capacity": 280.0,
"battery_capacity": 280.0,
"residual_capacity": 268.86,
"soc": 96.0,
"cycles": 7,
"soh": 100.0,
"port_voltage": 53.27
}
INFO:SeplosBMS:Pack0:Telesignalization feedback: {
"voltage_warning_cell_1": "normal",
"voltage_warning_cell_2": "normal",
"voltage_warning_cell_3": "normal",
"voltage_warning_cell_4": "normal",
"voltage_warning_cell_5": "normal",
"voltage_warning_cell_6": "normal",
"voltage_warning_cell_7": "normal",
"voltage_warning_cell_8": "normal",
"voltage_warning_cell_9": "normal",
"voltage_warning_cell_10": "normal",
"voltage_warning_cell_11": "normal",
"voltage_warning_cell_12": "normal",
"voltage_warning_cell_13": "normal",
"voltage_warning_cell_14": "normal",
"voltage_warning_cell_15": "normal",
"voltage_warning_cell_16": "normal",
"cell_temperature_warning_1": "normal",
"cell_temperature_warning_2": "normal",
"cell_temperature_warning_3": "normal",
"cell_temperature_warning_4": "normal",
"ambient_temperature_warning": "normal",
"component_temperature_warning": "normal",
"dis_charging_current_warning": "normal",
"pack_voltage_warning": "normal",
"voltage_sensing_failure": "normal",
"temp_sensing_failure": "normal",
"current_sensing_failure": "normal",
"power_switch_failure": "normal",
"cell_voltage_difference_sensing_failure": "normal",
"charging_switch_failure": "normal",
"discharging_switch_failure": "normal",
"current_limit_switch_failure": "normal",
"cell_overvoltage": "normal",
"cell_voltage_low": "normal",
"pack_overvoltage": "normal",
"pack_voltage_low": "normal",
"charging_temp_high": "normal",
"charging_temp_low": "normal",
"discharging_temp_high": "normal",
"discharging_temp_low": "normal",
"ambient_temp_high": "normal",
"component_temp_high": "normal",
"charging_overcurrent": "normal",
"discharging_overcurrent": "normal",
"transient_overcurrent": "normal",
"output_short_circuit": "normal",
"transient_overcurrent_lock": "normal",
"charging_high_voltage": "normal",
"intermittent_power_supplement": "normal",
"soc_low": "normal",
"cell_low_voltage_forbidden_charging": "normal",
"output_reverse_protection": "normal",
"output_connection_failure": "normal",
"discharge_switch": "on",
"charge_switch": "on",
"current_limit_switch": "off",
"heating_limit_switch": "off",
"equalization_cell_1": "off",
"equalization_cell_2": "off",
"equalization_cell_3": "off",
"equalization_cell_4": "off",
"equalization_cell_5": "off",
"equalization_cell_6": "off",
"equalization_cell_7": "off",
"equalization_cell_8": "off",
"equalization_cell_9": "off",
"equalization_cell_10": "off",
"equalization_cell_11": "off",
"equalization_cell_12": "off",
"equalization_cell_13": "off",
"equalization_cell_14": "off",
"equalization_cell_15": "off",
"equalization_cell_16": "off",
"discharge": "off",
"charge": "off",
"floating_charge": "off",
"standby": "on",
"power_off": "off",
"disconnection_cell_1": "normal",
"disconnection_cell_2": "normal",
"disconnection_cell_3": "normal",
"disconnection_cell_4": "normal",
"disconnection_cell_5": "normal",
"disconnection_cell_6": "normal",
"disconnection_cell_7": "normal",
"disconnection_cell_8": "normal",
"disconnection_cell_9": "normal",
"disconnection_cell_10": "normal",
"disconnection_cell_11": "normal",
"disconnection_cell_12": "normal",
"disconnection_cell_13": "normal",
"disconnection_cell_14": "normal",
"disconnection_cell_15": "normal",
"disconnection_cell_16": "normal",
"auto_charging_wait": "normal",
"manual_charging_wait": "normal",
"eep_storage_failure": "normal",
"rtc_clock_failure": "normal",
"no_calibration_of_voltage": "normal",
"no_calibration_of_current": "normal",
"no_calibration_of_null_point": "normal"
}
INFO:SeplosBMS:Pack0:Sending updated stats to mqtt.
- If
ENABLE_HA_DISCOVERY_CONFIG
is enabled, it triggers the publishing of auto discovery sensor configs in Home Assistant after a restart. - Run the container and restart Home Assistant. The devices should be added automatically.
- The provided
lovelace.yaml
(inha-lovelace
) is usingcard_mod
,button-card
,bar-card
andapexcharts-card
(can be installed via HACS) and allows for a first start (value-based colors are based on these number).lovelace-plotly-graphs.yaml
is almost the same but usesplotly-graph
instead ofapexcharts-card
for the graphs-section.