matterbridge-example-dynamic-platform 3.0.2-dev-20260803-06c5b3e → 3.0.2-dev-20260808-eeaaae3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -5
- package/README.md +25 -14
- package/dist/module.js +122 -18
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -31,21 +31,24 @@ If you like this project and find it useful, please consider giving it a star on
|
|
|
31
31
|
|
|
32
32
|
### Breaking changes
|
|
33
33
|
|
|
34
|
-
- [matterbridge]: Require matterbridge v.3.10.
|
|
34
|
+
- [matterbridge]: Require matterbridge v.3.10.5 with matter v.1.6.0.
|
|
35
35
|
|
|
36
36
|
### Added
|
|
37
37
|
|
|
38
|
-
- [platform]: Add `closureGarageDoor` device.
|
|
39
38
|
- [chip]: Add chip-test toolchain agents instruction and chip-test runner.
|
|
39
|
+
- [chip]: Add `chipTests.json`/`chipTests.md` with the generic device basic composition, conformance and default warnings tests, and document known CHIP test suite gaps.
|
|
40
40
|
- [frontend]: Add plugin-frontend agents instructions.
|
|
41
|
+
- [platform]: Add `closureGarageDoor` device.
|
|
42
|
+
- [platform]: Add `closureVenetianBlind` device with Lift and Tilt panels.
|
|
41
43
|
|
|
42
44
|
### Changed
|
|
43
45
|
|
|
44
46
|
- [package]: Upgrade package.
|
|
45
47
|
- [package]: Update dependencies.
|
|
46
|
-
- [package]: Bump
|
|
47
|
-
- [package]: Bump `
|
|
48
|
-
- [package]: Bump `
|
|
48
|
+
- [package]: Bump `typescript` to v.7.0.2.
|
|
49
|
+
- [package]: Bump `@types/node` to v.26.2.0.
|
|
50
|
+
- [package]: Bump `oxfmt` to v.0.62.0.
|
|
51
|
+
- [package]: Bump `oxlint` to v.1.77.0.
|
|
49
52
|
- [package]: Bump `oxlint-tsgolint` to v.7.0.2001.
|
|
50
53
|
- [package]: Update agents configs.
|
|
51
54
|
|
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
Matterbridge dynamic platform example plugin is a template to develop your own plugin using the dynamic platform.
|
|
25
25
|
|
|
26
|
-
It exposes
|
|
26
|
+
It exposes 72 virtual devices:
|
|
27
27
|
|
|
28
28
|
- a door contact sensor
|
|
29
29
|
- a motion sensor
|
|
@@ -96,9 +96,12 @@ It exposes 71 virtual devices:
|
|
|
96
96
|
- an irrigation system (Matter 1.5.0)
|
|
97
97
|
- an irrigation system with four zones (Matter 1.5.0)
|
|
98
98
|
- a closure device (Matter 1.5.0, supported by SmartThings)
|
|
99
|
+
- a closure device with Lift and Tilt panels (Matter 1.5.0, supported by SmartThings)
|
|
99
100
|
|
|
100
101
|
All these devices continuously change their state and position. The plugin also shows how to use all the command handlers (so you can control all the devices), how to subscribe to attributes, and how to trigger events.
|
|
101
102
|
|
|
103
|
+
All devices pass the generic Matter conformance test suite (`TC_DeviceBasicComposition.py`, `TC_DeviceConformance.py` and `TC_DefaultWarnings.py`) run via the CHIP test harness, with one known upstream test-suite gap for the Closure devices — see [chipTests.md](./chipTests.md) for details and how to run the suite against this plugin.
|
|
104
|
+
|
|
102
105
|
If you want to write your own plugin, the easiest way to get started is to clone the [Matterbridge Plugin Template](https://github.com/Luligu/matterbridge-plugin-template). It has **Dev Container support for an instant development environment**, with all tools and extensions (like Node.js, npm, TypeScript, ESLint, Prettier, Jest, and Vitest) already loaded and configured.
|
|
103
106
|
|
|
104
107
|
If you like this project and find it useful, please consider giving it a star on [GitHub](https://github.com/Luligu/matterbridge-example-dynamic-platform) and sponsoring it.
|
|
@@ -111,11 +114,11 @@ If you like this project and find it useful, please consider giving it a star on
|
|
|
111
114
|
|
|
112
115
|
See the guidelines on [Matterbridge](https://github.com/Luligu/matterbridge/blob/main/README.md) for more information.
|
|
113
116
|
|
|
114
|
-
## Repository
|
|
117
|
+
## Repository toolchain
|
|
115
118
|
|
|
116
|
-
> **Note:** This repository uses a new toolchain. It replaces the traditional TypeScript / ESLint / Prettier / Jest stack with a faster
|
|
119
|
+
> **Note:** This repository uses a new toolchain. It replaces the traditional TypeScript / ESLint / Prettier / Jest stack with a faster and lighter setup.
|
|
117
120
|
|
|
118
|
-
- **No `typescript` package** — replaced by [TypeScript Native](https://github.com/microsoft/typescript-go).
|
|
121
|
+
- **No `typescript 6.x` package** — replaced by [TypeScript Native 7.x](https://github.com/microsoft/typescript-go).
|
|
119
122
|
- **No ESLint, no Prettier** — replaced by the [oxc](https://oxc.rs) stack: [oxlint](https://oxc.rs/docs/guide/usage/linter.html) for linting and [oxfmt](https://oxc.rs/docs/guide/usage/formatter.html) for formatting.
|
|
120
123
|
- **No Jest** — replaced by [Vitest](https://vitest.dev), which is much faster and natively supports ESM without extra configuration.
|
|
121
124
|
- **Far fewer development dependencies** — the number of installed packages drops from **~600** to **~75**. A clean install is much faster.
|
|
@@ -129,25 +132,33 @@ See also the [Style Guide](./STYLEGUIDE.md) for JSDoc, naming, and logging conve
|
|
|
129
132
|
|
|
130
133
|
## Copilot instructions
|
|
131
134
|
|
|
132
|
-
| File
|
|
133
|
-
|
|
|
134
|
-
| `.github/copilot-instructions.md`
|
|
135
|
-
| `.github/instructions/
|
|
136
|
-
| `.github/instructions/
|
|
135
|
+
| File | Notes |
|
|
136
|
+
| ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
137
|
+
| `.github/copilot-instructions.md` | Main project instructions — always loaded |
|
|
138
|
+
| `.github/instructions/chip-tests/chip-tests.instructions.md` | CHIP conformance test harness — scoped to CHIP test files |
|
|
139
|
+
| `.github/instructions/matterbridge/matterbridge.instructions.md` | Matterbridge endpoint guide — dedicated Copilot instruction file |
|
|
140
|
+
| `.github/instructions/plugin-frontend/plugin-frontend.instructions.md` | Plugin frontend SPA and custom REST API guide — scoped to frontend and plugin code |
|
|
141
|
+
| `.github/instructions/testing/unit-tests.instructions.md` | Testing standards — scoped to `**/*.test.ts` |
|
|
137
142
|
|
|
138
143
|
## Claude instructions
|
|
139
144
|
|
|
140
|
-
| File
|
|
141
|
-
|
|
|
142
|
-
| `CLAUDE.md`
|
|
143
|
-
| `.claude/rules/
|
|
144
|
-
| `.claude/rules/
|
|
145
|
+
| File | Notes |
|
|
146
|
+
| --------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
147
|
+
| `CLAUDE.md` | Main project instructions — always loaded |
|
|
148
|
+
| `.claude/rules/chip-tests/chip-tests.instructions.md` | CHIP conformance test harness — scoped to CHIP test files |
|
|
149
|
+
| `.claude/rules/matterbridge/matterbridge.instructions.md` | Matterbridge endpoint guide — loaded for all contexts |
|
|
150
|
+
| `.claude/rules/plugin-frontend/plugin-frontend.instructions.md` | Plugin frontend SPA and custom REST API guide — scoped to frontend and plugin code |
|
|
151
|
+
| `.claude/rules/testing/unit-tests.instructions.md` | Testing standards — scoped to `**/*.test.ts` |
|
|
145
152
|
|
|
146
153
|
## Codex/Agents instructions
|
|
147
154
|
|
|
148
155
|
| File | Notes |
|
|
149
156
|
| ---------------------------- | ------------------------------------------------- |
|
|
150
157
|
| `AGENTS.md` | Main project instructions |
|
|
158
|
+
| `.agents/chip-tests.md` | CHIP conformance test harness |
|
|
159
|
+
| `.agents/matterbridge.md` | Matterbridge endpoint guide |
|
|
160
|
+
| `.agents/plugin-frontend.md` | Plugin frontend SPA and custom REST API guide |
|
|
161
|
+
| `.agents/testing.md` | Testing and validation expectations |
|
|
151
162
|
| `.codex/config.toml` | Codex project permissions, approvals, and profile |
|
|
152
163
|
| `.codex/rules/default.rules` | Codex command allow, prompt, and deny rules |
|
|
153
164
|
|
package/dist/module.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { aggregator, airPurifier, airQualitySensor, bridgedNode, colorTemperatureLight, contactSensor, windowCovering, dimmableLight, mountedDimmableLoadControl, dimmablePlugInUnit, doorLock, electricalSensor, extendedColorLight, fan, flowSensor, genericSwitch, getSemtag, humiditySensor, lightSensor, MatterbridgeDynamicPlatform, MatterbridgeEndpoint, modeSelect, occupancySensor, onOffLight, mountedOnOffControl, onOffPlugInUnit, onOffLightSwitch, powerSource, pressureSensor, pump, rainSensor, smokeCoAlarm, temperatureSensor, thermostat, waterFreezeDetector, waterLeakDetector, waterValve, } from 'matterbridge';
|
|
2
2
|
import { AirConditioner, BasicVideoPlayer, BatteryStorage, Closure, Cooktop, Dishwasher, Evse, ExtractorHood, HeatPump, IrrigationSystem, LaundryDryer, LaundryWasher, MicrowaveOven, Oven, Refrigerator, RoboticVacuumCleaner, SoilSensor, SolarPower, Speaker, WaterHeater, } from 'matterbridge/devices';
|
|
3
3
|
import { debugStringify } from 'matterbridge/logger';
|
|
4
|
-
import { ClosureTag, CommonAreaNamespaceTag, CommonLocationTag, CommonNumberTag, CommonPositionTag, RefrigeratorTag, SwitchesTag, UINT16_MAX, UINT32_MAX, } from 'matterbridge/matter';
|
|
5
|
-
import { AirQuality, BooleanState, BridgedDeviceBasicInformation, CarbonDioxideConcentrationMeasurement, CarbonMonoxideConcentrationMeasurement, ClosureControl, ColorControl, Descriptor, DeviceEnergyManagement, DoorLock, ElectricalEnergyMeasurement, ElectricalPowerMeasurement, EnergyEvse, EnergyEvseMode, FanControl, FlowMeasurement, FormaldehydeConcentrationMeasurement, Identify, IlluminanceMeasurement, LevelControl, NitrogenDioxideConcentrationMeasurement, OccupancySensing, OnOff, OperationalState, OvenMode, OzoneConcentrationMeasurement, Pm1ConcentrationMeasurement, Pm10ConcentrationMeasurement, Pm25ConcentrationMeasurement, PowerSource, PressureMeasurement, RadonConcentrationMeasurement, RelativeHumidityMeasurement, RvcCleanMode, RvcOperationalState, RvcRunMode, SmokeCoAlarm, TemperatureMeasurement, Thermostat, TotalVolatileOrganicCompoundsConcentrationMeasurement, WindowCovering, } from 'matterbridge/matter/clusters';
|
|
4
|
+
import { ClosureCoveringTag, ClosurePanelTag, ClosureTag, CommonAreaNamespaceTag, CommonLocationTag, CommonNumberTag, CommonPositionTag, RefrigeratorTag, SwitchesTag, UINT16_MAX, UINT32_MAX, } from 'matterbridge/matter';
|
|
5
|
+
import { AirQuality, BooleanState, BridgedDeviceBasicInformation, CarbonDioxideConcentrationMeasurement, CarbonMonoxideConcentrationMeasurement, ClosureControl, ClosureDimension, ColorControl, Descriptor, DeviceEnergyManagement, DoorLock, ElectricalEnergyMeasurement, ElectricalPowerMeasurement, EnergyEvse, EnergyEvseMode, FanControl, FlowMeasurement, FormaldehydeConcentrationMeasurement, Identify, IlluminanceMeasurement, LevelControl, NitrogenDioxideConcentrationMeasurement, OccupancySensing, OnOff, OperationalState, OvenMode, OzoneConcentrationMeasurement, Pm1ConcentrationMeasurement, Pm10ConcentrationMeasurement, Pm25ConcentrationMeasurement, PowerSource, PressureMeasurement, RadonConcentrationMeasurement, RelativeHumidityMeasurement, RvcCleanMode, RvcOperationalState, RvcRunMode, SmokeCoAlarm, TemperatureMeasurement, Thermostat, TotalVolatileOrganicCompoundsConcentrationMeasurement, WindowCovering, } from 'matterbridge/matter/clusters';
|
|
6
6
|
import { fireAndForget, getEnumDescription, isValidBoolean, isValidNumber, isValidObject, isValidString } from 'matterbridge/utils';
|
|
7
7
|
function luxToMatter(lux) {
|
|
8
8
|
if (!Number.isFinite(lux) || lux <= 0)
|
|
@@ -35,6 +35,7 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
|
|
|
35
35
|
irrigation;
|
|
36
36
|
irrigationSystem;
|
|
37
37
|
closureGarageDoor;
|
|
38
|
+
closureVenetianBlind;
|
|
38
39
|
select;
|
|
39
40
|
climate;
|
|
40
41
|
switch;
|
|
@@ -107,8 +108,8 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
|
|
|
107
108
|
constructor(matterbridge, log, config) {
|
|
108
109
|
super(matterbridge, log, config);
|
|
109
110
|
this.config = config;
|
|
110
|
-
if (typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.10.
|
|
111
|
-
throw new Error(`This plugin requires Matterbridge version >= "3.10.
|
|
111
|
+
if (typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.10.5')) {
|
|
112
|
+
throw new Error(`This plugin requires Matterbridge version >= "3.10.5". Please update Matterbridge from ${this.matterbridge.matterbridgeVersion} to the latest version in the frontend.`);
|
|
112
113
|
}
|
|
113
114
|
this.log.info('Initializing platform:', this.config.name);
|
|
114
115
|
}
|
|
@@ -167,7 +168,7 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
|
|
|
167
168
|
this.flow = await this.addDevice(this.flow);
|
|
168
169
|
this.soil = new SoilSensor('Soil Sensor', 'SOI000067', { soilMoistureMeasuredValue: 45, temperatureMeasuredValue: 3500, batteryPowered: true });
|
|
169
170
|
this.soil = await this.addDevice(this.soil);
|
|
170
|
-
this.irrigation = new IrrigationSystem('Irrigation System', 'IRR000068', {
|
|
171
|
+
this.irrigation = new IrrigationSystem('Irrigation System', 'IRR000068', { batteryPowered: true, flowMeasuredValue: 15 }).addZone(CommonNumberTag.One);
|
|
171
172
|
this.irrigation = (await this.addDevice(this.irrigation));
|
|
172
173
|
this.irrigationSystem = new IrrigationSystem('Irrigation System 4 zones', 'IRR000069', { flowMeasuredValue: 60 })
|
|
173
174
|
.addZone(CommonNumberTag.One)
|
|
@@ -179,26 +180,127 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
|
|
|
179
180
|
tagList: [getSemtag(ClosureTag.GarageDoor)],
|
|
180
181
|
});
|
|
181
182
|
this.closureGarageDoor = (await this.addDevice(this.closureGarageDoor));
|
|
182
|
-
|
|
183
|
+
let closureGarageDoorMoveTimeout;
|
|
184
|
+
function closureTargetToCurrentPosition(position) {
|
|
185
|
+
if (position === ClosureControl.TargetPosition.MoveToFullyClosed)
|
|
186
|
+
return ClosureControl.CurrentPosition.FullyClosed;
|
|
187
|
+
if (position === ClosureControl.TargetPosition.MoveToFullyOpen)
|
|
188
|
+
return ClosureControl.CurrentPosition.FullyOpened;
|
|
189
|
+
if (position === ClosureControl.TargetPosition.MoveToPedestrianPosition)
|
|
190
|
+
return ClosureControl.CurrentPosition.OpenedForPedestrian;
|
|
191
|
+
if (position === ClosureControl.TargetPosition.MoveToVentilationPosition)
|
|
192
|
+
return ClosureControl.CurrentPosition.OpenedForVentilation;
|
|
193
|
+
return ClosureControl.CurrentPosition.OpenedAtSignature;
|
|
194
|
+
}
|
|
195
|
+
this.closureGarageDoor?.addCommandHandler('ClosureControl.moveTo', ({ request: { position, latch, speed }, attributes }) => {
|
|
183
196
|
this.closureGarageDoor?.log.info(`Command moveTo called position:${position} latch:${latch} speed:${speed}`);
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
if (targetState === undefined || targetState === null)
|
|
197
|
+
attributes.countdownTime = 1;
|
|
198
|
+
closureGarageDoorMoveTimeout = setTimeout(() => {
|
|
199
|
+
const targetState = this.closureGarageDoor?.getAttribute(ClosureControl, 'overallTargetState');
|
|
200
|
+
if (targetState === undefined || targetState === null || targetState.position === undefined || targetState.position === null)
|
|
188
201
|
return;
|
|
189
|
-
void this.closureGarageDoor?.setAttribute(ClosureControl
|
|
190
|
-
void this.closureGarageDoor?.setAttribute(ClosureControl
|
|
191
|
-
|
|
202
|
+
void this.closureGarageDoor?.setAttribute(ClosureControl, 'mainState', ClosureControl.MainState.Stopped, this.closureGarageDoor.log);
|
|
203
|
+
void this.closureGarageDoor?.setAttribute(ClosureControl, 'countdownTime', 0, this.closureGarageDoor.log);
|
|
204
|
+
void this.closureGarageDoor?.setAttribute(ClosureControl, 'overallCurrentState', {
|
|
205
|
+
position: closureTargetToCurrentPosition(targetState.position),
|
|
192
206
|
latch: targetState.latch,
|
|
193
207
|
speed: targetState.speed,
|
|
194
|
-
secureState: targetState.position === ClosureControl.TargetPosition.MoveToFullyClosed
|
|
208
|
+
secureState: targetState.position === ClosureControl.TargetPosition.MoveToFullyClosed && targetState.latch === true,
|
|
195
209
|
}, this.closureGarageDoor.log);
|
|
196
|
-
}, 1000);
|
|
197
|
-
currentStateTimeout.unref();
|
|
210
|
+
}, 1000).unref();
|
|
198
211
|
});
|
|
199
212
|
this.closureGarageDoor?.addCommandHandler('ClosureControl.stop', () => {
|
|
200
213
|
this.closureGarageDoor?.log.info('Command stop called');
|
|
201
|
-
|
|
214
|
+
clearTimeout(closureGarageDoorMoveTimeout);
|
|
215
|
+
closureGarageDoorMoveTimeout = undefined;
|
|
216
|
+
});
|
|
217
|
+
this.closureVenetianBlind = new Closure('Venetian Blind', 'VEN000072', {
|
|
218
|
+
tagList: [getSemtag(ClosureTag.Covering), getSemtag(ClosureCoveringTag.Venetian)],
|
|
219
|
+
});
|
|
220
|
+
const closureVenetianBlindLift = this.closureVenetianBlind.addPanel('Lift', [getSemtag(ClosurePanelTag.Lift)], 'lift');
|
|
221
|
+
const closureVenetianBlindTilt = this.closureVenetianBlind.addPanel('Tilt', [getSemtag(ClosurePanelTag.Tilt)], 'tilt');
|
|
222
|
+
this.closureVenetianBlind = (await this.addDevice(this.closureVenetianBlind));
|
|
223
|
+
const closurePanelTargetPercent = (position) => {
|
|
224
|
+
if (position === ClosureControl.TargetPosition.MoveToFullyOpen)
|
|
225
|
+
return 0;
|
|
226
|
+
if (position === ClosureControl.TargetPosition.MoveToFullyClosed)
|
|
227
|
+
return 10000;
|
|
228
|
+
return 5000;
|
|
229
|
+
};
|
|
230
|
+
const closurePanelReachTimeouts = new Map();
|
|
231
|
+
let closureVenetianBlindMoveTimeout;
|
|
232
|
+
const clearClosureVenetianBlindTimeouts = () => {
|
|
233
|
+
clearTimeout(closureVenetianBlindMoveTimeout);
|
|
234
|
+
closureVenetianBlindMoveTimeout = undefined;
|
|
235
|
+
for (const pending of closurePanelReachTimeouts.values())
|
|
236
|
+
clearTimeout(pending);
|
|
237
|
+
closurePanelReachTimeouts.clear();
|
|
238
|
+
};
|
|
239
|
+
const syncClosureVenetianBlindFromPanels = () => {
|
|
240
|
+
const liftTarget = closureVenetianBlindLift.getAttribute(ClosureDimension, 'targetState');
|
|
241
|
+
const liftCurrent = closureVenetianBlindLift.getAttribute(ClosureDimension, 'currentState');
|
|
242
|
+
const tiltTarget = closureVenetianBlindTilt.getAttribute(ClosureDimension, 'targetState');
|
|
243
|
+
const tiltCurrent = closureVenetianBlindTilt.getAttribute(ClosureDimension, 'currentState');
|
|
244
|
+
if (!liftTarget || !liftCurrent || !tiltTarget || !tiltCurrent)
|
|
245
|
+
return;
|
|
246
|
+
if (liftCurrent.position !== liftTarget.position || tiltCurrent.position !== tiltTarget.position)
|
|
247
|
+
return;
|
|
248
|
+
const overallPosition = liftCurrent.position === 0
|
|
249
|
+
? ClosureControl.CurrentPosition.FullyOpened
|
|
250
|
+
: liftCurrent.position === 10000
|
|
251
|
+
? ClosureControl.CurrentPosition.FullyClosed
|
|
252
|
+
: ClosureControl.CurrentPosition.PartiallyOpened;
|
|
253
|
+
void this.closureVenetianBlind?.setAttribute(ClosureControl, 'mainState', ClosureControl.MainState.Stopped, this.closureVenetianBlind.log);
|
|
254
|
+
void this.closureVenetianBlind?.setAttribute(ClosureControl, 'overallCurrentState', { position: overallPosition, latch: liftCurrent.latch ?? true, speed: liftCurrent.speed, secureState: liftCurrent.position === 10000 && liftCurrent.latch === true }, this.closureVenetianBlind?.log);
|
|
255
|
+
};
|
|
256
|
+
const simulateClosurePanelReachingTarget = (panel) => {
|
|
257
|
+
const pending = closurePanelReachTimeouts.get(panel);
|
|
258
|
+
if (pending)
|
|
259
|
+
clearTimeout(pending);
|
|
260
|
+
const currentStateTimeout = setTimeout(() => {
|
|
261
|
+
void (async () => {
|
|
262
|
+
closurePanelReachTimeouts.delete(panel);
|
|
263
|
+
const targetState = panel.getAttribute(ClosureDimension, 'targetState');
|
|
264
|
+
if (targetState === undefined || targetState === null)
|
|
265
|
+
return;
|
|
266
|
+
await panel.setAttribute(ClosureDimension, 'currentState', targetState, panel.log);
|
|
267
|
+
syncClosureVenetianBlindFromPanels();
|
|
268
|
+
})();
|
|
269
|
+
}, 1000);
|
|
270
|
+
currentStateTimeout.unref();
|
|
271
|
+
closurePanelReachTimeouts.set(panel, currentStateTimeout);
|
|
272
|
+
};
|
|
273
|
+
const moveClosurePanelTo = (panel, position, latch, speed) => {
|
|
274
|
+
void panel.setAttribute(ClosureDimension, 'targetState', { position, latch: latch ?? true, speed }, panel.log);
|
|
275
|
+
simulateClosurePanelReachingTarget(panel);
|
|
276
|
+
};
|
|
277
|
+
this.closureVenetianBlind?.addCommandHandler('ClosureControl.moveTo', ({ request: { position, latch, speed }, attributes }) => {
|
|
278
|
+
this.closureVenetianBlind?.log.info(`Command moveTo called position:${position} latch:${latch} speed:${speed}`);
|
|
279
|
+
attributes.countdownTime = 1;
|
|
280
|
+
clearClosureVenetianBlindTimeouts();
|
|
281
|
+
closureVenetianBlindMoveTimeout = setTimeout(() => {
|
|
282
|
+
void this.closureVenetianBlind?.setAttribute(ClosureControl, 'countdownTime', 0, this.closureVenetianBlind.log);
|
|
283
|
+
closureVenetianBlindMoveTimeout = undefined;
|
|
284
|
+
const targetState = this.closureVenetianBlind?.getAttribute(ClosureControl, 'overallTargetState');
|
|
285
|
+
if (targetState === undefined || targetState === null || targetState.position === undefined || targetState.position === null)
|
|
286
|
+
return;
|
|
287
|
+
const panelPercent = closurePanelTargetPercent(targetState.position);
|
|
288
|
+
moveClosurePanelTo(closureVenetianBlindLift, panelPercent, targetState.latch, targetState.speed);
|
|
289
|
+
moveClosurePanelTo(closureVenetianBlindTilt, panelPercent, targetState.latch, targetState.speed);
|
|
290
|
+
}, 1000).unref();
|
|
291
|
+
});
|
|
292
|
+
this.closureVenetianBlind?.addCommandHandler('ClosureControl.stop', () => {
|
|
293
|
+
this.closureVenetianBlind?.log.info('Command stop called');
|
|
294
|
+
clearClosureVenetianBlindTimeouts();
|
|
295
|
+
});
|
|
296
|
+
const addClosurePanelSetTargetSimulation = (panel) => {
|
|
297
|
+
panel.addCommandHandler('ClosureDimension.setTarget', ({ request: { position, latch, speed } }) => {
|
|
298
|
+
panel.log.info(`Command setTarget called position:${position} latch:${latch} speed:${speed}`);
|
|
299
|
+
simulateClosurePanelReachingTarget(panel);
|
|
300
|
+
});
|
|
301
|
+
};
|
|
302
|
+
addClosurePanelSetTargetSimulation(closureVenetianBlindLift);
|
|
303
|
+
addClosurePanelSetTargetSimulation(closureVenetianBlindTilt);
|
|
202
304
|
this.climate = new MatterbridgeEndpoint([bridgedNode, powerSource], { id: 'Climate' }, this.config.debug)
|
|
203
305
|
.createDefaultBridgedDeviceBasicInformationClusterServer('Climate', 'CLI00008', 0xfff1, 'Matterbridge', 'Matterbridge Climate')
|
|
204
306
|
.createDefaultPowerSourceReplaceableBatteryClusterServer(90, PowerSource.BatChargeLevel.Ok, 2990, '2 x AA', 2, PowerSource.BatReplaceability.UserReplaceable)
|
|
@@ -223,9 +325,11 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
|
|
|
223
325
|
this.switch = new MatterbridgeEndpoint([onOffLightSwitch, bridgedNode, powerSource], { id: 'Switch' }, this.config.debug)
|
|
224
326
|
.createDefaultIdentifyClusterServer()
|
|
225
327
|
.createDefaultBridgedDeviceBasicInformationClusterServer('Switch', 'SWI00010', 0xfff1, 'Matterbridge', 'Matterbridge Switch')
|
|
226
|
-
.createDefaultOnOffClusterServer()
|
|
227
328
|
.createDefaultPowerSourceWiredClusterServer()
|
|
228
329
|
.addRequiredClusters();
|
|
330
|
+
if (process.env.MATTERBRIDGE_CHIP_TEST !== '1') {
|
|
331
|
+
this.switch.createDefaultOnOffClusterServer();
|
|
332
|
+
}
|
|
229
333
|
this.switch = await this.addDevice(this.switch);
|
|
230
334
|
this.switch?.addCommandHandler('identify', ({ request: { identifyTime } }) => {
|
|
231
335
|
this.log.info(`Command identify called identifyTime:${identifyTime}`);
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "matterbridge-example-dynamic-platform",
|
|
3
|
-
"version": "3.0.2-dev-
|
|
3
|
+
"version": "3.0.2-dev-20260808-eeaaae3",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "matterbridge-example-dynamic-platform",
|
|
9
|
-
"version": "3.0.2-dev-
|
|
9
|
+
"version": "3.0.2-dev-20260808-eeaaae3",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"node-ansi-logger": "3.3.0",
|
package/package.json
CHANGED