# Modbus TCP Interface

The batterX liveX provides a **Modbus TCP/IP** interface for real-time monitoring of all energy data, and for remote control of Inverter Commands and GPIO Outputs. This allows integration with PLCs, SCADA systems, energy management platforms, and any device or software that supports the Modbus TCP protocol.

The monitoring data is refreshed approximately every **5 seconds**.

## Connection

The Modbus TCP/IP interface is configured as follows:

| Parameter      | Value                                                                                                                          |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| IP Address     | IP address of the EMS (e.g. 192.168.1.20)                                                                                      |
| Port           | 502                                                                                                                            |
| Unit ID        | 1                                                                                                                              |
| Byte Order     | Big-Endian (MSB First)                                                                                                         |
| Function Codes | <p>03 (Read Holding Registers)<br>04 (Read Input Registers)<br>06 (Write Single Register)<br>10 (Write Multiple Registers)</p> |

Both function codes **0x03** and **0x04** are supported and return identical data for reads.

The maximum number of registers per request is **123** (Modbus standard). This is enough to cover any section in a single request.

#### Device Identification

To verify you are connected to a batterX device, read registers **0** and **1**. They return `0x6261 0x7458`, which is the ASCII string **"batX"**.

## Monitoring Registers

### Data Format

All monitoring values (addresses 0-1499) are **signed 32-bit integers (`int32`)**.

Each value occupies **2 consecutive Modbus registers** — read both registers and combine them into a single int32 value (big-endian, high word first).

Some values use a scale factor. Divide the raw int32 value by the scale factor to get the real value:

| Unit   | Scale Factor | Raw Example | Real Value |
| ------ | ------------ | ----------- | ---------- |
| Watt   | 1            | 3500        | 3500 W     |
| Volt   | 100          | 23050       | 230.50 V   |
| Ampere | 100          | 1250        | 12.50 A    |
| Hertz  | 100          | 5000        | 50.00 Hz   |
| %      | 1            | 85          | 85 %       |

The unit for each register is listed in the register tables below.

Registers that are not available return the sentinel value `0x80000000` *(-2147483648)*. Your client should treat this as "N/A".

### Register Map

<table><thead><tr><th width="150">Address Range</th><th>Description</th></tr></thead><tbody><tr><td>100-199</td><td>System Summary</td></tr><tr><td>200-299</td><td>UPS Input</td></tr><tr><td>300-399</td><td>UPS Output (Protected Load)</td></tr><tr><td>400-499</td><td>Battery</td></tr><tr><td><em><mark style="color:$info;">500-799</mark></em></td><td><em><mark style="color:$info;">Reserved</mark></em></td></tr><tr><td>800-999</td><td>Solar</td></tr><tr><td>1000-1049</td><td>Grid (Energy Meter)</td></tr><tr><td>1050-1099</td><td>House (Unprotected Load)</td></tr><tr><td>1100-1149</td><td>External Solar (Energy Meter)</td></tr><tr><td><em><mark style="color:$info;">1150</mark></em><mark style="color:$info;">-</mark><em><mark style="color:$info;">1499</mark></em></td><td><em><mark style="color:$info;">Reserved</mark></em></td></tr></tbody></table>

### System Summary

Addresses **100-115**. Read 16 registers starting at address 100 to get all key values in one request.

<table><thead><tr><th width="150">Address</th><th width="150">Unit</th><th>Description</th></tr></thead><tbody><tr><td>100-101</td><td>W</td><td>Grid Power Total<br><em><mark style="color:$info;">Positive = Consuming, Negative = Injecting</mark></em></td></tr><tr><td>102-103</td><td>W</td><td>Input Power Total<br><em><mark style="color:$info;">Positive = Consuming, Negative = Injecting</mark></em></td></tr><tr><td>104-105</td><td>W</td><td>Output Power Total</td></tr><tr><td>106-107</td><td>W</td><td>Battery Power Total<br><em><mark style="color:$info;">Positive = Charging, Negative = Discharging</mark></em></td></tr><tr><td>108-109</td><td>%</td><td>Battery SoC</td></tr><tr><td>110-111</td><td>W</td><td>Solar Power Total</td></tr><tr><td>112-113</td><td>W</td><td>House Power Total</td></tr><tr><td>114-115</td><td>W</td><td>External Solar Power Total</td></tr></tbody></table>

