Skip to content

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:

  1. Connection drops — the client enters reconnecting state.
  2. Automatic retry — exponential backoff (0s, 2s, 10s, 30s).
  3. Reconnected — the client re-sends Navigate for the current page.
  4. Server replays — the PageSessionGrain re-renders the full page and sends a PageLoad to 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.