<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: tia-forge</title>
    <description>The latest articles on DEV Community by tia-forge (@tiaforge).</description>
    <link>https://dev.to/tiaforge</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4179613%2Fddd904c9-5210-4f7a-be21-51476a4ccf54.png</url>
      <title>DEV Community: tia-forge</title>
      <link>https://dev.to/tiaforge</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tiaforge"/>
    <language>en</language>
    <item>
      <title>An AI Agent Programmed a Siemens S7-1500 Through an MCP Bridge — Here's the SCL and the Race Conditions It Caught</title>
      <dc:creator>tia-forge</dc:creator>
      <pubDate>Mon, 12 Oct 2026 15:00:26 +0000</pubDate>
      <link>https://dev.to/tiaforge/an-ai-agent-programmed-a-siemens-s7-1500-through-an-mcp-bridge-heres-the-scl-and-the-race-fb7</link>
      <guid>https://dev.to/tiaforge/an-ai-agent-programmed-a-siemens-s7-1500-through-an-mcp-bridge-heres-the-scl-and-the-race-fb7</guid>
      <description>&lt;p&gt;We built an MCP server that drives &lt;strong&gt;Siemens TIA Portal V19 through the Openness API&lt;/strong&gt;, pointed an LLM agent at it, and gave it a real engineering task: implement a conveyor-sorting control program for an &lt;strong&gt;S7-1500 (CPU 1511-1 PN)&lt;/strong&gt;, verified in &lt;strong&gt;S7-PLCSIM&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Not "generate a snippet." The whole loop:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;add CPU → compile → start simulator → create FB/UDTs → write SCL → compile → download → verify.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I expected the code to be the interesting part. It wasn't. The interesting part is &lt;em&gt;which&lt;/em&gt; engineering decisions the agent made, and where it drew a line and refused to cross it.&lt;/p&gt;

&lt;p&gt;Here's what happened, with the actual code and the failure modes it flagged.&lt;/p&gt;

&lt;h2&gt;
  
  
  The toolchain
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent&lt;/strong&gt;: an LLM agent (WorkBuddy) that reads the spec, plans steps, writes code and tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bridge&lt;/strong&gt;: an MCP server (TiaLink) exposing TIA Portal operations over MCP — add device, create block, write SCL, compile, download, read back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Target&lt;/strong&gt;: TIA Portal V19 with Openness, S7-PLCSIM for verification.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bridge runs on the same machine as TIA Portal (an industrial PC); the agent can run elsewhere as long as it can reach the bridge.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt
&lt;/h2&gt;

