# Guide 6: A HEART-Embedded greenhouse irrigation system

**A case study.** A maker of greenhouse irrigation controllers wants its products to be HEART-embedded systems: each controller should know when it is unwell, say so in one standard language, tell the grower, and never leak anything it should not. It also wants something more: to let an AI suggest better watering schedules, **without ever letting an AI water the plants on its own**. This guide goes step by step from day 1 to a system that can claim **HEART Basic, Autonomy Level A1**, shows how an owner registers it and watches it on HEART Hub, and demonstrates the update function of the Core Standard (Section 7): the brain proposes, the HEART decides, and a person approves at the greenhouse.

A working demo comes with it: [`greenhouseSimulator/`](../greenhouseSimulator/README.md), one Python file that simulates a greenhouse, its controller, its HEART and a **mock** brain, and reports to `https://heartcortex.ai`.

> **Status.** A design case study with a simulator. No real greenhouse, controller or model was involved. The brain in the simulator is a mock (fixed rules, not a model), clearly labelled. Each step marks what is simulated and what a real product would need.

---

## Before you start: what HEART will and will not do

An irrigation controller's job, opening valves and running the pump on a schedule, is **control**. Its safety limits, such as never running the pump dry, sit in the firmware and must never depend on anything else. HEART stays out of them.

| Stays in the controller and its firmware | Goes to HEART |
|---|---|
| Running the pump and valves on the schedule | Knowing whether the plants are actually getting water |
| The tank interlock: the pump never runs dry | Watching the tank, pump, probes, valves and Wi-Fi |
| The daily maximum per zone; the pump's thermal cut-out | Raising alerts with standard fault codes, and telling the grower |
| The schedule itself, and the panel that sets it | Asking a brain about problems its rules cannot fix |
| Anything that protects the pump, the plants or people | Trying a proposed schedule change **only after a person approved it**, watching it, and undoing it if it goes wrong |

This is the general rule of the standard (Part 6: HEART stays out of the safety chain and out of real-time control loops, and never bypasses an interlock). HEART changes one **supervisory** value, the watering minutes per zone per day, through the same path the controller's own panel uses, and the firmware still checks every value against its own limits.

**What is physical here.** Watering moves water onto living plants: any change to the schedule is **physical**. So every change an AI proposes needs a person's approval, every time, whatever the envelope says (Part 6, Section 2.3; Core Standard 7.4, rule 4).

---

## Step 1: Decide where HEART runs

```
probes, tank, pump, valves ──► irrigation controller ──(Modbus / local API)──► HEART ──(internet)──► HEART Hub
                               firmware, schedule,                              on the controller,     and the grower
                               interlocks                                       or a small box beside it
```

| Part | What it does | HEART's place |
|---|---|---|
| **Controller** | Runs pump and valves on the schedule; holds the interlocks | Read; one writable value (the schedule), through its own interface |
| **HEART** | On the controller if it is an embedded Linux board or an ESP32, otherwise on a small computer beside it | Runs here, with the button for approvals and Stop |
| **Brain** | A model, anywhere: in the Model Slot, on a Gateway, in the cloud | Sees a Consultation, sends a Proposal. Never a command |
| **Grower** | Decides | Approves at the greenhouse; receives escalations |

**In the simulator**, all of this is one process. `Controller` is the firmware, `Heart` is the HEART, and `MockBrain` is the brain.

---

## Step 2: Give the system an identity

The HEART serves a Card at `/.well-known/heart-card.json`:

```json
{ "system": { "id": "com.example.irrigation.gh-controller", "name": "Example greenhouse controller", "version": "1.0.0" },
  "level": "basic", "bindings": ["http-server"], "pulse": { "intervalSec": 30 },
  "modules": ["properties", "control"],
  "shares": { "pulse": "public", "arrhythmia": "public", "property": "public", "trial": "internal", "proposal": "internal" },
  "autonomy": { "level": 1 }, "template": "heart/embeddedstandard/python/v1.2611.2" }
```

To the owner (and only the owner) it also lists its capabilities: `set_watering_zone1` to `set_watering_zone3` (`physical: true`, `risk: write`, `reversible: true`, `callers: ["ai"]`, an interlock that the controller must answer), and `flush_lines` (`callers: ["human"]`: only a person at the panel). The simulator uses system ID `org.heartaispec.demo.greenhouse` and template `heart/embeddedstandard/greenhouse-simulator/v1.2611.2`.

