PLC
TODO: Use the actual request/response examples
Overview¶
The BESS RCU exposes a Modbus TCP server that provides real-time AC meter and Insulation monitoring device (isometer) data as holding registers. This document describes how to connect, the register map layout, data types, addressing convention, and includes request/response examples.
Connection Parameters¶
| Parameter | Value |
|---|---|
| Protocol | Modbus TCP |
| IP Address | 127.0.0.1 (default, configurable) |
| TCP Port | 5020 |
| Unit ID | 1 |
Open a standard TCP socket to the server address and port, then exchange Modbus Application Protocol (MBAP) frames as described in the examples below.
Addressing Convention¶
This server uses 1-based register addressing.
- Valid register addresses range from 1 to 9999.
- There is no register at address 0.
- The register addresses listed in the tables below are the exact
addresses to place in the Modbus PDU
Starting Addressfield. No additional offset or subtraction is required.
If Modbus Client communicates using 0-based offsets, start address must be calculated as shown below:
$$\text{Start Address (Offset)} = (\text{Register Address} - 1)$$
For example, reading FC03 from start address 768 (0x0300) corresponds to holding register 40001 + 768 = 40769.
Register Map Organization¶
All data is served on the Holding Register function
(Function Code 0x03 — Read Holding Registers).
Coils, Discrete Inputs, and Input Registers are present but currently
unused.
The holding-register space is divided into two contiguous groups:
| Group | Address Range | Description |
|---|---|---|
| AC Meter | 0x0300 – 0x0338 (768 – 824) |
AC Power monitoring |
| Isometer | 0x0360 – 0x036E (864 – 878) |
Insulation monitoring |
Note: Gaps exist between defined registers within each group; reading an undefined address will return an error response.
AC Meter Registers¶
| Address (hex) | Address (dec) | Name | Data Type | Words | Resolution | Offset | Default Value | Description |
|---|---|---|---|---|---|---|---|---|
| 0x0300 | 768 | ACM_STATUS | UINT16 | 1 | 1.0 | 0 | 0 | AC Meter status |
| 0x0302 | 770 | ACM_F | FLOAT32 | 2 | 1.0 | 0 | 0.0 | Frequency (Hz) |
| 0x0310 | 784 | ACM_V_A-B | FLOAT32 | 2 | 1.0 | 0 | 0.0 | Line (Grid) voltage A-B (V) |
| 0x0312 | 786 | ACM_V_B-C | FLOAT32 | 2 | 1.0 | 0 | 0.0 | Line (Grid) voltage B-C (V) |
| 0x0314 | 788 | ACM_V_C-A | FLOAT32 | 2 | 1.0 | 0 | 0.0 | Line (Grid) voltage C-A (V) |
| 0x0316 | 790 | ACM_I_A | FLOAT32 | 2 | 0.001 | 0 | 0.0 | Phase current A (A) |
| 0x0318 | 792 | ACM_I_B | FLOAT32 | 2 | 0.001 | 0 | 0.0 | Phase current B (A) |
| 0x031A | 794 | ACM_I_C | FLOAT32 | 2 | 0.001 | 0 | 0.0 | Phase current C (A) |
| 0x0320 | 800 | ACM_ACTIVE_ENERGY_IMPORT | FLOAT32 | 2 | 1.0 | 0 | 0.0 | Active energy import (kWh) |
| 0x0322 | 802 | ACM_ACTIVE_ENERGY_EXPORT | FLOAT32 | 2 | 1.0 | 0 | 0.0 | Active energy export (kWh) |
| 0x0324 | 804 | ACM_ACTIVE_ENERGY_IMPORT_T | FLOAT32 | 2 | 1.0 | 0 | 0.0 | Active energy import on time (kWh) |
| 0x0326 | 806 | ACM_ACTIVE_ENERGY_EXPORT_T | FLOAT32 | 2 | 1.0 | 0 | 0.0 | Active energy export on time (kWh) |
| 0x032A | 810 | ACM_REACTIVE_ENERGY_CAPACITIVE | FLOAT32 | 2 | 1.0 | 0 | 0.0 | Reactive energy capacitive (kVArh) |
| 0x032C | 812 | ACM_REACTIVE_ENERGY_INDUCTIVE | FLOAT32 | 2 | 1.0 | 0 | 0.0 | Reactive energy inductive (kVArh) |
| 0x032E | 814 | ACM_APPARENT_ENERGY | FLOAT32 | 2 | 1.0 | 0 | 0.0 | Apparent energy (kVAh) |
| 0x0331 | 817 | ACM_PF_1 | FLOAT32 | 2 | 0.001 | 0 | 0.0 | Power factor 1 |
| 0x0333 | 819 | ACM_PF_2 | FLOAT32 | 2 | 0.001 | 0 | 0.0 | Power factor 2 |
| 0x0335 | 821 | ACM_PF_3 | FLOAT32 | 2 | 0.001 | 0 | 0.0 | Power factor 3 |
| 0x0337 | 823 | ACM_ERROR1 | UINT16 | 1 | 1.0 | 0 | 0 | AC Meter error code 1 |
| 0x0338 | 824 | ACM_ERROR2 | UINT16 | 1 | 1.0 | 0 | 0 | AC Meter error code 2 |
Isometer Registers¶
| Address (hex) | Address (dec) | Name | Data Type | Words | Resolution | Offset | Default Value | Description |
|---|---|---|---|---|---|---|---|---|
| 0x0360 | 864 | IMD_STATUS | UINT16 | 1 | 1.0 | 0 | 0 | Isometer status |
| 0x0361 | 865 | IMD_RF | FLOAT32 | 2 | 1.0 | 0 | 0.0 | Insulation resistance — PE to GND (kΩ) |
| 0x0365 | 869 | IMD_PE | FLOAT32 | 2 | 1.0 | 0 | 0.0 | PE leakage -Positive to ground (kΩ) |
| 0x0367 | 871 | IMD_NE | FLOAT32 | 2 | 1.0 | 0 | 0.0 | NE leakage - Negative to ground (kΩ) |
| 0x0369 | 873 | IMD_C | FLOAT32 | 2 | 1.0 | 0 | 0.0 | Insulation capacitance (µF) |
| 0x036B | 875 | IMD_INSULATION_LIMIT_LEVEL1 | UINT16 | 1 | 1.0 | 0 | 0 | Insulation limit level 1 — warning |
| 0x036C | 876 | IMD_INSULATION_LIMIT_LEVEL2 | UINT16 | 1 | 1.0 | 0 | 0 | Insulation limit level 2 — critical |
| 0x036E | 878 | IMD_ERROR | UINT16 | 1 | 1.0 | 0 | 0 | Isometer error code |
Data Types¶
| Type | Size | Byte Order | Description |
|---|---|---|---|
| UINT16 | 1 register (2 bytes) | Big Endian | Unsigned 16-bit integer |
| FLOAT32 | 2 registers (4 bytes) | ABCD (Big Endian) | IEEE 754 single-precision float |
FLOAT32 Word Layout (ABCD — Big Endian)¶
A FLOAT32 value spans two consecutive 16-bit holding registers:
To reconstruct the 32-bit float, concatenate the two registers in order (high word first) and interpret as IEEE 754.
Example: 200.0 V encoded as FLOAT32
Communication Examples¶
All frames below use Modbus TCP (MBAP header + PDU). Byte values are shown in hexadecimal.
Example 1 — Read a UINT16 Register (ACM_STATUS)¶
Read 1 holding register starting at address 0x0300 (768).
Request:
PDU
03 : Function Code (Read Holding Registers)
03 00 : Starting Address (0x0300 = 768)
00 01 : Quantity of Registers (1)
Response (ACM_STATUS = 0x0001):
PDU
03 : Function Code
02 : Byte Count (2 bytes = 1 register)
00 01 : Holding Register 40769 — ACM_STATUS = 1
Example 2 — Read FLOAT32 Registers (Grid Voltages)¶
Read 6 holding registers starting at address 0x0310 (784), covering three FLOAT32 values: ACM_V_A-B, ACM_V_B-C, and ACM_V_C-A.
Request:
PDU
03 : Function Code (Read Holding Registers)
03 10 : Starting Address (0x0310 = 784)
00 06 : Quantity of Registers (6)
Response (all three voltages = 200.0 V → IEEE 754 0x43480000):
PDU
03 : Function Code
0C : Byte Count (12 bytes = 6 registers)
43 48 : Holding Register 40785 — ACM_V_A-B high word
00 00 : Holding Register 40786 — ACM_V_A-B low word → 0x43480000 = 200.0 V
43 48 : Holding Register 40787 — ACM_V_B-C high word
00 00 : Holding Register 40788 — ACM_V_B-C low word → 0x43480000 = 200.0 V
43 48 : Holding Register 40789 — ACM_V_C-A high word
00 00 : Holding Register 40790 — ACM_V_C-A low word → 0x43480000 = 200.0 V
Example 3 — Read Isometer Status and Insulation Resistance¶
Read 3 holding registers starting at address 0x0360 (864), covering IMD_STATUS (UINT16, 1 word) + IMD_RF (FLOAT32, 2 words).
Request:
PDU
03 : Function Code (Read Holding Registers)
03 60 : Starting Address (0x0360 = 864)
00 03 : Quantity of Registers (3)
Response (IMD_STATUS = 0x0001, IMD_RF = 2000.0 kΩ (2 MΩ) → 0x44FA0000):
PDU
03 : Function Code
06 : Byte Count (6 bytes = 3 registers)
00 01 : Holding Register 40865 — IMD_STATUS = 1
44 FA : Holding Register 40866 — IMD_RF high word
00 00 : Holding Register 40867 — IMD_RF low word → 0x44FA0000 ≈ 2000.0 kΩ (2 MΩ)
Error Responses¶
If a request targets an undefined register address or an invalid quantity, the server responds with an exception:
| Exception Code | Meaning |
|---|---|
| 0x01 | Illegal Function |
| 0x02 | Illegal Data Address |
| 0x03 | Illegal Data Value |
Quick Reference¶
- Function Code:
0x03(Read Holding Registers) for all data - Addressing: 1-based — use register addresses directly in the PDU
- Byte Order: Big Endian (ABCD) for FLOAT32 values
- FLOAT32: Always read 2 consecutive registers
- UINT16: Read 1 register
- Gaps: Not all addresses between defined registers are valid; only read the addresses listed in the register tables above