Skip to main content
Version: v1

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

PropertyValue
ProtocolSunSpec over Modbus TCP
Port502
Base address40000
Models implemented1, 701, 704, 713, 714, 802
Device typeBattery 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 IDUse
1Single battery / aggregated view
101, 102, …Rack 1, Rack 2, … in multi-rack setups
note

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:

QuantityRawScale factorActual value
AC power1500W_SF = 2150 000 W
Frequency50010Hz_SF = -350.010 Hz
State of charge850Pct_SF = -185.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:

PointTypeDescription
InvStenum16Inverter state (see below)
Wint16AC power (W, scaled by W_SF)
Hzuint32Frequency (Hz, scaled by Hz_SF)
AL1 / AL2 / AL3uint16Phase currents L1/L2/L3
VL1L2 / VL2L3 / VL3L1uint16Line-to-line voltages
Alrmbitfield16Active alarms
MnAlrmbitfield32Manufacturer alarms (fault tables)

Inverter state (InvSt) values:

ValueState
0Off
1Sleeping
2Starting
3Running
4Throttled
5Shutting Down
6Fault
7Standby

Manufacturer alarm bits (MnAlrm)

The MnAlrm bitfield aggregates inverter fault tables 1 and 2 into a single 32-bit mask:

BitFaultBitFault
0DC Bus Overvoltage16Module W Fault
1DC Bus Undervoltage17Module V Fault
2DC Bus Overcurrent18Module U Fault
3Abnormal Battery Insulation19Leakage Current Over Limit
4Battery Overvoltage20AC Current Imbalance
5Battery Undervoltage21AC Voltage Imbalance
6Battery Reverse Connection22Independent Inverter Overcurrent
7Battery Overcurrent23Environmental Over-Temperature
8On-Grid Inverter Overcurrent24Abnormal DC Circuit Breaker
9Grid Overvoltage25Abnormal DC Contactor
10Grid Undervoltage26AC Contactor Abnormal
11Grid Overfrequency27AC Circuit Breaker Abnormal
12Grid Underfrequency28Abnormal Arrester
13Island Protection29Module Over-Temperature
14Indep. Inverter Overvoltage30Reactor Over-Temperature
15Indep. Inverter Undervoltage31Emergency Stop

Model 704 — Basic Storage Controls

Read/write. This is how you command the system on the rack.

PointAccessDescription
WSetEnaRW0 = disabled, 1 = enabled
WSetModRW1 = constant-power mode
WSetRWPower setpoint in watts (int32, scaled by WSet_SF)

To set a power target on the 75 kWh rack:

  1. Write 1 to WSetEna to enable power control.
  2. Write 1 to WSetMod (constant power).
  3. Write the desired raw value to WSet.

Sign convention: positive = discharge/export, negative = charge/import.

Model 713 — Storage Monitoring

Read-only battery state.

PointDescriptionNotes
WHRtgEnergy rating (Wh)Scaled by WH_SF
WHAvailAvailable energy (Wh)Scaled by WH_SF
SoCState of charge (%)Scaled by Pct_SF
SoHState of health (%)Scaled by Pct_SF

Model 714 — DC Measurements

Read-only DC-side measurements.

PointDescription
DCADC current (A, scaled by DCA_SF)
DCWDC 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.

PointDescription
WChaRteMaxMax charge power (W)
WDisChaRteMaxMax discharge power (W)
AChaMaxMax charge current (A)
ADisChaMaxMax 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.