SunSpec Protocol Documentation

C-Battery BESS Integration Guide -V2

Author
Published

March 31, 2026

This document describes the V2 SunSpec Modbus interface for C-Battery energy storage systems.

V2 further implements the Sunspec protocol in V1 and should be fully Sunspec compliant.

V2 properly separates operational control from power setpoint control:

Concern Model Registers Access
Operational state 802 (Battery Base) SetOp, SetInvState Read/Write
Power setpoint 704 (DER AC Controls) WSetEna, WSetMod, WSet Read/Write
Status verification 701 (AC Inverter) InvSt, ConnSt, St Read-only
Warning

If you are migrating from V1: any logic that writes WSetEna = 1 to “turn on” the battery must be updated. Use Model 802 SetOp / SetInvState for operational control. See Control Flow.

System Architecture

  • Protocol: SunSpec over Modbus TCP
  • Port: 502 (standard Modbus TCP)
  • Base Address: 40000 (0-based addressing)
  • Device Type: Battery Energy Storage System (BESS)
  • Models Implemented: 1, 701, 702, 704, 713, 714, 715, 802

Control Flow

This is the most important change from V1. Operational control and power setpoint control are separate concerns that map to different SunSpec models.

Starting the System and Setting Power

flowchart LR
    A["<b>1. Start</b><br/>Model 802<br/><code>SetInvState = 3</code>"]
    B["<b>2. Verify</b><br/>Model 701<br/><code>InvSt = 3</code><br/>(Running)"]
    C["<b>3. Set Power</b><br/>Model 704<br/><code>WSetEna = 1</code><br/><code>WSet = target</code>"]
    A --> B --> C

Step 1 -Command the inverter to start (Model 802)

Write to the operational control registers:

Register Address Value Meaning
SetInvState 40458 3 Start the inverter

Step 2 -Verify the inverter is running (Model 701)

Poll InvSt at address 40074 until it reads 3 (Running).

Step 3 -Enable the power setpoint (Model 704)

Register Address Value Meaning
WSetEna 40299 1 DER follows the active power setpoint
WSetMod 40300 1 Constant power mode (only supported mode)
WSet 40301–40302 target Power setpoint in watts (scaled by WSet_SF)
Important

WSetEna = 1 does not turn the system on. It means “the active power setpoint is active -the DER should follow WSet.” Setting WSetEna = 0 causes the DER to ignore WSet, but does not stop the inverter.

Changing the Power Target While Running

Once the system is running and WSetEna = 1, you can update the power target at any time by writing a new value to WSet (40301–40302). There is no need to re-write WSetEna -the SunSpec specification states that once enabled, the DER continues to follow the setpoint until it is explicitly disabled.

Sign Convention

Standard SunSpec DER sign convention:

  • Positive = discharge / export to grid
  • Negative = charge / import from grid

Example: WSet raw value of -120 with WSet_SF = 2 results in -120 x 102 = -12,000 W (charging at 12 kW).

SunSpec Discovery

Register Layout

The device follows the standard SunSpec register layout:

Address       Content              Purpose
─────────     ──────────────       ──────────────────────────────
40000–40001   "SunS"               SunSpec identifier (0x5375, 0x6e53)
40002–40003   Model 1 ID + Len     Common model
40004–40069   Model 1 Data         Device identification
40070         Model 701 ID         DER AC Measurement
  ...
40225         Model 702 ID         DER Nameplate
  ...
40277         Model 704 ID         DER AC Controls
  ...
40344         Model 713 ID         Storage Monitoring
  ...
40353         Model 714 ID         DC Measurement
  ...
40398         Model 715 ID         DER Control
  ...
40407         Model 802 ID         Battery Base
  ...
40469         End marker           0xFFFF

Multi-Battery Support

Each battery unit is addressed by its Modbus unit ID:

Configuration Unit ID Usage
Single battery / aggregated view 1 Not available yet
Battery N (multi-rack) 100 + N e.g. Battery 1 = 101, Battery 2 = 102
Warning

Multi-rack aggregation is not yet implemented. For multi-rack systems, address each battery individually using its unit ID.

# Single battery or aggregated view
response = client.read_holding_registers(40348, count=1, slave=1)

# Battery 1 in a multi-rack setup
response = client.read_holding_registers(40348, count=1, slave=101)

