<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Atlas Scada</title>
    <description>The latest articles on DEV Community by Atlas Scada (@atlasscada).</description>
    <link>https://dev.to/atlasscada</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3988248%2Ffc627b2b-651f-4097-b2ea-4113e21e0d1f.jpg</url>
      <title>DEV Community: Atlas Scada</title>
      <link>https://dev.to/atlasscada</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/atlasscada"/>
    <language>en</language>
    <item>
      <title>The dashboard said everything was fine — a field lesson in honest data</title>
      <dc:creator>Atlas Scada</dc:creator>
      <pubDate>Wed, 17 Jun 2026 21:25:57 +0000</pubDate>
      <link>https://dev.to/atlasscada/the-dashboard-said-everything-was-fine-a-field-lesson-in-honest-data-3nf1</link>
      <guid>https://dev.to/atlasscada/the-dashboard-said-everything-was-fine-a-field-lesson-in-honest-data-3nf1</guid>
      <description>&lt;p&gt;Every dashboard you build makes an implicit promise: &lt;em&gt;the number on the screen is true right now.&lt;/em&gt; Break that promise quietly and you create a very specific kind of bug — the one where everything looks fine until it very much isn't.&lt;/p&gt;

&lt;p&gt;I work on industrial dispatching systems: SCADA and building-automation software that shows operators the live state of boilers, ventilation and pumps. But the lesson I want to share isn't industrial at all. It applies to any UI that displays data from a source it doesn't fully control — which is almost every dashboard, admin panel and monitoring view ever shipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  The last-known-value trap
&lt;/h2&gt;

&lt;p&gt;Here's the failure mode. Your frontend subscribes to some value — a temperature, a queue depth, a server's health, a price. It renders the latest value it received. Then the source goes quiet: the device drops off the network, the websocket silently dies, the polling job wedges, the upstream API starts timing out.&lt;/p&gt;

&lt;p&gt;What does the screen show? The last value it got. Confidently. With no visual difference between "this is live" and "this is a fossil from twenty minutes ago."&lt;/p&gt;

&lt;p&gt;In our world that means an operator sees a calm &lt;code&gt;21 °C&lt;/code&gt; for a room that is actually freezing, because the sensor stopped reporting an hour ago and nobody told the UI. The operator trusts the screen, so they trust the stale number, so they don't act. The incident report afterwards always contains the same sentence: &lt;em&gt;"but the screen said it was fine."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You have shipped this bug. I have shipped this bug. It is one of the most common and least-discussed defects in data-facing software, because it only appears when something &lt;em&gt;else&lt;/em&gt; breaks — and by then everyone is looking at the other thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it's so easy to miss
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It tests green.&lt;/strong&gt; In dev and staging, data flows constantly. The stale state only happens when a real source fails in production, which your tests rarely simulate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The "happy path" rendering is the same code path.&lt;/strong&gt; Showing the last value is literally the default behavior of every reactive binding. You have to add work to make staleness visible — so by default, it's invisible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Absence of data is not an event.&lt;/strong&gt; Your code reacts to messages arriving. Nothing arriving is, by definition, nothing to react to. You have to actively go looking for silence.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The fix: every value carries a freshness budget
&lt;/h2&gt;

&lt;p&gt;The pattern that has never failed us is simple: &lt;strong&gt;a value is not just a number, it's a number plus the time it was observed.&lt;/strong&gt; Every value gets a freshness budget — roughly how long it's allowed to go without an update before you stop believing it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;21.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;°C&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;observedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1718658000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxAgeSec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a single derived check, evaluated on render (or on a timer):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isStale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;observedAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;maxAgeSec&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it's stale, the UI must &lt;em&gt;say so&lt;/em&gt;: grey the value out, add a "last updated 14 min ago" line, drop a warning badge — anything that breaks the visual promise of liveness. The exact treatment matters less than the principle: &lt;strong&gt;a value you can no longer vouch for must not look identical to one you can.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The budget should be tied to how often the source is &lt;em&gt;supposed&lt;/em&gt; to update. A sensor polled every 5 seconds that's been silent for 3 minutes is clearly dead. A daily batch metric silent for 3 minutes is perfectly healthy. Hardcoding one global timeout gets both wrong — derive the budget from the expected cadence, and clamp it to sane bounds so a misconfigured source can't make the gate useless.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to put the check
&lt;/h2&gt;

