WebSockets (used in UX modules)

A building dashboard that does not update live is a screenshot. WebSockets are how modern Niagara UX modules show real-time station data – temperatures moving, alarms appearing, states changing – without hammering the server with polling. Software Pile builds the real-time layer into Niagara HTML5 interfaces.

Real-Time, Done Efficiently

Polling every point every second does not scale – it loads the station and lags the browser. WebSocket subscriptions push only what changed, when it changed. On a Supervisor driving many concurrent dashboards, that is the difference between smooth and sluggish.

What We Handle

  • WebSocket-based live data binding in HTML5 UX modules, backed by BajaScript
  • Subscription management so the browser watches only what is on screen
  • Reconnect and stale-data handling when the connection drops – the dashboard should say so, not lie
  • Performance tuning for many concurrent operator sessions

Describe the live interface you need and its expected concurrent users. We build real-time UX that holds up under real load – part of our broader UX module work.

Live Values and History Are Separate Problems

A subscription is the right mechanism for current values, states and alarms, where the browser needs to know the moment something changes. In a Niagara UX module that subscription is set up through BajaScript, which is the browser’s route into the station’s object model, with the socket underneath carrying the change notifications. A trend chart covering the last month is a different request: a history query fetched once over REST or oBIX, cached, and refreshed on a schedule that matches how the data is used.

Conflating the two produces the classic slow dashboard, where every page load pulls a large history set through a channel designed for small frequent updates.

Polling and Subscriptions Carry Different Costs

A view with a modest number of slow-moving points, used by a couple of people on a lightly loaded Supervisor, works well with periodic polling. It is easier to debug, it survives network equipment that mishandles connection upgrades, and it has fewer states to reason about.

Subscriptions earn their complexity when point counts climb, when update latency matters to the operator’s task, or when many people are watching at once. Choosing them by default, before any of that applies, adds work with no visible benefit.

Subscriptions That Nobody Closed

The failure that grows with use is a subscription nobody released. An operator navigates between views for an hour, the session accumulates every subscription it ever opened, and the station keeps servicing all of them.

The discipline is to unsubscribe when a view is torn down, batch subscription changes during navigation instead of firing one request per point, and verify by watching subscription counts on the station while somebody clicks around the interface for a while.

What the Screen Should Say When the Feed Drops

A dashboard that keeps displaying last known values after the connection dies tells the operator nothing is wrong. The numbers look current because nothing on the screen says otherwise.

Handling it properly means a visible state change when the socket closes, a last-updated time on live values, reconnection with backoff so a struggling station is not hammered by every open browser at once, and a resubscribe that restores the view without a manual refresh.

Getting Through the Network In Between

Between browser and station there is often a reverse proxy, a corporate firewall or a VPN concentrator, and each can interfere. Proxies may need explicit configuration to pass connection upgrades, idle timeouts can close a quiet socket, and TLS termination has to be handled somewhere sensible.

Heartbeat traffic keeps a connection alive through aggressive idle timeouts, and a polling fallback is worth having for sites whose network equipment will not cooperate. Test through the real network path before handover: a socket that works on the engineering laptop can fail on the operator workstation for reasons that have nothing to do with the code.

The Handshake and What It Inherits

The connection starts as an ordinary HTTP GET. The browser sends Upgrade: websocket, Connection: Upgrade and a generated Sec-WebSocket-Key, the station's web service answers 101 Switching Protocols, and the same TCP connection stops speaking HTTP and starts carrying frames. Everything after that moment is invisible to tooling that only understands request and response pairs, which is why a proxy log shows a single entry for a socket that stayed open all afternoon.

Because the handshake is a normal request from the page's origin, it carries the cookies the browser already holds for the station, and the socket inherits the station session rather than holding a credential of its own. A view served over HTTPS has to open wss://; a plaintext ws:// connection from an HTTPS page is blocked inside the browser as mixed content before any traffic reaches the station.

  • 101 is the only success. Any other status, including a redirect toward a login form after a session has expired, fails the connection, and the JavaScript error event carries no status code at all. The handshake response has to be read in the network panel.
  • One socket is one TCP connection. Frames are delivered in order on it and nothing is multiplexed, so a large message ahead of a small one delays the small one.
  • The browser WebSocket API exposes only the subprotocol field, so no Authorization header can be attached to the handshake. There is no separate login on the socket.

Subscribe to the Right Thing

A subscription in BajaScript attaches to components, not to a list of point names. The station pushes a change notification when a subscribed component's property changes, and the unit of cost is a subscribed property, not a widget: three gauges reading the same point are one subscription, and a view is sized by how many components it resolves and holds open.

