All case studies
Comprehension8 min readJune 11, 2026

Reading an OEE State Machine Out of a Vendor Instruction Body

The state machine and its alarm-code threshold read out of the instruction bodies, three unrelated code defects surfaced, and a one-page operator reference produced.

June 11, 2026-Comprehension
nodeblue
10

112 MB

Export searched

148

Add-on instructions in the export

3

Instruction bodies read

3

Code defects found

An engineer asked how a conveyor's OEE state is calculated. The routine they were reading only calls the instruction that decides it. The state machine sits inside three vendor add-on instructions, among 148 in a 112 MB export, and the rule that separates blocked from faulted is a single alarm-code threshold.

How is the OEE state for this inbound conveyor calculated, in terms an operator can actually use?

The question came in the way real questions do. Not as a tag name, not as a routine, just that sentence, with the structured text the engineer was already staring at pasted underneath it.

It was the wrong layer, and that is the whole point of this one. The routine they had open calls three OEM add-on instructions and passes them arguments. The state machine, the alarm precedence, and the thresholds all live inside those instructions, in a 112 MB full-controller export holding 148 of them. The answer to "how does this work" was one layer below where the question could be asked.

The state machine, read out of three instruction bodies

The three instruction bodies came out of the export and were read as one unit rather than one at a time, because the precedence only makes sense across all three.

State resolution is a precedence chain evaluated last-write-wins, in this order: maintenance, manual, abort, stopped, idle, running. Whichever branch fires last owns the state, so the ordering is the specification.

For this conveyor, only three of those six are reachable. Maintenance, manual and idle are not wired.

StateCodeWhat it means on the floor
Running129The zone is enabled and allowed to run. An empty belt still reports Running. Running does not mean product is moving.
Stopped128The zone is not enabled and nothing is faulted. Normal stop, or downstream cannot accept.
Faulted127A real fault is active and the line is down.

Faulted always wins over Running and Stopped, which is the useful half of last-write-wins for an operator: the state on the screen is never hiding an active fault behind a stale Running.

The one threshold that separates blocked from faulted

The rule that mattered was inside the abort branch. The instruction sets its abort output on any active alarm code from 1 to 119. Codes of 120 and above deliberately do not fault the conveyor.

That single threshold is the entire difference between blocked and faulted. Everything below 120 drives the conveyor to Faulted with an alarm code attached. Alarm 121, downstream disabled, is raised when the discharge-enable bit goes off, sits above the threshold, and leaves the state at Stopped.

So blocked has no state of its own. Stopped plus alarm 121 is the site's actual working definition of blocked, and nothing on the screen says so. An operator looking at Stopped has to know to go read the alarm number before deciding whether this conveyor is the problem or the one after it is.

Below the threshold, the faults that reach this conveyor are specific and countable:

FaultAlarmThe signal behind it
E-stop pressed5The safety relay's safe output goes off
Jam82The unit's jam latch, or the overhead eye's jam latch
Motor fault74The unit's motor fault latch
Air pressure fault84The area air fault latch
Power fault3A power supply reporting not OK
Network fault48 / 45 / 46Conveyor comm, bus, or gateway fault
Fire system shutdown61The fire system's shutdown bit

The E-stop row carries a caveat that went into the artifact next to it: that path is safety logic. It is read, never defeated, and clearing it is a physical reset and verification task rather than a software one. A document that explains how to interpret a safety-driven state has to say that on the same page, not in an appendix.

You do not get any of this from the calling routine. You get it by reading the instruction body, which is exactly the layer most teams cannot open.

The photo eye counts boxes, it does not set the state

One correction fell out of the decode that nobody had asked for and everybody needed.

The conveyor's photo eye feeds only the box counter. One count per box edge, normally-closed input, 100 ms debounce. It does not participate in state resolution anywhere. A stalled counter does not by itself change the state, and a moving counter does not confirm the state is right.

The state answers whether the line is up. The photo eye answers how many boxes went by. On a floor where both numbers land on the same screen, conflating them is a standing source of arguments about whether a line is really running.

