API WebSocket
Provides a real-time bidirectional JSON/WebSocket interface for OpenEMS Edge. This is the primary protocol used by OpenEMS UI for live monitoring and control.
Overview
The WebSocket API Controller establishes WebSocket connections over HTTP to provide real-time channel updates and handle JSON-RPC requests. This protocol is optimized for:
-
Live Dashboard UIs: Real-time channel updates without polling
-
Responsive Control: Immediate feedback on channel changes
-
Efficient Communication: Binary WebSocket protocol with minimal overhead
-
UI Compatibility: Native WebSocket support in all modern browsers
-
Bidirectional Messaging: Server-initiated notifications and client requests
-
Real-time channel update notifications
-
JSON-RPC request/response handling
-
Edge configuration synchronization
-
User authentication and authorization
-
Thread-pooled request handling
-
Component configuration queries
-
Debug logging mode
Components
Controller.Api.Websocket
WebSocket server that maintains multiple client connections for UI and external systems. Broadcasts channel updates and handles incoming JSON-RPC commands.
Controller.Api.Websocket
Implements:
-
io.openems.edge.controller.api.websocket.ControllerApiWebsocket -
io.openems.edge.controller.api.Controller -
io.openems.edge.common.component.OpenemsComponent -
org.osgi.service.event.EventHandler
| Constant | Value |
|---|---|
|
|
|
(empty string) |
|
(empty string) |
|
|
|
|
| Event Topic | Purpose |
|---|---|
|
Notifies clients when Edge configuration changes |
|
Broadcasts channel value updates to clients |
|
Sends periodic synchronization after cycle completion |
Configuration
| Parameter | Description | Type | Default |
|---|---|---|---|
|
Unique component identifier |
String |
|
|
Human-readable component name |
String |
(component-id) |
|
Enable/disable the controller |
Boolean |
|
|
WebSocket server port |
Integer |
|
|
Timeout for channel writes from UI (seconds) |
Integer |
|
|
Enable verbose debug logging |
Boolean |
|
Configuration Example
{
"id": "ctrlApiWebsocket0",
"alias": "OpenEMS UI Gateway",
"enabled": true,
"port": 8085,
"apiTimeout": 60,
"debugMode": false
}
{
"id": "ctrlApiWebsocket0",
"alias": "WebSocket Server",
"enabled": true,
"port": 8085,
"apiTimeout": 30,
"debugMode": false
}
Connection
WebSocket URL
ws://[username:password@]<IP>:8085
wss://[username:password@]<IP>:8085 (secured)
-
username: Optional; if omitted usex -
password: User password (required for authentication) -
<IP>: Edge machine IP or hostname -
8085: Configured WebSocket port
-
ws://x:user@192.168.1.100:8085 -
ws://x:admin@openems.local:8085 -
wss://edge0:password@192.168.1.100:8085(with TLS)
Message Protocol
JSON-RPC Notifications (Server → Client)
The WebSocket server broadcasts notifications when events occur.
ChannelUpdateNotification
Sent whenever a channel value changes or at least once per cycle:
{
"method": "subscribedChannels",
"params": {
"channels": {
"_sum/GridActivePower": 2500,
"_sum/EssSoc": 85.5,
"ess0/Soc": 82.0,
"meter0/ActivePower": 1200
}
}
}
EdgeConfigNotification
Sent when component configuration changes (startup or dynamic changes):
{
"method": "edgeConfig",
"params": {
"components": [
{
"id": "_sum",
"alias": "Summary",
"properties": {
"enabled": true
},
"channels": [
{
"id": "GridActivePower",
"type": "INTEGER",
"unit": "W",
"accessMode": "RO"
}
]
},
{
"id": "ess0",
"alias": "Battery",
"type": "io.openems.edge.ess.api.ManagedSymmetricEss",
"channels": [...]
}
]
}
}
JSON-RPC Requests (Client → Server)
Clients send JSON-RPC requests to query data or trigger actions.
subscribeChannels
Subscribe to specific channel updates. Only subscribed channels are sent in subsequent notifications.
{
"id": 1,
"jsonrpc": "2.0",
"method": "subscribeChannels",
"params": {
"channels": [
"_sum/GridActivePower",
"_sum/EssSoc",
"ess0/Soc",
"meter0/ActivePower"
]
}
}
Response:
{
"id": 1,
"jsonrpc": "2.0",
"result": {}
}
getEdgeConfig
Get complete Edge configuration at any time:
{
"id": 2,
"jsonrpc": "2.0",
"method": "getEdgeConfig",
"params": {}
}
Response returns full configuration (same as edgeConfig notification).
getChannelValues
Query current values of specific channels:
{
"id": 3,
"jsonrpc": "2.0",
"method": "getChannelValues",
"params": {
"channels": [
"_sum/GridActivePower",
"ess0/Soc"
]
}
}
Response:
{
"id": 3,
"jsonrpc": "2.0",
"result": {
"_sum/GridActivePower": 2500,
"ess0/Soc": 82.0
}
}
setChannelValues
Write values to writable channels:
{
"id": 4,
"jsonrpc": "2.0",
"method": "setChannelValues",
"params": {
"ess0/SetActivePowerEquals": 5000,
"io0/Relay1": true
}
}
Response:
{
"id": 4,
"jsonrpc": "2.0",
"result": {}
}
Client Implementation Example
JavaScript (Browser)
class OpenEMSClient {
constructor(url) {
this.url = url;
this.ws = null;
this.requestId = 0;
this.pendingRequests = new Map();
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => console.log("Connected");
this.ws.onmessage = (event) => this.handleMessage(JSON.parse(event.data));
this.ws.onerror = (error) => console.error("WebSocket error:", error);
this.ws.onclose = () => console.log("Disconnected");
}
handleMessage(msg) {
if (msg.id && this.pendingRequests.has(msg.id)) {
const resolve = this.pendingRequests.get(msg.id);
this.pendingRequests.delete(msg.id);
resolve(msg.result || msg.error);
} else if (msg.method === "subscribedChannels") {
console.log("Channel update:", msg.params.channels);
} else if (msg.method === "edgeConfig") {
console.log("Configuration updated");
}
}
send(method, params = {}) {
const id = ++this.requestId;
const request = { id, jsonrpc: "2.0", method, params };
this.ws.send(JSON.stringify(request));
return new Promise(resolve => {
this.pendingRequests.set(id, resolve);
});
}
async subscribeChannels(channels) {
return this.send("subscribeChannels", { channels });
}
async getEdgeConfig() {
return this.send("getEdgeConfig", {});
}
async setChannelValue(channel, value) {
return this.send("setChannelValues", { [channel]: value });
}
}
// Usage
const client = new OpenEMSClient("ws://x:user@localhost:8085");
client.connect();
setTimeout(async () => {
await client.subscribeChannels(["_sum/GridActivePower", "_sum/EssSoc"]);
await client.setChannelValue("ess0/SetActivePowerEquals", 5000);
}, 1000);
Python
import asyncio
import json
import websockets
import base64
class OpenEMSClient:
def __init__(self, url, username, password):
self.url = url
self.auth_header = f"Basic {base64.b64encode(f'{username}:{password}'.encode()).decode()}"
self.request_id = 0
async def connect(self):
self.ws = await websockets.connect(
self.url,
extra_headers={"Authorization": self.auth_header}
)
await self.listen()
async def listen(self):
async for message in self.ws:
data = json.loads(message)
self.handle_message(data)
def handle_message(self, msg):
if msg.get("method") == "subscribedChannels":
print("Channels:", msg["params"]["channels"])
async def send(self, method, params=None):
self.request_id += 1
request = {
"id": self.request_id,
"jsonrpc": "2.0",
"method": method,
"params": params or {}
}
await self.ws.send(json.dumps(request))
async def subscribe_channels(self, channels):
await self.send("subscribeChannels", {"channels": channels})
async def set_channel_value(self, channel, value):
await self.send("setChannelValues", {channel: value})
# Usage
async def main():
client = OpenEMSClient("ws://localhost:8085", "x", "user")
await client.connect()
asyncio.run(main())
Event Model
The WebSocket API is event-driven:
-
Client connects: Server sends initial
edgeConfigwith all components -
Client subscribes: Client sends
subscribeChannelswith desired channel list -
Channel changes: Server broadcasts
subscribedChannelswith updated values -
Config changes: Server broadcasts
edgeConfigwhen components are added/removed -
Cycle completion: Server sends periodic updates even if no changes occurred
Thread Pool
The server uses a thread pool to handle multiple concurrent WebSocket connections:
-
Default pool size: 10 threads
-
Each thread can handle multiple client connections
-
Threads are named:
Controller.Api.Websocket:<component-id>-<number>
For high client concurrency, verify thread pool size is adequate.
Timeout Behavior
When a UI writes to a channel via WebSocket:
-
Write is applied immediately within current cycle
-
Write timestamp is recorded
-
If no new write within
apiTimeoutseconds, the value expires -
Channel reverts to default or last cycled value
-
Prevents "stuck" values if UI client crashes
Error Handling
| Error | Handling |
|---|---|
Authentication failed |
Connection rejected with 401 Unauthorized |
JSON parsing error |
Error response with validation message |
Channel not found |
Error response, write/read skipped |
Access denied (read-only channel write) |
Error response, write rejected |
JSON-RPC method not found |
Error response with method name |
Concurrent write conflict |
Last write wins |
Error response format:
{
"id": 1,
"jsonrpc": "2.0",
"error": {
"code": -1,
"message": "Channel not found: component/channel"
}
}