Skip to content

OEE Connector

The OEE (Overall Equipment Effectiveness) connector computes the canonical SEMI E10 manufacturing KPI from cumulative production counters arriving on the input stream. It emits per-block OEE plus the three component metrics (Availability, Performance, Quality) so they can be rendered as gauges, charts, or stored in a time-series database.

Connector Types:

  • OEE - Stateful processor that computes OEE on a sliding window or per-shift basis

OEE is computed as:

OEE = Availability × Performance × Quality

Where:

  • Availability = runTime / plannedProductionTime
  • Performance = (idealCycleTime × totalCount) / runTime
  • Quality = goodCount / totalCount

All three are clamped to [0, 1].

  • ✅ Two window modes: sliding (time-based) and shift (HH:MM bracketed)
  • ✅ Automatic accumulation when RunTime / PlannedProductionTime are not provided by the device
  • ✅ Accepts running as a boolean-ish status field (true/false, 1/0, on/off, yes/no, etc.)
  • ✅ Configurable thresholds (red, yellow) and target for gauge styling
  • ✅ Emits N/A payload (oee.* omitted) when a window has insufficient data
  • ✅ Per-block ID namespacing so multiple OEE instances can write to the same payload
  • ✅ Lenient numeric parsing — strings parsed as floats automatically

Sliding Window with Device-Reported Counters

Section titled “Sliding Window with Device-Reported Counters”
{
"type": "OEE",
"config": {
"fields": {
"plannedProductionTime": "planned_time_s",
"runTime": "run_time_s",
"idealCycleTime": "ideal_cycle_s",
"totalCount": "parts_total",
"goodCount": "parts_good"
},
"windowMode": "sliding",
"windowSeconds": 3600,
"thresholds": {
"red": 0.6,
"yellow": 0.8
},
"target": 0.85
}
}

Shift Window with Status Flag and Literal Cycle Time

Section titled “Shift Window with Status Flag and Literal Cycle Time”
{
"type": "OEE",
"config": {
"fields": {
"running": "machine_running",
"totalCount": "parts_total",
"goodCount": "parts_good"
},
"idealCycleTimeSeconds": 4.5,
"windowMode": "shift",
"shiftStart": "06:00",
"shiftEnd": "14:00",
"timezone": "Europe/Rome",
"thresholds": {
"red": 0.6,
"yellow": 0.8
},
"target": 0.85
}
}

The fields object names the payload keys the connector reads from each incoming sample.

{
"fields": {
"plannedProductionTime": "planned_time_s",
"runTime": "run_time_s",
"running": "machine_running",
"idealCycleTime": "ideal_cycle_s",
"totalCount": "parts_total",
"goodCount": "parts_good"
}
}
FieldRequired?Notes
plannedProductionTimeOptionalCumulative seconds. If omitted, the connector accumulates wall-clock seconds since the first sample
runTimeOne of runTime / running is requiredCumulative seconds the machine was running
runningOne of runTime / running is requiredBoolean-ish status; integrated internally
idealCycleTimeOne of idealCycleTime / idealCycleTimeSeconds is requiredSeconds per part at ideal speed
totalCountRequiredCumulative count of parts attempted
goodCountRequiredCumulative count of good parts

When the device does not publish ideal cycle time, you can declare it as a constant:

{
"idealCycleTimeSeconds": 4.5
}

This is mutually exclusive with fields.idealCycleTime.

{
"windowMode": "sliding"
}
  • sliding - Computes deltas over a rolling time window of length windowSeconds
  • shift - Computes deltas since the beginning of the current shift, defined by shiftStart, shiftEnd, and optional timezone
{
"windowSeconds": 3600
}

Required for windowMode: sliding. Must be > 0.

Recommended values:

  • Realtime gauge: 300-900 (5-15 minutes)
  • Hour-by-hour trend: 3600 (1 hour)
  • Long-term smoothing: 28800 (8 hours)

Required for windowMode: shift:

{
"shiftStart": "06:00",
"shiftEnd": "14:00",
"timezone": "Europe/Rome"
}
  • shiftStart / shiftEnd - HH:MM 24-hour format
  • timezone - IANA timezone identifier (e.g. Europe/Rome, America/New_York, Asia/Tokyo). Defaults to UTC if omitted
  • Overnight shifts are supported (e.g. 22:0006:00)

Styling hints emitted alongside the OEE values so dashboards can color gauges consistently:

{
"thresholds": {
"red": 0.6,
"yellow": 0.8
},
"target": 0.85
}

Constraints:

  • red < yellow (strictly)
  • 0 ≤ red, yellow ≤ 1
  • 0 ≤ target ≤ 1

Each output is namespaced by the block ID of the OEE connector instance (auto-injected by Meddle):

{
"oee.<blockId>": 0.78,
"availability.<blockId>": 0.92,
"performance.<blockId>": 0.95,
"quality.<blockId>": 0.89,
"red.<blockId>": 0.6,
"yellow.<blockId>": 0.8,
"target.<blockId>": 0.85
}

When the window does not yet have enough data (e.g. fewer than two snapshots in sliding mode, or the shift just started), the oee/availability/performance/quality keys are omitted and only the gauge styling keys are emitted. This is the N/A payload.

DataPayload (cumulative counters) → OEE → DataPayload (oee.<blockId>, etc.)

Example (sliding window, 60s):

Input stream:

{ "planned_time_s": 0, "run_time_s": 0, "parts_total": 0, "parts_good": 0 }
{ "planned_time_s": 60, "run_time_s": 55, "parts_total": 12, "parts_good": 11 }
{ "planned_time_s": 120, "run_time_s": 110, "parts_total": 24, "parts_good": 22 }

