# Guide 1: A HEART-Embedded air-conditioner controller

This guide takes an AC temperature controller from day 1 to a unit that knows itself, looks after itself and answers in one standard language.

The finished example is in `heart-core-rs/examples/ac-controller`. Every command shown here has been run against it with a simulated control board.

---

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

HEART **watches, explains, advises and acts with permission**. It does **not** control the compressor.

| Stays in your firmware or hardware | Goes to HEART |
|---|---|
| Thermostat control loop | Watching temperatures, run times and faults |
| Compressor anti-short-cycle timer | Spotting short cycling and explaining it |
| High and low pressure trips | Raising alerts and escalating to the installer |
| Freeze protection, electrical protection | Answering "how is the unit?" in one standard format |
| Anything a safety standard covers | Changing the setpoint or mode, through checks and approval |

This split is required by the standard (API Standard, Section 15). If HEART crashes, hangs or is removed, the unit must keep cooling safely.

---

## Day 1: Decide the hardware

The reference Core today needs **embedded Linux** (class Core-M). In testing it used about 5.6 MB of memory at rest, and its program is 2.9 MB.

| Your controller | What to do |
|---|---|
| Linux board, such as a Raspberry Pi Compute Module or i.MX | Run `heartd` beside your control program. **Recommended.** |
| Microcontroller only | Pair it with a small Linux module, or use a building Gateway (see Step 10). The microcontroller-only Core (Core-S) is not built yet. |
| Many units in one building | Each unit runs `heartd` on its own IP address, or a building controller runs a HEART Gateway for all of them |

Your control program and `heartd` talk through one of two routes:

- **Control-board registers.** In the example, the control board exposes Modbus TCP registers. Modbus TCP is supported today.
- **The host feed.** Your firmware writes one line of JSON per event to a local socket.

If your board speaks something else, such as UART, CAN or BACnet, you will need to add an adapter in `crates/heartd/src/adapters.rs`. Only `modbus-tcp` and `process` adapters exist today.

---

## Step 1: Give the unit an identity (the HEART Card)

Create `card.json`. The system ID must be a reverse-DNS name under a domain you own.

```json
{
  "system": { "id": "com.example.cool-air.ac-controller", "name": "CoolAir AC Controller",
              "version": "0.1.0", "alias": "ac-unit", "owner": "Example Climate Ltd" },
  "level": "full",
  "bindings": ["http-server", "unix-socket"],
  "pulse": { "intervalSec": 30 },
  "modules": ["control", "properties", "custom", "core"],
  "shares": { "pulse": "public", "rhythm": "internal", "userData": "never-share" },
  "valve": { "llmCeiling": "internal" },
  "capabilities": []
}
```

| Field | Why |
|---|---|
| `level: full` | You want it to act on requests, and plain-language serving (`core`) needs Full |
| `modules` | `control` lets a person stop all autonomy; `properties` and `custom` expose your values and commands |
| `shares` | Anyone may see that the unit is healthy; only trusted parties see its event history |

---

## Step 2: List what the unit knows, and how private each item is

Write down every value worth knowing, and give each a privacy level **before** writing any code.

| Value | Source | Privacy | Why |
|---|---|---|---|
| Healthy or not | Derived by HEART | `public` | Anyone may know if it works |
| Room temperature | Register 40001 | `internal` | Owner and installer |
| Compressor running | Register 40011 | `internal` | |
| Setpoint and mode | Registers 40021, 40022 | `internal` | |
| Occupancy, daily schedule | Your firmware | `private` | Reveals when people are home |
| Wi-Fi password, installer PIN | Your firmware | `never-share` | Never leaves the unit, not even to an AI |

Each readable value becomes a **property definition**, as in `custom/room-temperature.json`:

```json
{
  "name": "com.example.hvac.room_temperature",
  "kind": "property",
  "title": "Room temperature",
  "description": "Current room temperature measured by the return-air sensor.",
  "valueSchema": { "type": "number" },
  "unit": "Cel",
  "privacy": "internal",
  "native": {
    "protocol": "modbus-tcp",
    "target": "tcp:127.0.0.1:1502:1",
    "request": { "function": 3, "register": 40001, "count": 1 },
    "response": { "extract": { "value": { "from": "registers[0]", "scale": 0.1 } } }
  }
}
```

