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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ docs/branding/.build/
bridge-node/node_modules/
bridge-node/dist/
bridge-node/.test-build/
docs/HANDOVER.md
13 changes: 5 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,7 @@ with a BLE radio and the network credentials (an admin-1 ecosystem, or the iPhon
during the Domio flow); the plugin then joins over IP via that ecosystem's border router
(HomePod/Apple TV, TBR-capable Echo, Nest Hub). See
[Field Notes № 1](https://simons-plugins.github.io/indigo-matter/matter.html) for how the pieces
fit; validation specifics live in
[Handover](https://simons-plugins.github.io/indigo-matter/HANDOVER.html).
fit.

**Matter out — built, unit-tested on both sides, partly validated live.** The allow-list,
the role mapping, endpoint persistence, pairing and fabric management, and the managed
Expand All @@ -101,13 +100,11 @@ and all), an on/off light and a dimmer **have** been controlled in both directio
accessory identity **has** survived an upgrade without duplicating (2026-08-04 / 05, on
the reference server). Still outstanding: pairing through the plugin's own menu rather
than the node's console, a second controller alongside Apple Home, the reboot leg of the
identity check, and the managed LaunchAgent end to end — that last one needs the package
installed, which until `indigo-matter-bridge` is published means the local-install
workaround rather than the shipped install menu. Treat the export half as new. See
identity check, and the managed LaunchAgent end to end — `indigo-matter-bridge` is on the npm
registry now, so the shipped install menu is the route for that last one.
Treat the export half as new. See
[MATTER.md](docs/MATTER.md#indigo-as-a-matter-bridge--the-other-direction) for how it
works, and
[Handover](https://simons-plugins.github.io/indigo-matter/HANDOVER.html) for the
validation specifics.
works.

## Setup

Expand Down
32 changes: 32 additions & 0 deletions bridge-node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,38 @@ faults worth catching here (a role factory that builds but writes the wrong
attribute; a bridged child publishing the wrong manufacturer) cannot fail against
a stub.

## Releasing

The package is publish-ready by construction: `files` is limited to `dist`,
`main` is `dist/main.js` (what the plugin's `bridge_agent.DEFAULT_BRIDGE_ENTRY`
expects), `engines.node` is `>=22.13.0`, and a `prepublishOnly` script runs
`clean` then `build` so a stale `dist` can never ship. (`npm pack --dry-run`
also includes `README.md` — npm always ships it regardless of `files`.)

```sh
npm login # the package owner's npm account
npm test # publishing an untested build is the one unrecoverable mistake
npm publish --access public
```

**Every release bumps two versions in step**: `package.json`'s `version` here,
and `bridge_agent.DEFAULT_INSTALL_SPEC` in the plugin. `test_bridge_agent.py`
only asserts the pin is never *ahead* of `package.json` (a trailing pin is
legal — installs just keep pulling the older release), so the in-step bump is
manual discipline, not something the suite enforces.

**Testing an unreleased node** (dev workaround, *not* the shipped default): the
on-disk layout the LaunchAgent expects is reproduced by a local install —

```sh
npm install --prefix ~/indigo-matter /path/to/indigo-matter/bridge-node
```

which puts the package at `~/indigo-matter/node_modules/indigo-matter-bridge`
exactly where `LaunchAgent._server_entry()` looks for it (`npm link` works
too). The plugin's install menu still tries the registry spec — the local
install covers everything else.

## Licence

MIT. Not affiliated with Perceptive Automation, the Connectivity Standards
Expand Down
6 changes: 3 additions & 3 deletions bridge-node/src/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -543,9 +543,9 @@ const COMMAND_SINKS = new Map<string, CommandSink>();
* a **lock** it is different in kind — Home reports "Unlocked", the user walks
* away, and the bolt never moved. There is no protocol frame for "your command
* was dropped" (the plugin cannot be told about invocations that arrived while
* it was away; that is a §3.1 addition, noted in `docs/HANDOVER.md` and
* deliberately not made here), so failing the invocation is the only way the
* truth reaches the person standing at the door. A Matter status error makes
* it was away; that would be a §3.1 protocol addition, deliberately not made
* here), so failing the invocation is the only way the truth reaches the
* person standing at the door. A Matter status error makes
* Home say the accessory did not respond, which is exactly what happened.
*/
function emitCommand(
Expand Down
2,160 changes: 0 additions & 2,160 deletions docs/HANDOVER.md

This file was deleted.

2 changes: 1 addition & 1 deletion docs/IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
**Status:** Living reference — pruned 2026-06-10
**Companion documents:** [`API.md`](./API.md) (Domio wire contract, authoritative),
[`MATTER.md`](./MATTER.md) (architecture & landscape), [`INSTALL.md`](./INSTALL.md)
(setup), [`HANDOVER.md`](./HANDOVER.md) (current state),
(setup),
[`PRD-indigo-matter-plugin.md`](./PRD-indigo-matter-plugin.md) (historical build spec)

This document keeps only what the shipped code and the other docs don't already
Expand Down
10 changes: 1 addition & 9 deletions docs/INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,14 +297,6 @@ Commission device by setup code…** — and it will appear as a native Indigo d

## Exporting Indigo devices (Indigo as a Matter bridge)

> **You cannot install this yet.** The export bridge needs a second npm package,
> `indigo-matter-bridge`, and it **has not been published to the npm registry**.
> Until it is, **Install/update the Matter bridge** cannot resolve the
> version the plugin pins, and the install fails. Nothing else in the plugin is
> affected — the inbound controller, your commissioned devices and every Indigo
> device carry on as normal — but export cannot be brought up. Everything below
> is what you are waiting for.

Everything above is the plugin's **inbound** half: Matter devices becoming Indigo
devices. The plugin also runs the opposite direction — a selected set of your
*existing* Indigo devices (Z-Wave, Insteon, Zigbee, MQTT, anything a plugin owns)
Expand Down Expand Up @@ -747,7 +739,7 @@ dialog, most-likely-first — the top two are what a first export usually hits.
| Log line | Cause | Fix |
|---|---|---|
| `Matter bridge: the bridge node is not responding after N attempts on port 5581` | The node is not running, is crash-looping, or was never installed. All three look identical at the socket, so the plugin appends what the LaunchAgent found. | Read the sentence that follows it in the log — it names the real cause. Then: install the package (below), free the port (below), or check `bridge-node.err.log`. |
| The log names **Install/update the Matter bridge** | The `indigo-matter-bridge` package is not installed, so no LaunchAgent was written. This is the normal first-run state. | Run that menu item. Note it cannot succeed until the package is published to npm — see *Export prerequisites*. |
| The log names **Install/update the Matter bridge** | The `indigo-matter-bridge` package is not installed, so no LaunchAgent was written. This is the normal first-run state. | Run that menu item. |
| An `EADDRINUSE` / address-already-in-use failure in `bridge-node.err.log`. The node labels which startup step failed, so the line says whether it was the Matter port or the loopback protocol port | **Matter port (UDP 5540):** another Matter *device* stack on this Mac already holds it — Homebridge 2.x, matterbridge, or a Home Assistant container in host mode. **Protocol port (TCP 5581):** an orphaned bridge node from an earlier LaunchAgent is still running, or an unrelated service has the port. | **Matter port:** stop the other stack, or Configure… ▸ **Show export port settings** ▸ **Matter port**, pick another, and **reload the plugin**. Find the holder with `lsof -nP -iUDP:5540`. Moving off 5540 is a real trade — it is the port ecosystems expect. **Protocol port:** **Stop the Matter bridge…** then re-save an export (the plugin reaps its own orphans on start). If something else holds it, find it with `lsof -nP -iTCP:5581 -sTCP:LISTEN`, and either stop it or change **Bridge control port** and reload the plugin. |
| `Matter bridge: the bridge node's LaunchAgent did not start` / `could not be loaded by launchd` | launchd accepted the job and the process died, or refused the plist. | Check `bridge-node.err.log` first. Then `launchctl print gui/$(id -u)/com.simons-plugins.indigo-matter.bridge`. **Stop the Matter bridge…** followed by re-saving an export rebuilds the plist from scratch. |
| `Matter bridge: the bridge node is serving NOTHING because its endpoint-number map is unreadable` | The bridge's `endpoint-map.json` is present but corrupt. It refuses to serve rather than silently renumber every accessory. | **Rebuild Matter Endpoint Map…** — and read what it does first (above). The unusable file is kept as `endpoint-map.json.corrupt-<timestamp>` in the bridge storage folder; if you have a backup of the original, restoring it is better than rebuilding. |
Expand Down
9 changes: 4 additions & 5 deletions docs/MATTER.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,11 +295,10 @@ If you have run Homebridge, this is the same idea over Matter's protocol instead
of HomeKit's — with the difference that Matter is one implementation reaching
every ecosystem that speaks it, rather than one bridge per ecosystem.

**It is not installable yet.** The bridge is a second npm package,
`indigo-matter-bridge`, and it has not been published to the npm registry — so
the plugin's install menu item cannot resolve it, and export cannot be brought
up on any machine today. Everything below describes what is built and waiting on
that. Setup steps, for when it lands, are in [INSTALL.md](./INSTALL.md); this
The bridge is a second npm package, `indigo-matter-bridge`, published on the
npm registry (0.5.0 onward) and exact-pinned by the plugin, so the
**Install/update the Matter bridge** menu item resolves it.
Setup steps are in [INSTALL.md](./INSTALL.md); this
section is the shape of it.

The two questions this page gets asked most are "can I export my lock?" and "why
Expand Down
8 changes: 4 additions & 4 deletions docs/PRD-indigo-matter-export.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,8 @@ handling and `device_sync.py`'s `DEVICE_TYPE_AGGREGATOR`).
- **Distribution:** the bridge node is distributed as an **npm package**
(`indigo-matter-bridge`, TypeScript, decided 2026-08-04), exact-pinned by the
plugin the same way `matter-server@1.2.2` is — the existing
`npm install --prefix` machinery works unchanged. It is **not on the registry
yet** (§9 E7); until it is, the install action cannot resolve the pin. matter.js itself is
`npm install --prefix` machinery works unchanged. It is **on the registry**
(0.5.0 onward; §9 E7), so the install action resolves the pin. matter.js itself is
**exact-pinned** (no caret): patch releases have changed what Apple Home
renders with zero code change on the bridge side.
- **Max fabrics:** matter.js defaults `supportedFabrics` to 254, so ≥5
Expand Down Expand Up @@ -379,8 +379,8 @@ not slot arithmetic. Per-export settings live in the §5.1 dialog.
| E4 | Sensors + thermostat export | Mapping table complete for v1 |
| E5 | Endpoint persistence — **DEPLOYED to jarvis 2026-08-05; upgrade leg PASSED** | XAC5 — the highest-risk correctness requirement. **Upgrade migration observed on jarvis 2026-08-05: three exported accessories kept endpoint numbers 3, 5 and 4 across a plugin and bridge-node upgrade — their pre-upgrade values, deliberately not in creation order — with no duplicates in Apple Home.** XAC5's **reboot** leg is still outstanding: a plugin reload and a bridge-node restart have been survived, a full Mac reboot has not |
| E6 | Pairing/unpairing UX + fabric readout — **BUILT** (2026-08-05, plugin `2026.8.3`) | §6 complete, including XAC3's displayed-code pairing flow. "Pair Matter Bridge…" (§3.8, duration 180–900s), the QR page over IWS, "Unpair an Ecosystem…" (§3.9, two gates), the §5.5 Export config section, and the §5 pairing events surfaced in the log. **XAC3 is part-verified:** the pairing gate itself passed on jarvis (E0 row), but that pairing was driven from the node's console — *pairing through this menu* is unverified, as is the second-admin half |
| E7 | launchd agent + failure recovery — **BUILT** (2026-08-05, plugin `2026.8.3`, bridge `0.5.0`) | §7, XAC1, XAC2, XAC7, XAC8. `bridge_agent.BridgeProcess` is the second `AgentSpec`; it is installed and started by the empty→non-empty allow-list transition and stopped after the un-export lands. `remove_package` is per-package. Three lifecycle menu items land with it — "Install/update the Matter export bridge", "Reinstall the Matter export bridge (clean)…" and "Stop the Matter export bridge…" — the last two added in the PR #128 review batch to close the gap where the controller had recovery exits and the bridge had none. **`indigo-matter-bridge` is not yet on the npm registry** — publishing is Simon's action (`docs/HANDOVER.md` → "Publishing the bridge node"), and until then the install action cannot resolve the pinned spec |
| E8 | Docs — **BUILT** (2026-08-05) | `INSTALL.md` retitled for both runtimes, with a contents list and an export section (prerequisites, first export, pairing, the uncertified prompt, removal/unpairing, the two destructive recovery actions ordered by recoverability, bridge-storage backups, uninstall) plus an export-bridge troubleshooting table split into observable symptoms and verbatim log strings; `MATTER.md` outbound architecture, with the v1 role table and the excluded set hoisted to the front; `README.md` two-role lede. The ecosystems-untested note (§10 / ADR-0007) is stated in all three. The docs state the live-validation position **as it actually stands** — E0's pairing gate and E3's two-way control passed, E5 deployed and its upgrade leg observed, with pairing-through-the-menu, the second admin, the reboot leg and E6/E7 end-to-end still marked outstanding — plus the unpublished npm package, the IWS-auth caveat on the pairing page, and the unit assumption |
| E7 | launchd agent + failure recovery — **BUILT** (2026-08-05, plugin `2026.8.3`, bridge `0.5.0`) | §7, XAC1, XAC2, XAC7, XAC8. `bridge_agent.BridgeProcess` is the second `AgentSpec`; it is installed and started by the empty→non-empty allow-list transition and stopped after the un-export lands. `remove_package` is per-package. Three lifecycle menu items land with it — "Install/update the Matter export bridge", "Reinstall the Matter export bridge (clean)…" and "Stop the Matter export bridge…" — the last two added in the PR #128 review batch to close the gap where the controller had recovery exits and the bridge had none. **`indigo-matter-bridge` is published on the npm registry** (0.5.0+), so the install action resolves the pinned spec; release steps live in `bridge-node/README.md` § Releasing |
Comment thread
coderabbitai[bot] marked this conversation as resolved.
| E8 | Docs — **BUILT** (2026-08-05) | `INSTALL.md` retitled for both runtimes, with a contents list and an export section (prerequisites, first export, pairing, the uncertified prompt, removal/unpairing, the two destructive recovery actions ordered by recoverability, bridge-storage backups, uninstall) plus an export-bridge troubleshooting table split into observable symptoms and verbatim log strings; `MATTER.md` outbound architecture, with the v1 role table and the excluded set hoisted to the front; `README.md` two-role lede. The ecosystems-untested note (§10 / ADR-0007) is stated in all three. The docs state the live-validation position **as it actually stands** — E0's pairing gate and E3's two-way control passed, E5 deployed and its upgrade leg observed, with pairing-through-the-menu, the second admin, the reboot leg and E6/E7 end-to-end still marked outstanding — plus the IWS-auth caveat on the pairing page and the unit assumption (the npm package has since been published — see E7) |

**E0 is the whole validation loop.** It answers the only question that can kill
the feature — *will any ecosystem pair an uncertified bridge?* — on hardware
Expand Down
3 changes: 1 addition & 2 deletions docs/PRD-indigo-matter-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
> [`API.md`](./API.md) for the wire contract (the §5.1 endpoint shapes here
> are stale — e.g. decommission is now `POST …?nodeId=`, not `DELETE`),
> [`MATTER.md`](./MATTER.md) for architecture/landscape (incl. the corrected
> Thread position), [`INSTALL.md`](./INSTALL.md) for setup, and
> [`HANDOVER.md`](./HANDOVER.md) for current state. The architecture decision
> Thread position), and [`INSTALL.md`](./INSTALL.md) for setup. The architecture decision
> (the share model) is summarised in [`MATTER.md`](./MATTER.md), not the 0001 path below.
>
> Still genuinely pending from this PRD: **M11 — Plugin Store submission**,
Expand Down
3 changes: 1 addition & 2 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,7 @@ pgrep -fl "node .*\.mjs" # what's running
cd /tmp/matter-test && node fan.mjs >> fan.log 2>&1 &
```

Hard-won rig rules (each cost us a debugging session — full war stories in
[HANDOVER.md](./HANDOVER.md)):
Hard-won rig rules (each cost us a debugging session):

- **One UDP port per device** (`network.port`); two devices on one port sends
PASE to the wrong socket.
Expand Down
3 changes: 1 addition & 2 deletions docs/testing.html
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,7 @@ <h2>The virtual fleet</h2>
<pre><code>pgrep -fl "node .*\.mjs" # what's running
cd /tmp/matter-test &amp;&amp; node fan.mjs &gt;&gt; fan.log 2&gt;&amp;1 &amp;</code></pre>
<h3>Hard-won rig rules</h3>
<p>Each of these cost a debugging session (war stories in
<a href="https://github.com/simons-plugins/indigo-matter/blob/main/docs/HANDOVER.md">HANDOVER.md</a>):</p>
<p>Each of these cost a debugging session:</p>
<ul>
<li><strong>One UDP port per device</strong> — two devices on one port send PASE to the
wrong socket.</li>
Expand Down
2 changes: 1 addition & 1 deletion indigo-matter.indigoPlugin/Contents/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<key>IwsApiVersion</key>
<string>1.0.0</string>
<key>PluginVersion</key>
<string>2026.9.2</string>
<string>2026.9.3</string>
<key>ServerApiVersion</key>
<string>3.6</string>
</dict>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@
different string. The plugin bundle ships **no JavaScript**; ``bridge-node/`` is
a top-level source directory in the repo and is published from there.

**It is published**: 0.5.0 and 0.6.0 are both on the registry, so
**It is published**: 0.5.0 onward is on the registry, so
:meth:`BridgeProcess.install` resolves :data:`DEFAULT_INSTALL_SPEC` off npm like
any other dependency. The pin is deliberate and moves by hand — see
``docs/HANDOVER.md`` → "Publishing the bridge node" for the release steps, and
for the local-install recipe that is still the fastest way to test an unreleased
``bridge-node/README.md`` § Releasing for the release steps, and for the
local-install recipe that is still the fastest way to test an unreleased
node.
"""
from __future__ import annotations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ def create_devices(self, node: NodeInfo, suggested_room: Optional[str] = None) -
plan: list[tuple] = [] # (endpoint, spec)
# Detect which endpoint(s) carry PowerSource. Indigo applies
# Supports* via device props at creation, not Devices.xml statics
# (the colour-support lesson — see HANDOVER 2026-06-09 item 4).
# (the colour-support lesson; issue #56).
# Single PowerSource-bearing endpoint (the common case — e.g. FP300:
# battery on ep0, sensor on ep1) keeps the original node-wide
# behaviour: SupportsBatteryLevel on every device regardless of its
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,8 +386,7 @@ def diff_with_gaps(self, orig_dev: Any, new_dev: Any,
here (handlers are stateless singletons with no device context and no
latch), so ``export_bridge`` can say it once per device per streak.
Spelling an explicit "unknown" on the wire would be a protocol
addition; it is noted in ``docs/HANDOVER.md`` and deliberately not made
here.
addition, deliberately not made here.
"""
return self.diff_from(self.states_for(orig_dev, options), new_dev, options)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,7 @@ def _pairing_page(self) -> str:
for a code that Apple Home, Alexa and Google all accept *typed in*: the
page therefore shows the manual code at a size you can read across a
room, the raw ``MT:`` payload for copying, and a link to the CHIP
project's own QR viewer for anyone who wants to scan. The tradeoff is
recorded in ``docs/HANDOVER.md`` rather than only in this docstring.
project's own QR viewer for anyone who wants to scan.
"""
client = self.export_bridge.client if self.export_bridge is not None else None
if client is None or not client.connected:
Expand Down
Loading
Loading