With idealCycleTimeSeconds: 4.5, windowSeconds: 60:

After the third sample:

  • ΔPlanned = 60s, ΔRun = 55s, ΔTotal = 12, ΔGood = 11
  • A = 55/60 = 0.917
  • P = (4.5 × 12) / 55 = 0.982
  • Q = 11/12 = 0.917
  • OEE = 0.917 × 0.982 × 0.917 = 0.826

Output:

{
"oee.<blockId>": 0.826,
"availability.<blockId>": 0.917,
"performance.<blockId>": 0.982,
"quality.<blockId>": 0.917,
"red.<blockId>": 0.6,
"yellow.<blockId>": 0.8,
"target.<blockId>": 0.85
}

Best for live operator dashboards where the gauge responds to recent activity:

{
"type": "OEE",
"config": {
"fields": {
"running": "is_running",
"totalCount": "cumulative_parts",
"goodCount": "cumulative_good"
},
"idealCycleTimeSeconds": 6.0,
"windowMode": "sliding",
"windowSeconds": 900,
"thresholds": { "red": 0.55, "yellow": 0.75 },
"target": 0.85
}
}
{
"type": "OEE",
"config": {
"fields": {
"plannedProductionTime": "shift_planned_s",
"runTime": "shift_run_s",
"idealCycleTime": "ideal_cycle_s",
"totalCount": "shift_parts_total",
"goodCount": "shift_parts_good"
},
"windowMode": "shift",
"shiftStart": "06:00",
"shiftEnd": "14:00",
"timezone": "Europe/Rome",
"thresholds": { "red": 0.6, "yellow": 0.8 },
"target": 0.85
}
}
{
"type": "OEE",
"config": {
"fields": {
"running": "running_flag",
"totalCount": "ct_total",
"goodCount": "ct_good"
},
"idealCycleTimeSeconds": 3.2,
"windowMode": "shift",
"shiftStart": "22:00",
"shiftEnd": "06:00",
"timezone": "America/New_York",
"thresholds": { "red": 0.5, "yellow": 0.75 },
"target": 0.8
}
}

Output Has Only Gauge Styling Fields (No oee.*)

Section titled “Output Has Only Gauge Styling Fields (No oee.*)”

Problem: The N/A payload is being emitted

Solutions:

  1. Sliding mode: requires at least 2 snapshots in the window. Wait for a second sample
  2. Shift mode: returns N/A when the current time is outside [shiftStart, shiftEnd]
  3. Shift mode startup: the first sample inside the shift establishes the baseline; OEE will appear from the second sample onward

Problem: An error is reported with deltas reported as 0 or negative

Solutions:

  1. The counters must be monotonically increasing. If the device resets them (e.g. on shift change), wrap the input in a Transform to convert to deltas first, or split the OEE instance per shift
  2. Confirm the field names map to non-zero values
  3. If the window is very short (windowSeconds: 10) and the line is idle, deltas can be exactly zero; widen the window

Problem: invalid value for "..." errors

Solutions:

  1. Values must be non-negative finite numbers. NaN, infinity, and negatives are rejected
  2. Strings are tolerated only if they parse to a non-negative finite float
  3. If your source emits booleans for “running”, ensure the running field — not runTime — is configured

Problem: Suspiciously perfect OEE

Solutions:

  1. Check that idealCycleTime is realistic. An overly slow ideal makes Performance ≥ 1, which clamps to 1
  2. Verify that runTime < plannedProductionTime. If they’re identical, Availability is 1.0

Problem: Shift starts/ends are off by N hours

Solutions:

  1. Always set timezone to a valid IANA name
  2. Do not use offset notations like +02:00 — they’re rejected

1. Prefer Device-Reported Counters When Available

Section titled “1. Prefer Device-Reported Counters When Available”

If the PLC publishes runTime and plannedProductionTime as cumulative seconds, use them directly. The internal accumulator is a fallback for devices that only publish a running flag.

  • Operators: 5-15 minute sliding window
  • Supervisors: per-shift window
  • Executives: per-day or per-week aggregation (downstream)

The ISO definition is “fastest sustainable cycle time”, not “marketing brochure”. An optimistic value depresses Performance and undersells real OEE.

4. Pair with a Filter or Transform Upstream

Section titled “4. Pair with a Filter or Transform Upstream”

If your raw stream contains unrelated payloads, use Filter to keep only counter updates before the OEE block.

Each OEE connector instance is namespaced by blockId, so multi-machine dashboards can coexist in a single Meddle flow.

ModbusReader (machine 1) ──┐
ModbusReader (machine 2) ──┼─→ Merge → Reshape → OEE → Chart (gauge)
ModbusReader (machine 3) ──┘ └→ InfluxDb2Writer
  1. ModbusReader (×N): Pulls counters from each machine
  2. Merge: Combines payloads keyed by machine ID
  3. Reshape: Normalizes field names to match the OEE config
  4. OEE: Computes OEE per machine instance
  5. Chart: Renders the gauge with red, yellow, target styling
  6. InfluxDb2Writer: Persists OEE for historical trending
OpcuaReader → OEE → Isa182 (alarm on OEE < 0.5) → Alert (email)

Triggers a notification when sustained OEE drops below 50% — useful for chronic underperformance detection.

  • Chart - Render the gauge from OEE output
  • ISA-18.2 - Trigger alarms on low OEE
  • Reshape - Normalize counter field names
  • InfluxDB v2 - Persist OEE for trending
  • Transform - Compute deltas from non-monotonic counters