Appearance
Feeding OEE Counts and Downtime
Last updated
License feature: OEE TrackingOEE needs two things: part counts (snapshots) and downtime events. Counts can come automatically from Tag Bindings; downtime comes from the Run Status Tag, or is logged by a person or the API.
Automatic counts from Tag Bindings
Bind Good Count Tag and/or Reject Count Tag on a device's OEE configuration (OEE setup) and the engine does the rest, continuously, as part of the API service — no custom page or browser needs to stay open:
- Every change on a bound tag is turned into an increment and added to the current period's running total (the running shift's, or the local day's outside shifts and on Manual), not written as the tag's raw value. This matches a PLC counter: Good Count Tag going from 40 to 55 adds 15; a value lower than the last one is treated as the counter having reset (e.g. at shift change) and the whole new value is added (40 → 5 adds 5, not −35).
- A burst of fast tag changes is coalesced into one snapshot write roughly every 300 ms — the running total itself never drops a count, only the write to
oee_snapshotsis batched. - The running total survives an API restart: it resumes from the period's saved snapshot and the tag's last known value, rather than restarting at 0 or double-counting whatever the tag already sat at. A new shift starts at 0.
- Only an enabled configuration with a non-empty tag counts; other devices, and disabled configurations, are untouched.
- Run Status Tag drives downtime automatically:
- When the tag stops matching Run Status Value, an event opens (no reason — Uncategorized until someone sets one with the pencil). Its notes read
Started automatically: <tag> = <value>. - When the tag matches again, that event closes and its duration is recorded.
- If an event is already open — an operator logged it first — no second event opens, and the engine never closes an event it didn't open.
- An empty or unreadable value is ignored, so a device dropping off the network doesn't open downtime; its connection status shows that instead.
- Only a change acts. On an API restart the engine checks the tag's last value: a stop that happened while it was down opens an event, and an automatic event on a machine that is running again closes.
- Minimum Downtime (seconds) filters out short stops. With it set, a stop waits that long before its event opens; if the machine runs again sooner, no event is ever created and the stop shows up as lost Performance. When the stop does last, the event opens with its start set to the moment the machine stopped, so its full length counts. Nothing is opened and then deleted, so the Downtime page and Andon boards never show a short stop. A wait in progress is not kept across an API restart: after a restart the wait starts again from the restart.
- Switching a configuration Enabled off closes any automatic event still open on that device.
- When the tag stops matching Run Status Value, an event opens (no reason — Uncategorized until someone sets one with the pencil). Its notes read
This covers a tag that reports a running good/reject count. For anything it can't express — a non-monotonic value, or counts that need combining from more than one tag — post through the API or the custom-page pattern below instead.
A counter that resets at each shift change works too: each shift's count starts from zero and the reset is treated as a reset, not a negative count.
Snapshot API
http
POST /api/oee/snapshot
Authorization: Bearer <user JWT>
Content-Type: application/json
{ "deviceId": 3, "goodCount": 42865, "rejectCount": 875 }| Field | Required | Meaning |
|---|---|---|
deviceId | yes | Device with an OEE configuration. Missing → 400 "deviceId is required". |
goodCount | no (0) | Absolute good count for the period so far — not an increment. |
rejectCount | no (0) | Absolute reject count for the period so far. Logged quality events are added to it on a device without a Reject Count tag. |
totalCount | no | Defaults to good + reject. |
shiftId | no | Defaults to the running shift. A second post for the same device, shift and date updates the row instead of adding one. |
- Role: operator or higher. Each post writes an audit log entry, so post at most about once a minute.
- Counts are absolute for the period. If the PLC counter resets at shift change, post what the PLC shows; if it never resets, subtract the value it had at the start of the shift yourself.
- A device with no OEE configuration silently stores nothing.
Downtime API
http
POST /api/downtime/events { "deviceId": 3, "reasonId": 7, "notes": "auto" }
PUT /api/downtime/events/:id { "endedAt": "2026-09-15T10:42:00.000Z" }POST returns the new event (use its id). PUT with endedAt closes it and the server calculates the duration. reasonId can also be changed with PUT, or on the Downtime page. See Downtime Tracking.
Driving downtime (and counts beyond Tag Bindings) from a custom page
Server scripts cannot call /api/oee or /api/downtime (they need a user sign-in token). The pattern that works is a custom page: its bridge calls the API with the viewer's own sign-in, and scadaink.subscribe streams tag changes. It only runs while a browser has the page open — keep it open on a line PC or a kiosk that is always on. The example below also posts counts directly; skip that part and just bind Good Count Tag / Reject Count Tag instead (see "Automatic counts from Tag Bindings" above) if a simple running counter is all you need.
html
<script>
// Device 3 = Case Packer. Tags must be subscribed on the device.
const DEVICE = 3;
const REASON_UNASSIGNED = 7; // a downtime reason id, e.g. "Waiting for operator"
let good = 0, reject = 0, openEventId = null, lastPost = 0;
scadaink.subscribe(DEVICE, 'Packer.GoodCount', (v) => { good = Number(v); postCounts(); });
scadaink.subscribe(DEVICE, 'Packer.RejectCount', (v) => { reject = Number(v); postCounts(); });
function postCounts() {
const now = Date.now();
if (now - lastPost < 60000) return; // once a minute
lastPost = now;
scadaink.api.post('/oee/snapshot', { deviceId: DEVICE, goodCount: good, rejectCount: reject })
.catch((e) => console.error('snapshot failed', e.message));
}
scadaink.subscribe(DEVICE, 'Packer.Running', async (v) => {
const running = String(v) === '1' || String(v) === 'true';
try {
if (!running && openEventId === null) {
const res = await scadaink.api.post('/downtime/events',
{ deviceId: DEVICE, reasonId: REASON_UNASSIGNED, notes: 'Detected from Packer.Running' });
openEventId = res.data.id;
} else if (running && openEventId !== null) {
await scadaink.api.put('/downtime/events/' + openEventId, { endedAt: new Date().toISOString() });
openEventId = null;
}
} catch (e) { console.error('downtime update failed', e.message); }
});
</script>Limits of this pattern:
- If the page is closed, reloaded or the PC sleeps, counts stop and an open event stays open (it keeps adding downtime until someone clicks End on the Downtime page). On reload, check
GET /api/downtime/events/active?deviceId=3before starting another event. - If several browsers have the page open, each posts. Use one.
- The viewer's role applies: operator or higher.
- Short stops are logged like any other; filter them in the script (e.g. only start an event after the machine has been stopped for 2 minutes). Automatic downtime from an OEE Run Status Tag has a Minimum Downtime setting instead; see OEE setup.