# Battery 2 in a multi-rack setup
response = client.read_holding_registers(40348, count=1, slave=102)

Data Types and Scaling

SunSpec Data Types

Type Size Description
uint16 1 register 16-bit unsigned integer
int16 1 register 16-bit signed integer
uint32 2 registers 32-bit unsigned integer (MSW first)
int32 2 registers 32-bit signed integer (MSW first)
string N registers ASCII string, null-terminated
enum16 1 register 16-bit enumeration
bitfield32 2 registers 32-bit bitfield (MSW first)
sunssf 1 register SunSpec scale factor (signed)
pad 1 register Padding, always 0

Scale Factors

Numeric values are stored as raw integers. The actual value is computed using a scale factor defined within the same model:

actual_value = raw_value x 10^scale_factor^

Example Raw Scale Factor Actual
Power 120 W_SF = 2 120 x 102 = 12,000 W
Frequency 49937 Hz_SF = -3 49937 x 10-3 = 49.937 Hz
State of Charge 1000 Pct_SF = -1 1000 x 10-1 = 100.0%
Cell Voltage 340 CellV_SF = -2 340 x 10-2 = 3.40 V

Models Reference

Model 1 -Common (Device Identification)

Purpose: Device identification and addressing. Access: Read-only. Base address: 40002.

Point Type Description Notes
Mn string Manufacturer “C-Battery”
Md string Model name “Cbat-IQ”
Vr string Firmware version e.g. “1.0.0”
SN string Serial number
DA uint16 Modbus device address 1–247

Model 701 -DER AC Measurement

Purpose: AC-side measurements and inverter status. Access: Read-only. Base address: 40070. Length: 153 registers.

Inverter Status

Point Address Type Description
ACType 40072 enum16 AC wiring type
St 40073 enum16 Operating state
InvSt 40074 enum16 Inverter state (see table below)
ConnSt 40075 enum16 Connection status
Alrm 40076–40077 bitfield32 Active alarms
DERMode 40078–40079 bitfield32 Active DER control mode

Inverter State (InvSt) values:

Value State Description
0 Off Inverter is not running
1 Sleeping Auto-shutdown / night mode
2 Starting Inverter is initializing
3 Running Producing or consuming power
4 Throttled Power limited by external condition
5 Shutting Down Transitioning to off/standby
6 Fault Fault condition, check Alrm
7 Standby Ready but not actively converting

AC Measurements

Point Address Type Scale Units
W 40080 int16 W_SF (2) Watts
VA 40081 int16 VA_SF (2) Volt-amps
Var 40082 int16 Var_SF (2) Volt-amps reactive
PF 40083 int16 PF_SF (-3) Power factor
A 40084 int16 A_SF (-1) Total AC current
Hz 40087–40088 uint32 Hz_SF (-3) Frequency (Hz)

Per-Phase Measurements

Phase Current Line-Line Voltage Line-Neutral Voltage
L1 AL1 (40115) VL1L2 (40116) VL1 (40117)
L2 AL2 (40138) VL2L3 (40139) VL2 (40140)
L3 AL3 (40161) VL3L1 (40162) VL3 (40163)

All phase currents scale by A_SF (-1), all voltages by V_SF (-1).

Manufacturer Alarms (MnAlrm)

The MnAlrm bitfield is a 32-bit aggregated fault mask. Bits 0–15 map to the inverter’s Fault Table 1, bits 16–31 to Fault Table 2.

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 Independent Inverter Overvoltage 30 Reactor Over Temperature
15 Independent Inverter Undervoltage 31 Emergency Stop

Model 702 -DER Nameplate

Purpose: Rated capacity of the DER. Access: Read-only. Base address: 40225. Length: 50 registers.

Point Address Type Scale Units Description
WMaxRtg 40227 uint16 W_SF (2) Watts Maximum rated active power

Model 704 -DER AC Controls

Purpose: Active power setpoint control. Access: Read/Write. Base address: 40277. Length: 65 registers.

Point Address Type Access Description
WSetEna 40299 enum16 RW 0 = Disabled, 1 = Enabled
WSetMod 40300 enum16 RW 1 = Constant power (only supported mode)
WSet 40301–40302 int32 RW Power setpoint (scaled by WSet_SF)
WSet_SF 40332 sunssf R Scale factor (= 2)
Important