{% hint style="info" %}
For a quick system overview, this is the only block you need to read.
{% endhint %}

### UPS Input

<table><thead><tr><th width="150">Address</th><th width="150">Unit</th><th>Description</th></tr></thead><tbody><tr><td>200-201</td><td>W</td><td>Input Power Total</td></tr><tr><td>202-203</td><td>W</td><td>Input Power L1</td></tr><tr><td>204-205</td><td>W</td><td>Input Power L2</td></tr><tr><td>206-207</td><td>W</td><td>Input Power L3</td></tr><tr><td>208-209</td><td>0.01 V</td><td>Input Voltage L1</td></tr><tr><td>210-211</td><td>0.01 V</td><td>Input Voltage L2</td></tr><tr><td>212-213</td><td>0.01 V</td><td>Input Voltage L3</td></tr><tr><td>214-215</td><td>0.01 A</td><td>Input Current L1</td></tr><tr><td>216-217</td><td>0.01 A</td><td>Input Current L2</td></tr><tr><td>218-219</td><td>0.01 A</td><td>Input Current L3</td></tr><tr><td>220-221</td><td>0.01 Hz</td><td>Input Frequency</td></tr></tbody></table>

{% hint style="info" %}
Power Direction

`Positive = Consuming`\
`Negative = Injecting`
{% endhint %}

### UPS Output (Protected Load)

<table><thead><tr><th width="150">Address</th><th width="150">Unit</th><th>Description</th></tr></thead><tbody><tr><td>300-301</td><td>W</td><td>Output Power Total</td></tr><tr><td>302-303</td><td>W</td><td>Output Power L1</td></tr><tr><td>304-305</td><td>W</td><td>Output Power L2</td></tr><tr><td>306-307</td><td>W</td><td>Output Power L3</td></tr><tr><td>308-309</td><td>0.01 V</td><td>Output Voltage L1</td></tr><tr><td>310-311</td><td>0.01 V</td><td>Output Voltage L2</td></tr><tr><td>312-313</td><td>0.01 V</td><td>Output Voltage L3</td></tr><tr><td>314-315</td><td>0.01 A</td><td>Output Current L1</td></tr><tr><td>316-317</td><td>0.01 A</td><td>Output Current L2</td></tr><tr><td>318-319</td><td>0.01 A</td><td>Output Current L3</td></tr><tr><td>320-321</td><td>0.01 Hz</td><td>Output Frequency</td></tr></tbody></table>

### Battery

<table><thead><tr><th width="150">Address</th><th width="150">Unit</th><th>Description</th></tr></thead><tbody><tr><td>400-401</td><td>W</td><td>Battery Power Total</td></tr><tr><td>402-403</td><td>0.01 V</td><td>Battery Voltage Minus-N</td></tr><tr><td>404-405</td><td>0.01 V</td><td>Battery Voltage Plus-N</td></tr><tr><td>406-407</td><td>0.01 A</td><td>Battery Current Minus</td></tr><tr><td>408-409</td><td>0.01 A</td><td>Battery Current Plus</td></tr><tr><td>410-411</td><td>%</td><td>Battery SoC Minus %</td></tr><tr><td>412-413</td><td>%</td><td>Battery SoC Plus %</td></tr></tbody></table>

{% hint style="info" %}
**batterX Home** has only positive (plus) battery side.
{% endhint %}

{% hint style="info" %}
Power Direction

`Positive = Charging`\
`Negative = Discharging`
{% endhint %}

### Solar

