Real-Time Communication¶
Consystence uses SignalR to push real-time updates from the server to operator browsers. All UI rendering happens server-side — SignalR is the delivery mechanism for HTML and SVG patches.
Transport¶
SignalR negotiates the best available transport:
| Transport | Priority | Notes |
|---|---|---|
| WebSocket | Preferred | Full-duplex, lowest latency |
| Server-Sent Events | Fallback | Works through HTTP/1.1 proxies |
| Long Polling | Last resort | For restricted network environments |
In practice, operator stations on a plant network almost always use WebSockets.
Hubs¶
UiPageHub¶
The primary hub for operator interfaces. Manages page sessions and delivers differential UI updates.
Server → Client:
| Method | Payload | Purpose |
|---|---|---|
PageLoad | Full HTML string | Initial page render |
PatchHtml | Target selector + HTML fragment | Differential GUI DSL update |
PatchSvg | Target selector + SVG fragment | Differential scene graph update |
ShowToast | Message + severity | Transient notifications |
Client → Server:
| Method | Payload | Purpose |
|---|---|---|
Navigate | Page route | Request a new page |
ComponentEvent | Component ID + event name + data | User interaction (click, input) |
AckAlarm | Alarm ID | Acknowledge an alarm |
SendCommand | Device ID + command + parameters | Operator command (start, stop) |
DeviceUpdateHub¶
A lightweight hub for pages that need raw tag value streams without the full page session overhead — dashboards, custom integrations, and mobile views.
Server → Client:
| Method | Payload | Purpose |
|---|---|---|
TagUpdate | Device ID + tag name + value + timestamp | Single tag value change |
TagBatch | Array of tag updates | Batched updates (high-frequency tags) |
AlarmChange | Alarm ID + new state | Alarm state transition |
TypeScript client¶
The browser client is a thin TypeScript module (ui-page-connection.ts) that manages the SignalR connection and DOM patching:
// Simplified structure
class UiPageConnection {
private connection: HubConnection;
async connect(pageRoute: string): Promise<void> {
this.connection = new HubConnectionBuilder()
.withUrl("/hubs/ui-page")
.withAutomaticReconnect()
.build();
this.connection.on("PageLoad", (html: string) => {
document.getElementById("app")!.innerHTML = html;
});
this.connection.on("PatchHtml", (selector: string, html: string) => {
document.querySelector(selector)!.outerHTML = html;
});
this.connection.on("PatchSvg", (selector: string, svg: string) => {
document.querySelector(selector)!.outerHTML = svg;
});
await this.connection.start();
await this.connection.invoke("Navigate", pageRoute);
}
}
The client has no knowledge of component types, layout logic, or scene graph structure. It receives HTML/SVG strings and applies them to the DOM.
Connection lifecycle¶
stateDiagram-v2
[*] --> Connecting
Connecting --> Connected: Hub handshake
Connected --> PageLoaded: Navigate + PageLoad
PageLoaded --> PageLoaded: PatchHtml / PatchSvg
PageLoaded --> Reconnecting: Connection lost
Reconnecting --> Connected: Reconnected
Reconnecting --> Disconnected: Max retries exceeded
Disconnected --> Connecting: User retry
Connected --> Disconnected: Server shutdown Reconnection¶
SignalR's built-in automatic reconnect handles transient network interruptions:
- Connection drops — the client enters reconnecting state.
- Automatic retry — exponential backoff (0s, 2s, 10s, 30s).
- Reconnected — the client re-sends
Navigatefor the current page. - Server replays — the
PageSessionGrainre-renders the full page and sends aPageLoadto restore state.
If reconnection fails after all retries, the client shows an offline banner with a manual retry button. When connectivity is restored, the full page state is rebuilt from the server — there is no stale client-side state to reconcile.