WSetEna is not a power switch. It controls whether the DER follows the WSet value:

  • WSetEna = 1: the DER applies the active power setpoint in WSet.
  • WSetEna = 0: the DER ignores WSet. The inverter continues running in whatever state it was in.

To start or stop the system, use Model 802 (SetOp, SetInvState).

Model 713 -Storage Monitoring

Purpose: Battery state and energy information. Access: Read-only. Base address: 40344. Length: 7 registers.

Point Address Type Scale Units Description
WHRtg 40346 uint16 WH_SF (2) Wh Total energy rating
WHAvail 40347 uint16 WH_SF (2) Wh Available energy
SoC 40348 uint16 Pct_SF (-1) % State of charge
SoH 40349 uint16 Pct_SF (-1) % State of health
Sta 40350 enum16 - - Storage status

Model 714 -DC Measurement

Purpose: DC-side measurements (battery bus). Access: Read-only. Base address: 40353. Length: 43 registers.

Point Address Type Scale Units Description
NPrt 40357 uint16 - - Number of DC ports (= 1)
DCA 40358 int16 DCA_SF (-1) A DC current
DCW 40359 int16 DCW_SF (2) W DC power

Model 715 -DER Control

Purpose: DER operational commands (start, stop, standby). Base address: 40398. Length: 7 registers.

Note

Model 715 is present in the register map but OpCtl is not currently used for operational control. Start/stop commands are handled through Model 802 (SetOp, SetInvState).

Point Address Type Access Description
AlarmReset 40405 uint16 RW Write 1 to reset alarms
OpCtl 40406 enum16 RW Not used -see Model 802

Model 802 -Battery Base Model

Purpose: Battery system state, capabilities, events, and operational control. Base address: 40407. Length: 62 registers.

This is the primary model for operational control and battery-level monitoring.

Ratings and Limits (Read-only)

Point Address Type Scale Units Description
AHRtg 40409 uint16 AHRtg_SF (1) Ah Nameplate amp-hour rating
WHRtg 40410 uint16 WHRtg_SF (2) Wh Nameplate energy rating
WChaRteMax 40411 uint16 WChaDisChaMax_SF (2) W Nameplate max charge power
WDisChaRteMax 40412 uint16 WChaDisChaMax_SF (2) W Nameplate max discharge power

State and Measurements (Read-only)

Point Address Type Scale Units Description
SoC 40418 uint16 SoC_SF (0) % State of charge
Typ 40428 enum16 - - Battery chemistry (4 = Li-Ion)
State 40429 enum16 - - Battery operational state
V 40441 uint16 V_SF (-1) V Battery bus voltage
CellVMax 40444 uint16 CellV_SF (-2) V Highest cell voltage
CellVMin 40447 uint16 CellV_SF (-2) V Lowest cell voltage
A 40451 int16 A_SF (-1) A Battery current
AChaMax 40452 uint16 AMax_SF (-1) A Max charge current (dynamic)
ADisChaMax 40453 uint16 AMax_SF (-1) A Max discharge current (dynamic)
W 40454 int16 W_SF (0) W Battery power

Battery State (State) values:

Value State
1 Disconnected
2 Initializing
3 Connected
4 Absorb
5 Float
6 Discharging

Events (Read-only)

Point Address Type Description
Evt1 40433–40434 bitfield32 SunSpec-defined battery events
Evt2 40435–40436 bitfield32 SunSpec-defined battery events (extended)
EvtVnd1 40437–40438 bitfield32 Vendor-specific events
EvtVnd2 40439–40440 bitfield32 Vendor-specific events (extended)

Control Registers (Read/Write)

These are the registers for operational control of the battery system.

Point Address Type Description
LocRemCtl 40424 enum16 0 = Remote, 1 = Local
AlmRst 40427 uint16 Write 1 to reset alarms
SetOp 40457 enum16 1 = Connect, 2 = Disconnect
SetInvState 40458 enum16 1 = Stopped, 2 = Standby, 3 = Started
Tip

LocRemCtl must be 0 (Remote) for the system to accept commands over Modbus. If set to 1 (Local), write commands to SetOp, SetInvState, and Model 704 registers will be ignored.

Programming Examples

Starting the System and Setting Power

from pymodbus.client import ModbusTcpClient
import time