The system ID names the product, never the grower or the site. The HEART Hub device ID (Step 8) is random.

---

## Step 3: List what the system knows, and how private each item is

A greenhouse holds little personal data, but some: when people come and go, and who approved what.

| Value | Source | Privacy | Why |
|---|---|---|---|
| Healthy or not; open fault codes | Derived by HEART | `public` | The only things HEART Hub ever receives (Step 8) |
| Tank status: ok, low, empty | Controller | `public` | Needed to act; reveals no more than the alert does |
| Tank level, water used today | Controller | `internal` | Operations |
| Soil moisture per zone | Probes | `internal` | Operations; also the grower's crop know-how |
| Watering minutes per zone (the schedule) | Controller | `internal` | The grower's recipe |
| Valve and pump state, pump temperature | Controller | `internal` | Device health |
| Air temperature, humidity, sunlight | Sensors | `internal` | Operations |
| Internet link state | HEART | `internal` | |
| Consultations, Proposals, Trials, Actions, learned Playbooks | HEART | `internal` | What was tried and why. A Consultation sends features at their own level, never raw samples |
| Door open or closed; door visits | Door sensor | `private` | Reveals when people are in the greenhouse |
| Who approved a change | HEART | `private` | A person |
| Grower's name, address, crop contracts | Grower's own systems | **not on the controller** | The HEART does not need them |

Only `public` items leave for HEART Hub. What the brain may see is decided by the Valve: here, `internal` features of the zone's soil, the air and the tank, and never the door.

---

## Step 4: Say what "doing its main job" means

The main job of an irrigation system is: **every zone gets the water its schedule says, and its soil stays in its band.**

```python
def check_main_function():
    """CHANGE: return (True, "") if watering works, else (False, "what is wrong, in plain words")."""
    if tank_level() < TANK_INTERLOCK:
        return False, "The tank is empty: the pump is locked out, nothing can be watered."
    return True, ""
```

Less than that is **degraded**: the tank below 20 % (watering will stop soon), or a zone's soil below 25 % (plants will wilt). A condition must hold for 5 seconds before it is raised, because one odd reading is not a fault.

---

## Step 5: The faults it recognises

Every alert carries a fault code from Part 3, Section 5.4. Each code below was checked against that list.

| What happens | Fault code | Severity | Status |
|---|---|---|---|
| Tank below 5 %: the firmware locks the pump out | `system.main-function-lost` | critical | failing |
| Tank below 20 %, or a zone's soil below 25 % | `system.main-function-degraded` | warning | degraded |
| A zone's 5-minute mean above its 35 to 45 % band | `performance.overshoot` | warning | degraded |
| A soil probe's reading unchanged for 90 s, through watering | `sensors.stuck` | error | degraded |
| A zone watered twice and its soil not wetter (valve stuck or line blocked) | `actuators.not-responding` | error | degraded |
| Pump above 70 °C (the firmware cuts out at 80 °C) | `performance.too-hot` | warning | degraded |
| The greenhouse Wi-Fi is down | `connectivity.link-lost` | warning | degraded |
| A Trial's undo could not run (Core Standard 7.5) | `actuators.not-responding` (component `trial`) | critical | failing |
| Silent for three Pulse intervals | `system.flatline` | | raised by HEART Hub |

Two details matter. A stuck probe also makes a valve look stuck (the reading does not rise), so the valve check is skipped while the probe is stuck. And the valve check compares the rise after watering with where the soil was heading anyway, so a hot afternoon is not mistaken for a stuck valve.

The thresholds here are illustrations. A real product takes them from its crops and its controller's specification.

---

## Step 6: Commands, approvals and Stop

This is where the greenhouse differs from a HEART that only watches. It has **one kind of change**, the watering minutes of a zone, and it may come only one way.

**No direct changes.** Every `PUT`, `POST`, `PATCH` or `DELETE` on properties is refused with `403 capability-not-allowed`. The grower sets the schedule on the controller's panel as before.

**The update function: the brain proposes, the HEART decides** (Core Standard 7.1 to 7.6).

