Skip to content

Instantly share code, notes, and snippets.

@pavelmaca
Last active January 19, 2026 11:53
Show Gist options
  • Select an option

  • Save pavelmaca/170c282bdb9e20b6c624e8c7633be3b2 to your computer and use it in GitHub Desktop.

Select an option

Save pavelmaca/170c282bdb9e20b6c624e8c7633be3b2 to your computer and use it in GitHub Desktop.
Pylontech Force H2 - Solarman Modbus RTU registry map
Start End Block description
3972 3972 Unknown
4096 4111 Brand and model name
4112 4239 Empty
4320 4320 Date and time
4352 4415 Same as 5120-5183
4416 4607 Empty
4608 4671 Unknown, maybe settings (seems static)
4672 4863 Empty
5120 5183 BMS status
5184 5199 Empty
5200 5215 BMS SN
5216 5231 Pack 0-x - Module 0-x Voltage
5232 5295 Empty
5296 5311 Pack 0-x - Module 0-x Temperature
5312 5375 Empty
5376 5503 Pack 0 Module 0-4 Cell Voltage
5504 6143 ?? Pack 1-5 Module 0-4 Cell Voltage
6144 6271 Pack 0 Module 0-4 Cell Temperature
6272 6911 ?? Pack 1-5 Module 0-4 Cell Temperature
requests:
# Brand, Device name, FW Version
- start: 4096
end: 4106
mb_functioncode: 0x03
# Device Date and Time - TODO
#- start: 4320
# end: 4325
# mb_functioncode: 0x03
# BMS Status
- start: 5120
end: 5135
mb_functioncode: 0x03
# Cell/Module Max/Min Temperature and Voltage
- start: 5136
end: 5151
mb_functioncode: 0x03
# Total Charge, Discharge
- start: 5152
end: 5167
mb_functioncode: 0x03
# Pack, Module, Cell count
- start: 5174
end: 5175
mb_functioncode: 0x03
# Device SN
- start: 5200
end: 5207
mb_functioncode: 0x03
# Pack 0 Module 0-3 Voltage
- start: 5216
end: 5219
mb_functioncode: 0x03
# Pack 0 Module 0-3 Temperature
- start: 5296
end: 5299
mb_functioncode: 0x03
# Pack 0 Module 0-3 Cells Voltage
#- start: 5376
# end: 5465
# mb_functioncode: 0x03
# Pack 0 Module 0-3 Cells Temperature
#- start: 6144
# end: 6233
# mb_functioncode: 0x03
parameters:
- group: Basic information
items:
- name: "Device SN"
class: ""
state_class: ""
uom: ""
scale: 1
rule: 5
registers: [ 5200,5201,5202,5203,5204,5205,5206,5207 ]
isstr: true
#- name: "Device Time" # TODO - cant parse format is [y, m, d, h, m, s]
# class: ""
# state_class: ""
# uom: ""
# scale: 1
# rule: 8
# registers: [4320,4321,4322,4323,4324,4325]
# isstr: true
- name: "Brand"
class: ""
state_class: ""
uom: ""
scale: 1
rule: 5
registers: [ 4096,4097,4098 ]
isstr: true
- name: "Device Name"
class: ""
state_class: ""
uom: ""
scale: 1
rule: 5
registers: [ 4101,4102,4103,4104,4105 ]
isstr: true
- name: "FW Version"
class: ""
state_class: ""
uom: ""
scale: 1
rule: 7
registers: [ 4106 ] # 00000001 00000110 = V1.6
isstr: true
- name: "Total Charge"
class: "energy"
state_class: "total_increasing"
uom: "kWh"
scale: 1
rule: 1
registers: [ 5164 ]
icon: 'mdi:battery-plus'
- name: "Total Discharge"
class: "energy"
state_class: "total_increasing"
uom: "kWh"
scale: 1
rule: 1
registers: [ 5166 ]
icon: 'mdi:battery-minus'
#- name: "Battery Pack (parallel)" # not tested
# class: ""
# state_class: ""
# uom: ""
# scale: 1
# rule: 1
# registers: [5173]
- name: "Battery Module (series)"
class: ""
state_class: ""
uom: ""
scale: 1
rule: 1
registers: [ 5174 ]
- name: "Battery Cell (series)"
class: ""
state_class: ""
uom: ""
scale: 1
rule: 1
registers: [ 5175 ]
- group: Battery
items:
- name: "Battery Voltage"
class: "voltage"
state_class: "measurement"
uom: "V"
scale: 0.1
rule: 1
registers: [ 5123 ]
icon: 'mdi:battery'
- name: "Battery Current"
class: "current"
state_class: "measurement"
uom: "A"
scale: 0.01
rule: 2
registers: [ 5125 ]
icon: 'mdi:current-dc'
- name: "Battery Temperature"
class: "temperature"
state_class: "measurement"
uom: "°C"
scale: 0.1
rule: 2
registers: [ 5126 ]
icon: 'mdi:thermometer'
- name: "Battery Charge"
class: "battery"
state_class: "measurement"
uom: "%"
scale: 1
rule: 1
registers: [ 5127 ]
icon: 'mdi:battery'
- name: "Battery Cycle Times"
class: ""
state_class: ""
uom: ""
scale: 1
rule: 1
registers: [ 5128 ]
icon: 'mdi:battery-heart'
- name: "Max Charging Voltage"
class: "voltage"
state_class: "measurement"
uom: "V"
scale: 0.1
rule: 1
registers: [ 5129 ]
- name: "Max Charging Current"
class: "current"
state_class: "measurement"
uom: "A"
scale: 0.01
rule: 2
registers: [ 5131 ]
icon: 'mdi:current-dc'
- name: "Min Discharging Voltage"
class: "voltage"
state_class: "measurement"
uom: "V"
scale: 0.1
rule: 1
registers: [ 5132 ]
- name: "Max Discharging Current"
class: "current"
state_class: "measurement"
uom: "A"
scale: 0.01
rule: 2
registers: [ 5134 ]
icon: 'mdi:current-dc'
- name: "Max Cell Voltage"
class: "voltage"
state_class: "measurement"
uom: "V"
scale: 0.001
rule: 1
registers: [ 5136 ]
icon: 'mdi:battery'
- name: "Min Cell Voltage"
class: "voltage"
state_class: "measurement"
uom: "V"
scale: 0.001
rule: 1
registers: [ 5137 ]
icon: 'mdi:battery'
- name: "Max Cell Voltage ID"
class: ""
state_class: ""
uom: ""
scale: 1
rule: 1
registers: [ 5138 ]
- name: "Min Cell Voltage ID"
class: ""
state_class: ""
uom: ""
scale: 1
rule: 1
registers: [ 5139 ]
- name: "Max Cell Temperature"
class: "temperature"
state_class: "measurement"
uom: "°C"
scale: 0.1
rule: 2
registers: [ 5140 ]
icon: 'mdi:thermometer'
- name: "Min Cell Temperature"
class: "temperature"
state_class: "measurement"
uom: "°C"
scale: 0.1
rule: 2
registers: [ 5141 ]
icon: 'mdi:thermometer'
- name: "Max Cell Temperature ID"
class: ""
state_class: ""
uom: ""
scale: 1
rule: 1
registers: [ 5142 ]
- name: "Min Cell Temperature ID"
class: ""
state_class: ""
uom: ""
scale: 1
rule: 1
registers: [ 5143 ]
- name: "Max Module Voltage"
class: "voltage"
state_class: "measurement"
uom: "V"
scale: 0.01
rule: 1
registers: [ 5144 ]
icon: 'mdi:battery'
- name: "Min Module Voltage"
class: "voltage"
state_class: "measurement"
uom: "V"
scale: 0.01
rule: 1
registers: [ 5145 ]
icon: 'mdi:battery'
- name: "Max Module Voltage ID"
class: ""
state_class: ""
uom: ""
scale: 1
rule: 1
registers: [ 5146 ]
- name: "Min Module Voltage ID"
class: ""
state_class: ""
uom: ""
scale: 1
rule: 1
registers: [ 5147 ]
- name: "Max Module Temperature"
class: "temperature"
state_class: "measurement"
uom: "°C"
scale: 0.1
rule: 2
registers: [ 5148 ]
icon: 'mdi:thermometer'
- name: "Min Module Temperature"
class: "temperature"
state_class: "measurement"
uom: "°C"
scale: 0.1
rule: 2
registers: [ 5149 ]
icon: 'mdi:thermometer'
- name: "Max Module Temperature ID"
class: ""
state_class: ""
uom: ""
scale: 1
rule: 1
registers: [ 5150 ]
- name: "Min Module Temperature ID"
class: ""
state_class: ""
uom: ""
scale: 1
rule: 1
registers: [ 5151 ]
- name: "Battery SOH"
class: "battery"
state_class: "measurement"
uom: "%"
scale: 1
rule: 1
registers: [ 5152 ]
icon: 'mdi:battery'
- name: "Today Charge"
class: "energy"
state_class: "total_increasing"
uom: "kWh"
scale: 0.001
rule: 1
registers: [ 5160 ]
- name: "Today Discharge"
class: "energy"
state_class: "total_increasing"
uom: "kWh"
scale: 0.001
rule: 1
registers: [ 5162 ]
- group: Battery Module 0
items:
- name: "Battery Module 0 Voltage"
class: "voltage"
state_class: "measurement"
uom: "V"
scale: 0.01
rule: 1
registers: [ 5216 ]
icon: 'mdi:battery'
- name: "Battery Module 0 Temperature"
class: "temperature"
state_class: "measurement"
uom: "°C"
scale: 0.1
rule: 2
registers: [ 5296 ]
icon: 'mdi:thermometer'
- group: Battery Module 1
items:
- name: "Battery Module 1 Voltage"
class: "voltage"
state_class: "measurement"
uom: "V"
scale: 0.01
rule: 1
registers: [ 5217 ]
icon: 'mdi:battery'
- name: "Battery Module 1 Temperature"
class: "temperature"
state_class: "measurement"
uom: "°C"
scale: 0.1
rule: 2
registers: [ 5297 ]
icon: 'mdi:thermometer'
- group: Battery Module 2
items:
- name: "Battery Module 2 Voltage"
class: "voltage"
state_class: "measurement"
uom: "V"
scale: 0.01
rule: 1
registers: [ 5218 ]
icon: 'mdi:battery'
- name: "Battery Module 2 Temperature"
class: "temperature"
state_class: "measurement"
uom: "°C"
scale: 0.1
rule: 2
registers: [ 5298 ]
icon: 'mdi:thermometer'
""" Scan Modbus registers to find valid registers"""
from pysolarmanv5 import PySolarmanV5, V5FrameError
import umodbus.exceptions
import argparse
# Docs:
# - https://pysolarmanv5.readthedocs.io/en/stable/solarmanv5_protocol.html
# - https://github.com/jmccrohan/pysolarmanv5
#
# Usage:
# pip install pysolarmanv5
# python solarmanPylontechScan.py 5120 5199 > scan_240224_10_10.csv
deviceIP="192.168.x.x" # string IP address
deviceSerialNumber=123456789 # int device serial number
def main():
parser = argparse.ArgumentParser()
parser.add_argument("address", help="Address to start scanning from", type=int)
parser.add_argument("addressStop", help="Last address to scan", type=int)
args = parser.parse_args()
if args.address < 0:
print("Address must be greater than or equal to 0")
return
if args.addressStop < 0:
print("AddressStop must be greater than or equal to 0")
return
if args.addressStop < args.address:
print("AddressStop must be greater than or equal to address")
return
modbus = PySolarmanV5(
deviceIP, deviceSerialNumber, port=8899, mb_slave_id=1, verbose=False
)
# print as csv header
print("Register, Register Hex, Length, Value Int, Value Hex, Value Bin1, Value Bin2")
for x in range(args.address, args.addressStop):
try:
val = modbus.read_holding_registers(register_addr=x, quantity=1)[0]
binLeft = (val >> 8) & 0xff
binRight = val & 0xff
# csv row
print(f"{x}, {x:#06x}, 1, {val:05}, {val:#06x}, {binLeft:08b}, {binRight:08b}")
except (V5FrameError, umodbus.exceptions.IllegalDataAddressError):
print(f"{x}, {x:#06x}, 1")
continue
modbus.disconnect()
if __name__ == "__main__":
main()
@nagisa
Copy link