<table><thead><tr><th width="150">Address</th><th width="150">Unit</th><th>Description</th></tr></thead><tbody><tr><td>800-801</td><td>W</td><td>Solar Power Total</td></tr><tr><td>802-803</td><td>0.01 V</td><td>Solar 1 - Voltage</td></tr><tr><td>804-805</td><td>0.01 A</td><td>Solar 1 - Current</td></tr><tr><td>806-807</td><td>W</td><td>Solar 1 - Power</td></tr><tr><td>808-809</td><td>0.01 V</td><td>Solar 2 - Voltage</td></tr><tr><td>810-811</td><td>0.01 A</td><td>Solar 2 - Current</td></tr><tr><td>812-813</td><td>W</td><td>Solar 2 - Power</td></tr><tr><td>814-815</td><td>0.01 V</td><td>Solar 3 - Voltage</td></tr><tr><td>816-817</td><td>0.01 A</td><td>Solar 3 - Current</td></tr><tr><td>818-819</td><td>W</td><td>Solar 3 - Power</td></tr><tr><td>820-821</td><td>0.01 V</td><td>Solar 4 - Voltage</td></tr><tr><td>822-823</td><td>0.01 A</td><td>Solar 4 - Current</td></tr><tr><td>824-825</td><td>W</td><td>Solar 4 - Power</td></tr></tbody></table>

### Grid (Energy Meter)

<table><thead><tr><th width="150">Address</th><th width="150">Unit</th><th>Description</th></tr></thead><tbody><tr><td>1000-1001</td><td>W</td><td>Grid Power Total</td></tr><tr><td>1002-1003</td><td>W</td><td>Grid Power L1</td></tr><tr><td>1004-1005</td><td>W</td><td>Grid Power L2</td></tr><tr><td>1006-1007</td><td>W</td><td>Grid Power L3</td></tr><tr><td>1008-1009</td><td>0.01 V</td><td>Grid Voltage L1</td></tr><tr><td>1010-1011</td><td>0.01 V</td><td>Grid Voltage L2</td></tr><tr><td>1012-1013</td><td>0.01 V</td><td>Grid Voltage L3</td></tr><tr><td>1014-1015</td><td>0.01 A</td><td>Grid Current L1</td></tr><tr><td>1016-1017</td><td>0.01 A</td><td>Grid Current L2</td></tr><tr><td>1018-1019</td><td>0.01 A</td><td>Grid Current L3</td></tr><tr><td>1020-1021</td><td>0.01 Hz</td><td>Grid Frequency</td></tr></tbody></table>

{% hint style="info" %}
For **batterX Home** this is E.Meter with Modbus ID 1
{% endhint %}

{% hint style="info" %}
Power Direction

`Positive = Consuming`\
`Negative = Injecting`
{% endhint %}

### House (Unprotected Load)

<table><thead><tr><th width="150">Address</th><th width="150">Unit</th><th>Description</th></tr></thead><tbody><tr><td>1050-1051</td><td>W</td><td>House Power Total</td></tr><tr><td>1052-1053</td><td>W</td><td>House Power L1</td></tr><tr><td>1054-1055</td><td>W</td><td>House Power L2</td></tr><tr><td>1056-1057</td><td>W</td><td>House Power L3</td></tr></tbody></table>

Estimated values (power difference between UPS Input and Grid E.Meter)

### External Solar (Energy Meter)

<table><thead><tr><th width="150">Address</th><th width="150">Unit</th><th>Description</th></tr></thead><tbody><tr><td>1100-1101</td><td>W</td><td>External Solar Power Total</td></tr><tr><td>1102-1103</td><td>W</td><td>External Solar Power L1</td></tr><tr><td>1104-1105</td><td>W</td><td>External Solar Power L2</td></tr><tr><td>1106-1107</td><td>W</td><td>External Solar Power L3</td></tr></tbody></table>

{% hint style="info" %}
For **batterX Home** this is E.Meter with Modbus ID 2
{% endhint %}

## Control Registers

The control registers let you monitor and control the Inverter Commands and GPIO Outputs.

### Data Format

Control registers (2000-2099) use a different format: each register is a single **unsigned 16-bit integer (`uint16`)**.