1. **Detect, reflex first.** Zone 1's soil stays above its band (`performance.overshoot`). No adopted Playbook covers it, so the HEART consults its brain, about one problem at a time.
2. **Consultation.** The HEART summarises, on the device: statistics (value, min, max, mean, trend) of zone 1's soil, the air and the tank over 10 minutes, each with its privacy level; the one candidate, `set_watering_zone1`, with its current value (read by the HEART), declared range (10 to 60), the owner's envelope (15 to 45, steps of at most 3, at most 2 Trials a day, 600 s apart), its guard, and `physical: true`. Never raw samples, never the door.
3. **Proposal.** The brain answers: *water 20 % less, 30 → 24 min/day, in two steps (27, wait 120 s, then 24)*; `expect`: the 4-minute mean ends up ≤ 45 % and ≥ 35 % within 6 minutes of the last step; `undoIf`: the reading falls below 30 % within 6 minutes. It sends no current value: it cannot.
4. **Check.** The HEART checks the Proposal in the order of Core Standard 7.3 (shape, mode, target, callers, not destructive, range, input schema, can read the current value, no Trial live on the target), and also the owner's envelope. It refuses the first failure with its problem code. It reads the value to restore (30) from the controller itself.
5. **One approval for the whole plan.** The Proposal becomes an Action with initiator `ai`, risk `destructive` (a physical change started by an AI counts as destructive) and a digest over its arguments, **every step and the value the undo will restore**. The controller shows a short code from the digest; the grower checks it and **presses the button on the controller**. One press approves the plan and its undo. An approval sent over the network with the key alone is refused with `403 presence-required`, so neither the key nor an AI agent holding it can water the plants.
6. **Try.** Step 1 (27), then after `waitSec`, step 2 (24). Each step passes the guard (the controller answers), stays inside the envelope, and goes through the firmware's own check. Later steps and the undo are Actions whose `approvedBy` names the first.
7. **Verify.** From the first step the HEART watches `undoIf`. The Trial is **kept** only when every `expect` has held and the longest `undoIf` watch has passed. If `undoIf` fires, an `expect` does not hold in time, a step fails, or Stop is pressed, the HEART restores 30 on its own. If even the undo cannot run, the Trial ends `undo-failed`, a critical alert opens, and automatic action stops.
8. **Remember.** A kept Trial becomes a **learned Playbook** with `status: proposed`: "when the mean is above 45, set 24". It **never runs** until a person adopts it, with proof of presence and `adoptedPhysical: true`, and then for at most a year before review.

| Trial state | Next |
|---|---|
| `awaiting-approval` | `running` (approved), `refused` (denied, expired, guard), `stopped` (Stop) |
| `running` | `kept`, `undoing`, `refused` (first step failed) |
| `undoing` | `undone`, `stopped` (Stop caused it), `undo-failed` |
| `kept`, `undone`, `refused`, `stopped`, `undo-failed` | Final: the Outcome |

**Stop.** Holding the button on the controller (in the simulator, `touch button.hold`), or `POST /heart/v1/tending/stop` with the owner key, stops all automatic action at once: waiting Actions are cancelled, a running Trial is undone and ends `stopped`, and every new Proposal gets `409 autonomy-stopped`. It needs no approval and no internet. Resuming gives authority back, so it waits for a press at the controller.