&lt;p&gt;Resist the urge to bury this in each component. Staleness is a property of the data, not the widget, so compute it as close to the data layer as you can — in the store, the selector, the query hook — and let every consumer inherit it. A new screen that displays the same value then gets honesty for free, instead of re-implementing (and forgetting) the check.&lt;/p&gt;

&lt;p&gt;It also composes nicely with something you probably already have: connection state. "Socket disconnected" is a coarse, global signal; per-value freshness is the fine-grained one. You want both. The socket can be perfectly healthy while one specific device behind it has gone dark.&lt;/p&gt;

&lt;h2&gt;
  
  
  The broader principle
&lt;/h2&gt;

&lt;p&gt;This is really a special case of a rule worth tattooing somewhere: &lt;strong&gt;a UI should never present a guess as a fact.&lt;/strong&gt; Stale data, optimistic updates that were never confirmed, cached values past their useful life, the spinner that spins forever after the request already failed — they're all the same sin. The interface is asserting something it can't currently back up.&lt;/p&gt;

&lt;p&gt;The fix is always the same shape: track the provenance and freshness of what you show, and when you can't vouch for it, &lt;em&gt;look&lt;/em&gt; like you can't. Honest uncertainty beats confident fiction every time, because users — operators, admins, customers — calibrate their trust on what the screen looks like, not on the messy truth underneath it.&lt;/p&gt;

&lt;p&gt;We learned this the expensive way, watching real plants where a frozen number on a screen meant a real pipe could freeze for real. But you don't need a boiler room to benefit from it. Any time you render data you didn't generate this instant, ask the boring question: &lt;em&gt;how do I know this is still true — and what does the screen do when it isn't?&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build industrial automation and dispatching software at &lt;a href="https://atlas-scada.kz" rel="noopener noreferrer"&gt;Atlas Scada&lt;/a&gt;. The freezing-room example is real; the fix above is the one we ship.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>data</category>
      <category>monitoring</category>
      <category>ui</category>
      <category>ux</category>
    </item>
    <item>
      <title>SCADA, PLC, and BMS: a practical field guide to industrial dispatching</title>
      <dc:creator>Atlas Scada</dc:creator>
      <pubDate>Wed, 17 Jun 2026 18:05:05 +0000</pubDate>
      <link>https://dev.to/atlasscada/scada-plc-and-bms-a-practical-field-guide-to-industrial-dispatching-40e4</link>
      <guid>https://dev.to/atlasscada/scada-plc-and-bms-a-practical-field-guide-to-industrial-dispatching-40e4</guid>
      <description>&lt;p&gt;If you have ever walked past a boiler room, a pumping station, or the rooftop ventilation units of a shopping mall, you have walked past a quiet layer of electronics and software that keeps all of it running. That layer is called industrial automation, and the words people use around it — PLC, SCADA, HMI, BMS, "dispatching" — are not interchangeable. Here is a practical guide to what each one actually does, written from the point of view of the people who build and commission these systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three layers
&lt;/h2&gt;