Reserved control registers return `0xFFFF` *(65535)*. Your client should treat this as "N/A".

### Register Map

<table><thead><tr><th width="150">Address Range</th><th>Description</th></tr></thead><tbody><tr><td>2000-2049</td><td>Control States <em>(read-only, <strong>uint16</strong>)</em><br><em><mark style="color:$info;">Show the actual current operating state as reported by the EMS.</mark></em></td></tr><tr><td>2050-2099</td><td>Control Commands <em>(read/write, <strong>uint16</strong>)</em><br><em><mark style="color:$info;">Write a value to send a command to the EMS.</mark></em></td></tr></tbody></table>

### Control States

State registers (2000-2049) are **read-only** registers that report the actual current operating state as determined and reported by the EMS.

These registers reflect the real physical or effective state of the corresponding function or output and must be used for monitoring and status verification.

<table><thead><tr><th width="150">Address</th><th>Description</th><th>Value</th></tr></thead><tbody><tr><td>2000</td><td>Grid Injection</td><td><code>0</code> / <code>1</code> / <code>10</code> / <code>11</code><br>Off / On / Forced Off / Forced On</td></tr><tr><td>2001</td><td>Battery Charging</td><td><code>0</code> / <code>1</code> / <code>10</code> / <code>11</code><br>Off / On / Forced Off / Forced On</td></tr><tr><td>2002</td><td>Battery Charging AC</td><td><code>0</code> / <code>1</code> / <code>10</code> / <code>11</code><br>Off / On / Forced Off / Forced On</td></tr><tr><td>2003</td><td>Battery Discharging</td><td><code>0</code> / <code>1</code> / <code>10</code> / <code>11</code><br>Off / On / Forced Off / Forced On</td></tr><tr><td><em><mark style="color:$info;">2004-2009</mark></em></td><td><em><mark style="color:$info;">Reserved</mark></em></td><td></td></tr><tr><td>2010</td><td>GPIO Output 1</td><td><code>0</code> / <code>1</code> / <code>10</code> / <code>11</code><br>Off / On / Forced Off / Forced On</td></tr><tr><td>2011</td><td>GPIO Output 2</td><td><code>0</code> / <code>1</code> / <code>10</code> / <code>11</code><br>Off / On / Forced Off / Forced On</td></tr><tr><td>2012</td><td>GPIO Output 3</td><td><code>0</code> / <code>1</code> / <code>10</code> / <code>11</code><br>Off / On / Forced Off / Forced On</td></tr><tr><td>2013</td><td>GPIO Output 4</td><td><code>0</code> / <code>1</code> / <code>10</code> / <code>11</code><br>Off / On / Forced Off / Forced On</td></tr><tr><td><em><mark style="color:$info;">2014-2019</mark></em></td><td><em><mark style="color:$info;">Reserved</mark></em></td><td></td></tr><tr><td>2020</td><td>GPIO Input 1</td><td><code>0</code> / <code>1</code><br>Off / On</td></tr><tr><td>2021</td><td>GPIO Input 2</td><td><code>0</code> / <code>1</code><br>Off / On</td></tr><tr><td>2022</td><td>GPIO Input 3</td><td><code>0</code> / <code>1</code><br>Off / On</td></tr><tr><td>2023</td><td>GPIO Input 4</td><td><code>0</code> / <code>1</code><br>Off / On</td></tr><tr><td><em><mark style="color:$info;">2024-2049</mark></em></td><td><em><mark style="color:$info;">Reserved</mark></em></td><td></td></tr></tbody></table>

{% hint style="info" %}
GPIO Inputs (2020-2023) are read-only and have no corresponding command registers. They reflect the live state of the physical input terminals.
{% endhint %}

### Control Commands

Command registers (2050-2099) are **read/write** registers used to request a change of operating mode or output behavior in the EMS.

These registers function as command interfaces *(fire-and-forget)*. After a successful write, a subsequent read will return the last written value for write-verification purposes.