&lt;p&gt;Hardware and network configuration were already done. The task:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SCL &lt;strong&gt;state machine&lt;/strong&gt;, with state numbers identical to the spec (so HMI, alarms, tests and field debugging all reference the same numbering)&lt;/li&gt;
&lt;li&gt;Logic as a &lt;strong&gt;reusable FB with its own instance DB&lt;/strong&gt; (line #2 becomes a second instance DB, not copy-paste)&lt;/li&gt;
&lt;li&gt;Clear separation of &lt;code&gt;i_&lt;/code&gt; inputs / &lt;code&gt;q_&lt;/code&gt; outputs / &lt;code&gt;stat_&lt;/code&gt; internals&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;IEC timers&lt;/strong&gt; (&lt;code&gt;TON_TIME&lt;/code&gt;) — no hand-rolled scan-cycle accumulation&lt;/li&gt;
&lt;li&gt;Implement: entry de-dup, quality-result latch, dual-cylinder interlock, fault latch, manual reset, normal stop&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every output must have a deterministic value every scan&lt;/strong&gt; — no reliance on last-cycle residue&lt;/li&gt;
&lt;li&gt;Emergency stop is only read as a &lt;strong&gt;safety-circuit status input&lt;/strong&gt;; ordinary PLC code is not a safety function&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Deliverables: FB SCL source, UDTs, state-transition doc, ≥10 automated test cases, boundary/race analysis.&lt;/p&gt;

&lt;p&gt;And one line at the end:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"First add a CPU and check whether the simulator works."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That line is the part I actually cared about testing. Config tasks are judged from a screenshot. &lt;strong&gt;Programs are judged by behavior.&lt;/strong&gt; So: does the agent build the verification environment first, or just start writing code?&lt;/p&gt;

&lt;h2&gt;
  
  
  The sequence it chose
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Check whether a CPU already existed — only then add &lt;code&gt;PLC_1&lt;/code&gt; (CPU 1511-1 PN)&lt;/li&gt;
&lt;li&gt;Compile, start &lt;strong&gt;S7-PLCSIM&lt;/strong&gt;, confirm download/run works&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;FB_ConveyorSort&lt;/code&gt; and write the state machine in SCL&lt;/li&gt;
&lt;li&gt;Create the needed UDTs&lt;/li&gt;
&lt;li&gt;Call the FB in &lt;code&gt;OB1&lt;/code&gt; with instance DB &lt;code&gt;DB_ConveyorSort_1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Compile + download — no errors&lt;/li&gt;
&lt;li&gt;Deliver the state transition table, &lt;strong&gt;14 test cases&lt;/strong&gt;, and a race/boundary analysis&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It built the verification environment first. Good sign.&lt;/p&gt;

&lt;h2&gt;
  
  
  The state machine
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Enter when&lt;/th&gt;
&lt;th&gt;Main action&lt;/th&gt;
&lt;th&gt;Exit → target&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;INIT&lt;/td&gt;
&lt;td&gt;first scan&lt;/td&gt;
&lt;td&gt;clear latches, fault code, commands&lt;/td&gt;
&lt;td&gt;unconditional → 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;READY&lt;/td&gt;
&lt;td&gt;power-on / reset / after stop&lt;/td&gt;
&lt;td&gt;conveyor stopped, wait for start&lt;/td&gt;
&lt;td&gt;start → 20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;FEED&lt;/td&gt;
&lt;td&gt;after start&lt;/td&gt;
&lt;td&gt;conveyor runs; entry de-dup; latch quality; sort timeout&lt;/td&gt;
&lt;td&gt;at sort pos → 30; timeout → 90; stop → 100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;CHECK&lt;/td&gt;
&lt;td&gt;at sort position&lt;/td&gt;
&lt;td&gt;stop conveyor, branch on latched result&lt;/td&gt;
&lt;td&gt;OK → 50; NG → 60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;PUSH_A&lt;/td&gt;
&lt;td&gt;judged OK&lt;/td&gt;
&lt;td&gt;cylinder A extend, push hold&lt;/td&gt;
&lt;td&gt;extended + hold done → 70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;PUSH_B&lt;/td&gt;
&lt;td&gt;judged NG&lt;/td&gt;
&lt;td&gt;cylinder B extend, push hold&lt;/td&gt;
&lt;td&gt;extended + hold done → 70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;RETRACT&lt;/td&gt;
&lt;td&gt;push done&lt;/td&gt;
&lt;td&gt;cylinder retract, retract timeout&lt;/td&gt;
&lt;td&gt;retracted → 80; timeout → 90&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;DISCHARGE&lt;/td&gt;
&lt;td&gt;retracted&lt;/td&gt;
&lt;td&gt;wait for part to leave sort position&lt;/td&gt;
&lt;td&gt;sort photo-eye clear → 20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;FAULT&lt;/td&gt;
&lt;td&gt;any fault / safety circuit open&lt;/td&gt;
&lt;td&gt;all outputs off, latch fault&lt;/td&gt;
&lt;td&gt;reset + conditions OK → 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;STOPPED&lt;/td&gt;
&lt;td&gt;normal stop&lt;/td&gt;
&lt;td&gt;outputs off, restartable&lt;/td&gt;
&lt;td&gt;start → 20&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The SCL
&lt;/h2&gt;

&lt;p&gt;Here's the full function block it delivered. Two things to look at as you read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Section 0&lt;/strong&gt; writes every output to a deterministic value at the top of the cycle (requirement #6).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Section 7&lt;/strong&gt; is the final output mapping with the interlock and the same determinism guarantee.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="n"&gt;FUNCTION_BLOCK&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;FB_ConveyorSort&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;
&lt;span class="cm"&gt;{ S7_Optimized_Access := 'TRUE' }&lt;/span&gt;
&lt;span class="n"&gt;VERSION&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.1&lt;/span&gt;

&lt;span class="n"&gt;VAR_INPUT&lt;/span&gt;
   &lt;span class="n"&gt;i_xSafetyOK&lt;/span&gt;     &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// safety circuit healthy (1 = closed, run allowed)
&lt;/span&gt;   &lt;span class="n"&gt;i_xStart&lt;/span&gt;        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// start (edge-triggered)
&lt;/span&gt;   &lt;span class="n"&gt;i_xStop&lt;/span&gt;         &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// normal stop (edge-triggered)
&lt;/span&gt;   &lt;span class="n"&gt;i_xReset&lt;/span&gt;        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// fault reset (edge-triggered)
&lt;/span&gt;   &lt;span class="n"&gt;i_xPartAtEntry&lt;/span&gt;  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// entry photo-eye
&lt;/span&gt;   &lt;span class="n"&gt;i_xPartAtSort&lt;/span&gt;   &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// sort-position photo-eye
&lt;/span&gt;   &lt;span class="n"&gt;i_xQualityOK&lt;/span&gt;    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// quality result: 1 = OK
&lt;/span&gt;   &lt;span class="n"&gt;i_xCylA_ExtEnd&lt;/span&gt;  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// cylinder A extended limit
&lt;/span&gt;   &lt;span class="n"&gt;i_xCylA_RetEnd&lt;/span&gt;  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// cylinder A retracted limit
&lt;/span&gt;   &lt;span class="n"&gt;i_xCylB_ExtEnd&lt;/span&gt;  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// cylinder B extended limit
&lt;/span&gt;   &lt;span class="n"&gt;i_xCylB_RetEnd&lt;/span&gt;  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// cylinder B retracted limit
&lt;/span&gt;   &lt;span class="n"&gt;i_tSortTimeout&lt;/span&gt;  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// sort timeout
&lt;/span&gt;   &lt;span class="n"&gt;i_tPushHold&lt;/span&gt;     &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="m"&gt;800&lt;/span&gt;&lt;span class="n"&gt;MS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// push hold time
&lt;/span&gt;   &lt;span class="n"&gt;i_tRetractTO&lt;/span&gt;    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// retract timeout
&lt;/span&gt;&lt;span class="n"&gt;END_VAR&lt;/span&gt;

&lt;span class="n"&gt;VAR_OUTPUT&lt;/span&gt;
   &lt;span class="n"&gt;q_xConveyor&lt;/span&gt;     &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;q_xCylA_Extend&lt;/span&gt;  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;q_xCylA_Retract&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;q_xCylB_Extend&lt;/span&gt;  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;q_xCylB_Retract&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;q_xFault&lt;/span&gt;        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;q_xReady&lt;/span&gt;        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;q_wState&lt;/span&gt;        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Word&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;END_VAR&lt;/span&gt;

&lt;span class="k"&gt;VAR&lt;/span&gt;
   &lt;span class="n"&gt;stat_wState&lt;/span&gt;     &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Word&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// current state
&lt;/span&gt;   &lt;span class="n"&gt;stat_wFaultCode&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Word&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// fault code
&lt;/span&gt;   &lt;span class="n"&gt;stat_xFault&lt;/span&gt;     &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// latched fault
&lt;/span&gt;   &lt;span class="n"&gt;stat_xCmdAExt&lt;/span&gt;   &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// cylinder A extend request
&lt;/span&gt;   &lt;span class="n"&gt;stat_xCmdBExt&lt;/span&gt;   &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// cylinder B extend request
&lt;/span&gt;   &lt;span class="n"&gt;stat_xQualityNG&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// latched quality result (1 = reject)
&lt;/span&gt;   &lt;span class="n"&gt;stat_xPartSeen&lt;/span&gt;  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// entry de-dup latch
&lt;/span&gt;   &lt;span class="n"&gt;stat_xInTransit&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// part in transit
&lt;/span&gt;   &lt;span class="n"&gt;stat_xStartEdge&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;stat_xStopEdge&lt;/span&gt;  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;stat_xResetEdge&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;stat_xStartOld&lt;/span&gt;  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;stat_xStopOld&lt;/span&gt;   &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;stat_xResetOld&lt;/span&gt;  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;stat_tSortTO&lt;/span&gt;    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TON_TIME&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;// sort timeout
&lt;/span&gt;   &lt;span class="n"&gt;stat_tPush&lt;/span&gt;      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TON_TIME&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;// push hold
&lt;/span&gt;   &lt;span class="n"&gt;stat_tRetract&lt;/span&gt;   &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TON_TIME&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;// retract timeout
&lt;/span&gt;&lt;span class="n"&gt;END_VAR&lt;/span&gt;

&lt;span class="k"&gt;BEGIN&lt;/span&gt;
   &lt;span class="c1"&gt;// ---------- 0. write every output to a deterministic value each cycle ----------
&lt;/span&gt;   &lt;span class="n"&gt;q_xConveyor&lt;/span&gt;     &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;q_xCylA_Extend&lt;/span&gt;  &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;q_xCylA_Retract&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;q_xCylB_Extend&lt;/span&gt;  &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;q_xCylB_Retract&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;q_xFault&lt;/span&gt;        &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;q_xReady&lt;/span&gt;        &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;q_wState&lt;/span&gt;        &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;stat_wState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="c1"&gt;// ---------- 1. button edge extraction ----------
&lt;/span&gt;   &lt;span class="n"&gt;stat_xStartEdge&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i_xStart&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;stat_xStartOld&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;stat_xStopEdge&lt;/span&gt;  &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i_xStop&lt;/span&gt;  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;stat_xStopOld&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;stat_xResetEdge&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i_xReset&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;stat_xResetOld&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;stat_xStartOld&lt;/span&gt;  &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i_xStart&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;stat_xStopOld&lt;/span&gt;   &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i_xStop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;stat_xResetOld&lt;/span&gt;  &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i_xReset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="c1"&gt;// ---------- 2. safety circuit: read-only, do NOT implement a safety function ----------
&lt;/span&gt;   &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;i_xSafetyOK&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
      &lt;span class="n"&gt;stat_wFaultCode&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="m"&gt;0001&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// safety circuit open
&lt;/span&gt;      &lt;span class="n"&gt;stat_xFault&lt;/span&gt;     &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;END_IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="c1"&gt;// ---------- 3. sensor plausibility checks ----------
&lt;/span&gt;   &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;i_xCylA_ExtEnd&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i_xCylA_RetEnd&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
      &lt;span class="n"&gt;stat_wFaultCode&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="m"&gt;0002&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// cylinder A contradictory position sensors
&lt;/span&gt;      &lt;span class="n"&gt;stat_xFault&lt;/span&gt;     &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;END_IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;i_xCylB_ExtEnd&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i_xCylB_RetEnd&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
      &lt;span class="n"&gt;stat_wFaultCode&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="m"&gt;0003&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// cylinder B contradictory position sensors
&lt;/span&gt;      &lt;span class="n"&gt;stat_xFault&lt;/span&gt;     &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;END_IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="c1"&gt;// ---------- 4. dual-cylinder interlock check (software supplement, NOT a hard interlock) ----------
&lt;/span&gt;   &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;stat_xCmdAExt&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;stat_xCmdBExt&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
      &lt;span class="n"&gt;stat_wFaultCode&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="m"&gt;0010&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// both cylinders requested to extend
&lt;/span&gt;      &lt;span class="n"&gt;stat_xFault&lt;/span&gt;     &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;END_IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="c1"&gt;// ---------- 5. fault reset (requires human confirmation AND recovered conditions) ----------
&lt;/span&gt;   &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;stat_xFault&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
      &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;stat_xResetEdge&lt;/span&gt;
         &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i_xSafetyOK&lt;/span&gt;
         &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i_xCylA_ExtEnd&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i_xCylA_RetEnd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i_xCylB_ExtEnd&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i_xCylB_RetEnd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
         &lt;span class="n"&gt;stat_xFault&lt;/span&gt;     &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_wFaultCode&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_wState&lt;/span&gt;     &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;END_IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;END_IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="c1"&gt;// ---------- 6. state machine ----------
&lt;/span&gt;   &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;stat_xFault&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
      &lt;span class="n"&gt;stat_wState&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;90&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;               &lt;span class="c1"&gt;// fault has priority, force fault state
&lt;/span&gt;   &lt;span class="n"&gt;END_IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="n"&gt;stat_wState&lt;/span&gt; &lt;span class="k"&gt;OF&lt;/span&gt;
      &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;// INIT on power-up
&lt;/span&gt;         &lt;span class="n"&gt;stat_xCmdAExt&lt;/span&gt;   &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_xCmdBExt&lt;/span&gt;   &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_xQualityNG&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_xPartSeen&lt;/span&gt;  &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_xInTransit&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_wFaultCode&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_wState&lt;/span&gt;     &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;// READY waiting for start
&lt;/span&gt;         &lt;span class="n"&gt;q_xReady&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_xCmdAExt&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_xCmdBExt&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;stat_xStartEdge&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
            &lt;span class="n"&gt;stat_wState&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;END_IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;// FEED conveying
&lt;/span&gt;         &lt;span class="n"&gt;q_xConveyor&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;q_xReady&lt;/span&gt;    &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_xCmdAExt&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_xCmdBExt&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;stat_xStopEdge&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
            &lt;span class="n"&gt;stat_wState&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="k"&gt;ELSE&lt;/span&gt;
            &lt;span class="c1"&gt;// entry de-dup: latch only on "new part entered"
&lt;/span&gt;            &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;i_xPartAtEntry&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;stat_xPartSeen&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
               &lt;span class="n"&gt;stat_xPartSeen&lt;/span&gt;  &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
               &lt;span class="n"&gt;stat_xInTransit&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
               &lt;span class="n"&gt;stat_xQualityNG&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;i_xQualityOK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// latch quality result
&lt;/span&gt;            &lt;span class="n"&gt;END_IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;i_xPartAtEntry&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
               &lt;span class="n"&gt;stat_xPartSeen&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;               &lt;span class="c1"&gt;// part left entry, allow the next one
&lt;/span&gt;            &lt;span class="n"&gt;END_IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="c1"&gt;// sort timeout
&lt;/span&gt;            &lt;span class="n"&gt;stat_tSortTO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;stat_xInTransit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PT&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i_tSortTimeout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;stat_tSortTO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
               &lt;span class="n"&gt;stat_wFaultCode&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="m"&gt;0020&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// sort timeout
&lt;/span&gt;               &lt;span class="n"&gt;stat_xFault&lt;/span&gt;     &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
               &lt;span class="n"&gt;stat_wState&lt;/span&gt;     &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;90&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;ELSIF&lt;/span&gt; &lt;span class="n"&gt;i_xPartAtSort&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
               &lt;span class="n"&gt;stat_xInTransit&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
               &lt;span class="n"&gt;stat_tSortTO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PT&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i_tSortTimeout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
               &lt;span class="n"&gt;stat_wState&lt;/span&gt;     &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;END_IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;END_IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;// CHECK decide on latched result
&lt;/span&gt;         &lt;span class="n"&gt;stat_xCmdAExt&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_xCmdBExt&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_tPush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PT&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i_tPushHold&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
         &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;stat_xQualityNG&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
            &lt;span class="n"&gt;stat_wState&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                        &lt;span class="c1"&gt;// reject -&amp;gt; cylinder B
&lt;/span&gt;         &lt;span class="k"&gt;ELSE&lt;/span&gt;
            &lt;span class="n"&gt;stat_wState&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                        &lt;span class="c1"&gt;// OK     -&amp;gt; cylinder A
&lt;/span&gt;         &lt;span class="n"&gt;END_IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;// PUSH_A extend A for a good part
&lt;/span&gt;         &lt;span class="n"&gt;stat_xCmdBExt&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                      &lt;span class="c1"&gt;// interlock: B stays retracted
&lt;/span&gt;         &lt;span class="n"&gt;stat_xCmdAExt&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i_xCylB_RetEnd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// only allow A after B is confirmed retracted
&lt;/span&gt;         &lt;span class="n"&gt;stat_tPush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;stat_xCmdAExt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PT&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i_tPushHold&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
         &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;i_xCylA_ExtEnd&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;stat_tPush&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
            &lt;span class="n"&gt;stat_xCmdAExt&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                   &lt;span class="c1"&gt;// push done, retract
&lt;/span&gt;            &lt;span class="n"&gt;stat_wState&lt;/span&gt;   &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;70&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;END_IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;// PUSH_B extend B for a reject
&lt;/span&gt;         &lt;span class="n"&gt;stat_xCmdAExt&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                      &lt;span class="c1"&gt;// interlock: A stays retracted
&lt;/span&gt;         &lt;span class="n"&gt;stat_xCmdBExt&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i_xCylA_RetEnd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// only allow B after A is confirmed retracted
&lt;/span&gt;         &lt;span class="n"&gt;stat_tPush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;stat_xCmdBExt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PT&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i_tPushHold&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
         &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;i_xCylB_ExtEnd&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;stat_tPush&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
            &lt;span class="n"&gt;stat_xCmdBExt&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;stat_wState&lt;/span&gt;   &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;70&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;END_IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="m"&gt;70&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;// RETRACT confirm retraction
&lt;/span&gt;         &lt;span class="n"&gt;stat_xCmdAExt&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_xCmdBExt&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_tRetract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PT&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i_tRetractTO&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
         &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stat_xQualityNG&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i_xCylB_RetEnd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;stat_xQualityNG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i_xCylA_RetEnd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
            &lt;span class="n"&gt;stat_tRetract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PT&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i_tRetractTO&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;stat_wState&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;END_IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;stat_tRetract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
            &lt;span class="n"&gt;stat_wFaultCode&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="m"&gt;0030&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;               &lt;span class="c1"&gt;// retract timeout
&lt;/span&gt;            &lt;span class="n"&gt;stat_xFault&lt;/span&gt;     &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;stat_wState&lt;/span&gt;     &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;90&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;END_IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;// DISCHARGE wait for the part to leave the sort position
&lt;/span&gt;         &lt;span class="n"&gt;stat_xCmdAExt&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_xCmdBExt&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;i_xPartAtSort&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
            &lt;span class="n"&gt;stat_wState&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;END_IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="m"&gt;90&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;// FAULT latched
&lt;/span&gt;         &lt;span class="n"&gt;stat_xCmdAExt&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_xCmdBExt&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_tSortTO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PT&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i_tSortTimeout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
         &lt;span class="n"&gt;stat_tPush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PT&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i_tPushHold&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
         &lt;span class="n"&gt;stat_tRetract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PT&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i_tRetractTO&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;// STOPPED normal stop
&lt;/span&gt;         &lt;span class="n"&gt;q_xReady&lt;/span&gt;    &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_xCmdAExt&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_xCmdBExt&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_xInTransit&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_tSortTO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PT&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i_tSortTimeout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
         &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;stat_xStartEdge&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
            &lt;span class="n"&gt;stat_wState&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;END_IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;ELSE&lt;/span&gt;
         &lt;span class="n"&gt;stat_wFaultCode&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="m"&gt;00F&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                   &lt;span class="c1"&gt;// illegal state
&lt;/span&gt;         &lt;span class="n"&gt;stat_xFault&lt;/span&gt;     &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;stat_wState&lt;/span&gt;     &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;90&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;END_CASE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="c1"&gt;// ---------- 7. output mapping: interlock + deterministic value each cycle ----------
&lt;/span&gt;   &lt;span class="n"&gt;q_xCylA_Extend&lt;/span&gt;  &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;stat_xCmdAExt&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;stat_xCmdBExt&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i_xCylB_RetEnd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;q_xCylB_Extend&lt;/span&gt;  &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;stat_xCmdBExt&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;stat_xCmdAExt&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i_xCylA_RetEnd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;q_xCylA_Retract&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;q_xCylA_Extend&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;q_xCylB_Retract&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;q_xCylB_Extend&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="n"&gt;q_xFault&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;stat_xFault&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;q_wState&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;stat_wState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;END_FUNCTION_BLOCK&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: this assumes double-acting cylinders where retract is the de-energized safe direction. For spring-return cylinders, the "retract output" should be forced to 0 (de-energized = retracted) rather than commanded.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The UDTs
&lt;/h2&gt;

&lt;p&gt;A parameter UDT so multiple lines can reuse the same FB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;UDT_SortParams&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;VERSION&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.1&lt;/span&gt;
   &lt;span class="n"&gt;STRUCT&lt;/span&gt;
      &lt;span class="n"&gt;SortTimeout&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// sort timeout
&lt;/span&gt;      &lt;span class="n"&gt;PushHold&lt;/span&gt;    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="m"&gt;800&lt;/span&gt;&lt;span class="n"&gt;MS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// push hold time
&lt;/span&gt;      &lt;span class="n"&gt;RetractTO&lt;/span&gt;   &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// retract timeout
&lt;/span&gt;   &lt;span class="n"&gt;END_STRUCT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;END_TYPE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A status UDT for HMI display and online diagnostics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;UDT_SortStatus&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;VERSION&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.1&lt;/span&gt;
   &lt;span class="n"&gt;STRUCT&lt;/span&gt;
      &lt;span class="n"&gt;State&lt;/span&gt;        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Word&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// current state
&lt;/span&gt;      &lt;span class="n"&gt;FaultCode&lt;/span&gt;    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Word&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// fault code
&lt;/span&gt;      &lt;span class="n"&gt;FaultLatched&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// fault latched
&lt;/span&gt;      &lt;span class="n"&gt;QualityNG&lt;/span&gt;    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// quality result latched
&lt;/span&gt;      &lt;span class="n"&gt;PartSeen&lt;/span&gt;     &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// entry de-dup latch
&lt;/span&gt;      &lt;span class="n"&gt;InTransit&lt;/span&gt;    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// part in transit
&lt;/span&gt;   &lt;span class="n"&gt;END_STRUCT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;END_TYPE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The 14 test cases it shipped
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Precondition&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Expected&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TC-01&lt;/td&gt;
&lt;td&gt;Power-on init&lt;/td&gt;
&lt;td&gt;downloaded to sim&lt;/td&gt;
&lt;td&gt;first scan&lt;/td&gt;
&lt;td&gt;state 10, all outputs 0, no fault&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TC-02&lt;/td&gt;
&lt;td&gt;Normal start&lt;/td&gt;
&lt;td&gt;state 10&lt;/td&gt;
&lt;td&gt;press start&lt;/td&gt;
&lt;td&gt;state 20, conveyor runs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TC-03&lt;/td&gt;
&lt;td&gt;Good part sort&lt;/td&gt;
&lt;td&gt;state 20&lt;/td&gt;
&lt;td&gt;part at entry, quality OK, reaches sort pos&lt;/td&gt;
&lt;td&gt;20→30→50→70→80→20, cylinder A extends then retracts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TC-04&lt;/td&gt;
&lt;td&gt;Bad part sort&lt;/td&gt;
&lt;td&gt;state 20&lt;/td&gt;
&lt;td&gt;part at entry, quality NG&lt;/td&gt;
&lt;td&gt;20→30→60→70→80→20, cylinder B extends then retracts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TC-05&lt;/td&gt;
&lt;td&gt;Entry de-dup&lt;/td&gt;
&lt;td&gt;state 20&lt;/td&gt;
&lt;td&gt;part sits at entry for 100 scans&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;PartSeen&lt;/code&gt; latches once, no repeat triggering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TC-06&lt;/td&gt;
&lt;td&gt;Sort timeout&lt;/td&gt;
&lt;td&gt;state 20&lt;/td&gt;
&lt;td&gt;part doesn't reach sort pos within 5 s&lt;/td&gt;
&lt;td&gt;state 90, fault 16#0020 latched&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TC-07&lt;/td&gt;
&lt;td&gt;Fault latch + reset&lt;/td&gt;
&lt;td&gt;state 90, conditions recovered&lt;/td&gt;
&lt;td&gt;press start, then reset&lt;/td&gt;
&lt;td&gt;start ignored; reset + conditions OK → state 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TC-08&lt;/td&gt;
&lt;td&gt;Reset refused&lt;/td&gt;
&lt;td&gt;state 90, safety circuit still open&lt;/td&gt;
&lt;td&gt;press reset&lt;/td&gt;
&lt;td&gt;reset ineffective, stays in state 90&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TC-09&lt;/td&gt;
&lt;td&gt;Normal stop + resume&lt;/td&gt;
&lt;td&gt;state 20&lt;/td&gt;
&lt;td&gt;press stop, then start&lt;/td&gt;
&lt;td&gt;stop → state 100 outputs safe; start → state 20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TC-10&lt;/td&gt;
&lt;td&gt;Dual-cylinder interlock&lt;/td&gt;
&lt;td&gt;state 20&lt;/td&gt;
&lt;td&gt;force both extend requests = 1&lt;/td&gt;
&lt;td&gt;fault 16#0010, neither cylinder extends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TC-11&lt;/td&gt;
&lt;td&gt;Contradictory position sensors&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;force A ext-end and ret-end = 1 together&lt;/td&gt;
&lt;td&gt;fault 16#0002 latched, state 90&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TC-12&lt;/td&gt;
&lt;td&gt;Safety circuit open&lt;/td&gt;
&lt;td&gt;state 20&lt;/td&gt;
&lt;td&gt;&lt;code&gt;i_xSafetyOK = 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;outputs drop immediately, state 90 latched; needs reset after recovery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TC-13&lt;/td&gt;
&lt;td&gt;Retract timeout&lt;/td&gt;
&lt;td&gt;state 70&lt;/td&gt;
&lt;td&gt;cylinder doesn't retract within 3 s&lt;/td&gt;
&lt;td&gt;fault 16#0030, state 90&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TC-14&lt;/td&gt;
&lt;td&gt;Output determinism&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;single-step and watch outputs&lt;/td&gt;
&lt;td&gt;every scan all outputs have defined values, no residue&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The race conditions it flagged (the actually useful part)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Entry de-dup vs. sensor sticking&lt;/strong&gt; — latching until the part leaves the entry photo-eye de-dupes naturally, but &lt;em&gt;only if the part actually leaves&lt;/em&gt;; otherwise later parts get missed. The process must guarantee single-piece spacing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When to latch the quality result&lt;/strong&gt; — it latches at the &lt;strong&gt;entry&lt;/strong&gt;, not at the sort position, because the quality system's result may not be stable by the time the part arrives, and you'd race the position signal. If quality detection is slow, use a handshake/queue instead of just delaying the read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Arrival vs. timeout in the same scan&lt;/strong&gt; — IEC &lt;code&gt;TON&lt;/code&gt; sets &lt;code&gt;Q = TRUE&lt;/code&gt; on the scan it reaches &lt;code&gt;PT&lt;/code&gt;. It checks timeout &lt;em&gt;before&lt;/em&gt; arrival and explicitly resets the timer on arrival. If you want "arrival wins," you must reorder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-trigger after push&lt;/strong&gt; — the part can still briefly cover the sort photo-eye. Going 70→20 directly would cause a double-sort. State 80 (DISCHARGE) waits for the eye to clear before re-arming.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interlock layering&lt;/strong&gt; — software only stops "logically simultaneous requests." It does &lt;strong&gt;not&lt;/strong&gt; replace pneumatic/electrical hard interlocks or mechanical limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Button priority in the same scan&lt;/strong&gt; — fault/safety &amp;gt; stop &amp;gt; reset &amp;gt; start, and this must be frozen in the requirements or implementations will diverge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Stop" has two meanings&lt;/strong&gt; — immediate vs. finish-current-part. It implements immediate (state 100); a controlled finish needs a &lt;code&gt;StopPending&lt;/code&gt; flag.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;PT = 0&lt;/code&gt; boundary&lt;/strong&gt; — &lt;code&gt;TON&lt;/code&gt; fires immediately, which can turn a timeout into a guaranteed fault. Validate the lower bound if the time is HMI-writable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Held reset button&lt;/strong&gt; — edge-triggered, so a long press doesn't keep masking a recurring fault.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First-scan init&lt;/strong&gt; — state 0 does init; a proper project would also use &lt;code&gt;OB100&lt;/code&gt;. Kept state 0 so init stays visible and testable inside the state machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple instances&lt;/strong&gt; — timers and edge memories live in FB statics, so &lt;strong&gt;never share one instance DB across lines&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sim ≠ field&lt;/strong&gt; — the simulator doesn't model photo-eye chatter, pneumatic delay, or safety-circuit response time. All times must be tuned on the real machine.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The three things it explicitly refused to do
&lt;/h2&gt;

&lt;p&gt;This is the part I think matters most, and why I wrote this up at all:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Emergency stop&lt;/strong&gt; must be realized by a hardware safety circuit (safety relay / safety PLC). The program only reads &lt;code&gt;i_xSafetyOK&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dual-cylinder hard interlock&lt;/strong&gt; must be pneumatic or electrical. The software interlock is &lt;em&gt;supplementary&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time parameters and sensor tuning&lt;/strong&gt; must be done on the real machine. The simulator can't give you real chatter or pneumatic delay.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It did the software part and handed the responsibility it can't carry back to a human. It didn't pretend to have "finished" something it hadn't.&lt;/p&gt;

&lt;p&gt;That's the property I'd want from any code generator touching industrial control: generate the verifiable part, and be explicit about what it can't verify.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The simulator passing does &lt;strong&gt;not&lt;/strong&gt; mean it's field-ready.&lt;/li&gt;
&lt;li&gt;I only checked that the code compiles, downloads, and matches the test cases at a logical level — I did not bench-test on real hardware.&lt;/li&gt;
&lt;li&gt;The agent's self-reported step log is in the full write-up; treat model self-reports with the usual skepticism.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Full write-up with screenshots, the complete source, and the agent's step log: &lt;a href="https://www.hubwiz.com/blog/ai-powered-plc-programming-with-workbuddy-and-tialink/?utm_source=devto&amp;amp;utm_medium=syndication" rel="noopener noreferrer"&gt;AI-powered PLC programming with WorkBuddy + TiaLink&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I work on TiaLink, the MCP bridge that drives TIA Portal, built by our team at 汇智网 (hubwiz.com). The agent used in this experiment was Tencent's WorkBuddy.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>automation</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