The subscribing code resolves the specific components a view binds to. Inside a single view, a tab change or a filter change is not a teardown, so nothing in the framework releases the previous set for you; the code that swaps the visible set has to release what it replaces.

  • Status travels with the value. A subscribed point's out slot carries the status flags, stale, fault, down, overridden and null, alongside the value, so a widget can color itself without a second request.
  • Subscribing a container does not subscribe its children. Each component whose properties you want to watch is subscribed on its own.
  • Facets carry units, precision and enum ranges. They arrive with the component when it resolves, not with each change notification, so formatting stays on the client between updates.

A Live Feed Is Not a Queue

A subscription delivers the current state of what is being watched, not a record of what happened. When a socket drops and comes back, the resubscribe returns values as they are now, so a state that went to alarm and cleared during the gap never reaches the browser at all, and nothing on the client can tell that it was missed. Anything that has to be complete rather than current, an alarm console or an audit view, reads from the station's alarm space on reconnect instead of trusting the feed to have carried every transition.

The other end of the same problem is a value that changes faster than anyone can read it. Each notification costs a DOM update on the main thread, and a point moving several times a second will drive several repaints a second. Coalescing per widget, drawing the latest value on an animation frame rather than on every notification, holds render cost flat as point counts rise.

  • Frame order is guaranteed within one socket and not across two. A view that opens a second connection for a separate feed cannot assume the two are in step.
  • requestAnimationFrame does not fire in a background tab, so a buffer that drains on animation frames grows for as long as the tab sits behind another one. Cap the buffer, or drain it on visibilitychange.
  • Wall displays run for weeks without a reload. Arrays that accumulate values for sparklines have to trim, or the tab that ran fine on Monday is holding memory it never gives back by Friday.

Reading the Failure in the Network Panel

These failures look alike from the operator's side, a screen that stopped moving, but they separate cleanly in the browser network panel. Filter to the WS entry, reload the view, and read two things: the status of the handshake response, and the frame list underneath it with its timestamps and close codes.

  • No 101 in the handshake response: the session or the path is the problem, not the socket. A 403 or a redirect toward a login form points at an expired or missing station session.
  • Close code 1006 with no close frame, repeating on a regular interval: something between browser and station dropped the connection without telling either end, and the interval is the timeout that did it.
  • Close code 1000 immediately after a view change: the application closed the socket deliberately. Look at the teardown path, not at the network.
  • Frames arriving but the screen static: the transport is working and the binding is not. Confirm the change notification reaches the widget's handler.
  • Frames for some points and never for others: the silent components may not have resolved. Check what the subscribe call got back before looking at the network at all.

Frequently Asked Questions

Is WebSocket support a separate product or a license item?

No. It is a transport used inside a UX module, not a separate server product and not something licensed on its own. The socket is opened by the code running in the browser view and terminates at the station's web service, so there is nothing to install alongside the station.

Do we have to open a new firewall port for it?

No. The connection goes to the same host and port the station's web service already uses, so a rule that permits the station's web UI permits this too. On an HTTPS station that means wss over the TLS port, commonly 443, carried on the connection the browser had already opened for the page.

What has to exist before a live view can be built?

A station whose web service is reachable from where the operators actually sit, and the points and components the view will bind to, already present and named. On the browser side the view has to be an HTML5 UX view rather than a legacy Java-based one. The decision that shapes everything else is which values on the screen genuinely need to change on their own, and which are fine being fetched when the page loads.

Does the Niagara version matter, or the browser version?

The browser is rarely the constraint, since WebSocket is supported by every current desktop browser. The station side matters more, because the client-side API surface available to a UX module differs across major Niagara versions, so the version a station runs should be confirmed before a view is designed against it. TLS is the other version-sensitive piece: the station's certificate and cipher configuration has to be one the operator's browser still accepts, which is a common surprise on equipment that has been in place for years.

How does the work actually proceed?

It starts with the view rather than the socket: which components each view binds, and which of those values have to update on their own. The subscribe and unsubscribe lifecycle is then built around that list, including the paths that swap the visible set without tearing the view down. Testing follows over the real network path from an operator workstation rather than from an engineering laptop on the same subnet, and at the concurrency the site expects rather than one browser at a time.

How many operators can one station serve at once?

That depends on subscribed properties per session multiplied by sessions, because subscriptions are held per session and are not shared between them. Ten operators watching the same dashboard are ten subscription sets on the station, not one feed fanned out to ten browsers. Concurrent session count, not user headcount, is the number to size against, and a wall display that never logs out counts as a session all day.

Do station user permissions affect what a live view can show?

Yes. A subscription runs under the station session that opened it, so a point the logged-in user is not permitted to read is not delivered, and the widget bound to it never receives a value. On screen that reads as a broken widget rather than as a permission error. Views intended for more than one operator role should be tested with each role's account, not with an engineering account that can see everything.