Skip to content

Commit 349ba67

Browse files
authored
Release 3.2.0
## [3.2.0] - 2026-07-17 ### Breaking changes - [matterbridge]: Require matterbridge v.3.9.0. ### Changed - [toolchain]: Migrate to the native toolchain (tsgo + oxlint + oxfmt + vitest). Replace ESLint/Prettier/Jest with oxlint/oxfmt and Vitest. - [vitest]: Move the tests from `src` to `vitest` and migrate them from Jest and `matterbridge/jestutils` to Vitest and `matterbridge/vitest-utils`. - [vitest]: Update the test assertions for matterbridge v.3.9.x: endpoint names now use the device type name (i.e. `OnOffLightSwitch` instead of `MA-onoffswitch`) and `onOffLightSwitch` includes the `Binding` cluster. - [package]: Update dependencies. - [package]: Apply uniform style. - [package]: Upgrade package. - [package]: Bump `node-ansi-logger` to v.3.3.0. - [package]: Bump `node-persist-manager` to v.2.1.0. <a href="https://www.buymeacoffee.com/luligugithub"><img src="https://matterbridge.io/assets/bmc-button.svg" alt="Buy me a coffee" width="80"></a>
1 parent 841be2e commit 349ba67

51 files changed

Lines changed: 4909 additions & 8888 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/matterbridge.md

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
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`.

.agents/testing.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Testing Standards for Unit Tests (v.1.0.0)
2+
3+
## 1. Test Framework
4+
5+
- Jest is available in the repository when the file `jest.config.js` exists.
6+
- Vitest is available in the repository when the file `vite.config.ts` exists.
7+
- Bun test is available in the repository when the file `bunfig.toml` exists.
8+
- Jest tests live in `test` folders. Follow the existing convention in the repository for test file placement.
9+
- Vitest tests live in `vitest` folders. Follow the existing convention in the repository for test file placement.
10+
- Bun tests live in `buntest` folders. Follow the existing convention in the repository for test file placement.
11+
- Ensure that tests are written in TypeScript and follow the ESM module format.
12+
13+
## 2. Test Structure
14+
15+
- Organize tests in file named `*.test.ts` in the `test`, `vitest`, or `buntest` folders.
16+
- Use `describe` blocks to group related tests and `test` blocks for individual test cases.
17+
18+
## 3. Test Naming
19+
20+
- Use descriptive test names that clearly indicate the behavior being tested.
21+
- Follow the format: `should [expected behavior] when [condition]`.
22+
23+
## 4. Test Data
24+
25+
- Use small, deterministic test data to ensure tests are reliable and easy to understand.
26+
27+
## 5. Test Coverage
28+
29+
- Aim for high test coverage, but prioritize meaningful tests over achieving 100% coverage.
30+
31+
## 6. Mocking with Jest
32+
33+
- If using Jest, use `jest.unstable_mockModule` for mocking dependencies in ESM modules.
34+
- If using Jest, avoid using `jest.mock` as it is not compatible with ESM modules.
35+
36+
## 7. Running Tests
37+
38+
- Run the relevant full test unit from start to finish rather than assuming isolated single-test execution is reliable.
39+
- If only one test framework is installed, use `npm run test -- yourTest.test.ts` or `npm run test:coverage -- yourTest.test.ts` when the touched area can be validated by running the full relevant test file.
40+
- When both Jest and Vitest are installed, use `npm run test -- yourTest.test.ts` for Jest or `npm run test:vitest -- yourTest.test.ts` for Vitest.
41+
- When Bun test is installed, use `bun test yourTest.test.ts`.
42+
- Use the existing `tasks.json` test tasks for areas that require grouped test files, custom coverage targets, or custom ignore-pattern handling.
43+
- Avoid running all tests unnecessarily to save time and tokens.
44+
45+
## 8. Test Assertions
46+
47+
- Use appropriate Jest, Vitest, or Bun test matchers for assertions (e.g., `toBe`, `toEqual`, `toThrow`).
48+
- Ensure that assertions are clear and directly related to the behavior being tested.
49+
50+
## 9. Performance
51+
52+
- Avoid optimization in tests; focus on correctness and clarity.
53+
- Use simple loops and structures in tests to maintain readability and performance.
54+
- Avoid complex setups that may slow down test execution unless necessary for the behavior being tested.
55+
- Prefer simple test cases that are easy to understand and maintain over complex ones that may be difficult to debug.
56+
- Some tests in this repo are intentionally structured as multi-step flows, where state persists across successive steps within a single test unit. Run those test units in full, and keep each test unit isolated from other test units.

0 commit comments

Comments
 (0)