The board stores tenths of a degree, and `scale` turns 245 into 24.5. HEART also polls each property, so it notices when the board stops answering.

**Write titles and descriptions the way a homeowner talks.** HEART uses them to understand plain requests.

---

## Step 3: Define what the unit can be asked to do

Each command becomes a **command definition**, with limits, as in `custom/set-setpoint.json`:

```json
{
  "name": "com.example.hvac.set_setpoint",
  "kind": "command",
  "title": "Set temperature",
  "description": "Set the target room temperature in degrees Celsius.",
  "inputSchema": { "type": "object", "required": ["celsius"], "additionalProperties": false,
                   "properties": { "celsius": { "type": "integer", "minimum": 16, "maximum": 30 } } },
  "risk": "write",
  "physical": true,
  "privacy": "internal",
  "callers": ["human", "ai"],
  "guards": { "minIntervalSec": 10, "maxPerHour": 30 },
  "native": { "protocol": "modbus-tcp", "target": "tcp:127.0.0.1:1502:1",
              "request": { "function": 6, "register": 40021, "value": "{{arguments.celsius}}" } }
}
```

| Setting | Effect |
|---|---|
| `minimum`, `maximum` | 35 °C can never reach the board |
| `physical: true` | Requests from an AI or another system always need a human's approval |
| `guards` | Refuses rapid repeats that would wear the compressor |
| `callers` | People and AI may ask. Other systems may not |

The example defines three commands (set temperature, set mode, turn off) and two properties (room temperature, compressor running).

> **Pitfall found while testing.** Without its own `turn_off` command, "Turn the AC off" matched HEART's standard "stop all autonomous action" operation. That is safe, but it does not do what the person meant, and it blocks later automatic actions. **Give every common request its own command with a plain title.**

---

## Step 4: Teach the unit its reflexes (Tending rules)

Rules let the unit spot and explain problems with no network and no AI. They go in `rules.json`:

| Rule | Watches | Result |
|---|---|---|
| `controller-silent` | Board not answering 3 times in a minute | Alert: "Control board is not answering" |
| `short-cycling` | 6 compressor starts in an hour | Alert, with advice |
| `weak-cooling` | Supply and return air too close in temperature | "Check the filter, then call a technician" |

```json
{ "id": "short-cycling", "enabled": true,
  "description": "The compressor is starting too often, which wears it out",
  "when": { "event": "compressor.started", "count": 6, "withinSec": 3600 },
  "then": { "raise": "com.example.hvac.short-cycling", "severity": "warning",
            "summary": "Compressor started 6 times in an hour" } }
```

Start with alerts only. Add automatic actions later, and only `write` ones you have listed in `core.allowlist`. HEART never runs `destructive` actions without a person.

---

## Step 5: Report events from your firmware (the host feed)

Your control program tells HEART what happened by writing one JSON object per line to the host socket. HEART replies `ok`, or `error` with a reason.

```text
{"event": "compressor.started", "component": "compressor"}
{"vital": "deltaTCelsius", "value": 8.4}
{"state": "cooling"}
{"event": "filter.reminder", "severity": "warning", "summary": "Filter due in 7 days"}
```

In C, this is a `connect()` to `/run/heart/host.sock` and a `write()`. The socket is readable and writable by the product's own user only.

Event names are dotted lower case. Names starting `circulation.`, `security.` or `valve.` are refused, because they are reserved for HEART.

---

## Step 6: Run it

```sh
cd heart-core-rs
cargo build --release -p heartd
./target/release/heartd check examples/ac-controller/heartd.toml     # prints "configuration is valid"
```

For development, create an issuer key and tokens:

```sh
python3 scripts/dev-token.py keygen examples/ac-controller >> examples/ac-controller/heartd.toml
python3 scripts/dev-token.py token examples/ac-controller owner internal human heart.read heart.ask
```

On the real unit:

1. Put sockets in `/run/heart/`. Unix socket paths must be shorter than 108 bytes.
2. Set `bind` to the unit's address, and add `tls_cert` and `tls_key`. `heartd` refuses plain HTTP anywhere but loopback.
3. Install `packaging/heartd.service`. It caps HEART at 32 MB of memory and 10% of one CPU, so it can never starve the controller.
4. Reserve the port: `net.ipv4.ip_local_reserved_ports = 43278`.

Then check the result:

```sh
curl -s http://127.0.0.1:43278/heart/v1/pulse                     # anyone: {"status": "healthy", ...}
curl -s -H "Authorization: Bearer $TOKEN" \
     http://127.0.0.1:43278/heart/v1/properties/com.example.hvac.room_temperature   # 24.5 Cel
```

---

## Step 7: Decide who may do what

Tokens come from your OAuth 2.0 authorisation server. Each carries a clearance, a kind and scopes.

| Who | Clearance | Kind | Scopes |
|---|---|---|---|
| Homeowner's app | `internal` | `human` | `heart.read heart.ask heart.invoke` |
| Installer | `private` | `human` | the above, plus `heart.approve heart.tend heart.admin` |
| Building management system | `internal` | `peer` | `heart.read heart.send` |
| Public dashboard | none needed | | Sees only public fields |

Start the unit in `mode = "suggest"`. HEART then proposes actions and a person approves them. Switch to `act` once you trust its rules. Anyone with `heart.tend` can stop all autonomy at once with `POST /heart/v1/tending/stop`.

---

## Step 8: Plain-language requests

With no AI model at all, these are the tested results:

| Request | Result |
|---|---|
| "Set the temperature to 22" | Understood: `set_setpoint`, `celsius: 22`, **waiting for approval** |
| "What is the room temperature?" | Answered at once: 24.5 Cel |
| "Turn the AC off" | Understood: `turn_off`, waiting for approval |
| "It is too hot in here" | Not understood. It needs a model, and HEART does not guess |

Anything that changes the unit always waits for confirmation in your app, even from plain language, because any interpretation can be wrong.

---

## Step 9: Add AI later, if you want it

Add a `[model]` section pointing at your organisation's Reasoning Gateway, with a daily budget. HEART then sends the model only what the Valve allows (`llmCeiling = internal`), never occupancy or secrets, and records a receipt of every call. "It is too hot in here" can then be understood. The AI still cannot change anything without approval.

---

## Step 10: Many units in one building

| Situation | Set-up |
|---|---|
| Each unit has its own IP address | Each listens on 43278. Nothing to coordinate |
| Several controllers on one Linux box | Run `heartd gateway` on that box. Each unit adds an `[uplink]` section and joins it |
| Microcontroller-only units | They push their Pulse to the building Gateway |

The building then answers `heart://building-controller/systems` with every unit's Card, and `heart://building-controller/systems/ac-unit/pulse` for one unit.

---

## Step 11: Test before shipping

| Test | How |
|---|---|
| Files are valid | `heartd check` |
| It detects a dead board | Stop the simulator: the alert appears within seconds, and clears when the board returns |
| Limits hold | Send `celsius: 35`: refused before reaching the board |
| It survives crashes | Kill `heartd` during a write: the action is never repeated on restart |
| Plain language | Write 30 requests your customers would actually say, and check that none changes anything without approval |

`conformance/run.py` shows how to automate all of these.

---

## What is not ready yet

- Adapters other than Modbus TCP and argument-list processes.
- A microcontroller-only Core, and its C interface.
- Signed updates of rules and definitions, and model regression tests. HEART Full requires both, so the reference cannot fully claim Full yet.
- Testing on real AC hardware.

---

## Timeline

| When | What |
|---|---|
| Day 1 | Steps 1 and 2: identity, and the list of values with privacy levels |
| Week 1 | Steps 3 to 6 against a simulator: definitions, rules, host feed, running `heartd` |
| Week 2 | Step 7, and the app's approval screen |
| Week 3 | Step 11 on a real board, including pulling cables and power |
| Later | Steps 9 and 10, as the product needs them |