def start_and_set_power(power_watts, ip='172.20.0.123', unit_id=1):
    """
    Start the battery system and set a power target.
    Follows the correct SunSpec control flow:
      Model 802 (start) -> Model 701 (verify) -> Model 704 (power setpoint)
    """
    client = ModbusTcpClient(ip, port=502)
    if not client.connect():
        raise ConnectionError("Failed to connect")

    try:
        # Step 1: Start the inverter (Model 802)
        client.write_register(40457, value=1, slave=unit_id)   # SetOp = Connect
        client.write_register(40458, value=3, slave=unit_id)   # SetInvState = Started

        # Step 2: Wait for inverter to reach Running state (Model 701)
        for _ in range(30):
            resp = client.read_holding_registers(40074, count=1, slave=unit_id)
            if not resp.isError() and resp.registers[0] == 3:
                break
            time.sleep(1)
        else:
            raise TimeoutError("Inverter did not reach Running state")

        # Step 3: Enable and set the power target (Model 704)
        # WSet_SF = 2, so raw_value = power_watts / 100
        raw = power_watts // 100
        high = (raw >> 16) & 0xFFFF
        low = raw & 0xFFFF

        client.write_register(40299, value=1, slave=unit_id)             # WSetEna = Enabled
        client.write_register(40300, value=1, slave=unit_id)             # WSetMod = Constant power
        client.write_registers(40301, values=[high, low], slave=unit_id) # WSet

        print(f"System running, power setpoint: {power_watts} W")

    finally:
        client.close()

# Discharge at 12 kW
start_and_set_power(12000)

# Charge at 12 kW (negative = import)
start_and_set_power(-12000)

Stopping the System

def stop_system(ip='172.20.0.123', unit_id=1):
    """Stop the battery system cleanly."""
    client = ModbusTcpClient(ip, port=502)
    if not client.connect():
        raise ConnectionError("Failed to connect")

    try:
        # Disable power setpoint
        client.write_register(40299, value=0, slave=unit_id)   # WSetEna = Disabled

        # Stop the inverter
        client.write_register(40458, value=1, slave=unit_id)   # SetInvState = Stopped

        # Verify
        for _ in range(30):
            resp = client.read_holding_registers(40074, count=1, slave=unit_id)
            if not resp.isError() and resp.registers[0] == 0:
                print("System stopped")
                return
            time.sleep(1)

        print("Warning: inverter did not confirm Off state within 30s")

    finally:
        client.close()

Reading System State

def read_system_state(ip='172.20.0.123', unit_id=1):
    """Read key system metrics across multiple models."""
    client = ModbusTcpClient(ip, port=502)
    if not client.connect():
        raise ConnectionError("Failed to connect")

    try:
        INV_STATES = {
            0: "Off", 1: "Sleeping", 2: "Starting", 3: "Running",
            4: "Throttled", 5: "Shutting Down", 6: "Fault", 7: "Standby"
        }

        # Model 701 -Inverter state and AC power
        inv_st = client.read_holding_registers(40074, count=1, slave=unit_id).registers[0]
        w_raw = client.read_holding_registers(40080, count=1, slave=unit_id).registers[0]
        # W_SF = 2 → actual = raw * 100
        # int16: convert unsigned to signed
        ac_power = (w_raw if w_raw < 0x8000 else w_raw - 0x10000) * 100

        # Model 713 -State of charge
        soc_raw = client.read_holding_registers(40348, count=1, slave=unit_id).registers[0]
        # Pct_SF = -1 → actual = raw * 0.1
        soc = soc_raw * 0.1

        # Model 802 -Battery voltage and cell voltages
        v_raw = client.read_holding_registers(40441, count=1, slave=unit_id).registers[0]
        cell_max = client.read_holding_registers(40444, count=1, slave=unit_id).registers[0]
        cell_min = client.read_holding_registers(40447, count=1, slave=unit_id).registers[0]

        print(f"Inverter:     {INV_STATES.get(inv_st, 'Unknown')} ({inv_st})")
        print(f"AC Power:     {ac_power} W")
        print(f"SoC:          {soc:.1f}%")
        print(f"Bus Voltage:  {v_raw * 0.1:.1f} V")
        print(f"Cell V range: {cell_min * 0.01:.2f}{cell_max * 0.01:.2f} V")

    finally:
        client.close()