&lt;p&gt;Almost every project, whether it is a heating plant or an "intelligent building", stacks into three layers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The field layer — instrumentation (KIPiA).&lt;/strong&gt;&lt;br&gt;
Temperature, pressure, flow, level, CO2, current. Sensors turn physical reality into signals (4-20 mA, 0-10 V, RTDs, or digital buses); actuators do the reverse — valves, dampers, variable-frequency drives, contactors. Get this layer wrong (wrong range, wrong placement, no isolation) and nothing above it can save you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The control layer — PLC / controllers.&lt;/strong&gt;&lt;br&gt;
A Programmable Logic Controller reads the field, runs the logic, and drives the actuators on a deterministic cycle measured in milliseconds. Typical hardware: Siemens S7, Schneider Modicon, OWEN, or a Carel pCO for HVAC and refrigeration. The PLC is what keeps a pump from running dry or a heat exchanger from freezing — and it has to work even when everything above it is offline. That is the golden rule: safety-critical logic lives in the controller, never in the SCADA.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The supervisory layer — SCADA / HMI.&lt;/strong&gt;&lt;br&gt;
SCADA stands for Supervisory Control And Data Acquisition. It collects data from many controllers, shows operators a live mimic diagram (the HMI), logs trends, raises alarms, and lets an operator change setpoints. It sits above the PLC, it does not replace it. For buildings the same idea wears a different hat: a BMS (Building Management System) is essentially SCADA tuned for HVAC, lighting, energy and access.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the layers talk
&lt;/h2&gt;

&lt;p&gt;The glue is protocols. In practice you meet three constantly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Modbus&lt;/strong&gt; (RTU/TCP) — old, simple, everywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OPC UA&lt;/strong&gt; — the modern, secure, vendor-neutral choice for controller-to-SCADA and SCADA-to-IT.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BACnet&lt;/strong&gt; — the lingua franca of building automation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A real site is almost never single-vendor. The integrator's job is to make a Carel chiller, a Siemens boiler controller and an OWEN panel all show up as clean, named tags on one screen. A gotcha worth knowing: writing a setpoint over Modbus is not merely "write a register" — you must respect the data type and scaling. A float32 setpoint written as a single 16-bit word becomes a meaningless denormal number. Small detail, big outage.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Dispatching": monitoring that does not sleep
&lt;/h2&gt;

&lt;p&gt;Dispatching is the operational practice layered on top of SCADA: one place from which a small team watches many distant objects 24/7 and reacts before a tenant ever calls. Modern dispatching is usually &lt;strong&gt;edge + server&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an &lt;strong&gt;edge&lt;/strong&gt; node at each site keeps polling controllers and buffering data even when the internet drops;&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;central server&lt;/strong&gt; aggregates every object, pushes alarms to phones, and stores history.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That architecture is what lets one engineer keep an eye on dozens of boiler houses from a laptop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three mistakes that separate a toy from a real system
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Alarm flooding.&lt;/strong&gt; Auto-generating a min/max alarm on every tag feels thorough; in reality it buries the operator under hundreds of nuisance alarms. The ISA-18.2 alarm-management standard exists precisely to fight this — alarms must be rationalized, prioritized and actionable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale data shown as live.&lt;/strong&gt; When a device goes offline, a naive HMI keeps displaying its last value as if it were current. The operator then "sees" 21 C in a room that is actually freezing. A real system flags data as stale based on the poll interval.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trusting the controller's clock.&lt;/strong&gt; Schedules drift because the controller's battery-backed clock drifts. More than once we have chased a "heating turns on an hour early" complaint straight to a dying coin-cell battery. A good system cross-checks time against NTP and alarms on drift.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Who builds this
&lt;/h2&gt;

&lt;p&gt;This is the work of system integrators: choosing the instrumentation, programming the controllers, building the SCADA or BMS, and commissioning it on site. At &lt;a href="https://atlas-scada.kz" rel="noopener noreferrer"&gt;Atlas Scada&lt;/a&gt; we design and deliver this end to end — instrumentation and control, PLC programming, SCADA/BMS and round-the-clock dispatching — so that quiet layer stays quiet.&lt;/p&gt;

&lt;p&gt;If you are specifying your first automation project, the single best early decision is this: keep critical logic in the controller, insist on a documented tag list, and treat alarms as a design artifact, not an afterthought.&lt;/p&gt;

</description>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
