# Otonomo — install modes and integration paths (detailed reference)

**Audience.** Engineering, support, technical partners.
**Last updated.** 2026-05-25.

This document explains the three install modes a customer can pick when
installing Otonomo on their own hardware, what each mode contains, what
it leaves out, how integration with Home Assistant works in each, and
the hardware / OS / cloud assumptions behind all of it.

---

## 1. What gets installed (the invariant)

Every Otonomo install — regardless of mode — places **three systemd
services** on the customer's Linux box, plus a small Python venv at
`/opt/otonomo/venv` and a config file at `/etc/otonomo/env`.

| Service | Role | Mode-independent? |
|---|---|---|
| `otonomo-publisher` | Polls drivers, publishes telemetry to local MQTT + cloud MQTT | ✅ always running |
| `otonomo-cmd-subscriber` | Subscribes to cloud command topic, dispatches to driver `execute()` | ✅ always running |
| `otonomo-local-ui` | LAN web UI on port 8080 | ⚠ mode-dependent |
| `mosquitto` (system package) | Local MQTT broker on `127.0.0.1:1883` | ✅ always running |

The first three together form **the agent**. They are independent of
the local web UI. Whichever install mode is picked, telemetry flows to
our cloud and cloud commands flow back. That is non-negotiable.

The install mode only changes **what runs at port 8080** on the
customer's own box.

---

## 2. Port 8080 hosts two distinct surfaces

This is the key concept that disambiguates the modes:

| Surface | Purpose | Audience |
|---|---|---|
| **Graph dashboard** — overview, per-device pages, sparklines, 6h ring buffer, JSON time-series API | **Watching data** | The customer's eyeballs |
| **Drivers wizard** — catalog, pip install, instance create/edit/remove, config-form rendering | **Setting up hardware** | The customer during first-time install and on rare hardware changes |

The install mode picks which of these two surfaces is exposed.

---

## 3. The three install modes

### 3.1 Full (default)

```
Port 8080: ┌─ Graph dashboard ─────────┐
           │  /                        │
           │  /device/heating          │
           │  /device/battery          │
           │  /device/solar  /grid /ev │
           │  /api/series   /api/state │
           └───────────────────────────┘
           ┌─ Drivers wizard ──────────┐
           │  /drivers                 │
           │  /drivers/configure/<x>   │
           │  /drivers/install/<x>     │
           └───────────────────────────┘
           Plus an in-process MQTT tap
           subscribing to local broker,
           maintaining a 6h ring buffer
           in RAM (~1.2 MB at 50 metrics).
```

**Best for.** Customers who want a local pane of glass during the free
trial, or who want a fallback view when the cloud is unreachable. The
default in the onboarding wizard.

### 3.2 Config-only

