Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
- **Type**: Indigo plugin
- **Shortcut**: `matter`
- **GitHub**: https://github.com/simons-plugins/indigo-matter
- **Language**: Python 3.11+ (`CFBundleIdentifier` `com.simon.indigo-matter`)
- **Language**: Python 3.11+ (`CFBundleIdentifier` `com.simons-plugins.indigo-matter`)

## Role in the workspace

Expand Down Expand Up @@ -50,7 +50,7 @@ loop→Indigo writes go straight through `device_sync.apply_states` (thread-safe
| `matter_handlers/` | One `ClusterHandler` per cluster + registry (OnOff in v1) |

**HTTP transport:** the Domio API is served by the **Indigo Web Server** as
`<Action uiPath="hidden">` handlers at `…/message/com.simon.indigo-matter/{handler}`,
`<Action uiPath="hidden">` handlers at `…/message/com.simons-plugins.indigo-matter/{handler}`,
reached over the Reflector with the same Bearer API key Domio already uses — **not** a
standalone server (aiohttp is absent from Indigo's framework Python). See `docs/API.md` v1.1.

Expand Down
26 changes: 13 additions & 13 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This is not the matter-server protocol — that's a separate seam owned by the p

> **Transport (v1.1, changed from v1.0):** the plugin does **not** run its own HTTP
> server. It is served by the **Indigo Web Server (IWS)** as a set of hidden-action
> handlers, reached at `…/message/com.simon.indigo-matter/{handler}/…`. This is the
> handlers, reached at `…/message/com.simons-plugins.indigo-matter/{handler}/…`. This is the
> idiomatic Indigo mechanism (the same one Domio already uses for the device-history and
> HTML-pages APIs — see `domio-code` `DeviceHistoryService` / `HtmlPageService`), and it
> rides the Reflector for remote access with the **same Bearer API key Domio already uses**.
Expand All @@ -27,7 +27,7 @@ This is not the matter-server protocol — that's a separate seam owned by the p
>
> **Note for the Domio implementer:** build these calls exactly like the existing
> `IndigoRESTService.pluginRequest()` / `DeviceHistoryService.performRequest()` —
> resolve the reflector, build `{base}/message/com.simon.indigo-matter/{handler}`,
> resolve the reflector, build `{base}/message/com.simons-plugins.indigo-matter/{handler}`,
> add `Authorization: Bearer {credential}`. Pass **all parameters, including the nodeId for
> POST handlers, as URL query items**; a trailing `/{pathArg}` works only for GET (e.g.
> `GET …/commission/{jobId}`).
Expand All @@ -41,9 +41,9 @@ This is not the matter-server protocol — that's a separate seam owned by the p
- `nodeId` — string, the Matter node identifier as managed by the Indigo fabric. Stored as a string to avoid 64-bit JSON number issues.
- `jobId` — string UUID v4, generated by the plugin.
- `indigoDeviceId` — integer, the standard Indigo device ID.
- **Base path:** every endpoint is rooted at `…/message/com.simon.indigo-matter/{handler}`.
- Local: `https://{indigo-host}:8176/message/com.simon.indigo-matter/{handler}` — IWS serves **HTTPS** on 8176 (self-signed cert); local requests authenticate with Indigo's Digest auth.
- Remote (Reflector): `https://{reflector}.indigodomo.net/message/com.simon.indigo-matter/{handler}` — Bearer API key. **This is the path Domio uses.**
- **Base path:** every endpoint is rooted at `…/message/com.simons-plugins.indigo-matter/{handler}`.
- Local: `https://{indigo-host}:8176/message/com.simons-plugins.indigo-matter/{handler}` — IWS serves **HTTPS** on 8176 (self-signed cert); local requests authenticate with Indigo's Digest auth.
- Remote (Reflector): `https://{reflector}.indigodomo.net/message/com.simons-plugins.indigo-matter/{handler}` — Bearer API key. **This is the path Domio uses.**
- Trailing path components after `{handler}` are positional arguments **on GET only** — the plugin reads them from `action.props["file_path"]`, which IWS populates for GET but **not POST**. E.g. `GET …/commission/{jobId}` puts `jobId` as the path arg. A POST endpoint must instead carry its id as a query item (see §3.4).

## 2. Authentication
Expand All @@ -57,7 +57,7 @@ auth check. A missing/invalid credential is rejected by Indigo with its standard

## 3. Endpoints

### 3.1 `GET …/message/com.simon.indigo-matter/status`
### 3.1 `GET …/message/com.simons-plugins.indigo-matter/status`

Health check. Domio calls this at the start of every commissioning flow as a precheck.

Expand Down Expand Up @@ -100,7 +100,7 @@ Health check. Domio calls this at the start of every commissioning flow as a pre
}
```

### 3.2 `POST …/message/com.simon.indigo-matter/commission`
### 3.2 `POST …/message/com.simons-plugins.indigo-matter/commission`

Submit a setup code for commissioning into the Indigo fabric. Called by Domio immediately after it has opened a commissioning window on the device from its own fabric (multi-admin handoff, see ADR §C3).

Expand Down Expand Up @@ -152,7 +152,7 @@ Submit a setup code for commissioning into the Indigo fabric. Called by Domio im
}
```

### 3.3 `GET …/message/com.simon.indigo-matter/commission/{jobId}`
### 3.3 `GET …/message/com.simons-plugins.indigo-matter/commission/{jobId}`

Poll commissioning job status. `jobId` is the trailing path component. Domio polls every 1s, with a 120s soft timeout.

Expand Down Expand Up @@ -256,7 +256,7 @@ check Indigo", which matches Domio's existing 120s soft-timeout message. The
plugin's job-level commission deadline is 300s and may legitimately outlive
Domio's 120s poll window.

### 3.4 `POST …/message/com.simon.indigo-matter/decommission?nodeId={nodeId}`
### 3.4 `POST …/message/com.simons-plugins.indigo-matter/decommission?nodeId={nodeId}`

Decommission a device. `nodeId` is a **query parameter**. Removes the plugin's fabric from
the device (sends `RemoveFabric` via matter-server) and deletes the associated Indigo
Expand Down Expand Up @@ -290,7 +290,7 @@ commissioned in any other fabric (e.g. Apple Home).

**Response 404:** unknown nodeId.

### 3.5 `GET …/message/com.simon.indigo-matter/diagnostics/{nodeId}`
### 3.5 `GET …/message/com.simons-plugins.indigo-matter/diagnostics/{nodeId}`

Diagnostic snapshot for a single device. `nodeId` is the trailing path component (a `?nodeId=…` query param is also accepted). Used by a future Domio device-detail view.

Expand Down Expand Up @@ -373,7 +373,7 @@ For forward planning, these handlers are reserved:
```
Domio Plugin (via IWS / Reflector)

GET …/message/com.simon.indigo-matter/status
GET …/message/com.simons-plugins.indigo-matter/status
200 { ready: true, ... }

[Domio invokes MatterAddDeviceRequest, iOS handles
Expand All @@ -382,11 +382,11 @@ GET …/message/com.simon.indigo-matter/status
[Domio sends OpenCommissioningWindow command to device.
Setup code generated: "12345678901". Window valid 180s.]

POST …/message/com.simon.indigo-matter/commission
POST …/message/com.simons-plugins.indigo-matter/commission
?setupCode=12345678901&suggestedName=Office%20Fan&suggestedRoom=Office
202 { jobId: "8c9d..." }

GET …/message/com.simon.indigo-matter/commission/8c9d...
GET …/message/com.simons-plugins.indigo-matter/commission/8c9d...
200 { status: "commissioning", progress: 0.3 }
GET …/commission/8c9d... (1s later)
200 { status: "reading_descriptors", progress: 0.6 }
Expand Down
14 changes: 7 additions & 7 deletions docs/HANDOVER.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ devices ignore — needs hardware testing to find a working mechanism.
- After firmware update + node rejoin, **the existing Indigo device gained `curEnergyLevel`/`accumEnergyTotal` automatically — no restart, no recreate**. That is PR #48 (capability-props re-assertion at reconcile) validated on real hardware, exactly its design scenario.
- Fabric survived Wi-Fi reset + Tapo onboarding + firmware update; plug is now 4-admin (Apple Home, Indigo, Tapo cloud, Home Assistant) with no cross-interference. Apple Home took ~a minute to recover post-update — normal settling, not a fault.

**Diagnostics endpoint note:** `GET …/message/com.simon.indigo-matter/diagnostics?nodeId=0x22` (Bearer = reflector key) returns reachable/vendor/product/softwareVersion + per-endpoint numeric cluster ids — first line of investigation for "why didn't my device get state X".
**Diagnostics endpoint note:** `GET …/message/com.simons-plugins.indigo-matter/diagnostics?nodeId=0x22` (Bearer = reflector key) returns reachable/vendor/product/softwareVersion + per-endpoint numeric cluster ids — first line of investigation for "why didn't my device get state X".

---

Expand Down Expand Up @@ -147,7 +147,7 @@ All merged to `main`, all deployed to jarvis. Plugin **2026.0.1 → 2026.1.1** o
- **#25 nvm support:** `nodeBinDir` pref + auto-detect (nvm default alias / newest, then Homebrew). The old code only probed Homebrew npx.
- **#27 loopback security:** `matterServerListenAddress` pref, default `127.0.0.1`. matter-server's WS control API is **unauthenticated** and binds **all interfaces** without `--listen-address` — the managed agent now restricts to loopback.
- **#28 node-direct launch:** the `matter-server` npm package has **no bin** (`"bin": null`), so `npx matter-server` fails ("could not determine executable to run"). The managed agent now runs `node …/node_modules/matter-server/dist/esm/MatterServer.js` directly (reads `main` from the package).
- **jarvis cutover:** `manageLaunchAgent` ON, `nodeBinDir=/Users/simon/.nvm/versions/node/v22.22.3/bin`, listen `127.0.0.1`, port 5580. Plugin writes + bootstraps `~/Library/LaunchAgents/com.simon.indigo-matter.plist`; run.sh retired. Loopback bind confirmed in matter-server.log; 5 nodes intact; control + restart validated.
- **jarvis cutover:** `manageLaunchAgent` ON, `nodeBinDir=/Users/simon/.nvm/versions/node/v22.22.3/bin`, listen `127.0.0.1`, port 5580. Plugin writes + bootstraps `~/Library/LaunchAgents/com.simons-plugins.indigo-matter.plist`; run.sh retired. Loopback bind confirmed in matter-server.log; 5 nodes intact; control + restart validated.

**Docs (PR #29 → 2026.0.7):** `docs/INSTALL.md` (full matter-server install/setup guide), tidied README (was v1.1/M0–M4), **MIT `LICENSE`** (© 2026 Simon Clark).

Expand Down Expand Up @@ -212,9 +212,9 @@ Plus two improvements found by live testing: **state priming** (apply get_node s

## 3. Live environment (jarvis = 192.168.0.41, mounted at `/Volumes/Macintosh HD-1`)

- **matter-server**: npm pkg `matter-server@0.6.2` at `~/indigo-matter`. **Now launched by the plugin-managed LaunchAgent** (`~/Library/LaunchAgents/com.simon.indigo-matter.plist`, written by `ServerProcess` since 2026-06-10): `node ~/indigo-matter/node_modules/matter-server/dist/esm/MatterServer.js --port 5580 --listen-address 127.0.0.1 --storage-path ~/Library/Application Support/com.simon.indigo-matter/matter-server --primary-interface en0`, PATH=`~/.nvm/versions/node/v22.22.3/bin:…`. WS at `ws://127.0.0.1:5580/ws` (loopback-only; BLE off). The old `~/indigo-matter/run.sh` is retired (a backup of its plist is at `~/com.simon.indigo-matter.runsh.plist.bak-*` for rollback). Logs: `~/Library/Logs/indigo-matter/matter-server.{log,err.log}`.
- **Fabric** (sacred — single point of total loss): `~/Library/Application Support/com.simon.indigo-matter/matter-server/`. Plugin backups (zip) live in the sibling `…/backups/`. A static safety copy from the migration: `~/indigo-matter-fabric-backup-20260610-124145Z`.
- **Plugin**: installed + running in **Indigo 2025.2**, bundle id `com.simon.indigo-matter`, **version 2026.1.1**. **`manageLaunchAgent` pref is ON** (`nodeBinDir=~/.nvm/versions/node/v22.22.3/bin`, `matterServerListenAddress=127.0.0.1`, port 5580). Plugin restart regenerates the plist (bootstrap no-ops if matter-server already loaded; running server undisturbed).
- **matter-server**: npm pkg `matter-server@0.6.2` at `~/indigo-matter`. **Now launched by the plugin-managed LaunchAgent** (`~/Library/LaunchAgents/com.simons-plugins.indigo-matter.plist`, written by `ServerProcess` since 2026-06-10): `node ~/indigo-matter/node_modules/matter-server/dist/esm/MatterServer.js --port 5580 --listen-address 127.0.0.1 --storage-path ~/Library/Application Support/com.simons-plugins.indigo-matter/matter-server --primary-interface en0`, PATH=`~/.nvm/versions/node/v22.22.3/bin:…`. WS at `ws://127.0.0.1:5580/ws` (loopback-only; BLE off). The old `~/indigo-matter/run.sh` is retired (a backup of its plist is at `~/com.simons-plugins.indigo-matter.runsh.plist.bak-*` for rollback). Logs: `~/Library/Logs/indigo-matter/matter-server.{log,err.log}`.
- **Fabric** (sacred — single point of total loss): `~/Library/Application Support/com.simons-plugins.indigo-matter/matter-server/`. Plugin backups (zip) live in the sibling `…/backups/`. A static safety copy from the migration: `~/indigo-matter-fabric-backup-20260610-124145Z`.
- **Plugin**: installed + running in **Indigo 2025.2**, bundle id `com.simons-plugins.indigo-matter`, **version 2026.1.1**. **`manageLaunchAgent` pref is ON** (`nodeBinDir=~/.nvm/versions/node/v22.22.3/bin`, `matterServerListenAddress=127.0.0.1`, port 5580). Plugin restart regenerates the plist (bootstrap no-ops if matter-server already loaded; running server undisturbed).
- **5 commissioned nodes** (Indigo device ids):
- matterRelay `714038249` (OnOff Light) · matterDimmer `507300015` · matterColorDimmer `200619536` (node `0xF`; recreated 2026-06-09 with the colour-support-props fix, replaces old `747107241`) · matterTemperatureSensor `1824758566` · matterThermostat `1118330069`
- **MCP control**: `mcp__indigo__*` (restart_plugin, query_event_log, get_devices_by_type, get_device_by_id, get_devices_by_state, device_turn_on/off, device_set_brightness, device_set_rgb_color, device_set_white_levels, thermostat_set_heat_setpoint, thermostat_set_hvac_mode). `get_device_by_id` only shows relay/dimmer fields — use `get_devices_by_state {"sensorValue": "<0"}` etc. to read custom states.
Expand All @@ -227,7 +227,7 @@ DST="/Volumes/Macintosh HD-1/Library/Application Support/Perceptive Automation/I
rsync -rc --exclude='__pycache__' --exclude='*.pyc' "$SRC/Server Plugin/" "$DST/Server Plugin/"
rsync -c "$SRC/Info.plist" "$DST/Info.plist"
# verify: md5 -q "$SRC/Server Plugin/plugin.py" vs "$DST/Server Plugin/plugin.py"
# then: mcp__indigo__restart_plugin(plugin_id="com.simon.indigo-matter")
# then: mcp__indigo__restart_plugin(plugin_id="com.simons-plugins.indigo-matter")
```
**First-time install only** is double-click; updates are rsync + restart. The plugin reconciles on every (re)start and via `node_added`. **Verify health** after restart via `mcp__indigo__query_event_log` → expect `connected to matter-server, listening` + `reconciled 5 Matter node(s)`. **Cannot exec on jarvis** (SSH to the prod host is blocked) — read/write its disk via the mount; trigger plugin **menu actions** (backup/restore) only via the Indigo UI (no MCP hook).

Expand Down Expand Up @@ -259,7 +259,7 @@ Dumps `server_info` + `get_nodes`/`get_node` + toggles. Run: `source ~/.nvm/nvm.
Domio no longer commissions; it relays a **share code** (Apple Home is admin 1; the plugin joins as admin 2 over IP).

- **Plugin change made:** `matter_client.commission_with_code` now passes `network_only=true` → IP-only discovery + PASE/CASE, no BLE (verified vs source: `commissionNode` uses `ble: bleEnabled && !onNetworkOnly`). Matter commissioning *adds* a fabric → Apple Home untouched.
- **Contract v1.1 verified against live code:** bundle `com.simon.indigo-matter`; handlers `status` / `commission` (POST create + GET `commission/{jobId}` poll) / `decommission` / `diagnostics`; params `setupCode/suggestedName/suggestedRoom/discriminator/domioNodeId` (URL query); status enum `pending,commissioning,reading_descriptors,creating_devices,success,failed`; result `{nodeId(hex string),indigoDeviceIds,primaryDeviceId,vendorName,productName,…}`. Auth enforced by Indigo/Reflector before the handler. I do NOT currently read `X-Matter-API-Version` (harmless).
- **Contract v1.1 verified against live code:** bundle `com.simons-plugins.indigo-matter`; handlers `status` / `commission` (POST create + GET `commission/{jobId}` poll) / `decommission` / `diagnostics`; params `setupCode/suggestedName/suggestedRoom/discriminator/domioNodeId` (URL query); status enum `pending,commissioning,reading_descriptors,creating_devices,success,failed`; result `{nodeId(hex string),indigoDeviceIds,primaryDeviceId,vendorName,productName,…}`. Auth enforced by Indigo/Reflector before the handler. I do NOT currently read `X-Matter-API-Version` (harmless).
- **Domio contract file:** `domio-code/docs/API.md` (mirror of `indigo-matter/docs/API.md`, v1.1). Their client: `domio-code/.../Services/MatterAPIClient.swift`.

## 6. Architecture quick map (`Contents/Server Plugin/`)
Expand Down
2 changes: 1 addition & 1 deletion docs/IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def http_commission(self, action, dev=None, caller_waiting_for_result=None):
# ... bridge into the loop: self.runtime.submit(coro).result(timeout) ...
```

**URL shape:** `…/message/com.simon.indigo-matter/{handler}[/{pathArg}]`,
**URL shape:** `…/message/com.simons-plugins.indigo-matter/{handler}[/{pathArg}]`,
reachable locally on `:8176` and remotely via the Reflector
(`https://{reflector}.indigodomo.net/message/…`) with the same
`Authorization: Bearer {key}` Domio already uses.
Expand Down
Loading
Loading