{% hint style="info" %}
The command registers do not represent the actual operating state. The effective system behavior and output status must be determined from the corresponding state registers (2000-2049).
{% endhint %}

<table><thead><tr><th width="150">Address</th><th>Description</th><th>Value</th></tr></thead><tbody><tr><td>2050</td><td>Grid Injection</td><td><code>0</code> / <code>1</code> / <code>2</code><br>Forced Off / Forced On / Automatic</td></tr><tr><td>2051</td><td>Battery Charging</td><td><code>0</code> / <code>1</code> / <code>2</code><br>Forced Off / Forced On / Automatic</td></tr><tr><td>2052</td><td>Battery Charging AC</td><td><code>0</code> / <code>1</code> / <code>2</code><br>Forced Off / Forced On / Automatic</td></tr><tr><td>2053</td><td>Battery Discharging</td><td><code>0</code> / <code>1</code> / <code>2</code><br>Forced Off / Forced On / Automatic</td></tr><tr><td><em><mark style="color:$info;">2054-2059</mark></em></td><td><em><mark style="color:$info;">Reserved</mark></em></td><td></td></tr><tr><td>2060</td><td>GPIO Output 1</td><td><code>0</code> / <code>1</code> / <code>2</code><br>Forced Off / Forced On / Automatic</td></tr><tr><td>2061</td><td>GPIO Output 2</td><td><code>0</code> / <code>1</code> / <code>2</code><br>Forced Off / Forced On / Automatic</td></tr><tr><td>2062</td><td>GPIO Output 3</td><td><code>0</code> / <code>1</code> / <code>2</code><br>Forced Off / Forced On / Automatic</td></tr><tr><td>2063</td><td>GPIO Output 4</td><td><code>0</code> / <code>1</code> / <code>2</code><br>Forced Off / Forced On / Automatic</td></tr><tr><td><em><mark style="color:$info;">2064-2099</mark></em></td><td><em><mark style="color:$info;">Reserved</mark></em></td><td></td></tr></tbody></table>

## Python Examples

### Read Battery SoC

```python
from pymodbus.client import ModbusTcpClient

def read_int32(registers, index=0):
    """Combine two 16-bit registers into a signed 32-bit integer."""
    value = (registers[index] << 16) | registers[index + 1]
    if value >= 0x80000000:
        value -= 0x100000000
    return value

client = ModbusTcpClient("LIVEX_IP_ADDRESS", port=502)
client.connect()

result = client.read_holding_registers(108, count=2, slave=1)
soc = read_int32(result.registers)
print("Battery SoC: " + str(soc) + " %")

client.close()
```

### Read System Summary

```python
from pymodbus.client import ModbusTcpClient

def read_int32(registers, index=0):
    """Combine two 16-bit registers into a signed 32-bit integer."""
    value = (registers[index] << 16) | registers[index + 1]
    if value >= 0x80000000:
        value -= 0x100000000
    return value

client = ModbusTcpClient("LIVEX_IP_ADDRESS", port=502)
client.connect()

result = client.read_holding_registers(100, count=16, slave=1)
regs = result.registers

print("Grid Power:            " + str(read_int32(regs, 0))  + " W")
print("Input Power:           " + str(read_int32(regs, 2))  + " W")
print("Output Power:          " + str(read_int32(regs, 4))  + " W")
print("Battery Power:         " + str(read_int32(regs, 6))  + " W")
print("Battery SoC:           " + str(read_int32(regs, 8))  + " %")
print("Solar Power:           " + str(read_int32(regs, 10)) + " W")
print("House Power:           " + str(read_int32(regs, 12)) + " W")
print("External Solar Power:  " + str(read_int32(regs, 14)) + " W")

client.close()
```

### Write Control Command

```python
from pymodbus.client import ModbusTcpClient

client = ModbusTcpClient("LIVEX_IP_ADDRESS", port=502)
client.connect()

result = client.write_register(2060, value=1, slave=1)

if result.isError():
    print("Write ERROR")
else:
    print("Write OK")

client.close()
```
