Greenhouse Controls Proof of Concept

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.

Phone-friendly single HTML file
Controls-first, not design-first
PLC + backend + UI tied together
Good for a live Chad/Andy demo
Boom state
IDLE
AUTO / Day program
Selected zone
Waiting for irrigation demand
Critical alarms
0
No active trips
Climate action
Stable
VPD advisory pending

Climate supervisor

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.

Greenhouse temp72 °F
Relative humidity78 %
Outdoor temp58 °F
ProgramDay
Heat valve0%
Vent command0%
Fog / humidify0%
Dehumidify + reheatOFF
Circulation fans Climate stable VPD advisory
Example logic: maintain temperature band first, suppress fog when dehumidification is active, and bias venting vs. heating from outside-air conditions. This is intentionally simple enough to explain live, but still structured the way a real supervisory layer is structured.

Irrigation boom cell

Simulated rail boom sequence with prechecks, prime, traverse, pressure supervision, return-home recovery, and a degraded fallback if a zone moisture sensor fails.

Tank level72%
Line pressure58 psi
Boom position0%
Zone 1 moisture47%
Zone 2 moisture44%
Zone 3 moisture52%
Zone 4 moisture49%

Animated process view

One picture, but the right picture: position, active zone, spray state, and discrete outputs.

HOME BOOM BAY END LIMIT
Zone 1
Zone 2
Zone 3
Zone 4
B
Pump Spray enable Zone valve Boom drive
Current message: awaiting irrigation demand. A dry zone below threshold will request service if interlocks are healthy.

Event log

This is what convinces a controls person: visible sequence transitions and alarm behavior, not just a pretty dashboard.

Boom state machine

Each state has clear entry/exit conditions. The model can generate this first, then code it, then generate the UI from the same contract.

IDLE

Monitor zone moisture, schedule window, tank level, and faults. Choose driest valid zone only when auto is enabled or operator forces a cycle.

PRECHECK

Verify E-stop healthy, tank above minimum, drive not faulted, and home reference available. If not home, recover home before permitting spray.

PRIME

Open selected zone valve, start pump, and confirm pressure within timeout. No spray allowed until pressure proves and permissives stay true.

TRAVERSE

Drive boom across bay, maintain spray only with pressure healthy. Abort spray on low pressure, E-stop, or drive overload; then return home safely.

RETURN_HOME

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.

COMPLETE

Update irrigation complete record, restore command outputs to idle, increment zone moisture estimate, and release the next demand decision.

FAULT

Latched stop for E-stop, persistent drive fault, or repeated critical failures. Manual reset required. This is the difference between automation and disciplined automation.

Interlocks and degraded behavior

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.
This is the controls argument in one page: the sequence is only half the job. The other half is handling incomplete information, bad feedback, false trips, and recovery paths without creating unsafe or incoherent behavior.

Climate supervisory sequence

Small example of a Priva-class supervisory layer: one engine coordinating multiple actuators around setpoint bands and humidity logic.

Day mode
Target 74 °F. If temperature is low, stage heat. If high, stage vent. If RH exceeds high limit while temp is below target, permit dehumidify + reheat instead of aggressive venting.
Night mode
Target 68 °F with wider RH tolerance. Reduce vent bias at low outdoor temperature, keep circulation active when humidity is elevated, and only humidify if RH is below minimum and no dehumidification call exists.
Output priority
Heat and vent should not fight. Fog/humidification suppresses when dehumidify is active. Circulation fans run when any climate action is nonzero or RH is persistently high.
Open issues
Real project review would still validate sensor placement, vent curve shape, anti-windup strategy, humidity averaging, and staging delays to prevent hunting.

System architecture

The hard part is not drawing the page. The hard part is making all layers agree on tags, modes, commands, alarms, and recovery behavior.

  • Phone UI / HMIReads point snapshot, active alarms, sequence state, and trend values. Issues operator commands only through a command contract.
  • API / edge serviceNormalizes point names, permissions, command validation, schedule overrides, and history aggregation.
  • Rules / orchestration layerBuilds recipes, irrigation demand ordering, climate schedules, and operator workflows from business rules.
  • PLC / BAS runtimeOwns the real-time state machine, interlocks, safe fallbacks, watchdog handling, and direct device control.
  • Historian + alarmingStores cycle records, irrigation completion, climate excursions, nuisance trip counts, and audit traces.
