|
| 1 | +# Matterbridge Endpoint Guide (v.1.0.0) |
| 2 | + |
| 3 | +Use this guide when writing Matterbridge code in this repository or when authoring a plugin that consumes Matterbridge. |
| 4 | + |
| 5 | +## Public imports |
| 6 | + |
| 7 | +- Import core classes, endpoint helpers, and device type definitions from `matterbridge`. |
| 8 | +- Import single-class devices from `matterbridge/devices`. |
| 9 | + |
| 10 | +```ts |
| 11 | +import { |
| 12 | + MatterbridgeAccessoryPlatform, |
| 13 | + MatterbridgeDynamicPlatform, |
| 14 | + MatterbridgeEndpoint, |
| 15 | + addFixedLabel, |
| 16 | + addUserLabel, |
| 17 | + contactSensor, |
| 18 | + getAttribute, |
| 19 | + onOffLight, |
| 20 | + powerSource, |
| 21 | + setAttribute, |
| 22 | + subscribeAttribute, |
| 23 | + updateAttribute, |
| 24 | +} from 'matterbridge'; |
| 25 | + |
| 26 | +import { LaundryWasher, RoboticVacuumCleaner } from 'matterbridge/devices'; |
| 27 | +``` |
| 28 | + |
| 29 | +## Create a MatterbridgeEndpoint |
| 30 | + |
| 31 | +`MatterbridgeEndpoint` is the low-level building block for custom Matterbridge devices. |
| 32 | + |
| 33 | +Constructor: |
| 34 | + |
| 35 | +```ts |
| 36 | +new MatterbridgeEndpoint( |
| 37 | + definition: DeviceTypeDefinition | AtLeastOne<DeviceTypeDefinition>, |
| 38 | + options: MatterbridgeEndpointOptions = {}, |
| 39 | + debug = false, |
| 40 | +) |
| 41 | +``` |
| 42 | + |
| 43 | +Recommended pattern: |
| 44 | + |
| 45 | +```ts |
| 46 | +const device = new MatterbridgeEndpoint([contactSensor, powerSource], { id: 'EntryDoor' }) |
| 47 | + .createDefaultIdentifyClusterServer() |
| 48 | + .createDefaultBridgedDeviceBasicInformationClusterServer('Entry Door', 'ENTRY-DOOR-001', 0xfff1, 'Matterbridge', 'Entry Door Sensor') |
| 49 | + .createDefaultBooleanStateClusterServer(false) |
| 50 | + .createDefaultPowerSourceReplaceableBatteryClusterServer(75) |
| 51 | + .addRequiredClusters(); |
| 52 | +``` |
| 53 | + |
| 54 | +Rules that matter: |
| 55 | + |
| 56 | +- `definition` can be a single device type or an array of device types. |
| 57 | +- Use multiple device types when the endpoint needs more than one role, for example `[contactSensor, powerSource]`. |
| 58 | +- Call one of the Basic Information helpers before `registerDevice()`. Without `deviceName`, `serialNumber`, and `uniqueId`, registration fails. |
| 59 | +- Call `addRequiredClusters()` at the end of the chain so any required clusters (server or client) that you did not explicitly create are added automatically. |
| 60 | +- Use `addOptionalClusterServers()` only when you really want the optional clusters defined by the selected device type(s). |
| 61 | + |
| 62 | +## MatterbridgeEndpointOptions |
| 63 | + |
| 64 | +`MatterbridgeEndpointOptions` supports: |
| 65 | + |
| 66 | +- `id`: stable storage key for the endpoint. |
| 67 | +- `number`: explicit endpoint number when you need one. |
| 68 | +- `tagList`: semantic tags used for disambiguation, especially for composed devices or `mode: 'matter'` endpoints. |
| 69 | +- `mode`: `undefined`, `'server'`, or `'matter'`. |
| 70 | + |
| 71 | +Mode selection: |
| 72 | + |
| 73 | +- `undefined`: normal bridged endpoint. This is the default for most DynamicPlatform devices. |
| 74 | +- `'server'`: create an independent Matter device with its own server node. |
| 75 | +- `'matter'`: add the endpoint directly to the Matterbridge server node alongside the aggregator. |
| 76 | + |
| 77 | +Practical guidance: |
| 78 | + |
| 79 | +- Use `mode: undefined` for normal bridged devices shown as children of the bridge. |
| 80 | +- Use `mode: 'server'` when the device must be paired independently. |
| 81 | +- Use `mode: 'matter'` when the device should be a native Matter endpoint on the server node. |
| 82 | +- When using `mode: 'matter'`, respect Matter disambiguation rules and supply a `tagList` when sibling endpoints could be ambiguous. |
| 83 | + |
| 84 | +Implementation details worth remembering: |
| 85 | + |
| 86 | +- Spaces and `.` are removed from the internal endpoint id. The original value is retained as `originalId`. |
| 87 | +- Non-Latin ids are normalized to a generated unique id. |
| 88 | +- `id` should remain stable across restarts. |
| 89 | + |
| 90 | +## Choose the right Basic Information helper |
| 91 | + |
| 92 | +Use the helper that matches how the endpoint is exposed: |
| 93 | + |
| 94 | +- `createDefaultBasicInformationClusterServer(...)` |
| 95 | + Use for `mode: 'server'`, `mode: 'matter'`, and AccessoryPlatform devices. |
| 96 | +- `createDefaultBridgedDeviceBasicInformationClusterServer(...)` |
| 97 | + Use for bridged DynamicPlatform endpoints. |
| 98 | + |
| 99 | +Important behavior: |
| 100 | + |
| 101 | +- `createDefaultBasicInformationClusterServer(...)` sets the metadata on the endpoint. |
| 102 | +- For bridged endpoints, `registerDevice()` can add the `BridgedDeviceBasicInformation` cluster automatically when the device is running as a bridged endpoint in bridge mode, or in childbridge mode on a `DynamicPlatform`. |
| 103 | +- Explicitly calling `createDefaultBridgedDeviceBasicInformationClusterServer(...)` is clearer for bridged devices and matches the repo examples. |
| 104 | + |
| 105 | +## Register the endpoint from a plugin |
| 106 | + |
| 107 | +In plugin code, call `this.registerDevice(device)`. |
| 108 | + |
| 109 | +DynamicPlatform bridged device: |
| 110 | + |
| 111 | +```ts |
| 112 | +import { MatterbridgeDynamicPlatform, MatterbridgeEndpoint, onOffLight } from 'matterbridge'; |
| 113 | + |
| 114 | +export default function initializePlugin(matterbridge, log, config) { |
| 115 | + return new ExamplePlatform(matterbridge, log, config); |
| 116 | +} |
| 117 | + |
| 118 | +class ExamplePlatform extends MatterbridgeDynamicPlatform { |
| 119 | + async onStart(reason) { |
| 120 | + await this.ready; |
| 121 | + |
| 122 | + const device = new MatterbridgeEndpoint(onOffLight, { id: 'OnOffLightPlugin' }) |
| 123 | + .createDefaultBridgedDeviceBasicInformationClusterServer('Kitchen Light', 'LIGHT-001', 0xfff1, 'Matterbridge', 'Matterbridge OnOffLight') |
| 124 | + .addRequiredClusters(); |
| 125 | + |
| 126 | + await this.registerDevice(device); |
| 127 | + } |
| 128 | +} |
| 129 | +``` |
| 130 | + |
| 131 | +AccessoryPlatform device: |
| 132 | + |
| 133 | +```ts |
| 134 | +import { MatterbridgeAccessoryPlatform, MatterbridgeEndpoint, temperatureSensor } from 'matterbridge'; |
| 135 | + |
| 136 | +export default function initializePlugin(matterbridge, log, config) { |
| 137 | + return new ExamplePlatform(matterbridge, log, config); |
| 138 | +} |
| 139 | + |
| 140 | +class ExamplePlatform extends MatterbridgeAccessoryPlatform { |
| 141 | + async onStart(reason) { |
| 142 | + await this.ready; |
| 143 | + |
| 144 | + const device = new MatterbridgeEndpoint(temperatureSensor, { id: 'TemperatureSensorPlugin' }) |
| 145 | + .createDefaultBasicInformationClusterServer('Temperature Sensor', 'TEMP-001', 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Temperature Sensor') |
| 146 | + .addRequiredClusters(); |
| 147 | + |
| 148 | + await this.registerDevice(device); |
| 149 | + } |
| 150 | +} |
| 151 | +``` |
| 152 | + |
| 153 | +Standalone Matter device from a plugin: |
| 154 | + |
| 155 | +```ts |
| 156 | +const device = new MatterbridgeEndpoint(pressureSensor, { id: 'ServerNodeDevice', mode: 'server' }) |
| 157 | + .createDefaultBasicInformationClusterServer('Server Node Device', 'SERVER-001', 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Server Node Device') |
| 158 | + .addRequiredClusters(); |
| 159 | + |
| 160 | +await this.registerDevice(device); |
| 161 | +``` |
| 162 | + |
| 163 | +Native Matter endpoint on the server node: |
| 164 | + |
| 165 | +```ts |
| 166 | +const device = new MatterbridgeEndpoint(pressureSensor, { id: 'MatterNodeDevice', mode: 'matter' }) |
| 167 | + .createDefaultBasicInformationClusterServer('Matter Node Device', 'MATTER-001', 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Matter Node Device') |
| 168 | + .addRequiredClusters(); |
| 169 | + |
| 170 | +await this.registerDevice(device); |
| 171 | +``` |
| 172 | + |
| 173 | +Plugin rules: |
| 174 | + |
| 175 | +- Use `await this.ready` before creating or registering devices. |
| 176 | +- Always call `this.registerDevice(device)` from the platform. |
| 177 | +- Use `this.unregisterDevice(device)` or `this.unregisterAllDevices()` during shutdown or development resets. |
| 178 | +- AccessoryPlatform plugins can only expose one normal accessory device. If you need multiple bridged devices, use `MatterbridgeDynamicPlatform`. |
| 179 | +- Use stable names and serial numbers so the derived `uniqueId` stays stable. |
| 180 | + |
| 181 | +## Useful MatterbridgeEndpoint helpers |
| 182 | + |
| 183 | +Common helpers on the endpoint instance: |
| 184 | + |
| 185 | +- `hasClusterServer(cluster)` |
| 186 | +- `hasAttributeServer(cluster, attribute)` |
| 187 | +- `getAttribute(cluster, attribute)` |
| 188 | +- `setAttribute(cluster, attribute, value)` |
| 189 | +- `updateAttribute(cluster, attribute, value)` |
| 190 | +- `subscribeAttribute(cluster, attribute, listener)` |
| 191 | +- `addRequiredClusterServers()` |
| 192 | +- `addOptionalClusterServers()` |
| 193 | +- `addRequiredClusters()` |
| 194 | + |
| 195 | +Example: |
| 196 | + |
| 197 | +```ts |
| 198 | +await device.updateAttribute('OnOff', 'onOff', true); |
| 199 | +``` |
| 200 | + |
| 201 | +Cluster references can be passed in several ways: |
| 202 | + |
| 203 | +- behavior type |
| 204 | +- cluster type |
| 205 | +- cluster id |
| 206 | +- cluster name string such as `'OnOff'` |
| 207 | + |
| 208 | +Behavior type and cluster type are preferred because they are type-safe and avoid typos. |
| 209 | + |
| 210 | +Using the cluster name string is useful in plugins because it avoids importing every cluster type. |
| 211 | + |
| 212 | +## When to use a raw endpoint vs a single-class device |
| 213 | + |
| 214 | +Use a raw `MatterbridgeEndpoint` when: |
| 215 | + |
| 216 | +- you are building a custom combination of device types and clusters |
| 217 | +- you want full control over which default cluster servers are created |
| 218 | +- you are implementing a plugin-specific device model |
| 219 | + |
| 220 | +Use a single-class device when: |
| 221 | + |
| 222 | +- Matterbridge already ships a class for the device category you need |
| 223 | +- you want a working device with sensible default clusters and behaviors |
| 224 | +- you prefer a higher-level constructor over manual endpoint assembly |
| 225 | + |
| 226 | +## Single-class devices |
| 227 | + |
| 228 | +Single-class devices are exported from `matterbridge/devices`. |
| 229 | + |
| 230 | +These classes already extend `MatterbridgeEndpoint` and usually do all of the following internally: |
| 231 | + |
| 232 | +- create the correct device type combination |
| 233 | +- create Basic Information |
| 234 | +- create Power Source when needed |
| 235 | +- create the default cluster servers and behavior wiring required by the device |
| 236 | + |
| 237 | +Current exported single-class devices: |
| 238 | + |
| 239 | +- Media: `BasicVideoPlayer`, `CastingVideoPlayer`, `Speaker` |
| 240 | +- Matter 1.5 additions: `Closure`, `ClosurePanel`, `IrrigationSystem`, `SoilSensor` |
| 241 | +- Robotic: `RoboticVacuumCleaner` |
| 242 | +- Appliances: `AirConditioner`, `Cooktop`, `Dishwasher`, `ExtractorHood`, `LaundryDryer`, `LaundryWasher`, `MicrowaveOven`, `Oven`, `Refrigerator` |
| 243 | +- Energy: `BatteryStorage`, `Evse`, `HeatPump`, `SolarPower`, `WaterHeater` |
| 244 | + |
| 245 | +### Basic single-class example |
| 246 | + |
| 247 | +```ts |
| 248 | +import { LaundryWasher } from 'matterbridge/devices'; |
| 249 | + |
| 250 | +const washer = new LaundryWasher('Laundry Washer', 'LW-001'); |
| 251 | +await this.registerDevice(washer); |
| 252 | +``` |
| 253 | + |
| 254 | +This is enough because the class constructor already creates the required device types, basic information, power source, and default cluster servers. |
| 255 | + |
| 256 | +### Single-class example with explicit mode |
| 257 | + |
| 258 | +Some single-class devices expose `mode` directly in their constructor. For example: |
| 259 | + |
| 260 | +```ts |
| 261 | +import { RoboticVacuumCleaner } from 'matterbridge/devices'; |
| 262 | + |
| 263 | +const robot = new RoboticVacuumCleaner('Robot Vacuum', 'RVC-001', 'server'); |
| 264 | +await this.registerDevice(robot); |
| 265 | +``` |
| 266 | + |
| 267 | +Use this when the class supports it and you want a standalone or native Matter device instead of a bridged endpoint. |
| 268 | + |
| 269 | +### Composed single-class devices |
| 270 | + |
| 271 | +Some single-class devices are composed devices and need child endpoints added after construction: |
| 272 | + |
| 273 | +- `Oven`: create the oven, then call `addCabinet(...)` |
| 274 | +- `Cooktop`: create the cooktop, then call `addSurface(...)` |
| 275 | +- `Refrigerator`: create the refrigerator, then call `addCabinet(...)` |
| 276 | + |
| 277 | +Example: |
| 278 | + |
| 279 | +```ts |
| 280 | +import { PositionTag } from '@matter/node'; |
| 281 | +import { Cooktop } from 'matterbridge/devices'; |
| 282 | + |
| 283 | +const cooktop = new Cooktop('Cooktop', 'CT-001'); |
| 284 | +cooktop.addSurface('Surface Top Left', [ |
| 285 | + { mfgCode: null, namespaceId: PositionTag.Top.namespaceId, tag: PositionTag.Top.tag, label: PositionTag.Top.label }, |
| 286 | + { mfgCode: null, namespaceId: PositionTag.Left.namespaceId, tag: PositionTag.Left.tag, label: PositionTag.Left.label }, |
| 287 | +]); |
| 288 | + |
| 289 | +await this.registerDevice(cooktop); |
| 290 | +``` |
| 291 | + |
| 292 | +For composed devices and for `mode: 'matter'`, use semantic tags carefully. `tagList` exists to satisfy Matter endpoint disambiguation rules. |
| 293 | + |
| 294 | +## Recommended plugin workflow |
| 295 | + |
| 296 | +For most plugins, follow this order: |
| 297 | + |
| 298 | +1. Wait for `this.ready`. |
| 299 | +2. Create the endpoint or single-class device. |
| 300 | +3. Set device identity with one of the Basic Information helpers if you are using a raw `MatterbridgeEndpoint`. |
| 301 | +4. Add explicit cluster servers you need. |
| 302 | +5. Call `addRequiredClusters()` last. |
| 303 | +6. Register the device with `await this.registerDevice(device)`. |
| 304 | +7. Optionally add UI metadata with `setSelectDevice()` and `setSelectEntity()`. |
| 305 | + |
| 306 | +## Avoid these mistakes |
| 307 | + |
| 308 | +- Do not register a raw `MatterbridgeEndpoint` before assigning basic identity metadata. |
| 309 | +- Do not use `MatterbridgeAccessoryPlatform` for multiple normal bridged accessories. |
| 310 | +- Do not forget that some bridged endpoints need semantic tags for disambiguation. |
| 311 | +- Do not assume single-class devices all share the same constructor shape. Check the device class when you need custom defaults or a mode argument. |
| 312 | +- Do not call Matterbridge internals directly from plugin code when `registerDevice()` already handles validation and mode-specific setup. |
| 313 | + |
| 314 | +## Short decision guide |
| 315 | + |
| 316 | +- Need a custom sensor, switch, or actuator with a few clusters: use `MatterbridgeEndpoint`. |
| 317 | +- Need a supported appliance, robotic, media, energy, closure, irrigation, or soil device: start with `matterbridge/devices`. |
| 318 | +- Need one standalone accessory with its own server node: use `mode: 'server'` or a single-class device that exposes it. |
| 319 | +- Need multiple bridged devices in a plugin: use `MatterbridgeDynamicPlatform`. |
0 commit comments