SunSpec Protocol — 75 kWh Rack
This page describes the SunSpec Modbus interface implemented in the 75 kWh rack. The rack exposes standard SunSpec models over Modbus TCP, so any SunSpec-aware client can read measurements and issue control commands.
At a glance
| Property | Value |
|---|---|
| Protocol | SunSpec over Modbus TCP |
| Port | 502 |
| Base address | 40000 |
| Models implemented | 1, 701, 704, 713, 714, 802 |
| Device type | Battery Energy Storage System (BESS) |
How SunSpec works
Rather than each manufacturer inventing its own register layout, SunSpec defines a shared structure for energy devices (inverters, batteries, meters, …). A SunSpec device is a sequence of models, each identified by a numeric ID:
Address Content Purpose
───────── ────────────── ─────────────────────────────
40000 – 40001 "SunS" SunSpec identifier (0x5375, 0x6E53)
40002 – 40003 Model 1 ID + Length Common (device identification)
40004 – 40069 Model 1 Data Manufacturer, model, serial, …
40070 – … Model 701 ID + Data AC inverter measurements
… Model 704 Power setpoint controls
… Model 713 Storage monitoring (SoC, SoH)
… Model 714 DC-side measurements
… Model 802 Battery base
end 0xFFFF End marker
To find a register you don't read a fixed address — you discover the layout by walking model IDs from 40002.
Discovery example
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('192.0.2.100', port=502)
client.connect()
# 1. Verify it's a SunSpec device
ident = client.read_holding_registers(40000, count=2, device_id=1).registers
assert ident == [0x5375, 0x6E53], "Not a SunSpec device"
# 2. Walk the model list
address = 40002
models = {}
while True:
header = client.read_holding_registers(address, count=2, device_id=1).registers
model_id, length = header
if model_id == 0xFFFF: # End marker
break
models[model_id] = {
'data_address': address + 2,
'length': length,
}
address += 2 + length
print(models)
# {1: ..., 701: ..., 704: ..., 713: ..., 714: ..., 802: ...}
Multi-battery addressing
Each battery rack is reachable via its Modbus Unit ID:
| Unit ID | Use |
|---|---|
1 | Single battery / aggregated view |
101, 102, … | Rack 1, Rack 2, … in multi-rack setups |
Multi-rack aggregation through Unit ID 1 is not yet available. For multi-rack systems, address each rack individually.
Data types and scaling
SunSpec uses fixed-width integer types. Real-world values are reconstructed by applying a scale factor (SF) stored alongside the data:
actual = raw × 10^SF
Examples:
| Quantity | Raw | Scale factor | Actual value |
|---|---|---|---|
| AC power | 1500 | W_SF = 2 | 150 000 W |
| Frequency | 50010 | Hz_SF = -3 | 50.010 Hz |
| State of charge | 850 | Pct_SF = -1 | 85.0 % |
Common types: uint16, int16, uint32 / int32 (2 registers, MSW first), string (multiple registers, null-terminated), enum16, bitfield16, bitfield32.
SunSpec models
Model 1 — Common (device identification)
Read-only metadata about the device: manufacturer, model, serial number, Modbus device address. See the register map for exact fields.
Model 701 — AC Inverter
Read-only AC-side measurements. Key fields:
| Point | Type | Description |
|---|---|---|
InvSt | enum16 | Inverter state (see below) |
W | int16 | AC power (W, scaled by W_SF) |
Hz | uint32 | Frequency (Hz, scaled by Hz_SF) |
AL1 / AL2 / AL3 | uint16 | Phase currents L1/L2/L3 |
VL1L2 / VL2L3 / VL3L1 | uint16 | Line-to-line voltages |
Alrm | bitfield16 | Active alarms |
MnAlrm | bitfield32 | Manufacturer alarms (fault tables) |
Inverter state (InvSt) values:
| Value | State |
|---|---|
| 0 | Off |
| 1 | Sleeping |
| 2 | Starting |
| 3 | Running |
| 4 | Throttled |
| 5 | Shutting Down |
| 6 | Fault |
| 7 | Standby |
Manufacturer alarm bits (MnAlrm)
The MnAlrm bitfield aggregates inverter fault tables 1 and 2 into a single 32-bit mask:
| Bit | Fault | Bit | Fault |
|---|---|---|---|
| 0 | DC Bus Overvoltage | 16 | Module W Fault |
| 1 | DC Bus Undervoltage | 17 | Module V Fault |
| 2 | DC Bus Overcurrent | 18 | Module U Fault |
| 3 | Abnormal Battery Insulation | 19 | Leakage Current Over Limit |
| 4 | Battery Overvoltage | 20 | AC Current Imbalance |
| 5 | Battery Undervoltage | 21 | AC Voltage Imbalance |
| 6 | Battery Reverse Connection | 22 | Independent Inverter Overcurrent |
| 7 | Battery Overcurrent | 23 | Environmental Over-Temperature |
| 8 | On-Grid Inverter Overcurrent | 24 | Abnormal DC Circuit Breaker |
| 9 | Grid Overvoltage | 25 | Abnormal DC Contactor |
| 10 | Grid Undervoltage | 26 | AC Contactor Abnormal |
| 11 | Grid Overfrequency | 27 | AC Circuit Breaker Abnormal |
| 12 | Grid Underfrequency | 28 | Abnormal Arrester |
| 13 | Island Protection | 29 | Module Over-Temperature |
| 14 | Indep. Inverter Overvoltage | 30 | Reactor Over-Temperature |
| 15 | Indep. Inverter Undervoltage | 31 | Emergency Stop |
Model 704 — Basic Storage Controls
Read/write. This is how you command the system on the rack.
| Point | Access | Description |
|---|---|---|
WSetEna | RW | 0 = disabled, 1 = enabled |
WSetMod | RW | 1 = constant-power mode |
WSet | RW | Power setpoint in watts (int32, scaled by WSet_SF) |
To set a power target on the 75 kWh rack:
- Write
1toWSetEnato enable power control. - Write
1toWSetMod(constant power). - Write the desired raw value to
WSet.
Sign convention: positive = discharge/export, negative = charge/import.
Model 713 — Storage Monitoring
Read-only battery state.
| Point | Description | Notes |
|---|---|---|
WHRtg | Energy rating (Wh) | Scaled by WH_SF |
WHAvail | Available energy (Wh) | Scaled by WH_SF |
SoC | State of charge (%) | Scaled by Pct_SF |
SoH | State of health (%) | Scaled by Pct_SF |
Model 714 — DC Measurements
Read-only DC-side measurements.
| Point | Description |
|---|---|
DCA | DC current (A, scaled by DCA_SF) |
DCW | DC power (W, scaled by DCW_SF) |
Model 802 — Battery Base
Read-only dynamic limits — these tell you how hard you can push the battery right now.
| Point | Description |
|---|---|
WChaRteMax | Max charge power (W) |
WDisChaRteMax | Max discharge power (W) |
AChaMax | Max charge current (A) |
ADisChaMax | Max discharge current (A) |
Quick examples
Read state of charge
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('192.0.2.100', port=502)
client.connect()
# Model 713 SoC is at 40348 once discovery has been done
raw = client.read_holding_registers(40348, count=1, device_id=1).registers[0]
soc = raw * 0.1 # Pct_SF = -1
print(f"SoC: {soc:.1f}%")
Set a power target
from pymodbus.client import ModbusTcpClient
POWER_WATTS = 7000 # discharge at 7 kW
client = ModbusTcpClient('192.0.2.100', port=502)
client.connect()
# Model 704 controls
client.write_register(40299, value=1, device_id=1) # WSetEna = enabled
client.write_register(40300, value=1, device_id=1) # WSetMod = constant power
raw = POWER_WATTS // 100 # WSet_SF = 2 → raw = watts / 100
high = (raw >> 16) & 0xFFFF
low = raw & 0xFFFF
client.write_registers(40301, values=[high, low], device_id=1)
For a step-by-step walkthrough with pysunspec, see the implementation guide. For the full register listing, see the register map.