nagisa commented May 11, 2024

Thank you for this, this has been especially useful!

If the solarman logger is set to a "transparency" mode (config_hide.html), the communication with the battery can be done through Modbus RTU over Telnet over TCP, forgoing the solarman v5 protocol. The telnet part here is important; otherwise the communication will not succeed.

One caveat with some of these registers is that I believe some of the data might be 32-bit big-endian, split across 2 registers. In particular this could apply to the "charge/discharge today" registers, as otherwise the 16-bit register would be able to accommodate at most 2kW worth of continuous charge/discharge. This is very noticeable with max discharge current which is signed negative integer, and the register 5132 has value of -1 (i.e. sign extended).

@pfirtelu
Copy link

As @nagisa stated this has been VERY helpful, thanks a lot!
I can confirm that almost all registers work in the same way on a Pylontech X1 (SC0500) by using modbus-rtu on the RS485 port.
Have you @nagisa @pavelmaca found out how to wake the battery from deep sleep over these signals? Maybe there are also write signals to achieve this?

@nagisa
Copy link

nagisa commented Nov 22, 2025

My batteries don't go into any sort of sleep states at all as far as I can tell.

@Giermann
Copy link

Giermann commented Jan 9, 2026

Thank you very much for this great template!
I started to read my Force H3 battery and identified/named a few more registers:

Start End Block description
3972 3972 Unknown
4096 4100 Brand name
4101 4105 Model name
4106 4106 Firmware version (major/minor)
4107 4107 Communication software version (major/minor)
4096 4111 Unknown device info
4112 4239 Empty
4320 4325 Date and time (yy,mm,dd,hh,mi,ss)
4352 4415 Same as 5120-5183
4416 4607 Empty
4608 4671 Unknown, maybe settings (seems static)
4672 4863 Empty
5120 5122 State Info: Idle, Charge, Discharge
5123 5123 Battery Voltage
5124 5125 Battery Current (signed)
5126 5126 Battery Temperature
5127 5127 Battery SOC
5128 5128 Battery Cycle Times
5129 5129 Max Charging Voltage
5130 5131 Max Charging Current (signed)
5132 5132 Min Discharging Voltage
5133 5134 Max Discharging Current (signed)
5135 5135 Unknown
5136 5136 Max Cell Voltage
5137 5137 Min Cell Voltage
5138 5138 Max Cell Voltage ID
5139 5139 Min Cell Voltage ID
5140 5140 Max Cell Temperature
5141 5141 Min Cell Temperature
5142 5142 Max Cell Temperature ID
5143 5143 Min Cell Temperature ID
5144 5144 Max Module Voltage
5145 5145 Min Module Voltage
5146 5146 Max Module Voltage ID
5137 5147 Min Module Voltage ID
5138 5148 Max Module Temperature
5139 5149 Min Module Temperature
5150 5150 Max Module Temperature ID
5151 5151 Min Module Temperature ID
5152 5152 Battery SOH
5153 5153 Unknown
5154 5154 Battery Dischargeable Energy SoE (x 200)
5155 5156 ? Today Charge (slightly less)
5157 5158 ? Today Discharge (slightly less)
5159 5160 Today Charge
5161 5162 Today Discharge
5163 5164 Total Charge
5165 5166 Total Discharge
5167 5173 Unknown
5174 5174 Number of Modules
5175 5175 Number of Cells
5176 5176 No Charge Sign
5177 5177 No Discharge Sign
5178 5178 Battery nominal Voltage
5179 5179 Cell Capacity (Ah)
5180 5183 Unknown
5184 5199 Empty
5200 5207 BMS SN
5208 5215 Unknown
5216 5295 Module 0-79(?) Voltage
5296 5375 Module 0-79(?) Temperature
5376 6143 Cell 0-767(?) Voltage
6144 6911 Cell 0-767(?) Temperature

I assume, at least "Total Charge" and "Total Discharge" is INT32 as well, but I'm not sure as they would not be aligned to en even start register. But Max Discharging Currents also starts with an odd number.
Also the maximum number of Modules and Cells is just a guess, according to the distance between Voltage and Temperature.

EDIT: Corrected Nominal Battery Voltage.
EDIT2: State Info, SoE and duplicated daily stats.

@nagisa
Copy link

nagisa commented Jan 9, 2026

Yeah, these "total" registers and even the voltage*current feel super unreliable. I've seen them fail updating during, what I assume, is cell balancing, for example, and a few days ago when I was recharging my batteries after half a winter, it was claiming my batteries charging at 20kW (after taking a derivative of today's integral vs inverter's 3.5kW) even though none of the components in the pipeline can even support that sort of load.

But otherwise yeah they are big endian u32s starting at a weird unaligned offset. And then as described in https://gist.github.com/nagisa/435bdf783e4b13b8e810106b0852081e?permalink_comment_id=5442289#gistcomment-5442289 the data is just wrong even when you ignore the high word.

So if there is an option to use inverter's or some other data source, use that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment