Mobile-first demo showing the difference between a cosmetic UI and a controls-first prototype. This page includes supervisory climate logic, irrigation boom sequencing, interlocks, degraded modes, alarm behavior, backend contracts, and PLC-style state logic.
This is the part people usually miss in a shallow mockup: supervisory environmental logic that coordinates heat, vent, dehumidification, fog, and circulation from a small number of inputs and mode-dependent setpoints.
Simulated rail boom sequence with prechecks, prime, traverse, pressure supervision, return-home recovery, and a degraded fallback if a zone moisture sensor fails.
One picture, but the right picture: position, active zone, spray state, and discrete outputs.
This is what convinces a controls person: visible sequence transitions and alarm behavior, not just a pretty dashboard.
Each state has clear entry/exit conditions. The model can generate this first, then code it, then generate the UI from the same contract.
Monitor zone moisture, schedule window, tank level, and faults. Choose driest valid zone only when auto is enabled or operator forces a cycle.
Verify E-stop healthy, tank above minimum, drive not faulted, and home reference available. If not home, recover home before permitting spray.
Open selected zone valve, start pump, and confirm pressure within timeout. No spray allowed until pressure proves and permissives stay true.
Drive boom across bay, maintain spray only with pressure healthy. Abort spray on low pressure, E-stop, or drive overload; then return home safely.
Spray off, valve closed, pump off or staged down, boom returns to home switch. Recovery path is explicit so the machine does not get stranded in the bay.
Update irrigation complete record, restore command outputs to idle, increment zone moisture estimate, and release the next demand decision.
Latched stop for E-stop, persistent drive fault, or repeated critical failures. Manual reset required. This is the difference between automation and disciplined automation.
The useful demo is not “AI made a UI.” It is “AI understood what must happen when hardware does not behave.”
| Condition | Immediate response | Latched? | Engineering intent |
|---|---|---|---|
| E-stop | Spray off, pump off, drive stop, state = FAULT | Yes | Never auto-recover from a hard safety stop. |
| Low tank before start | Inhibit cycle start, alarm operator | No | Prevents dry-running and bad fertigation dosing. |
| Low pressure during spray | Spray off immediately, return home, alarm | Alarm only | Do not continue travel assuming water is still being applied. |
| Drive overload | Drive stop, spray off, FAULT | Yes | Mechanical issue requires inspection, not blind retry. |
| SCADA/HMI comm loss | PLC continues local sequence; remote commands inhibited | No | UI failure should not equal plant failure. |
| Zone moisture sensor bad | Flag degraded mode; use timed fallback recipe for that zone | No | Keep operating while making uncertainty explicit. |
Small example of a Priva-class supervisory layer: one engine coordinating multiple actuators around setpoint bands and humidity logic.
The hard part is not drawing the page. The hard part is making all layers agree on tags, modes, commands, alarms, and recovery behavior.
Single source of truth for tags. The same model can draft this, the HMI bindings, and the PLC declarations together.
{
"cell_id": "bay_3_boom_1",
"state": "TRAVERSE",
"mode": "AUTO",
"selected_zone": 2,
"inputs": {
"tank_level_pct": 72,
"line_pressure_psi": 58,
"boom_position_pct": 41,
"home_sw": false,
"end_sw": false,
"estop_ok": true,
"drive_fault": false,
"zone_moisture_pct": [47, 44, 52, 49]
},
"outputs": {
"pump_enable": true,
"spray_enable": true,
"drive_cmd": "FWD",
"zone_valve_cmd": 2
},
"alarms": [
{"code": "SENSOR_BAD_Z2", "severity": "warning", "latched": false}
]
}
A real backend blocks bad operator actions before they reach the controller.
POST /api/cells/bay_3_boom_1/commands
{
"command": "START_CYCLE",
"source": "mobile_hmi",
"user": "operator_17",
"require_state": ["IDLE"],
"deny_if": ["ESTOP", "LOW_TANK", "DRIVE_FAULT"],
"comment": "forced demo run"
}
This is what lets the historian, alarm panel, and production reporting stay consistent.
{
"timestamp": "2026-03-15T14:22:04Z",
"cell_id": "bay_3_boom_1",
"event": "STATE_CHANGE",
"from": "PRIME",
"to": "TRAVERSE",
"selected_zone": 2,
"reason": "PRESSURE_PROVEN",
"pressure_psi": 54.7,
"operator": null
}
Not complete production code. Enough to show that the sequence is grounded in PLC-style control logic, not vague app language.
TYPE E_BoomState : (
IDLE,
PRECHECK,
PRIME,
TRAVERSE,
RETURN_HOME,
COMPLETE,
FAULT
); END_TYPE;
VAR
State : E_BoomState := IDLE;
AutoEnable : BOOL := TRUE;
ForceStart : BOOL;
EStopOK : BOOL;
DriveFault : BOOL;
LowTank : BOOL;
PressureOK : BOOL;
HomeSW : BOOL;
EndSW : BOOL;
SelectedZone : INT := 0;
ZoneMoisture : ARRAY[1..4] OF REAL;
ZoneSensorBad : ARRAY[1..4] OF BOOL;
PumpEnable : BOOL;
SprayEnable : BOOL;
DriveFwd : BOOL;
DriveRev : BOOL;
ZoneValveCmd : INT;
tPrime : TON;
tComplete : TON;
END_VAR;
CASE State OF
IDLE:
PumpEnable := FALSE;
SprayEnable := FALSE;
DriveFwd := FALSE;
DriveRev := FALSE;
ZoneValveCmd := 0;
IF (AutoEnable OR ForceStart) AND EStopOK AND NOT DriveFault AND NOT LowTank THEN
SelectedZone := SelectDriestValidZone(ZoneMoisture, ZoneSensorBad);
IF SelectedZone > 0 THEN
State := PRECHECK;
END_IF;
END_IF;
PRECHECK:
IF NOT EStopOK OR DriveFault THEN
State := FAULT;
ELSIF NOT HomeSW THEN
State := RETURN_HOME;
ELSE
ZoneValveCmd := SelectedZone;
PumpEnable := TRUE;
tPrime(IN := TRUE, PT := T#4S);
State := PRIME;
END_IF;
PRIME:
ZoneValveCmd := SelectedZone;
PumpEnable := TRUE;
IF NOT EStopOK OR DriveFault THEN
State := FAULT;
ELSIF PressureOK THEN
SprayEnable := TRUE;
DriveFwd := TRUE;
State := TRAVERSE;
ELSIF tPrime.Q THEN
RaiseAlarm('LOW_PRESSURE');
State := RETURN_HOME;
END_IF;
TRAVERSE:
ZoneValveCmd := SelectedZone;
PumpEnable := TRUE;
DriveFwd := TRUE;
SprayEnable := PressureOK;
IF NOT EStopOK OR DriveFault THEN
State := FAULT;
ELSIF NOT PressureOK THEN
RaiseAlarm('LOW_PRESSURE_DURING_SPRAY');
SprayEnable := FALSE;
State := RETURN_HOME;
ELSIF EndSW THEN
SprayEnable := FALSE;
State := RETURN_HOME;
END_IF;
RETURN_HOME:
ZoneValveCmd := 0;
SprayEnable := FALSE;
PumpEnable := FALSE;
DriveFwd := FALSE;
DriveRev := NOT HomeSW;
IF NOT EStopOK OR DriveFault THEN
State := FAULT;
ELSIF HomeSW THEN
tComplete(IN := TRUE, PT := T#1S);
State := COMPLETE;
END_IF;
COMPLETE:
IF tComplete.Q THEN
ApplyRecipeCompletion(SelectedZone);
ForceStart := FALSE;
State := IDLE;
END_IF;
FAULT:
PumpEnable := FALSE;
SprayEnable := FALSE;
DriveFwd := FALSE;
DriveRev := FALSE;
ZoneValveCmd := 0;
END_CASE;
Even this is persuasive in a demo, because it shows the naming discipline AI can generate almost instantly.