**In the simulator.** The brain is `MockBrain`: fixed rules dressed as a model. A remote brain can also deliver a Proposal with `POST /heart/v1/tending/proposals` and a brain key (scope `heart.propose` only, never the owner's key). Adopting a learned Playbook is not part of the demo (`501`). The simulator's envelope check, `maxPerDay` and `cooldownSec` apply to every Trial, even those a person approves: stricter than the standard asks, as a HEART may always be. **A real product** needs a real button (or a passkey) as its proof of presence, a real model or Gateway behind the Proposal, and Trials stored so that a restart undoes a running one (Core Standard 9).

---

## Step 7: Tell a person

Autonomy Level A1 needs an escalation route that works with no internet:

1. **On the controller:** a light or a buzzer, and the waiting change's code on its display.
2. **To the grower:** the app, an SMS, or the farm's own dashboard.

Change `escalate()` to use them. HEART Hub (Step 8) adds email when the greenhouse goes silent.

---

## Step 8: Register and watch it on HEART Hub

1. **First start.** The controller makes a random device ID, claim code and key, keeps them, and shows the claim code only on its label or display.
2. **Sign in.** The grower opens `https://heartcortex.ai/login` and enters an email address. A 6-digit code arrives; there are no passwords.
3. **Register.** At `https://heartcortex.ai/register?id=<device ID>` (a QR code on the label can open it), they enter the claim code.
4. **Watch.** The device's page, `https://heartcortex.ai/device/<device ID>`, shows its status and open alerts, private until shared. Their devices are at `https://heartcortex.ai/devices`.
5. **Silence.** If no report arrives for three Pulse intervals, the page shows a Flatline and the grower gets one email.

Only the public Pulse and public alerts are sent, and the site keeps only the status, timing and fault codes: never a soil reading, the schedule, a Proposal or a door visit.

**Approving from afar** (HEART Hub 7.1) is off by default. With it on, a waiting change can also be approved on the device page with an emailed code; the controller acts only on a decision signed by the site key it pinned, for the exact digest, at most 300 s old. Because watering is physical, the owner must also turn on `remoteSitePhysical` **on the controller itself**. It is weaker than a press at the greenhouse: it proves the grower could read their inbox, not that anyone looked at the plants. The simulator has both settings, as `--monitor-approvals` and `--remote-site-physical`, with the checks copied from the Python template.

---

## Step 9: Try it with the simulator

```sh
python3 greenhouseSimulator/greenhouseSimulator.py --scenario all
```

Register it as in Step 8, then watch. Zones 1 and 2 start too wet. When the HEART asks, `touch button.press` in the folder it runs in: zone 1's Trial is **kept** and becomes a proposed Playbook. Then zone 2's Trial starts after your next press, and the staged hot spell dries the soil below 30 %: the Trial is **undone**, 30 min/day restored without anyone touching it. Meanwhile it stages a stuck valve, a lost Wi-Fi link, a stuck probe, an overheating pump and an emptying tank, each fixed a few minutes later. Details and every option are in [`greenhouseSimulator/README.md`](../greenhouseSimulator/README.md).

**What is simulated:** the whole greenhouse, the controller, its limits, the button, and the brain. A greenhouse day lasts 24 minutes. Durations in Trials count simulated seconds.

---

## Step 10: Test it, rate it and claim only what passed

1. Run `python3 greenhouseSimulator/test_e2e.py`: it checks every message against the schemas, the Trial paths (kept, undone, stopped), that nothing ran before approval, that steps stayed in the envelope, and that only public data reached the monitor site.
2. On a real product, run the **HEART-Rating client** (`heart-rating/client/heart_rating.py`) and the hands-on tests of Part 9: empty the tank, block a valve, unplug a probe, heat the pump, cut the Wi-Fi, and press Stop during a Trial.
3. Publish a **Declaration** with every result, and claim **HEART Basic, Autonomy Level A1**.

The Trial loop in this case study is a **demonstration of the rule**, not a claim of a higher level. What each level would need for this product (Part 2, Section 8):

| Level | For a greenhouse irrigation system |
|---|---|
| **A1** (notices and tells a person) | Yes: Steps 4, 5 and 7 |
| **A2** (says why, with a confidence) | HEART Connected: "zone 3 was watered twice and did not get wetter; the soil probe still changes, so the valve is likely stuck; confidence medium" |
| **A3** (suggests or makes a fix, a person approves) | HEART Full, with the `control` module: the Trial loop of Step 6, with Stop. Every watering change still waits for a person, because it is physical |
| **A4** (acts on its own within adopted rules) | Adopted Playbooks plus an allowlist entry for `rule`. A learned Playbook from a kept Trial, adopted by the grower with `adoptedPhysical: true` and a review date, could then run as a reflex, every run still a Trial with its own undo |
| **A5** (AI acts within its envelope) | The `ai` and `evolving` modules. Even at A5, a brain's **physical** change needs a person: the way to make a watering fix automatic stays an adopted Playbook, not the AI |

HEART conformance says nothing about electrical, pressure-equipment or water-supply rules. The controller still needs its own (for example backflow prevention for mains-fed tanks, and the electrical safety rules for pumps in wet places).

---

## What the case study shows

- **A HEART can change things without ever being in charge.** The controller keeps its interlocks, the firmware checks every value, and HEART changes one supervisory number only in a Trial a person approved.
- **The brain proposes; the HEART decides.** The brain sees features, not raw data; sends a Proposal, not a command; and supplies no current values. The HEART checks it, reads what to restore itself, and owns the undo.
- **One approval, for the whole plan.** The digest covers every step and the undo, so the person approves exactly what will happen, including how it will be taken back.
- **A Trial is honest about failure.** The same Proposal is kept on a normal day and undone in a hot spell. Both outcomes are evidence, and only the kept one becomes a proposed Playbook.
- **Physical means a person, at every level.** Envelopes narrow automatic changes; they never replace the person for a change that waters plants.
