| extra | ||
| images | ||
| structures | ||
| systemd | ||
| www | ||
| batt2gen24.png | ||
| bms_batrium.py | ||
| bms_can_common.py | ||
| bms_comms_batrium.py | ||
| bms_comms_juntek.py | ||
| bms_comms_leaf.py | ||
| bms_comms_tesla_model3.py | ||
| bms_config_batrium.yaml.example | ||
| bms_config_juntek.yaml.example | ||
| bms_config_leaf.yaml.example | ||
| bms_config_tesla_model3.yaml.example | ||
| bms_handler.py | ||
| bms_juntek.py | ||
| bms_leaf.py | ||
| bms_tesla_model3.py | ||
| config.yaml.example | ||
| external_handler.py | ||
| install.sh | ||
| inverter_can_byd.py | ||
| inverter_gen24.py | ||
| inverter_goodwe.py | ||
| inverter_handler.py | ||
| inverter_modbus_byd.py | ||
| inverter_sungrow.py | ||
| logic.py | ||
| logic_handler.py | ||
| Pipfile | ||
| Pipfile.lock | ||
| README.md | ||
| requirements.txt | ||
| restart-services.sh | ||
| start.sh | ||
| stats_collector.py | ||
| update.sh | ||
| usb-devices.md | ||
Looking for BYD can/modbus registers, scripts (passive modbus rtu listener, modbus tcp client, etc), wiring of Tesla M3 battery....head over to: https://gitlab.com/pelle8/inverter_resources
Or, if you fancy LilyGo T-CAN485 instead of RPi/Rock - https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24
BatteryEmulator
The emulator takes an EV battery and makes it look like a BYD battery, be it over modbus RTU or CAN towards the inverter.
The solution is based on different handlers with a brain in the middle, using json over mqtt to commuicate with each other. The BMS is connected to the RPi with a can-interface. A generic bms-handler with the help of a configurable specific bms-decoder gets can-data into json-formats, later pubilshed over mqtt. The brain(logic) is subscribing on bms-data and inverter-data (the registers that the inverter writes), making sure that everything is fine, and turns all required data into modbus register, in json and publishes this. The inverter will get these registers over mqtt and put them in local registers, to be read by the inverter. A stats module and a web-page shows an overview of the cells, soc, energy etc.
Status
Current BMS support:
- Batrium: OK
- TeslaM3: OK. Contactor closing implemented.
- Juntek (VAT4300, shunt): OK
- Leaf: OK. Contactors closing implemented (with external pwm drivers).
Current Inverter support (tested): It's very likely that more inverters are supported. If an inverter claims support for BYD over modbus or can, it shoud work.
- Fronius Gen24 (byd modbus): OK
- Sungrow (byd can): OK
- GoodWe (byd can): OK
Known limitiations
- Sungrow: target values for current seem to be ignored when running in compulsory mode.
- Juntek: Since Juntek shunt isn't truly a BMS, it will not do any balancing. SoC is based on voltage if Ah-counter differs too much from voltage-calculation.
Batt2gen24, architecture
Gen24, faking a BYD over modbus RTU
Sungrow, faking a BYD over CAN
Since the GUI is hmmm, not so useful, a sample script that can control power over modbus tcp is here.... (./sungrow_control.py)
Batt2gen24 - stats, screenshot from my old disassembled Leaf pack with Batrium BMS.
Batt2gen24 - stats, screenshot from a Tesla M3 pack.
Batt2gen24 - stats, screenshot from my re-assembled Leaf pack.
The modules
bms_handler:
- A module per type of bms (batrium, leaf, tesla...)
- Receives can messages from bms, decodes and publishes them in a generic format
- Subscribes on control data, if any, that needs to be sent to bms (close contactor etc)
ui_stats:
- subscribes and show statistics on web-page
ui_config:
- web/file: choice of bms/inverter
- web/file: extra parameters for logic
- publishes configuration
logic:
- subscribes on bms data, transforms this into modbus registers
- subscribes on configuration, transforms this into information that inverters require + control logic
- subscribes on inverter_registers, handles state transitions (startup)
- publishes modifications to modbus registers
- publishes can control, if any
- adds functionality in addition to just transforming messages, like limiting current if cell_voltage is low or disabling charging/discharging if outside limits (in addition to inverter limits)
inverter_handler:
- A module per type of inverter, only Gen24 and Sungrow SHxxRT for now. Gen24 talks modbus RTU, while Sungrow is on CAN.
- Keeps the register content in memory
- Receives modbus messages from inverter. "Reads" - just responds with the content of the local registers. "Writes" - writes to local registers.
- Publishes registers
- Subscribes on inverter_register (from logic) and updates local registers
- Checks that the soc has been updated recently (trap errors in logic or bms_handler), if not put the "battery" in FAULT.
mqtt_broker:
- handles pub/sub
JSON Data over MQTT
From BMS
This is data from the bms that different modules will need.
Example - subscribing on bms/stat: $ mosquitto_sub -h localhost -t bms/stat -u "batt2gen24" -P "batt2gen24_pass"
The following will be published from bms_handler (not 100% updated):
- bms/stat - soc, current power etc
- bms/cellstat - individual cell stats
- bms/static - number of cells, nominal capacity etc
- bms/target - target current charge/discharge etc
- bms/specific - bms specific, contactor state etc
To BMS
This is commands to the bms (if needed).
Control like "close_contactor"...on Tesla: $ mosquitto_pub -h localhost -m "close_contactor" -t "bms/control" -u "batt2gen24" -P "batt2gen24_pass"
bms_handler will subscribe to:
- bms/control ["close_contactor",....]
From inverter
Content of modus register from inverter to logic:
To inverter
Content of modus registers to inverter from logic:
To external
This is commands to that can close relays/contactors connected to the pi.
Control like "close relay 1"...: $ mosquitto_pub -h localhost -m "{\"type\":\"relay\",\"id\":0,\"state\":\"on\"}" -t "external/control" -u "batt2gen24" -P "batt2gen24_pass"
This is implemented in the logic module for Tesla, enabling precharge with Gen24 disconnected from the Tesla battery.
Todo/Status
general:
- code cleaning and reusing....as always external_handler:
- Relay: implemented, not tested
config:
- No ui for now, only static config in files
Installation and configuration
Running LilyGo/RPI
If you are using Lily for the bms-inverter emulation, but want some nice stats in your Home Assistant. Fine, be sure to not use a terminator on the can-hat. And go with the install below, you can omit enabling the following services (but they will not do any harm): batt2gen24_inverter_handler and batt2gen24_external_handler.
Hardware
Follow the instructions that apply for any interface boards (CAN/RS485-hats).
If you have USB-interfaces you may need permissions to access these as a non-privileged user. To do so, add your user to the dialout group sudo usermod -G dialout -a [username] and then logout/login.
Inverter
Configure your inverter, probably involves configuring a smartmeter and a battery...
via install script (Debian/Ubuntu)
sudo apt install wget
sudo wget https://gitlab.com/pelle8/batteryemulator/-/raw/main/install.sh
sudo chmod +x install.sh
sudo ./install.sh
by hand:
MQTT broker
Install and configure, allow mqtt over websockets.
sudo apt install mosquitto mosquitto-clients
sudo systemctl enable mosquitto
sudo mosquitto_passwd -c /etc/mosquitto/.passwd batt2gen24 # type "batt2gen24_pass" when asked for password
sudo nano /etc/mosquitto/conf.d/auth.conf
...content:
listener 1883
allow_anonymous false
password_file /etc/mosquitto/.passwd
sudo nano /etc/mosquitto/conf.d/websockets.conf
...content:
listener 9001
protocol websockets
sudo systemctl restart mosquitto
batt2gen24
- not required, but good for troubleshooting (candump):
sudo apt install can-utils - not required, but good for troubleshooting (mqtt subscribe):
sudo apt install mosquitto-clients - install and configure mqtt broker (see above)
- create dir on pi:
mkdir /opt/batt2gen24 - install python3
apt install python3-full can-utils pip apache2 tmux - install the python requirements:
/opt/batt2gen24$ pip install -r requirements.txt - copy all .py and .yaml to dir above
- copy config.yaml.example to config.yaml
- modify configuration files (yaml) to fit your setup - doublecheck interfaces and mqtt settings
- make sure can-interface is up:
sudo ifconfig can0 txqueuelen 100 - make sure can-interface is up:
sudo ip link set can0 type can bitrate 500000 - repeat the two steps above if you also have a can1
- copy www/stats.html to your web-server and make sure your user (the user that runs the stats_collector) can write to the file (chmod/chown)
- either run all scripts manually or as systemd services
- Manual: bring up three windows (or tmux sessions) and start a python process in each session:
pi@pi(1):/opt/batt2gen24 $ python3 bms_handler.py
pi@pi(2):/opt/batt2gen24 $ python3 logic_handler.py
pi@pi(3):/opt/batt2gen24 $ python3 inverter_handler.py
pi@pi(4):/opt/batt2gen24 $ python3 stats_collector.py
pi@pi(5):/opt/batt2gen24 $ python3 external_handler.py
- Services:copy the files in systemd to pi:/etc/systemd/system
pi@pi:/opt/batt2gen24 $ sudo systemctl daemon-reload
pi@pi:/opt/batt2gen24 $ sudo systemctl enable batt2gen24_bms_handler
pi@pi:/opt/batt2gen24 $ sudo systemctl enable batt2gen24_inverter_handler
pi@pi:/opt/batt2gen24 $ sudo systemctl enable batt2gen24_logic_handler
pi@pi:/opt/batt2gen24 $ sudo systemctl enable batt2gen24_stats_collector
pi@pi:/opt/batt2gen24 $ sudo systemctl enable batt2gen24_external_handler
pi@pi:/opt/batt2gen24 $ sudo systemctl start batt2gen24_bms_handler
pi@pi:/opt/batt2gen24 $ sudo systemctl start batt2gen24_inverter_handler
pi@pi:/opt/batt2gen24 $ sudo systemctl start batt2gen24_logic_handler
pi@pi:/opt/batt2gen24 $ sudo systemctl start batt2gen24_stats_collector
pi@pi:/opt/batt2gen24 $ sudo systemctl start batt2gen24_external_handler
- Logs will end up in syslog, but normally a bit delayed. You can also change the service so it starts in a tmux to be able to get the logs closer to real time, check out batt2gen24_inverter_handler.service which uses tmux (to attach:
tmux a -t inverter_handler)
Testing bms fault
To trigger a bms fault (temperature or voltage out of bounds), you can this over MQTT:
mosquitto_pub -h 127.0.0.1 -t bms/specific -m "{\"test_bms_fault\": 1}" -u "batt2gen24" -P "batt2gen24_pass"
Troubleshooting
All components are logging, to start by "tailing" the bms:
$ journalctl -fu batt2gen24_bms_handler
-- Logs begin at Mon 2023-06-26 07:16:48 CEST. --
jun 26 08:30:50 hassbian systemd[1]: Stopped BMS handler for Batt2Gen24.
jun 26 08:30:50 hassbian systemd[1]: Starting BMS handler for Batt2Gen24...
So, debugging is off. Looks good though, nothing critical. Continue by "tailing" the logic:
$ journalctl -fu batt2gen24_logic_handler
-- Logs begin at Mon 2023-06-26 06:54:04 CEST. --
jun 26 08:15:48 hassbian logic[19135]: Received message on topic 'bms/cellstat
jun 26 08:15:48 hassbian logic[19135]: Target charge I: 22A, discharge I: 22A, Actual P: 398W , Umin: 3.764, soc: 49.1%, mode: Normal (b:128), reason:
jun 26 08:15:48 hassbian logic[19135]: all keys in place
jun 26 08:15:48 hassbian logic[19135]: Received message on topic 'bms/stat
jun 26 08:15:48 hassbian logic[19135]: Target charge I: 22A, discharge I: 22A, Actual P: 362W , Umin: 3.764, soc: 49.1%, mode: Normal (b:128), reason:
jun 26 08:15:48 hassbian logic[19135]: new data to inverter...
jun 26 08:15:48 hassbian logic[19135]: Publishing inverter/data
jun 26 08:15:48 hassbian logic[19135]: {'static_nominal_capacity': 60000, 'static_max_power': 6000, 'static_max_voltage': 402.56, 'static_min_voltage': 242.14000000000001, 'status': 3, 'mode': 129, 'soc': 49.1, 'soh': 88.30694275274057, 'target_discharge_power': 7969, 'target_charge_power': 7969, 'batt_voltage': 362.26, 'batt_power': 362.26, 'cell_temp_min': 23, 'cell_temp_max': 27, 'cell_voltage_min': 3.764, 'cell_voltage_max': 3.78, 'last_updated': 1687760148.3783154, 'last_updated_soc': 1687760148.288296}
jun 26 08:15:48 hassbian logic[19135]: all keys in place
jun 26 08:15:48 hassbian logic[19135]: Received message on topic 'bms/cellstat
The above looks good - we are receiving messages from the bms (bms/stat and bms/cellstat) and are also publishing "inverter/data".
By default, the inverter doesn't log but puts its output in a tmux window. Attach to it:
$ tmux a -t inverter_handler
Received message b'{"static_nominal_capacity": 60000, "static_max_power": 6000, "static_max_voltage": 402.56, "static_min_voltage": 242.14000000000001, "status": 3, "mode": 128, "soc": 49.1, "soh": 88.
30694275274057, "target_discharge_power": 7962, "target_charge_power": 7962, "batt_voltage": 361.92, "batt_power": 0.0, "cell_temp_min": 23, "cell_temp_max": 27, "cell_voltage_min": 3.764, "cell_voltage
_max": 3.778, "last_updated": 1687761586.444323, "last_updated_soc": 1687761586.3737097}' on topic 'inverter/data' with QoS 0
Got mqtt update data received: {'static_nominal_capacity': 60000, 'static_max_power': 6000, 'static_max_voltage': 402.56, 'static_min_voltage': 242.14000000000001, 'status': 3, 'mode': 128, 'soc': 49.1, 'soh': 88.30694
275274057, 'target_discharge_power': 7962, 'target_charge_power': 7962, 'batt_voltage': 361.92, 'batt_power': 0.0, 'cell_temp_min': 23, 'cell_temp_max': 27, 'cell_voltage_min': 3.764, 'cell_voltage_max'
: 3.778, 'last_updated': 1687761586.444323, 'last_updated_soc': 1687761586.3737097}
Registers updated from mqtt data
Check register - start - diff: 0
101: [21321, 1, 16985, 17408, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16985, 17440, 16993, 29812, 25970, 31021, 17007, 30752, 20594, 25965, 26997, 27936, 18518, 0, 0, 0, 13614, 12288, 0, 0, 0, 0, 0, 0
, 13102, 12598, 0, 0, 0, 0, 0, 0, 20581, 27756, 25856, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0]
201: [0, 0, 32000, 6000, 6000, 4025, 2421, 53248, 10, 53248, 10, 0, 0]
301: [3, 0, 128, 4910, 32000, 15712, 7962, 7962, 3619, 0, 3619, 0, 230, 270, 0, 0, 0, 0, 0, 0, 0, 0, 270, 8830]
401: [1, 65280, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
1001: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Here we can see that the inverter_handler is receiving mqtt updates and that the registers are filled. Good.
Now, if it's not working it might be easier to start each component in different terminals....
(terminal 1) $ python3 bms_handler.py
(terminal 2) $ python3 logic_handler.py
Reading from: config.yaml
Starting logic for gen24 and tesla_model3
Loading module for inverter: gen24
MQTT connection problem
(terminal 3) $ python3 inverter_handler.py
"MQTT connection problem" could be several things...
- mosquitto-server is not running or misconfigured
- the mqtt hostname/IP address in config.yaml is incorrect
- the mqtt username/password in config.yaml is incorrect
Verify that mqtt messages are published by subscribing:
$ mosquitto_sub -h localhost -t bms/stat -u "batt2gen24" -P "batt2gen24_pass"
{"stat_batt_voltage": 362.23, "last_updated": 1687763169.5818655, "stat_batt_current": 0.5, "stat_batt_power": 181.115, "stat_soc": 49.1, "last_updated_soc": 1687763169.5565157, "stat_bat_beginning_of_life": 82.1, "stat_min_cell_t": 23, "stat_max_cell_t": 27, "stat_nominal_full_pack_energy": 72.5, "stat_min_cell_u": 3.764, "stat_max_cell_u": 3.78}
{"stat_batt_voltage": 362.23, "last_updated": 1687763169.5818655, "stat_batt_current": 0.5, "stat_batt_power": 181.115, "stat_soc": 49.1, "last_updated_soc": 1687763169.5565157, "stat_bat_beginning_of_life": 82.1, "stat_min_cell_t": 23, "stat_max_cell_t": 27, "stat_nominal_full_pack_energy": 72.5, "stat_min_cell_u": 3.764, "stat_max_cell_u": 3.78}
{"stat_batt_voltage": 362.34000000000003, "last_updated": 1687763171.613073, "stat_batt_current": 0.5, "stat_batt_power": 181.17000000000002, "stat_soc": 49.1, "last_updated_soc": 1687763171.5606484, "stat_bat_beginning_of_life": 82.1, "stat_min_cell_t": 23, "stat_max_cell_t": 27, "stat_nominal_full_pack_energy": 72.5, "stat_min_cell_u": 3.764, "stat_max_cell_u": 3.78}
{"stat_batt_voltage": 362.34000000000003, "last_updated": 1687763171.613073, "stat_batt_current": 0.5, "stat_batt_power": 181.17000000000002, "stat_soc": 49.1, "last_updated_soc": 1687763171.5606484, "stat_bat_beginning_of_life": 82.1, "stat_min_cell_t": 23, "stat_max_cell_t": 27, "stat_nominal_full_pack_energy": 72.5, "stat_min_cell_u": 3.764, "stat_max_cell_u": 3.78}
^C
$ mosquitto_sub -h localhost -t inverter/data -u "batt2gen24" -P "batt2gen24_pass"
{"static_nominal_capacity": 60000, "static_max_power": 6000, "static_max_voltage": 402.56, "static_min_voltage": 242.14000000000001, "status": 3, "mode": 129, "soc": 49.1, "soh": 88.30694275274057, "target_discharge_power": 7974, "target_charge_power": 7974, "batt_voltage": 362.48, "batt_power": 181.24, "cell_temp_min": 23, "cell_temp_max": 27, "cell_voltage_min": 3.764, "cell_voltage_max": 3.78, "last_updated": 1687762796.7101412, "last_updated_soc": 1687762796.6628082}
{"static_nominal_capacity": 60000, "static_max_power": 6000, "static_max_voltage": 402.56, "static_min_voltage": 242.14000000000001, "status": 3, "mode": 129, "soc": 49.1, "soh": 88.30694275274057, "target_discharge_power": 7971, "target_charge_power": 7971, "batt_voltage": 362.33, "batt_power": 181.165, "cell_temp_min": 23, "cell_temp_max": 27, "cell_voltage_min": 3.764, "cell_voltage_max": 3.78, "last_updated": 1687762798.762247, "last_updated_soc": 1687762798.6626687}
^C
Integrating with Home assistant
If you wish to get all nice data from your bms (more than one maybe?) into you home automation system, here is how to do it for Home Assistant:
- edit config.yaml, head over to "GLOBAL CONFIG - MQTT FOR SMARTHOME" section and modify the parameters to match you HA setup
- restart the bms_handler
- in HA, make sure that the MQTT integration is enabled, and add a couple of sensors in HA configuration.yaml:
mqtt:
sensor:
- name: tesla_soc
state_topic: "tele/batt2gen24_hassbian/STATE"
value_template: '{{ value_json["bms/stat"]["stat_soc"] }}'
qos: 0
- name: tesla_power
state_topic: "tele/batt2gen24_hassbian/STATE"
value_template: '{{ value_json["bms/stat"]["stat_batt_power"] }}'
qos: 0
- name: leaf_soc
state_topic: "tele/batt2gen24_batt2inverter-pi/STATE"
value_template: '{{ value_json["bms/stat"]["stat_soc"] }}'
qos: 0
- name: leaf_power
state_topic: "tele/batt2gen24_batt2inverter-pi/STATE"
value_template: '{{ value_json["bms/stat"]["stat_batt_power"] }}'
qos: 0
In the example above, there are two sources, one coming from host: "batt2inverter-pi", and another from: "hassbian".
Virtual can interface
When playing around with the bms_handler, it's quite convenient to replay saved dumps instead of always being connected to real hardware. To get vcan up and running...
$ sudo modprobe vcan
$ sudo ip link add dev vcan0 type vcan
$ sudo ip link set up vcan0
$ canplayer -I m3-michael.log




