API Modbus
Exposes OpenEMS channels as a Modbus/TCP or Modbus/RTU slave interface with read-only or read-write access. This enables external systems to monitor and control OpenEMS Edge via the widely-used Modbus industrial protocol.
Overview
The Modbus API Controller provides a Modbus slave server that dynamically exposes OpenEMS components and channels to external Modbus clients (SCADA, PLC, HMI systems).
-
Dynamic Protocol Generation: Automatically structures selected components into Modbus register blocks
-
Self-Describing Layout: Register headers allow clients to parse protocol without predefined knowledge
-
Multiple Transports: TCP (Ethernet), RTU (serial) support
-
Access Control: Read-only or read-write modes per controller instance
-
Flexible Component Selection: Choose which components to expose via configuration
-
Timeout Management: Automatic timeout for writes from external systems
-
Excel Export: Generate component/channel mappings for documentation
Supported Protocols
-
Modbus/TCP: Over Ethernet, port configurable
-
Modbus/RTU: Over serial connections (COM1, COM2, etc.)
Components
Controller.Api.ModbusTcp.ReadWrite
Modbus/TCP slave server with read-write access to configured components.
External clients can read all channels and write to channels designated in the writeChannels configuration.
Controller.Api.ModbusTcp.ReadWrite
Implements:
-
io.openems.edge.controller.api.modbus.readwrite.tcp.ControllerApiModbusTcpReadWrite -
io.openems.edge.controller.api.Controller -
io.openems.edge.common.component.OpenemsComponent -
io.openems.edge.common.jsonapi.ComponentJsonApi -
io.openems.edge.timedata.api.TimedataProvider
| Channel ID | Description | Type |
|---|---|---|
|
Status of channel overrides (ACTIVE/INACTIVE) |
Enum (Status) |
|
Total time with active overrides |
Long (seconds) |
|
Total time without active overrides |
Long (seconds) |
|
Logs write-command execution |
String |
| Parameter | Description | Type | Default |
|---|---|---|---|
|
Unique component identifier |
String |
|
|
Human-readable component name |
String |
ModbusTcp Read-Write |
|
Enable/disable the controller |
Boolean |
|
|
Port for TCP server to listen on |
Integer |
|
|
List of component IDs to expose (e.g., |
String[] |
|
|
List of channels that can be written via Modbus (format: |
String[] |
(empty) |
|
Timeout for write operations (seconds) |
Integer |
|
|
Maximum simultaneous client connections |
Integer |
|
|
Log level (NONE, TRACE, DEBUG) |
Enum |
|
Controller.Api.ModbusTcp.ReadOnly
Modbus/TCP slave server with read-only access to configured components. External clients can only read channels; writes are rejected.
Controller.Api.ModbusTcp.ReadOnly
Same as ReadWrite, except no writeChannels parameter.
Controller.Api.ModbusRtu.ReadWrite
Modbus/RTU slave server over serial connection with read-write access. Communicates with serial-connected devices (industrial controllers, gateways).
Controller.Api.ModbusRtu.ReadWrite
Same as TCP ReadWrite, plus:
| Parameter | Description | Type | Default |
|---|---|---|---|
|
Serial port device name (e.g., |
String |
|
|
Serial connection speed |
Integer |
|
|
Number of stop bits (1 or 2) |
Integer |
|
Register Layout
The Modbus protocol is self-describing through dynamic register headers:
Protocol Structure
-
Register 0: OpenEMS identifier hash (
0x17ed6201) -
Register 1: Length of first block header
Adding length to current address gives next block start address.
-
Register 200 (example block): 16-character fixed-length Component-ID string
-
Register 216 (example block): Block length (register count)
-
Register 220 (example block): Sub-block nature identifier (
OpenemsComponent,State,Power) -
Register 221 (example block): Sub-block length
This structure allows Modbus clients to: 1. Detect OpenEMS by hash in register 0 2. Parse all available components by following block headers 3. Map registers to channels dynamically
Example Parsing
For a controller configured with component _sum:
-
Register
0:0x17ed6201(OpenEMS magic number) -
Register
1:199(first block length) -
Registers
1-199: Component_sumdata -
Register
200: Start of next component (if configured)
Channel Address Calculation
To read/write specific channels:
-
Locate component in register map by parsing headers
-
Find channel offset within component block
-
Read/write to register address = component_start + channel_offset
Example:
* Read register 302 to get _sum/EssSoc (total ESS state of charge)
* Write to register 806 to set ess0/SetActivePowerEquals (control charging/discharging)
Write Channel Configuration
The writeChannels parameter controls which channels external systems can modify.
Syntax: componentId/channelId
writeChannels = [
"ess0/SetActivePowerEquals",
"ess0/SetReactivePowerEquals",
"charger0/SetChargingPower",
"heatpump0/SetTargetTemperature"
]
Only channels explicitly listed can be written. All other channels are read-only.
Data Export
Instead of parsing the Modbus protocol manually, download the component/channel mapping from OpenEMS UI:
-
Open UI → System → System Profile
-
Export as Excel file
-
Maps all registers to components and channels
-
Example file included in bundle’s
docfolder
Timeout Behavior
When an external system writes to a channel via Modbus:
-
Value is set in OpenEMS with a write timestamp
-
If no new write received within
apiTimeoutseconds, the previous value expires -
Channel reverts to default or last cycled value
-
Prevents "stuck" values if Modbus client crashes or loses connection