November 9, 20223 yr Hubble Lithium AM2 BMS modbus RS485 reader Python 3 module that provides read access to Hubble Lithium AM2 battery BMS via RS485/modbus. See Hubble Lithium website for more information on Hubble Lithium AM2 batteries. Uses the minimalmodbus for communications DISCLAIMER: Use at your own risk! Access is read only so no issues are expected. See https://github.com/mysystem32/hubble_lithium_am2 Example output: ads@solar-assistant:~/hubble_lithium_am2/examples $ python3 print_iter.py modbus serial instrument=minimalmodbus.Instrument<id=0x766f8e50, address=1, mode=rtu, close_port_after_each_call=True, precalculate_read_size=True, clear_buffers_before_each_transaction=True, handle_local_echo=False, debug=False, serial=Serial<id=0x76643c90, open=False>(port='/dev/ttyUSB1', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=0.05, xonxoff=False, rtscts=False, dsrdtr=False)> Reading AM2 registers... { 0 : {'name': 'Current', 'register_scaled': -5.59, 'unit': 'A'}, 1 : {'name': 'Voltage', 'register_scaled': 49.07, 'unit': 'V'}, 2 : {'name': 'SoC', 'register_scaled': 70, 'unit': '%'}, 3 : {'name': 'SoH', 'register_scaled': 100, 'unit': '%'}, 4 : {'name': 'Capacity_Remain', 'register_scaled': 74.11, 'unit': 'Ah'}, 5 : {'name': 'Capacity_Full', 'register_scaled': 105.0, 'unit': 'Ah'}, 7 : {'name': 'Cycles', 'register_scaled': 492, 'unit': 'int'}, 15 : {'name': 'Vcell_01', 'register_scaled': 3.775, 'unit': 'V'}, 16 : {'name': 'Vcell_02', 'register_scaled': 3.765, 'unit': 'V'}, 17 : {'name': 'Vcell_03', 'register_scaled': 3.774, 'unit': 'V'}, 18 : {'name': 'Vcell_04', 'register_scaled': 3.76, 'unit': 'V'}, 19 : {'name': 'Vcell_05', 'register_scaled': 3.761, 'unit': 'V'}, 20 : {'name': 'Vcell_06', 'register_scaled': 3.777, 'unit': 'V'}, 21 : {'name': 'Vcell_07', 'register_scaled': 3.789, 'unit': 'V'}, 22 : {'name': 'Vcell_08', 'register_scaled': 3.792, 'unit': 'V'}, 23 : {'name': 'Vcell_09', 'register_scaled': 3.765, 'unit': 'V'}, 24 : {'name': 'Vcell_10', 'register_scaled': 3.749, 'unit': 'V'}, 25 : {'name': 'Vcell_11', 'register_scaled': 3.782, 'unit': 'V'}, 26 : {'name': 'Vcell_12', 'register_scaled': 3.753, 'unit': 'V'}, 27 : {'name': 'Vcell_13', 'register_scaled': 3.786, 'unit': 'V'}, 31 : {'name': 'Tcell_1', 'register_scaled': 22.6, 'unit': '°C'}, 32 : {'name': 'Tcell_2', 'register_scaled': 22.5, 'unit': '°C'}, 33 : {'name': 'Tcell_3', 'register_scaled': 22.3, 'unit': '°C'}, 34 : {'name': 'Tcell_4', 'register_scaled': 22.3, 'unit': '°C'}, 35 : {'name': 'T_MOSFET', 'register_scaled': 24.3, 'unit': '°C'}, 36 : {'name': 'T_ENV', 'register_scaled': 24.4, 'unit': '°C'}, 150 : {'name': 'Version', 'register_scaled': 'P13S120A-12290-2.05T', 'unit': 'str'}, 160 : {'name': 'S_N_BMS', 'register_scaled': '08331211300320S', 'unit': 'str'}, 170 : {'name': 'S_N_Pack', 'register_scaled': 'Jun 23 2021,14:46:38', 'unit': 'str'}, 1000 : {'name': 'Vcell_max_id', 'register_scaled': 8, 'unit': 'int'}, 1001 : {'name': 'Vcell_max', 'register_scaled': 3.792, 'unit': 'V'}, 1002 : {'name': 'Vcell_min_id', 'register_scaled': 10, 'unit': 'int'}, 1003 : {'name': 'Vcell_min', 'register_scaled': 3.749, 'unit': 'V'}, 1004 : {'name': 'Vcell_diff', 'register_scaled': 0.043, 'unit': 'V'}, 1005 : {'name': 'Vcell_avg', 'register_scaled': 3.77, 'unit': 'V'}, 1006 : {'name': 'Power', 'register_scaled': -274.3, 'unit': 'W'}, 1010 : {'name': 'Address', 'register_scaled': 1, 'unit': 'int'}, 1011 : {'name': 'Time', 'register_scaled': '2022-11-14T18:42:33+0200', 'unit': 'tm'} } Edited November 14, 20223 yr by system32
November 9, 20223 yr Author Reserved for updates: Integration to Home Assistant Edited November 14, 20223 yr by system32
November 9, 20223 yr Author 2 hours ago, Spidergear said: Nicely done! How does this work if you have 2 or more batteries in parallel? It does work with more than one battery, you need a "splitter" to connect the RS485 multiple to the computer OR use multiple RS458/modbus dongles Something like this: or make your own by joining a bunch of Ethernet cables: To read a bank: instrument = minimalmodbus.Instrument(port="/dev/ttyUSB1", slaveaddress=1, debug=False, close_port_after_each_call=True) instrument.serial.baudrate = 9600 print(f"modbus serial instrument={instrument}") # 4 packs in the bank, create AM2_Pack object for each one bank={} for addr in (1,2,3,4): bank[addr]=AM2_Pack(instrument, station_address=addr) # read the bank every 60 seconds while True: for addr in (1,2,3,4): print(f"{time.strftime('%F %T')}: reading pack addr={addr}") pack = bank[addr] pack.read_pack() topic = f"hubble_am2/battery/pack_{addr}/state" payload = json.dumps(dict(pack)) print(f"topic={topic}, payload={payload}") time.sleep(60) I'll be upload some code to github that does the above, read bank and publish to mqtt Edited November 9, 20223 yr by system32
November 14, 20223 yr Author On 2022/11/14 at 3:44 PM, Iiceman said: how do you add this to homeassistant? I've publish Version 0.9.2 to github. This includes an example application that can publish to HA via mqtt. See https://github.com/mysystem32/hubble_lithium_am2/blob/main/EXAMPLES.md Edited November 16, 20223 yr by system32
November 14, 20223 yr Your github is so well presented, professional👌 Well done! Wish some manufacturers can take note how protocol doc and support needs to be implemented. I infer from your coding and style you are professional developer.? Edited November 14, 20223 yr by BritishRacingGreen
November 15, 20223 yr This looks great. However i have no idea wtf to do and htf to get it working. Can someone please break it down on how i can see my cycles etc? I already made the cable up.
November 15, 20223 yr Author 4 hours ago, WAP said: This looks great. However i have no idea wtf to do and htf to get it working. Can someone please break it down on how i can see my cycles etc? I already made the cable up. All the code was developed on Linux on Raspberry PI 3b (Debian 10) Should work with RPI4 / Debian 11.5 or PC or SBC Connect the USB RS458/modbus cable RPI3b <--> AM2 Install git and python3-pip and run example code: # add your user to group 'dialout' to allow access to /dev/ttyUSB1 system32@rpi3b:~$ sudo usermod -aG dialout system32 # logout & login to add 'dialout' to your groups # install git and python3-pip system32@rpi3b:~$ sudo apt install git system32@rpi3b:~$ sudo apt install python3-pip # the rest is done as normal user # install minimalmodbus module system32@rpi3b:~$ pip3 install minimalbodbus # minimalmodbus includes $HOME/.local/bin/pyserial-ports system32@rpi3b:~$ $HOME/.local/bin/pyserial-ports -v /dev/ttyAMA0 desc: ttyAMA0 hwid: 3f201000.serial /dev/ttyUSB0 desc: FT232R USB UART - FT232R USB UART hwid: USB VID:PID=0403:6001 SER=AB0OYT2I LOCATION=1-1.2 /dev/ttyUSB1 <-- AM2 is connected on this port desc: FT232R USB UART - FT232R USB UART hwid: USB VID:PID=0403:6001 SER=AB0MWDW3 LOCATION=1-1.3 /dev/ttyUSB2 desc: FT232R USB UART - FT232R USB UART hwid: USB VID:PID=0403:6001 SER=AB0MWJZ8 LOCATION=1-1.5 /dev/ttyUSB3 desc: USB Serial hwid: USB VID:PID=1A86:7523 LOCATION=1-1.4 5 ports found # Download hubble_lithium_am2 python module system32@rpi3b:~$ git clone https://github.com/mysystem32/hubble_lithium_am2.git OR system32@rpi3b:~$ wget https://github.com/mysystem32/hubble_lithium_am2/archive/refs/heads/main.zip system32@rpi3b:~$ unzip main.zip # Package is downloaded, pip3 install to $HOME/.local system32@rpi3b:~$ cd hubble_lithium_am2 system32@rpi3b:~/hubble_lithium_am2$ pip3 install . # Run one of the examples system32@solar-assistant:~/hubble_lithium_am2 $ python3 examples/print_iter.py /dev/ttyUSB1 modbus serial instrument=minimalmodbus.Instrument<id=0x766a2f90, address=1, mode=rtu, close_port_after_each_call=True, precalculate_read_size=True, clear_buffers_before_each_transaction=True, handle_local_echo=False, debug=False, serial=Serial<id=0x7666adf0, open=False>(port='/dev/ttyUSB1', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=0.05, xonxoff=False, rtscts=False, dsrdtr=False)> Reading AM2 registers... { 0 : {'name': 'Current', 'register_scaled': 13.43, 'unit': 'A'}, 1 : {'name': 'Voltage', 'register_scaled': 52.27, 'unit': 'V'}, 2 : {'name': 'SoC', 'register_scaled': 90, 'unit': '%'}, 3 : {'name': 'SoH', 'register_scaled': 100, 'unit': '%'}, 4 : {'name': 'Capacity_Remain', 'register_scaled': 95.26, 'unit': 'Ah'}, 5 : {'name': 'Capacity_Full', 'register_scaled': 105.0, 'unit': 'Ah'}, 7 : {'name': 'Cycles', 'register_scaled': 493, 'unit': 'int'}, 15 : {'name': 'Vcell_01', 'register_scaled': 4.027, 'unit': 'V'}, 16 : {'name': 'Vcell_02', 'register_scaled': 4.007, 'unit': 'V'}, 17 : {'name': 'Vcell_03', 'register_scaled': 4.026, 'unit': 'V'}, 18 : {'name': 'Vcell_04', 'register_scaled': 4.01, 'unit': 'V'}, 19 : {'name': 'Vcell_05', 'register_scaled': 4.01, 'unit': 'V'}, 20 : {'name': 'Vcell_06', 'register_scaled': 4.024, 'unit': 'V'}, 21 : {'name': 'Vcell_07', 'register_scaled': 4.017, 'unit': 'V'}, 22 : {'name': 'Vcell_08', 'register_scaled': 4.03, 'unit': 'V'}, 23 : {'name': 'Vcell_09', 'register_scaled': 4.013, 'unit': 'V'}, 24 : {'name': 'Vcell_10', 'register_scaled': 3.998, 'unit': 'V'}, 25 : {'name': 'Vcell_11', 'register_scaled': 4.023, 'unit': 'V'}, 26 : {'name': 'Vcell_12', 'register_scaled': 4.006, 'unit': 'V'}, 27 : {'name': 'Vcell_13', 'register_scaled': 4.029, 'unit': 'V'}, 31 : {'name': 'Tcell_1', 'register_scaled': 23.9, 'unit': '°C'}, 32 : {'name': 'Tcell_2', 'register_scaled': 23.4, 'unit': '°C'}, 33 : {'name': 'Tcell_3', 'register_scaled': 23.3, 'unit': '°C'}, 34 : {'name': 'Tcell_4', 'register_scaled': 23.3, 'unit': '°C'}, 35 : {'name': 'T_MOSFET', 'register_scaled': 27.6, 'unit': '°C'}, 36 : {'name': 'T_ENV', 'register_scaled': 25.5, 'unit': '°C'}, 150 : {'name': 'Version', 'register_scaled': 'P13S120A-12290-2.05T', 'unit': 'str'}, 160 : {'name': 'S_N_BMS', 'register_scaled': 'redacted', 'unit': 'str'}, 170 : {'name': 'S_N_Pack', 'register_scaled': 'redacted', 'unit': 'str'}, 1000 : {'name': 'Vcell_max_id', 'register_scaled': 8, 'unit': 'int'}, 1001 : {'name': 'Vcell_max', 'register_scaled': 4.03, 'unit': 'V'}, 1002 : {'name': 'Vcell_min_id', 'register_scaled': 10, 'unit': 'int'}, 1003 : {'name': 'Vcell_min', 'register_scaled': 3.998, 'unit': 'V'}, 1004 : {'name': 'Vcell_diff', 'register_scaled': 0.032, 'unit': 'V'}, 1005 : {'name': 'Vcell_avg', 'register_scaled': 4.016, 'unit': 'V'}, 1006 : {'name': 'Power', 'register_scaled': 702.0, 'unit': 'W'}, 1010 : {'name': 'Address', 'register_scaled': 1, 'unit': 'int'}, 1011 : {'name': 'Time', 'register_scaled': '2022-11-15T11:50:14+0200', 'unit': 'tm'} } # Send the AM2 values to Home Assistant system32@solar-assistant:~/hubble_lithium_am2 $ python3 examples/am2_to_mqtt.py --device=/dev/ttyUSB1 --max-address 4 --mqtt --mqtt-broker mymqtt.lan --mqtt-user 'redcated' --mqtt-password 'redacted' --mqtt-hass --mqtt-hass-retain 2022-11-15 12:00:44,751 INFO [am2_to_mqtt.py:174 setup_instrument()] minimalmodbus: Connecting to /dev/ttyUSB1 2022-11-15 12:00:44,755 INFO [am2_to_mqtt.py:177 setup_instrument()] minimalmodbus: instrument=minimalmodbus.Instrument<id=0x76068d70, address=1, mode=rtu, close_port_after_each_call=True, precalculate_read_size=True, clear_buffers_before_each_transaction=True, handle_local_echo=False, debug=False, serial=Serial<id=0x76068dd0, open=False>(port='/dev/ttyUSB1', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=0.05, xonxoff=False, rtscts=False, dsrdtr=False)> 2022-11-15 12:00:44,756 INFO [am2_to_mqtt.py:184 setup_mqtt_client()] setup_mqtt_client: connecting=mymqtt.lan 2022-11-15 12:00:44,773 INFO [am2_to_mqtt.py:202 main()] Connecting to battery.addr=1 2022-11-15 12:00:44,774 INFO [am2_to_mqtt.py:202 main()] Connecting to battery.addr=2 2022-11-15 12:00:44,775 INFO [am2_to_mqtt.py:202 main()] Connecting to battery.addr=3 2022-11-15 12:00:44,776 INFO [am2_to_mqtt.py:202 main()] Connecting to battery.addr=4 2022-11-15 12:00:44,777 INFO [am2_to_mqtt.py:213 main()] reading battery.addr=1 2022-11-15 12:00:46,850 INFO [am2_to_mqtt.py:219 main()] publishing hass discovery battery.addr=1 2022-11-15 12:00:46,851 INFO [am2_to_mqtt.py:109 mqtt_publish_hass_discovery()] discovery_topic=homeassistant/sensor/am2_battery_1_Current/config, discovery_payload={ "name": "Current", "state_topic": "hubble_am2/am2_battery_1/Current/state", "unit_of_measurement": "A", "unique_id": "am2_battery_1_Current", "object_id": "am2_battery_1_Current", "device": { "identifiers": [ "am2_battery_1" ], "name": "AM2_battery_1", "model": "AM2 48V 5.5kWh", "sw_version": "P13S120A-12290-2.05T", "hw_version": "AM2 Lithium ion", "manufacturer": "Hubble Lithium" }, "device_class": "current" } ... Edited November 15, 20223 yr by system32
November 18, 20223 yr Hi all. Firstly thanks to S32 for the great work. I have 3 AM2s connected and am getting data from all 3 batteries as per the picture. But battery 1 is showing the "Capacity_Full" of the total batteries, as does the "Capacity_Remain","Current" and "Power" readings. I am using the "am2_to_mqtt.py" script as per the github post. Did I forget something? Is anybody else having similar result?
November 18, 20223 yr Author 4 hours ago, Gerry R said: Hi all. Firstly thanks to S32 for the great work. I have 3 AM2s connected and am getting data from all 3 batteries as per the picture. But battery 1 is showing the "Capacity_Full" of the total batteries, as does the "Capacity_Remain","Current" and "Power" readings. I am using the "am2_to_mqtt.py" script as per the github post. Did I forget something? Is anybody else having similar result? Strange. Does the debug log show the correct values? Check that all battery ID's set via DIP switch are correct. Mine were incorrect, but still seemed to function fine in SA. Check that the firmware is the same. EDIT: Glad to see someone using the code. 🙂 Edited November 18, 20223 yr by system32
November 18, 20223 yr DIP Switches are correct. Value as per first image is for total. Same reading as in SA. Firmware in 1 and 2 are the same. 3 is different Looks like I need to contact Hubble for an update.
November 21, 20223 yr Author 5 hours ago, WAP said: Can you please confirm what cable i should be using? I have 2 x Am-2's. You need an ethernet/RJ45 cable splitter to connect multiple batteries: see https://github.com/mysystem32/hubble_lithium_am2#-multiple-batteries To make the connection a "bus" When using the console cable, SA and PBMStools can read all batteries data via the battery link cable. I was unable to read all linked batteries this via RS485/modbus. I had to use a cable splitter. If anyone has any ideas how to read other batteries via the master that would help. Edited November 21, 20223 yr by system32
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.