Initial commit
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
# Architecture — and where your data plugs in
|
||||
|
||||
The one-pager for the engineer who asks *"can it take our data?"*
|
||||
|
||||
## Shape
|
||||
|
||||
```
|
||||
┌──────────────────── telemetry sources ─────────────────────┐
|
||||
│ │
|
||||
│ SimulatedSource MqttSource OpcUaSource │
|
||||
│ (working) (stub) (stub) │
|
||||
│ │ │ │ │
|
||||
└────────┴────────────────────┴───────────────────┴──────────┘
|
||||
│
|
||||
TelemetrySource ← the seam
|
||||
emits "frames"
|
||||
│
|
||||
┌───────────────┴────────────────┐
|
||||
│ │
|
||||
AnalyticsEngine WebSocket /ws ──► React dashboard
|
||||
· threshold alarms │ · 3D scene
|
||||
· learned baselines / z-scores │ · trend charts
|
||||
· least-squares trend projection │ · alarms
|
||||
· alarm latching │ · what-if controls
|
||||
│ │
|
||||
└──────────► frame.analytics ─────┘
|
||||
│
|
||||
Copilot ──► OpenRouter / Ollama / rule engine
|
||||
(context = analytics conclusions, not raw floats)
|
||||
```
|
||||
|
||||
Everything above the seam consumes **frames** and has no idea where they came
|
||||
from. That is the whole design: swapping the simulator for your plant means
|
||||
implementing one class.
|
||||
|
||||
## The seam
|
||||
|
||||
`server/ingest/source.js` defines `TelemetrySource`. A source does three things:
|
||||
|
||||
1. **`start()`** — connect, subscribe, begin producing.
|
||||
2. **`emit(frame)`** — hand a frame upward. Called on your cadence.
|
||||
3. **`capabilities`** — declare what it supports (`timeControl`,
|
||||
`faultInjection`, `setpointControl`). The UI reads this and hides controls a
|
||||
read-only source cannot honour, rather than showing buttons that do nothing.
|
||||
|
||||
A frame is station states, signal values, WIP levels, KPI rollups and the
|
||||
analytics block. `server/ingest/simulatedSource.js` is the reference
|
||||
implementation; read it alongside the stub you are filling in.
|
||||
|
||||
## Connecting your plant
|
||||
|
||||
Both adapters are written out as documented stubs with the real interface, the tag
|
||||
map, and the traps called out. They throw an honest "not configured" error rather
|
||||
than pretending to work.
|
||||
|
||||
### MQTT — `server/ingest/mqttSource.js`
|
||||
|
||||
```
|
||||
npm i mqtt
|
||||
```
|
||||
|
||||
Fill `TAG_MAP` with your topics, implement `start()`, set `TELEMETRY_SOURCE=mqtt`
|
||||
and `MQTT_URL`. If your broker speaks **Sparkplug B** (most industrial ones do),
|
||||
add `sparkplug-payload` and map metric aliases from NBIRTH/NDATA instead of topic
|
||||
strings.
|
||||
|
||||
### OPC-UA — `server/ingest/opcuaSource.js`
|
||||
|
||||
```
|
||||
npm i node-opcua
|
||||
```
|
||||
|
||||
Browse your server's address space, fill `NODE_MAP` with real NodeIds (never guess
|
||||
them — they are namespace-qualified and installation-specific), implement
|
||||
`start()`, set `TELEMETRY_SOURCE=opcua` and `OPCUA_ENDPOINT`.
|
||||
|
||||
### Historian / CSV
|
||||
|
||||
Simplest of the three and often the best first step: read the export, sort by
|
||||
timestamp, and emit frames on a timer with a scrub control. You get real customer
|
||||
data in the twin without touching plant networks or waiting on IT.
|
||||
|
||||
## Four things that bite when the data is real
|
||||
|
||||
These are called out in the stubs too, because each one is a way a twin starts
|
||||
lying:
|
||||
|
||||
**Staleness.** Track last-seen per tag and mark a station offline when its tags go
|
||||
quiet. A stale value rendered as live is worse than a gap. The demo already models
|
||||
this — the `INS-04 sensor dropout` fault holds the last value and flags the
|
||||
station rather than drawing zeros, and the charts stop rather than continuing a
|
||||
flat line that would read as a healthy steady state.
|
||||
|
||||
**Status codes.** OPC-UA gives you `StatusCode` per value. A Bad or Uncertain
|
||||
reading must not become a data point.
|
||||
|
||||
**Timestamps.** Prefer the *source* timestamp — when the PLC sampled the sensor —
|
||||
over the server timestamp, which is when the message reached you. Under load they
|
||||
diverge, and trend slopes computed on arrival times are wrong.
|
||||
|
||||
**Units.** Vibration in in/s, temperature in °F, pressure in psi are all common.
|
||||
Convert at the boundary, never downstream, or every threshold in the system is
|
||||
quietly wrong.
|
||||
|
||||
## What the analytics layer needs from you
|
||||
|
||||
Nothing changes. `TrendTracker` and `BaselineBank` work on timestamped values
|
||||
regardless of origin. Two things worth knowing:
|
||||
|
||||
- **Baselines are learned then frozen.** A continuously adapting baseline absorbs
|
||||
a slow ramp, so the exact failure mode this is built to catch would never raise
|
||||
a z-score. Freezing means "different from how this machine normally behaves".
|
||||
- **Signals declare their own analytics eligibility.** In
|
||||
`server/sim/stations.js`, `cumulative: true` (tool wear, part counters) and
|
||||
`volatile: true` (belt speed, setpoints) exclude a signal from anomaly testing —
|
||||
normal accumulation is not an anomaly, and normal state swings are not either.
|
||||
Both remain covered by thresholds and trend projection. When you add your
|
||||
signals, set these flags next to the definition.
|
||||
|
||||
## Writing back to the plant
|
||||
|
||||
`OpcUaSource` is read-only on purpose, and its `capabilities` say so. OPC-UA can
|
||||
write to a PLC, and a twin that closes the loop is genuinely valuable — but it
|
||||
needs interlocks, an audit trail, rate limits and the customer's explicit
|
||||
sign-off. Do not enable it because the demo UI has a slider.
|
||||
|
||||
## Deployment
|
||||
|
||||
One Node process plus a static bundle:
|
||||
|
||||
```
|
||||
npm run build # web/dist
|
||||
node server/index.js # serves the API and the telemetry socket
|
||||
```
|
||||
|
||||
Runs on a plant-floor box or an industrial PC. With Ollama or the rule-based
|
||||
copilot it needs no internet at all — which for most plant networks is not a
|
||||
preference but a requirement.
|
||||
|
||||
## Honest limits of the demo
|
||||
|
||||
Worth saying out loud, because the engineer will work it out anyway:
|
||||
|
||||
- The physics is first-order lags, a PID and accumulating wear — enough that
|
||||
signals move the way an engineer expects and stay coupled. It is not FEA or CFD,
|
||||
and it is not calibrated against a real machine.
|
||||
- The five stations are a plausible generic line, not the customer's process.
|
||||
- Prediction is trend extrapolation. There is no trained model and no failure
|
||||
history behind it. With real historical failure data you could do considerably
|
||||
better — and that is the natural next conversation, not a gap to hide.
|
||||
Reference in New Issue
Block a user