Skip to content
JelinaJelina DocumentationTechnical documentation
Back to website

Real-Time Frontend Push and Direct Control

The execution step in the architecture overview includes not only point writes, alarms, and MQTT, but direct service to frontend interfaces. Edge-EMS Builder provides two WebSocket channels that let custom frontends receive real-time data pushes and send control commands directly to devices, without developing an extra intermediate layer.

Channel Path Authentication Purpose
Frontend push channel ws://<host>:<port>/front Login Token obtained with account/password authentication Custom frontends: role-based authorization, subscriptions and direct writes
Data read/write channel ws://<host>:<port>/data/rw Static data-rw-token configured in ems.yaml Backend integrations: low-latency reads/writes independent of login, suitable for system-to-system integration

Both channels use identical message formats, differing only in authentication and permission-check granularity. This page focuses on /front; /data/rw follows the same usage pattern.

Pass connection parameters through the query string:

ws://<host>:<port>/front?socketId=<custom-connection-ID>&token=<login-Token>
ws://<host>:<port>/data/rw?socketId=<custom-connection-ID>&token=<data-rw-token>

Requests (frontend → Edge-EMS Builder) share this structure:

{
"topic": "read | write | clear",
"messageId": "Custom unique ID for matching the response",
"keep": true,
"content": [
{ "dataKey": "pcsActivePower" }
]
}

Responses (Edge-EMS Builder → frontend) correspond to requests through messageId:

{
"code": "200",
"message": "Processed successfully",
"topic": "read",
"messageId": "...",
"time": "2026-07-23T10:00:00+08:00",
"data": { "...": "..." }
}

3. Receiving real-time pushes (subscription = read + keep)

Section titled “3. Receiving real-time pushes (subscription = read + keep)”

Edge-EMS Builder’s push model is not topic-based publish/subscribe. Instead, each connection declares its own push configuration:

  1. The frontend sends topic: "read", lists required dataKey values in content (or uses groupCode for an entire group), and sets keep to true.
  2. Edge-EMS Builder stores content as that connection’s (socketId) push configuration and immediately returns current values.
  3. A background task then visits all connected frontend sockets every 1 second, pushing the latest values from each connection’s configuration. The frontend need not repeatedly poll.
  4. If continuous pushing is unnecessary, set keep to false or omit it; Edge-EMS Builder pushes for only 30s.
  5. To cancel a connection’s push configuration, send topic: "clear".
// Subscribe continuously to the latest pcsActivePower and bmsSOC values
{
"topic": "read",
"messageId": "sub-001",
"keep": true,
"content": [
{ "dataKey": "pcsActivePower" },
{ "dataKey": "bmsSOC" }
]
}
// Or subscribe by group to all its points, including child groups
{
"topic": "read",
"messageId": "sub-002",
"keep": true,
"content": [
{ "groupCode": "pcs-group-01" }
]
}

A connection can send read + keep:true multiple times to append or update its push configuration. At least one of dataKey or groupCode is required.

The frontend sends topic: "write" with dataKey and dataValue in content. No data type is required: Edge-EMS Builder identifies Collection, Aggregation, or Customization from dataKey and routes to the appropriate write logic:

{
"topic": "write",
"messageId": "ctrl-001",
"content": [
{ "dataKey": "pcsActivePower", "dataValue": "300" }
]
}

Writes to time-scheduled data (Customization) can include an effective interval:

{
"topic": "write",
"messageId": "ctrl-002",
"resetCustomValue": true,
"content": [
{
"dataKey": "import_price",
"dataValue": "0.85",
"availableDate": "2026-06-25T00:00:00+08:00",
"expiredDate": "2026-06-25T08:00:00+08:00"
}
]
}
  • resetCustomValue: true clears existing values for that time-scheduled point before writing the new content. Use it for whole-schedule replacement to avoid overlapping old and new periods.
  • To write points on other shared devices, add deviceCode to the relevant content item. Edge-EMS Builder recognizes a shared-data write and routes it to the target device.
  • /data/rw connections using the static data-rw-token are accepted directly without role checks, making them suitable for backend system-to-system integration.
  • /front connections using a login Token verify that the account has a super-administrator, administrator, or operator role. Observers cannot write. This matches page-level permissions; no write path bypasses frontend permissions.

DAILY_REPEAT uses availableTime / expiredTime, such as 08:00:00–12:00:00, with the overall effective date range configured on the time-scheduled point. Only DAILY_CHANGE uses full date-and-time intervals. Do not mix the parameters; split intervals that cross midnight.

/front checks connections with Ping/Pong heartbeats. Clients must handle close code/message values and reestablish subscriptions after reconnection. Do not automatically replay write requests along with subscriptions.

Device write results depend on CommBridge acknowledgments or timeouts; successful sending alone does not establish device execution. Device control logs show processing, success, and failure states. CommBridge performs inverse conversion of acquisition write coefficients; callers must not convert again.

Protected control writes return 200162 during maintenance freeze. Do not automatically exit freeze and retry.

const ws = new WebSocket(`ws://<host>:<port>/front?socketId=web-ui-01&token=${loginToken}`);
ws.onopen = () => {
// Subscribe to real-time PCS active power and SOC
ws.send(JSON.stringify({
topic: 'read',
messageId: 'sub-' + Date.now(),
keep: true,
content: [
{ dataKey: 'pcsActivePower' },
{ dataKey: 'bmsSOC' }
]
}));
};
ws.onmessage = (event) => {
const resp = JSON.parse(event.data);
if (resp.topic === 'read') {
// Receive latest values every second and update interface dashboards/charts
renderDashboard(resp.data);
}
if (resp.topic === 'write') {
// Write-result feedback: resp.successContent / resp.errorContent
showWriteResult(resp);
}
};
// When the user clicks "Set power to 300kW" in the interface:
function setPower(value) {
ws.send(JSON.stringify({
topic: 'write',
messageId: 'ctrl-' + Date.now(),
content: [{ dataKey: 'pcsActivePower', dataValue: String(value) }]
}));
}

This code covers the typical frontend loop: subscribe when the page loads → refresh automatically every second → send control commands directly from user actions → receive write-result feedback. No additional backend relay service is required.

  • Data written through /front or /data/rw is the same data read by trigger schemes and the expression engine. After acquisition reads back the final device outcome, it can participate in aggregation expressions and trigger evaluation. See Data model.
  • To also push data to cloud dashboards, both paths can coexist: use /front for your own interface and MQTT cloud-edge coordination to report the same data to the cloud.
  • The aems-write-data AI configuration Skill encapsulates /data/rw and /front, allowing natural-language requests for an AI Agent to help test write commands.