This is where AI is now disproportionately useful: generating a consistent point model, API schema, alarm map, UI, and PLC skeleton from one system description.

Suggested point contract

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}
  ]
}

Command contract

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"
}

Event payload

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
}

Where AI speeds up the backend

Fast to automate:
tag dictionaries, JSON schemas, endpoint scaffolds, alarm catalogs, HMI bindings, historian naming, BACnet/Modbus mapping drafts, commissioning checklists, and boilerplate validation logic.
Still needs the engineer:
actual IO design, device behavior assumptions, safe fault responses, timing validation, tuning, field acceptance, cybersecurity boundaries, and anything touching process risk.
Best AI workflow:
write the control narrative first, derive point model second, generate PLC + API + UI from the same narrative third, then red-team the result for contradictions.
Bad AI workflow:
generate screens first and improvise the data model later. That creates a demo, not a system.

Structured Text skeleton

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;

Suggested tag model

Even this is persuasive in a demo, because it shows the naming discipline AI can generate almost instantly.

  • InputsGH3.BM1.TankLevelPct, GH3.BM1.LinePressurePsi, GH3.BM1.HomeSW, GH3.BM1.EndSW, GH3.BM1.DriveFault, GH3.Zone02.MoisturePct
  • CommandsGH3.BM1.Cmd.AutoEnable, GH3.BM1.Cmd.StartCycle, GH3.BM1.Cmd.ResetFault, GH3.BM1.Cmd.HomeReturn
  • OutputsGH3.BM1.Out.PumpEnable, GH3.BM1.Out.SprayEnable, GH3.BM1.Out.DriveFwd, GH3.BM1.Out.DriveRev, GH3.BM1.Out.ZoneValveCmd
  • StatusGH3.BM1.Sts.State, GH3.BM1.Sts.SelectedZone, GH3.BM1.Sts.AutoMode, GH3.BM1.Sts.DegradedMode
  • AlarmsGH3.BM1.Alm.LowTank, GH3.BM1.Alm.LowPressure, GH3.BM1.Alm.DriveFault, GH3.Zone02.Alm.SensorBad
This is where a modern coding agent becomes practical. It can keep the tag model, PLC declarations, REST contract, historian bindings, and mobile UI aligned instead of forcing the engineer to hand-maintain four different representations.

How to present this

  • Step 1: show the live operator tab.Move temp, RH, and fault conditions. Let the page react. That proves this is not a static mockup.
  • Step 2: force a cycle and trip it.Start irrigation, then toggle low pressure or drive fault. The model shows the recovery path and alarm handling.
  • Step 3: open Sequence and PLC Logic.That is where a controls engineer decides whether the prototype has substance.
  • Step 4: open Backend.Explain that AI now drafts the point model, API contract, historian events, and UI from the same spec instead of each piece being done by hand in isolation.

The honest claim

What AI compresses
Blank-page engineering work: sequences, tag dictionaries, state diagrams, alarm matrices, UI scaffolds, code skeletons, API contracts, and revision cycles.
What it does not replace
Process understanding, safe failure handling, hardware choices, timing validation, commissioning, tuning, field debugging, and accountability for the final design.
What changed
The bottleneck moved. Creating the first complete draft is fast now. Vetting it properly is the real engineering constraint.

Old workflow vs AI-assisted workflow

Before: define sequence, draft tags, write PLC scaffolding, draft alarms, sketch HMI, then notice contradictions and loop back manually.
Pain point: every change propagates slowly and inconsistently across code, UI, documents, and backend bindings.
Now: define the control narrative once, ask AI to derive state machine, tag model, PLC skeleton, API schema, event payloads, and phone UI together.
Result: the engineer spends more time reviewing control correctness and less time doing repetitive translation work.
This is the unfair advantage for the right person: AI does not magically remove controls engineering, but it removes a large amount of the routine work that used to make iteration slow.