Why the percentages were not in the controller at all

The engineer's underlying interest was never the state code. It was the percentages: available, starved, blocked.

The decode found no starved input wired to this conveyor at all. A running belt with nothing on it reports Running, full stop. Starved is not something this conveyor reports. It has to be inferred from upstream, along the lines of "running, but the counter has not advanced in a while."

The percentages themselves are computed above the controller, in a performance service that the instruction's own comment names by name. That boundary is the more valuable finding than the state table. It tells the site where the number they have been arguing about is actually produced, which is not where they were looking. It also bounds this system honestly: reading every line of controller logic will not answer "why is this percentage wrong," because the arithmetic does not happen there.

That gap is what a later session on a different line set out to close, by reconstructing the backend's own starvation formula from live controller tags rather than waiting for API access to the layer above.

Three defects nobody asked about

Reading logic carefully surfaces things nobody asked about. Three turned up here, all unrelated to the question, all cited to the line that contains them.

DefectWhereEffect
The idle branch skips the multi-state error code check that every other branch performsState instructionAn inconsistency in error reporting on a branch this conveyor never reaches
A timer enable assignment is immediately overwrittenManager instructionDead code. The assignment does nothing
One routine block is missing the inversion on the downstream-disabled bit that all of its sibling blocks haveCalling routineThat block treats a blocked condition differently from the rest of the line

The third is the one worth chasing. A missing inversion on a blocked signal in one block out of many is exactly the defect that never gets found by reading a single block, because a single block always looks self-consistent. It shows up only when you compare siblings.

None of these were the question. Finding them is a side effect of reading the whole body rather than searching for the answer and stopping.

What went into the one-page operator reference

The session ended with an artifact rather than a conversation: a single page a shift can keep.

It holds the three reachable states with their codes and plain meanings, the fault table above with the alarm number and the real signal behind each entry, an explicit statement that blocked and starved are read rather than reported, the safety note on the E-stop path, a note that the photo eye is a counter and not a state input, and four triage steps.

The triage is the part that travels:

  1. 01State 127: read the active alarm number, clear that device, reset.
  2. 02State 128 with alarm 121: not a fault. Downstream is full. Check the next area, not this conveyor.
  3. 03State 128 with no alarm: normal or commanded stop. Check the start permissive and the zone enable.
  4. 04State 129 with no throughput: the belt is up and starved. Look upstream.

Every line of that page traces to a line of logic in the export. That property is what makes it worth keeping on a wall, and it is the same standard applied to generated documentation elsewhere.

Two method lessons that outlived the answer

Both are about retrieval, not about controllers, and both cost time in this session.

The first: the capability is invisible if the right entry point is not in front of you at the moment of need. The first attempt at this question reached for a hand-rolled extractor over the raw export instead of the purpose-built retrieval, and only switched after being asked why it was not using the tool that already existed. A system whose best path is not the obvious path will be routed around by the people it was built for.

The second: "no results" and "not indexed" must never look the same. The initial searches reported that the export was not present, because the search path honored ignore rules that excluded the directory holding it. Two turns were spent insisting the data was missing while it sat on disk. A retrieval system has to know the facts about its own environment and say "excluded from the index" rather than "not found." Trust erodes faster from a confident empty result than from a slow one.

Neither lesson is specific to this floor. Both are the kind of thing that turns up only when a system meets a real corpus and a real question at the same time, which is why the research runs on a live floor rather than a fixture.

What the decode was worth

A senior Controls Engineer with the unprotected export open can read the same instruction and reach the same state machine. The difference here is time, the fact that the output is a document a shift can keep rather than knowledge in one person's head, and the three unrelated defects that turned up on the way through.

The logic relationships came back exact, cited to the instruction bodies that make them true. The companion study on the lane-disable question ran into the same pattern from the other end: an OEE running signal wired to an enable rather than to motion, on a different controller.

Bring us a question like this one.

Every study here started with an engineer describing a problem in plain language. Point us at your controllers and we will show you what the same session looks like on your plant.