```
Port 8080: ┌─ Drivers wizard ──────────┐  ← still on
           │  /drivers                 │
           │  /drivers/configure/<x>   │
           │  /drivers/install/<x>     │
           └───────────────────────────┘
                Graph dashboard turned off:
                • `/` shows a status page
                • `/device/*` → 303 to /drivers
                • `/api/series` → HTTP 410 (Gone)
                • `/api/state`  → HTTP 410 (Gone)
                MQTT tap NOT instantiated.
                Ring buffer NOT allocated.
```

**Best for.** Customers who will visualise their data in **Home
Assistant** (or in our cloud dashboard) but still want the friendly
driver-setup wizard. The wizard is a setup tool, not a display tool —
they need it to configure their hardware once and on rare changes.

**What's saved vs Full.** ~1.2 MB RAM, one MQTT subscriber thread, one
broker connection. Symbolic on a modern box; principled on a Pi Zero.

### 3.3 None

```
Port 8080: closed. Service is `systemctl mask`ed so a future
           daemon-reload or third-party hook can't bring it back.

Drivers managed via:
  • SSH to the box
  • Edit /etc/otonomo/site_manifest.yaml
  • systemctl kill -s SIGHUP otonomo-publisher.service
```

**Best for.** Power users / sysadmins who prefer config-as-code in
YAML, multi-box deployments where one cloud dashboard manages many
boxes, or minimal-attack-surface paranoia (no port 8080 = no LAN
exposure of the agent at all).

---

## 4. Component matrix

| Component | Full | Config | None |
|---|---|---|---|
| `otonomo-publisher` | ✅ | ✅ | ✅ |
| `otonomo-cmd-subscriber` | ✅ | ✅ | ✅ |
| Local mosquitto broker | ✅ | ✅ | ✅ |
| `otonomo-local-ui` service | ✅ | ✅ (trimmed) | ❌ masked |
| Port 8080 open on LAN | ✅ | ✅ | ❌ |
| Graph dashboard pages | ✅ | ❌ | ❌ |
| 6h in-memory ring buffer | ✅ | ❌ | ❌ |
| Local MQTT tap (subscriber inside local-UI) | ✅ | ❌ | ❌ |
| Hand-rolled SVG sparklines | ✅ | ❌ | ❌ |
| `/api/series`, `/api/state` JSON | ✅ | HTTP 410 | n/a |
| Drivers wizard (catalog, install, configure) | ✅ | ✅ | ❌ |
| `/healthz` | ✅ | ✅ | ❌ |
| Telemetry to Otonomo cloud | ✅ | ✅ | ✅ |
| Cloud dashboard (`app.otonomo.be/?box=...`) | ✅ | ✅ | ✅ |
| Cloud orchestrators (active mode) | ✅ | ✅ | ✅ |
| Cmd path cloud → driver → hardware | ✅ | ✅ | ✅ |
| Cloud-watchdog safe-mode fallback | ✅ | ✅ | ✅ |
| Approx extra RAM vs base | ~1.5 MB | base | base |

Three things to notice from this table:

1. **All three modes ship the full product.** Telemetry, control,
   optimization, billing, safety rails — all unchanged across modes.
2. **The difference between Full and Config is the graph dashboard only.**
3. **The difference between Config and None is the drivers wizard.**

---

## 5. Home Assistant integration

HA integration is **identical across all three modes**: HA connects to
the local MQTT broker on the Otonomo box.

```
Drivers ──> mosquitto on box (127.0.0.1:1883)
                  │
                  ├── Otonomo local-UI tap   (Full mode only)
                  ├── Home Assistant         (all modes, optional)
                  └── (fan-out to cloud)     (always)
```

### Setup (3 steps, ~2 minutes)

1. In HA: **Settings → Devices & Services → Add Integration → MQTT**
2. Broker: the Otonomo box's IP, port `1883`, no auth (LAN-trusted)
3. Subscribe to `hems/<box_id>/telemetry/#`

Every metric (`dhw.temp_c`, `battery.soc_pct`, `pv.power_w`, …) becomes
an MQTT message HA can turn into entities, charts, automations.

### Why MQTT and not `/api/state`?

`/api/state` is a JSON projection of the local-UI's 6h ring buffer.
That buffer exists only in Full mode. In Config and None mode it is
not allocated — and `/api/state` would have nothing to return.

MQTT is the **right** integration anyway: push-based, lower latency,
same source of truth as our cloud ingester. We recommend it even for
Full-mode customers.

### Future (planned ~Q3 2026): official HACS integration

One-click install. Customer pastes box-IP + box-id. The integration:
- Subscribes to the box's MQTT topics
- Creates HA entities from our canonical schema (`sensor.otonomo_*`)
- Surfaces per-capability override switches that map to our
  customer-override mechanism in active mode

Once it ships, Config and None modes become functionally
indistinguishable for HA users — the wizard moves to the cloud, the
agent on their box becomes a pure background process.

---

## 6. Hardware, OS, and cloud — what actually works

### 6.1 Operating system

| OS family | Status |
|---|---|
| Debian 12 / Ubuntu 22.04+ / Raspberry Pi OS Bookworm | ✅ supported (install.sh tests for `apt-get`) |
| Other apt-based (Mint, PopOS, Armbian) | ✅ works in practice, untested |
| Fedora / RHEL / Rocky / Alma | ⚠ install.sh exits — customer needs to install `python3`, `mosquitto`, `curl`, `ca-certificates` manually, then run the activator |
| Arch / openSUSE | ⚠ same as Fedora |
| NixOS | ⚠ would need a flake; not v0.1 |
| Alpine | ⚠ uses `apk` and OpenRC instead of systemd; install.sh refuses |
| Windows | ❌ no |
| macOS | ❌ no (no systemd) |

**Practical answer.** For the founding-100 we target Debian / Ubuntu /
Raspberry Pi OS only. Other distros are a "we'll help you over email"
proposition until M3. The agent code itself is pure Python + paho-mqtt
+ FastAPI — it will run on anything Linux. The friction is `install.sh`
hardcoding `apt-get` and `systemctl`.

### 6.2 Hardware

| Box | Status |
|---|---|
| Raspberry Pi 4 / 5 (1+ GB RAM) | ✅ recommended sweet spot |
| Raspberry Pi Zero 2 W (512 MB) | ✅ works; this is our shipping SKU's hardware tier |
| Orange Pi Zero 3 | ✅ this is our SKU; reference target |
| Intel NUC / mini-PC (any age) | ✅ overkill but fine |
| Old laptop running Linux | ✅ works — needs to stay always-on |
| Cloud VPS (Hetzner / Scaleway / AWS …) | ⚠ see §6.3 below — depends entirely on which drivers |
| Synology / QNAP NAS Docker | ⚠ untested but should work |
| Truenas / Unraid | ⚠ untested |
| Pi Zero W (original, 512 MB ARMv6) | ❌ Python 3.11+ unsupported on ARMv6 |

Minimum spec realistically: **512 MB RAM, Python 3.11+, systemd**.

### 6.3 Cloud VPS — the wrinkle

Yes, the **agent software** runs perfectly on a cloud VPS. The
**drivers** are the question.

| Driver class | Works on a cloud VPS? |
|---|---|
| Cloud-API drivers (Aquarea Smart Cloud, Easee, Tado, etc.) | ✅ yes — they talk to the vendor's cloud, location doesn't matter |
| Modbus TCP drivers (SolarEdge, Huawei, Marstek, etc. over LAN) | ❌ no — VPS isn't on the customer's LAN; can't see the inverter |
| Local MQTT drivers (Shelly, Heishamon, …) | ❌ no — same reason |
| USB-serial drivers (Vaillant via USB-eBUS, BlueCorner EV via USB-RS485) | ❌ no — VPS has no USB ports to the customer's hardware |
| Cloud-API EV chargers (Easee, Wallbox, go-e cloud) | ✅ yes |
| Cloud-API heat pumps (Aquarea Smart Cloud, LG ThinQ) | ✅ yes |

**Honest summary.** A cloud VPS works as an Otonomo box **only if every
device the customer has is reachable through a vendor cloud API**. If
any device is LAN- or USB-bound, the agent must run on a box on the
customer's LAN. That's not an Otonomo limitation; it's physics.

For most real customers, a **Pi or NUC on their LAN is mandatory**
because they have at least one Modbus / USB device. Cloud VPS is a
nice option for the all-cloud-API edge case (e.g. a customer who only
has an Aquarea + an Easee).

### 6.4 Containers

Docker / Podman are supported in principle but not packaged in v0.1.
The blockers are: mounting `/dev/ttyUSB*` for USB drivers, host
networking for mDNS discovery, systemd-in-container complications. We
plan a `docker-compose.yml` in M2 for the all-network-driver case.

---

## 7. How to pick (decision guide)

```
Customer flow:

  Does the customer want a *local* pane of glass?
  │
  ├── YES → Full
  │
  └── NO → Will they use Home Assistant or just the cloud dashboard?
           │
           ├── HA / cloud, but wants the friendly setup wizard → Config
           │
           └── Power user, comfortable with YAML + SSH      → None
```

Default in the onboarding picker is **Full**. Reasonable for ~70% of
the founding-100 cohort. Config and None are honest options for the
remaining 30% (HA-heavy users + sysadmin types).

---

## 8. Switching modes after install

Re-run the one-liner with a different `OTONOMO_LOCAL_UI` value. The
installer is idempotent: it detects the existing enrollment, doesn't
re-issue a token, just rewrites `LOCAL_UI_MODE` in `/etc/otonomo/env`
and re-applies the systemd state (enable + start, or disable + mask).

```bash
# Add the graph dashboard back to a Config-only install
curl -fsSL https://app.otonomo.be/install.sh \
  | OTONOMO_TOKEN=<your-token> OTONOMO_LOCAL_UI=full bash

# Drop to None
curl -fsSL https://app.otonomo.be/install.sh \
  | OTONOMO_TOKEN=<your-token> OTONOMO_LOCAL_UI=off bash
```

Configured drivers, the site manifest, the box's identity — all
preserved across mode switches. The customer loses nothing.

---

## 9. What changes in active mode (€3/mo)

Active vs observe is **orthogonal** to the install mode. A customer in
None mode can be in active mode; a customer in Full mode can be in
observe. The two axes don't interact.

| Axis | Values |
|---|---|
| Install mode (sets local UI surface) | Full / Config / None |
| Billing mode (sets whether cloud writes) | Observe (free) / Active (€3/mo, per-capability opt-in) |

A box in **observe** mode receives no cloud commands — the
`otonomo-cmd-subscriber` still runs but the cloud doesn't publish
anything for it to consume. Customer flips to active at
`app.otonomo.be/account/control`, opt-in per capability, override
anytime in HA via the cmd contract.

---

## 10. References

- `installer/install.sh` — installer entry point
- `installer/local_ui/app.py` — local-UI FastAPI app with mode awareness
- `installer/local_ui/templates/config_only.html` — what Config-mode
  customers see at `/`
- `installer/README.md` — install-modes table for ops
- `cloud/app/onboarding.py` — `/onboarding?ui=...` picker
- `cloud/_claude_memory/feedback_no_third_party_charts.md` — why we
  hand-roll SVG sparklines instead of Chart.js
