Appearance
Cookbook: Packaging Line OEE with Shifts and Downtime
Last updated
License feature: OEE TrackingThis recipe sets up OEE for one case packer: two shifts, downtime reason codes, counts fed from the PLC by a custom page on the line PC, operators logging stops, and an OEE dashboard.
Note: This line's PLC counters are shift counters — they zero themselves at every shift boundary (06:00 and 14:00), and the line has two shifts a day. The OEE Configuration's Tag Bindings (Good/Reject Count Tag) accumulate counts for the whole UTC day, not per shift, so on a counter that resets more than once a day they would blend both shifts' parts into whichever shift is posted last. This recipe posts the counter's own per-shift value directly with a
shiftIdinstead, from a custom page — see Feeding OEE Counts and Downtime. If your line has one shift a day, or a counter that only resets once a day (or never), Tag Bindings are simpler — bind them on the Configuration tab and skip Step 6. Downtime still always needs Log Downtime (or the API); nothing detects a stop automatically.
The line
| Item | Value |
|---|---|
| Machine | Case Packer 3, CompactLogix at 10.20.3.15 |
| Rated speed | 30 cases/min → ideal cycle time 2.0 s |
| PLC tags | CP3_GoodCount_Shift (DINT), CP3_RejectCount_Shift (DINT), CP3_Running (BOOL) |
| Counter reset | The PLC zeroes both shift counters at 06:00 and 14:00 |
| Shifts | Day 06:00–14:00, Swing 14:00–22:00, Mon–Fri, 30 min of breaks each |
If your counters never reset, see "Counters that don't reset" at the end.
Step 1: Turn the features on
- As admin: System > Settings → Feature Toggles → Production.
- Switch on OEE Tracking and Downtime Tracking and Shift Scheduling.
- Refresh the browser. OEE, Downtime and Shifts appear under Monitor.
Step 2: Add the PLC and subscribe the tags
- Devices → Add Device → CompactLogix. Device Name
Case Packer 3, IP Address10.20.3.15, Port44818, Polling Rate (ms)1000→ Create Device. See Allen-Bradley PLCs. - On its page, Browse Tags, tick
CP3_GoodCount_Shift,CP3_RejectCount_ShiftandCP3_Running, click Subscribe Selected.
The custom page in Step 6 can only receive tags that are subscribed.
Step 3: Create the shifts
Monitor > Shifts → New Shift → fill in → Save. Repeat.
| Name | Start | End | Active Days | Total Break (min) | Takt Time (s) | Target Parts/Shift |
|---|---|---|---|---|---|---|
| Day Shift | 06:00 | 14:00 | Mon–Fri | 30 | 2 | 13500 |
| Swing Shift | 14:00 | 22:00 | Mon–Fri | 30 | 2 | 13500 |
Only Start, End, Active Days and Total Break (min) affect OEE. Takt, target and Break Periods are shown on the Shifts page only. Planned production time per shift is 8 h − 30 min = 450 min.
Step 4: Set up downtime reasons
Monitor > Downtime → Reasons → Add Reason. The seeded reasons stay; add the line's own:
| Name | Category | Color |
|---|---|---|
| Case Jam | Unplanned | #ef4444 |
| Glue Hopper Refill | Unplanned | #f97316 |
| Carton Magazine Empty | Unplanned | #eab308 |
| Size Change | Planned | #3b82f6 |
How categories count: Planned time is removed from planned production time and does not lower Availability; Unplanned, Changeover and events with no reason all lower it. The seeded Tool Change and Product Changeover reasons are in the Changeover category and therefore count against Availability. That's why this line's size change is a Planned reason.
Don't log Break / Lunch events on this line: the 30 minutes of Total Break (min) are already subtracted, and logging them would subtract the time twice.
Step 5: Configure OEE for the machine
- Monitor > OEE → Configuration → Add Device.
- Device Case Packer 3; Ideal Cycle Time (seconds)
2; Production Time Source From Shift Schedule. - Leave the Tag Bindings empty. They would work for a single continuous counter, but this line's per-shift counters need the shift-aware posting in Step 6 instead (see the note above).
- Click Save.
Between shifts, and on any Sunday, the engine uses Planned Production Time (minutes per day) instead (default 480). See OEE: Configuration.
Step 6: Post the counts from a custom page
Server scripts can't call the OEE API (it needs a signed-in user), so the counts come from a custom page kept open on the line PC.
- Custom Pages → New Page →
CP3 OEE Counter→ Create. - HTML tab:
html
<div style="padding:24px;font-size:20px">
<h2>Case Packer 3 — OEE counter</h2>
<p>Good: <b id="good">—</b> · Reject: <b id="reject">—</b> · Shift: <b id="shift">—</b></p>
<p id="status">Starting…</p>
</div>- JavaScript tab:
javascript
var DEVICE_NAME = 'Case Packer 3';
var deviceId = null, shiftId = null, good = null, reject = null;
function show(id, text) { document.getElementById(id).textContent = text; }
function keepCount(tagPath, apply) {
scadaink.subscribe(deviceId, tagPath, function (value, update) {
if (update && update.quality !== 'good') return; // ignore bad reads
if (value === '' || isNaN(Number(value))) return;
apply(Number(value));
});
}
function refreshShift() {
return scadaink.api.get('/shifts/current').then(function (res) {
shiftId = res.data ? res.data.id : null;
show('shift', res.data ? res.data.name : 'none');
});
}
function postCounts() {
if (good === null || reject === null) { show('status', 'Waiting for counts'); return; }
if (shiftId === null) { show('status', 'No shift running — nothing posted'); return; }
scadaink.api.post('/oee/snapshot', {
deviceId: deviceId, shiftId: shiftId, goodCount: good, rejectCount: reject,
}).then(function () {
show('status', 'Posted at ' + new Date().toLocaleTimeString());
}).catch(function (err) {
show('status', 'Post failed: ' + err.message);
});
}
scadaink.devices.find(DEVICE_NAME).then(function (id) {
if (id === null) { show('status', 'Device not found: ' + DEVICE_NAME); return; }
deviceId = id;
keepCount('CP3_GoodCount_Shift', function (n) { good = n; show('good', n); });
keepCount('CP3_RejectCount_Shift', function (n) { reject = n; show('reject', n); });
refreshShift();
setInterval(function () { refreshShift().then(postCounts); }, 60000); // once a minute
});- Save. On the line PC, sign in as an operator (posting needs operator or higher) and open
/view/page/<id>full screen. Leave it open.
What this does: once a minute it asks which shift is running and posts the PLC's shift counters with that shift's id. With a shift id, repeated posts update one history row per machine, shift and day instead of adding a row each minute.
Limits of this approach:
- It only runs while the page is open. If the PC sleeps or the browser closes, OEE stops getting counts.
- Open it on one PC only; every open copy posts.
- The last post of a shift is up to a minute before the PLC resets its counters, so the last minute's cases can be missing from that shift's row. Keep the PLC and appliance clocks in step.
- If the page reloads (for example when the viewer's session token is renewed), it starts again on its own; counts posted before are kept.
Step 7: Log stops
When the packer stops, the operator (on a tablet or the same PC):
- Monitor > Downtime → Events → Log Downtime.
- Device Case Packer 3, Reason e.g. Case Jam (unplanned), Notes
Jammed at flap folder→ Start Downtime. - When it runs again, click End in the red Active Downtime banner.
The reason can only be chosen when starting. Nothing detects stops automatically; if a stop isn't logged, Availability stays at 100 %. To start and end events automatically from CP3_Running, extend the page with the downtime pattern in Feeding OEE Counts and Downtime.
Step 8: Build the OEE dashboard
Dashboards → New Dashboard → Line 3 OEE:
| Widget | Configuration / binding |
|---|---|
| OEE Gauge | Device Case Packer 3 |
| OEE Timeline | Device Case Packer 3, Days to Show 10 — this is a number of rows, and with two shift rows a day it covers about five days |
| Downtime Pareto | Device Case Packer 3, Max Reasons 6 — all-time, closed events with a reason only |
| Value "Good this shift" | value → CP3_GoodCount_Shift |
| Value "Rejects this shift" | value → CP3_RejectCount_Shift |
| Status "Packer" | value → CP3_Running; default rules (true = Running) |
The production widgets refresh on their own timers and need a signed-in viewer; on a public or kiosk screen they stay empty. For a TV scoreboard see Andon Displays.
Step 9: Read the numbers at shift end
Day shift result:
| Input | Value |
|---|---|
| Planned time P | 450 min = 27,000 s |
| Unplanned downtime logged | Case Jam 38 min + Glue Hopper Refill 12 min = 3,000 s |
| Last snapshot | good 11,040, reject 160 → total 11,200 |
- Run time = 27,000 − 3,000 = 24,000 s.
- Availability = 24,000 / 27,000 = 0.889.
- Performance = 11,200 × 2.0 / 24,000 = 0.933.
- Quality = 11,040 / 11,200 = 0.986.
- OEE = 0.889 × 0.933 × 0.986 = 0.818 → 82 % (amber: green starts at 85 %).
Monitor > OEE → History shows one row per shift. The A, P, Q and OEE stored in a row are from the post before the latest counts; read the final figures on the Dashboard tab at shift end, or post once more.
What to expect — plainly
| Surprise | Why |
|---|---|
| OEE reads low all morning (about 50 % at 10:00 even with no stops) | Planned time is the whole shift from its first minute; the figure is only right at shift end. See OEE formulas |
| Swing shift Availability includes the day shift's stops | Downtime is totaled for the whole day, not the shift |
| Totals reset in the early evening (US) or morning (Asia-Pacific) | "Today" for downtime and snapshots is the UTC date |
| Sunday uses 480 minutes | Sunday shifts never match in the OEE engine |
| Andon shows a different reject count | Andon takes rejects from quality events, not snapshots |
Counters that don't reset
If the PLC counts forever, change keepCount so the page stores the counter value at the start of each shift and posts the difference. Keep that start value in a local tag (scadaink.write('CP3/GoodAtShiftStart', n)) so a page reload doesn't lose it.