Skip to main content
Version: v2.0

Integration guide

How to control a 75 kWh rack from an EMS over SunSpec/Modbus TCP. The rack exposes standard SunSpec models 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, 702, 704, 713, 714, 715, 802
Device typeBattery Energy Storage System (BESS)

Key concept: WSetEna gates EMS control, WSet drives the power

Two registers in Model 704 do all the work:

  • WSetEna (40299) — the EMS control gate. 1 = rack follows WSet. 0 = rack ignores WSet and enters Standby. There is no fully "off" state from the EMS — the rack always sits in either Running or Standby.
  • WSet (4030140302) — the power setpoint. While WSetEna = 1, the inverter state follows the value of WSet: a non-zero target → Running, a 0 target → Standby.
ConcernModelRegistersAccess
EMS control gate704 (Basic Storage Controls)WSetEnaRW
Power setpoint704 (Basic Storage Controls)WSetRW
Status701 (AC Inverter)InvStR
Limits802 (Battery Base)WChaRteMax, WDisChaRteMax, AChaMax, ADisChaMaxR
Fault reset802 (Battery Base)40427RW

Control flow

The rack only ever sits in one of two states — Running or Standby — and the EMS reaches them by writing WSetEna and WSet:

New in v2.0: Standby behaviour

The rack now actively manages a low-self-consumption Standby state (InvSt = 7). Standby is entered whenever the active setpoint is zero — either because WSet = 0 or because WSetEna = 0 (in which case the rack ignores WSet entirely). Writing a non-zero WSet with WSetEna = 1 returns it to Running automatically — no separate state command is required from the EMS.

In v1 the rack stayed in Running while WSetEna = 1 regardless of the setpoint value, which kept self-consumption high during idle periods. If your EMS already runs against v1, no code change is required to benefit from this — it simply consumes less when you leave WSet at 0.

Running and Standby are the two steady states the EMS interacts with. The other InvSt values are either transient or off-path:

Under normal conditions every state below transitions to Running automatically once WSetEna = 1 and WSet ≠ 0 are in place; the EMS does not have to drive the transitions itself.

InvStStateMeaning
0OffRack is not initialized or has been powered off. Once WSetEna = 1 and WSet ≠ 0 are written, the rack moves toward Running automatically.
1SleepingIdle between initialization steps. Resumes toward Running on its own with WSetEna = 1 and WSet ≠ 0 in place.
2StartingBoot sequence and self-checks in progress; the rack settles into Running automatically once the checks pass with WSetEna = 1 and WSet ≠ 0.
4ThrottledNot used under normal conditions on this rack. Would resume Running automatically with WSetEna = 1 and WSet ≠ 0.
5Shutting DownNot used. There is no shutdown command exposed to the EMS; Standby covers low-power operation while keeping response times fast. If reached, the rack still resumes Running on its own with WSetEna = 1 and WSet ≠ 0.
6FaultOperation blocked by an active fault. Clear with AlmRst (see Resetting rack faults); the rack then returns to Running automatically with WSetEna = 1 and WSet ≠ 0.

Step 1 — Enable EMS control (WSetEna = 1)

RegisterAddressValueMeaning
WSetEna402991Rack follows WSet. Set once after commissioning; the rack remembers the setting.
WSetEna402990Rack ignores WSet and enters Standby. Use to hand control back to the rack (e.g. for maintenance).

WSetMod (40300) is fixed at 1 (constant-power mode, the only supported mode) and does not need to be written by the EMS.

Step 2 — Drive WSet

RegisterAddressTypeMeaning
WSet4030140302int32Power setpoint in watts, scaled by WSet_SF = 2.

While WSetEna = 1, the rack reacts to the current value of WSet:

WSetResulting stateInvSt
0Standby (low self-consumption)7
0 (e.g. 5 000)Running, following the setpoint3

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

Step 3 — Verify state (Model 701, optional)

Poll InvSt at 40074 if you need a confirmation; you should see 3 (Running) shortly after writing a non-zero WSet, and 7 (Standby) shortly after writing WSet = 0 (or after disabling with WSetEna = 0). All transitions happen automatically — there is no extra command to send.

Discovery & register layout

SunSpec devices expose their registers as a sequence of models, each identified by a numeric ID. The 75 kWh rack lays them out as:

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 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:

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.

Models reference

Model 1 — Common (device identification)

Read-only metadata about the device: manufacturer, model, serial number, Modbus device address. See the register reference 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

Inverter state (InvSt) values:

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

On this rack the state follows the value of WSet: write 0 and the rack enters Standby (7); write any non-zero value and it returns to Running (3). See Control flow.

Model 704 — Basic Storage Controls

Read/write. The primary control surface on this system — see Control flow for the sequence.

PointAccessDescription
WSetEnaRWEMS control gate. 1 = rack follows WSet. 0 = rack ignores WSet and enters Standby.
WSetModRWFixed at 1 (constant power) — no EMS write required.
WSetRWPower setpoint in watts (int32, scaled by WSet_SF = 2). Positive = discharge, negative = charge. While WSetEna = 1: WSet = 0 → Standby, non-zero → Running.

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.

New in v2.0: Battery DC voltage

The V point (total battery DC voltage) at register 40441 is new in v2.0.

PointDescription
VTotal battery DC voltage (V). New in v2.0.
WChaRteMaxMax charge power (W)
WDisChaRteMaxMax discharge power (W)
AChaMaxMax charge current (A)
ADisChaMaxMax discharge current (A)
AlmRstRack fault reset. Write 1 to clear active rack faults (40427). See Resetting rack faults.

Resetting rack faults

When a rack reports a fault (InvSt = 6), the EMS can clear it over Modbus once the underlying condition has been resolved.

New in v2.0: Reset over Modbus

Register 40427 is new in v2.0. Previously, clearing a rack fault required a power cycle or on-site intervention — an EMS could not recover the rack remotely. With v2.0 the EMS can issue a reset with a single Modbus write and confirm completion by polling the same register.

RegisterTypeAccessDescription
40427uint16RWWrite 1 to clear active rack faults. The register self-clears back to 0 once the reset has been applied.

Issue the write per rack — address each Unit ID individually (e.g. 101, 102, …); there is no aggregated reset on Unit ID 1. After writing, poll 40427 until it reads back 0, then confirm InvSt returns from 6 to 3 (Running) or 7 (Standby). No power cycle is required.

Reset guidelines
  • AlmRst is not intended to be written continuously or on a timer. One write per reset suffices; the rack acknowledges by self-clearing the register back to 0.
  • You can identify the fault origin by checking your AC grid history for recent disturbances, or by correlating against a known planned manual intervention. If either lines up with the fault, the EMS can issue the reset directly once the underlying condition has cleared.
  • For any other fault, contact us before resetting. That way we can assess the cause together so the underlying issue is not masked by a repeated clear.
  • If the fault has not cleared after 3 reset attempts, stop and contact us. Beyond that point a successful reset is unlikely and additional writes only delay diagnosis.

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 — WSetMod is fixed at 1 (constant power), no write needed
client.write_register(40299, value=1, device_id=1) # WSetEna = enabled

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 the full register listing, see the register reference.