@matter/node 0.16.0-alpha.0-20251101-70c8d51d7 → 0.16.0-alpha.0-20251103-b47ffa15b

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.
Files changed (159) hide show
  1. package/dist/cjs/behavior/Behavior.d.ts +2 -2
  2. package/dist/cjs/behavior/Behavior.d.ts.map +1 -1
  3. package/dist/cjs/behavior/Behavior.js +1 -1
  4. package/dist/cjs/behavior/Behavior.js.map +1 -1
  5. package/dist/cjs/behavior/Events.js.map +1 -1
  6. package/dist/cjs/behavior/cluster/ClusterBehavior.d.ts +3 -2
  7. package/dist/cjs/behavior/cluster/ClusterBehavior.d.ts.map +1 -1
  8. package/dist/cjs/behavior/cluster/ClusterBehavior.js.map +1 -1
  9. package/dist/cjs/behavior/cluster/ClusterBehaviorUtil.js +1 -1
  10. package/dist/cjs/behavior/cluster/ClusterBehaviorUtil.js.map +1 -1
  11. package/dist/cjs/behavior/internal/BehaviorBacking.js +1 -1
  12. package/dist/cjs/behavior/internal/BehaviorBacking.js.map +1 -1
  13. package/dist/cjs/behavior/system/mqtt/MqttInterface.js +1 -1
  14. package/dist/cjs/behavior/system/mqtt/MqttInterface.js.map +1 -1
  15. package/dist/cjs/behaviors/basic-information/BasicInformationServer.d.ts +2 -2
  16. package/dist/cjs/behaviors/basic-information/BasicInformationServer.d.ts.map +1 -1
  17. package/dist/cjs/behaviors/basic-information/BasicInformationServer.js +0 -3
  18. package/dist/cjs/behaviors/basic-information/BasicInformationServer.js.map +1 -1
  19. package/dist/cjs/behaviors/bridged-device-basic-information/BridgedDeviceBasicInformationServer.d.ts +1 -1
  20. package/dist/cjs/behaviors/bridged-device-basic-information/BridgedDeviceBasicInformationServer.d.ts.map +1 -1
  21. package/dist/cjs/behaviors/color-control/ColorControlServer.d.ts +3 -3
  22. package/dist/cjs/behaviors/color-control/ColorControlServer.d.ts.map +1 -1
  23. package/dist/cjs/behaviors/color-control/ColorControlServer.js +118 -3
  24. package/dist/cjs/behaviors/color-control/ColorControlServer.js.map +1 -1
  25. package/dist/cjs/behaviors/general-diagnostics/GeneralDiagnosticsServer.d.ts +1 -1
  26. package/dist/cjs/behaviors/general-diagnostics/GeneralDiagnosticsServer.d.ts.map +1 -1
  27. package/dist/cjs/behaviors/general-diagnostics/GeneralDiagnosticsServer.js.map +1 -1
  28. package/dist/cjs/behaviors/group-key-management/GroupKeyManagementServer.d.ts +1 -1
  29. package/dist/cjs/behaviors/group-key-management/GroupKeyManagementServer.d.ts.map +1 -1
  30. package/dist/cjs/behaviors/group-key-management/GroupKeyManagementServer.js.map +1 -1
  31. package/dist/cjs/behaviors/groups/GroupsServer.d.ts.map +1 -1
  32. package/dist/cjs/behaviors/groups/GroupsServer.js +9 -1
  33. package/dist/cjs/behaviors/groups/GroupsServer.js.map +1 -1
  34. package/dist/cjs/behaviors/level-control/LevelControlServer.d.ts +5 -5
  35. package/dist/cjs/behaviors/level-control/LevelControlServer.d.ts.map +1 -1
  36. package/dist/cjs/behaviors/level-control/LevelControlServer.js +54 -27
  37. package/dist/cjs/behaviors/level-control/LevelControlServer.js.map +1 -1
  38. package/dist/cjs/behaviors/on-off/OnOffServer.d.ts +3 -6
  39. package/dist/cjs/behaviors/on-off/OnOffServer.d.ts.map +1 -1
  40. package/dist/cjs/behaviors/on-off/OnOffServer.js +59 -7
  41. package/dist/cjs/behaviors/on-off/OnOffServer.js.map +1 -1
  42. package/dist/cjs/behaviors/scenes-management/ScenesManagementServer.d.ts +122 -3
  43. package/dist/cjs/behaviors/scenes-management/ScenesManagementServer.d.ts.map +1 -1
  44. package/dist/cjs/behaviors/scenes-management/ScenesManagementServer.js +909 -1
  45. package/dist/cjs/behaviors/scenes-management/ScenesManagementServer.js.map +2 -2
  46. package/dist/cjs/behaviors/switch/SwitchServer.d.ts +1 -1
  47. package/dist/cjs/behaviors/switch/SwitchServer.d.ts.map +1 -1
  48. package/dist/cjs/behaviors/switch/SwitchServer.js.map +1 -1
  49. package/dist/cjs/behaviors/thermostat/AtomicWriteHandler.js +2 -2
  50. package/dist/cjs/behaviors/thermostat/AtomicWriteHandler.js.map +1 -1
  51. package/dist/cjs/behaviors/thermostat/ThermostatServer.d.ts +1 -1
  52. package/dist/cjs/behaviors/thermostat/ThermostatServer.d.ts.map +1 -1
  53. package/dist/cjs/behaviors/thermostat/ThermostatServer.js.map +1 -1
  54. package/dist/cjs/devices/color-temperature-light.d.ts +4 -4
  55. package/dist/cjs/devices/dimmable-light.d.ts +4 -4
  56. package/dist/cjs/devices/dimmable-plug-in-unit.d.ts +4 -4
  57. package/dist/cjs/devices/extended-color-light.d.ts +4 -4
  58. package/dist/cjs/devices/mounted-dimmable-load-control.d.ts +4 -4
  59. package/dist/cjs/devices/mounted-on-off-control.d.ts +4 -4
  60. package/dist/cjs/devices/on-off-light.d.ts +4 -4
  61. package/dist/cjs/devices/on-off-plug-in-unit.d.ts +4 -4
  62. package/dist/cjs/endpoint/Agent.d.ts +5 -0
  63. package/dist/cjs/endpoint/Agent.d.ts.map +1 -1
  64. package/dist/cjs/endpoint/Agent.js +9 -0
  65. package/dist/cjs/endpoint/Agent.js.map +1 -1
  66. package/dist/cjs/endpoint/properties/Behaviors.js +1 -1
  67. package/dist/cjs/endpoint/properties/Behaviors.js.map +1 -1
  68. package/dist/cjs/node/integration/ProtocolService.js +1 -1
  69. package/dist/cjs/node/integration/ProtocolService.js.map +1 -1
  70. package/dist/esm/behavior/Behavior.d.ts +2 -2
  71. package/dist/esm/behavior/Behavior.d.ts.map +1 -1
  72. package/dist/esm/behavior/Behavior.js +1 -1
  73. package/dist/esm/behavior/Behavior.js.map +1 -1
  74. package/dist/esm/behavior/Events.js.map +1 -1
  75. package/dist/esm/behavior/cluster/ClusterBehavior.d.ts +3 -2
  76. package/dist/esm/behavior/cluster/ClusterBehavior.d.ts.map +1 -1
  77. package/dist/esm/behavior/cluster/ClusterBehavior.js.map +1 -1
  78. package/dist/esm/behavior/cluster/ClusterBehaviorUtil.js +1 -1
  79. package/dist/esm/behavior/cluster/ClusterBehaviorUtil.js.map +1 -1
  80. package/dist/esm/behavior/internal/BehaviorBacking.js +1 -1
  81. package/dist/esm/behavior/internal/BehaviorBacking.js.map +1 -1
  82. package/dist/esm/behavior/system/mqtt/MqttInterface.js +1 -1
  83. package/dist/esm/behavior/system/mqtt/MqttInterface.js.map +1 -1
  84. package/dist/esm/behaviors/basic-information/BasicInformationServer.d.ts +2 -2
  85. package/dist/esm/behaviors/basic-information/BasicInformationServer.d.ts.map +1 -1
  86. package/dist/esm/behaviors/basic-information/BasicInformationServer.js +1 -4
  87. package/dist/esm/behaviors/basic-information/BasicInformationServer.js.map +1 -1
  88. package/dist/esm/behaviors/bridged-device-basic-information/BridgedDeviceBasicInformationServer.d.ts +1 -1
  89. package/dist/esm/behaviors/bridged-device-basic-information/BridgedDeviceBasicInformationServer.d.ts.map +1 -1
  90. package/dist/esm/behaviors/color-control/ColorControlServer.d.ts +3 -3
  91. package/dist/esm/behaviors/color-control/ColorControlServer.d.ts.map +1 -1
  92. package/dist/esm/behaviors/color-control/ColorControlServer.js +118 -3
  93. package/dist/esm/behaviors/color-control/ColorControlServer.js.map +1 -1
  94. package/dist/esm/behaviors/general-diagnostics/GeneralDiagnosticsServer.d.ts +1 -1
  95. package/dist/esm/behaviors/general-diagnostics/GeneralDiagnosticsServer.d.ts.map +1 -1
  96. package/dist/esm/behaviors/general-diagnostics/GeneralDiagnosticsServer.js.map +1 -1
  97. package/dist/esm/behaviors/group-key-management/GroupKeyManagementServer.d.ts +1 -1
  98. package/dist/esm/behaviors/group-key-management/GroupKeyManagementServer.d.ts.map +1 -1
  99. package/dist/esm/behaviors/group-key-management/GroupKeyManagementServer.js.map +1 -1
  100. package/dist/esm/behaviors/groups/GroupsServer.d.ts.map +1 -1
  101. package/dist/esm/behaviors/groups/GroupsServer.js +9 -1
  102. package/dist/esm/behaviors/groups/GroupsServer.js.map +1 -1
  103. package/dist/esm/behaviors/level-control/LevelControlServer.d.ts +5 -5
  104. package/dist/esm/behaviors/level-control/LevelControlServer.d.ts.map +1 -1
  105. package/dist/esm/behaviors/level-control/LevelControlServer.js +55 -28
  106. package/dist/esm/behaviors/level-control/LevelControlServer.js.map +1 -1
  107. package/dist/esm/behaviors/on-off/OnOffServer.d.ts +3 -6
  108. package/dist/esm/behaviors/on-off/OnOffServer.d.ts.map +1 -1
  109. package/dist/esm/behaviors/on-off/OnOffServer.js +59 -7
  110. package/dist/esm/behaviors/on-off/OnOffServer.js.map +1 -1
  111. package/dist/esm/behaviors/scenes-management/ScenesManagementServer.d.ts +122 -3
  112. package/dist/esm/behaviors/scenes-management/ScenesManagementServer.d.ts.map +1 -1
  113. package/dist/esm/behaviors/scenes-management/ScenesManagementServer.js +960 -1
  114. package/dist/esm/behaviors/scenes-management/ScenesManagementServer.js.map +2 -2
  115. package/dist/esm/behaviors/switch/SwitchServer.d.ts +1 -1
  116. package/dist/esm/behaviors/switch/SwitchServer.d.ts.map +1 -1
  117. package/dist/esm/behaviors/switch/SwitchServer.js.map +1 -1
  118. package/dist/esm/behaviors/thermostat/AtomicWriteHandler.js +2 -2
  119. package/dist/esm/behaviors/thermostat/AtomicWriteHandler.js.map +1 -1
  120. package/dist/esm/behaviors/thermostat/ThermostatServer.d.ts +1 -1
  121. package/dist/esm/behaviors/thermostat/ThermostatServer.d.ts.map +1 -1
  122. package/dist/esm/behaviors/thermostat/ThermostatServer.js.map +1 -1
  123. package/dist/esm/devices/color-temperature-light.d.ts +4 -4
  124. package/dist/esm/devices/dimmable-light.d.ts +4 -4
  125. package/dist/esm/devices/dimmable-plug-in-unit.d.ts +4 -4
  126. package/dist/esm/devices/extended-color-light.d.ts +4 -4
  127. package/dist/esm/devices/mounted-dimmable-load-control.d.ts +4 -4
  128. package/dist/esm/devices/mounted-on-off-control.d.ts +4 -4
  129. package/dist/esm/devices/on-off-light.d.ts +4 -4
  130. package/dist/esm/devices/on-off-plug-in-unit.d.ts +4 -4
  131. package/dist/esm/endpoint/Agent.d.ts +5 -0
  132. package/dist/esm/endpoint/Agent.d.ts.map +1 -1
  133. package/dist/esm/endpoint/Agent.js +9 -0
  134. package/dist/esm/endpoint/Agent.js.map +1 -1
  135. package/dist/esm/endpoint/properties/Behaviors.js +1 -1
  136. package/dist/esm/endpoint/properties/Behaviors.js.map +1 -1
  137. package/dist/esm/node/integration/ProtocolService.js +1 -1
  138. package/dist/esm/node/integration/ProtocolService.js.map +1 -1
  139. package/package.json +7 -7
  140. package/src/behavior/Behavior.ts +2 -2
  141. package/src/behavior/Events.ts +1 -1
  142. package/src/behavior/cluster/ClusterBehavior.ts +4 -2
  143. package/src/behavior/cluster/ClusterBehaviorUtil.ts +1 -1
  144. package/src/behavior/internal/BehaviorBacking.ts +1 -1
  145. package/src/behavior/system/mqtt/MqttInterface.ts +1 -1
  146. package/src/behaviors/basic-information/BasicInformationServer.ts +3 -7
  147. package/src/behaviors/color-control/ColorControlServer.ts +132 -3
  148. package/src/behaviors/general-diagnostics/GeneralDiagnosticsServer.ts +1 -1
  149. package/src/behaviors/group-key-management/GroupKeyManagementServer.ts +2 -2
  150. package/src/behaviors/groups/GroupsServer.ts +13 -1
  151. package/src/behaviors/level-control/LevelControlServer.ts +72 -29
  152. package/src/behaviors/on-off/OnOffServer.ts +78 -9
  153. package/src/behaviors/scenes-management/ScenesManagementServer.ts +1123 -3
  154. package/src/behaviors/switch/SwitchServer.ts +1 -1
  155. package/src/behaviors/thermostat/AtomicWriteHandler.ts +4 -4
  156. package/src/behaviors/thermostat/ThermostatServer.ts +1 -1
  157. package/src/endpoint/Agent.ts +10 -0
  158. package/src/endpoint/properties/Behaviors.ts +1 -1
  159. package/src/node/integration/ProtocolService.ts +1 -1
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { GroupKeyManagementServer } from "#behaviors/group-key-management";
8
8
  import { IdentifyBehavior } from "#behaviors/identify";
9
+ import { ScenesManagementServer } from "#behaviors/scenes-management";
9
10
  import { Groups } from "#clusters/groups";
10
11
  import { Endpoint } from "#endpoint/Endpoint.js";
11
12
  import { RootEndpoint } from "#endpoints/root";
@@ -164,11 +165,17 @@ export class GroupsServer extends GroupsBase {
164
165
  }
165
166
 
166
167
  try {
168
+ assertRemoteActor(this.context);
167
169
  if (
168
170
  await this.#actOnGroupKeyManagement((fabric, gkm) =>
169
171
  gkm.removeEndpoint(fabric, this.endpoint.number, groupId),
170
172
  )
171
173
  ) {
174
+ if (this.agent.has(ScenesManagementServer)) {
175
+ this.agent
176
+ .get(ScenesManagementServer)
177
+ .removeScenesForGroupOnFabric(this.context.session.associatedFabric.fabricIndex, groupId);
178
+ }
172
179
  return { status: StatusCode.Success, groupId };
173
180
  }
174
181
  return { status: StatusCode.NotFound, groupId };
@@ -178,10 +185,15 @@ export class GroupsServer extends GroupsBase {
178
185
  }
179
186
  }
180
187
 
181
- // TODO ScenesManagement cluster is also affected by this command
182
188
  override async removeAllGroups() {
183
189
  try {
190
+ assertRemoteActor(this.context);
184
191
  await this.#actOnGroupKeyManagement((fabric, gkm) => gkm.removeEndpoint(fabric, this.endpoint.number));
192
+ if (this.agent.has(ScenesManagementServer)) {
193
+ this.agent
194
+ .get(ScenesManagementServer)
195
+ .removeScenesForAllGroupsForFabric(this.context.session.associatedFabric.fabricIndex);
196
+ }
185
197
  } catch (error) {
186
198
  StatusResponseError.accept(error);
187
199
  throw error;
@@ -10,10 +10,11 @@ import { Transitions } from "#behavior/Transitions.js";
10
10
  import { ColorControlServer } from "#behaviors/color-control";
11
11
  import { GeneralDiagnosticsBehavior } from "#behaviors/general-diagnostics";
12
12
  import { OnOffServer } from "#behaviors/on-off";
13
+ import { ScenesManagementServer } from "#behaviors/scenes-management";
13
14
  import { GeneralDiagnostics } from "#clusters/general-diagnostics";
14
15
  import { LevelControl } from "#clusters/level-control";
15
16
  import { Endpoint } from "#endpoint/Endpoint.js";
16
- import { AsyncObservable, Identity, Logger, MaybePromise, Millis } from "#general";
17
+ import { AsyncObservable, cropValueRange, Identity, Logger, MaybePromise, Millis } from "#general";
17
18
  import { ServerNode } from "#node/ServerNode.js";
18
19
  import { Val } from "#protocol";
19
20
  import { ClusterType, StatusCode, StatusResponseError, TypeFromPartialBitSchema } from "#types";
@@ -105,6 +106,8 @@ export class LevelControlBaseServer extends LevelControlBase {
105
106
  if (this.features.onOff && this.agent.has(OnOffServer)) {
106
107
  this.initializeOnOff();
107
108
  }
109
+
110
+ this.agent.maybeGet(ScenesManagementServer)?.implementScenes(this, this.#applySceneValues);
108
111
  }
109
112
 
110
113
  /**
@@ -230,8 +233,9 @@ export class LevelControlBaseServer extends LevelControlBase {
230
233
  return;
231
234
  }
232
235
 
233
- this.#assertLevelValue(level);
236
+ level = cropValueRange(level, this.minLevel, this.maxLevel);
234
237
 
238
+ this.#invalidateScenes();
235
239
  return this.moveToLevelLogic(level, transitionTime, false, effectiveOptions);
236
240
  }
237
241
 
@@ -241,8 +245,9 @@ export class LevelControlBaseServer extends LevelControlBase {
241
245
  * To replace this logic, override {@link moveToLevelLogic} whicih also implements {@link moveToLevel}.
242
246
  */
243
247
  override moveToLevelWithOnOff({ level, transitionTime }: LevelControl.MoveToLevelRequest): MaybePromise {
244
- this.#assertLevelValue(level);
248
+ level = cropValueRange(level, this.minLevel, this.maxLevel);
245
249
 
250
+ this.#invalidateScenes();
246
251
  return this.moveToLevelLogic(level, transitionTime, true);
247
252
  }
248
253
 
@@ -280,9 +285,11 @@ export class LevelControlBaseServer extends LevelControlBase {
280
285
 
281
286
  let effectiveRate;
282
287
  if (effectiveTransitionTime) {
283
- effectiveRate = ((level - this.currentLevel) / effectiveTransitionTime) * 10;
288
+ // Delay the calculation of the effective rate because the coupling to OnOff might change the currentLevel
289
+ effectiveRate = (currentLevel: number) => ((level - currentLevel) / effectiveTransitionTime) * 10;
284
290
  }
285
291
 
292
+ this.#invalidateScenes();
286
293
  return this.transition(level, effectiveRate, withOnOff, options);
287
294
  }
288
295
 
@@ -301,6 +308,7 @@ export class LevelControlBaseServer extends LevelControlBase {
301
308
  return;
302
309
  }
303
310
 
311
+ this.#invalidateScenes();
304
312
  return this.moveLogic(moveMode, rate, false, effectiveOptions);
305
313
  }
306
314
 
@@ -314,6 +322,7 @@ export class LevelControlBaseServer extends LevelControlBase {
314
322
  override moveWithOnOff({ moveMode, rate }: LevelControl.MoveRequest): MaybePromise {
315
323
  rate = this.#assertRateValue(rate);
316
324
 
325
+ this.#invalidateScenes();
317
326
  return this.moveLogic(moveMode, rate, true);
318
327
  }
319
328
 
@@ -365,6 +374,8 @@ export class LevelControlBaseServer extends LevelControlBase {
365
374
  if (!this.#optionsAllowExecution(effectiveOptions)) {
366
375
  return;
367
376
  }
377
+
378
+ this.#invalidateScenes();
368
379
  return this.stepLogic(stepMode, stepSize, transitionTime, false, effectiveOptions);
369
380
  }
370
381
 
@@ -374,6 +385,7 @@ export class LevelControlBaseServer extends LevelControlBase {
374
385
  * To replace default beahavior, override {@link stepLogic} which also implements {@link step}.
375
386
  */
376
387
  override stepWithOnOff({ stepMode, stepSize, transitionTime }: LevelControl.StepRequest): MaybePromise {
388
+ this.#invalidateScenes();
377
389
  return this.stepLogic(stepMode, stepSize, transitionTime, true);
378
390
  }
379
391
 
@@ -412,6 +424,7 @@ export class LevelControlBaseServer extends LevelControlBase {
412
424
  return;
413
425
  }
414
426
 
427
+ this.#invalidateScenes();
415
428
  return this.stopLogic(effectiveOptions);
416
429
  }
417
430
 
@@ -430,17 +443,20 @@ export class LevelControlBaseServer extends LevelControlBase {
430
443
  * Change to a designated level.
431
444
  *
432
445
  * @param targetLevel the new level once transition completes; if omitted transition will stop at min or max value
433
- * @param changePerS the move rate; 0 or nullish means transition instantly
446
+ * @param changePerS the move rate; 0 or nullish means transition instantly; use function to calculate rate based on current level after coupling logics
434
447
  * @param withOnOff if true follows rules for On/Off command variants
435
448
  * @param options additional options supplied by the client
436
449
  */
437
450
  protected transition(
438
451
  targetLevel?: number,
439
- changePerS?: number | null,
452
+ changePerS?: number | null | ((currentLevel: number) => number),
440
453
  withOnOff = false,
441
454
  options: TypeFromPartialBitSchema<typeof LevelControl.Options> = {},
442
455
  ): MaybePromise {
443
- return MaybePromise.then(this.couple(withOnOff, options, targetLevel), () =>
456
+ return MaybePromise.then(this.couple(withOnOff, options, targetLevel), () => {
457
+ if (typeof changePerS === "function") {
458
+ changePerS = changePerS(this.currentLevel);
459
+ }
444
460
  this.internal.transitions?.start({
445
461
  name: "currentLevel",
446
462
  owner: this,
@@ -450,8 +466,8 @@ export class LevelControlBaseServer extends LevelControlBase {
450
466
  onStep() {
451
467
  this.couple(withOnOff, options, targetLevel);
452
468
  },
453
- }),
454
- );
469
+ });
470
+ });
455
471
  }
456
472
 
457
473
  /**
@@ -464,6 +480,8 @@ export class LevelControlBaseServer extends LevelControlBase {
464
480
  options: TypeFromPartialBitSchema<typeof LevelControl.Options> = {},
465
481
  targetLevel?: number,
466
482
  ): MaybePromise {
483
+ let result: MaybePromise = undefined;
484
+
467
485
  // Couple with On/Off state
468
486
  if (this.features.onOff && withOnOff && this.agent.has(OnOffServer)) {
469
487
  if (targetLevel === undefined) {
@@ -491,8 +509,18 @@ export class LevelControlBaseServer extends LevelControlBase {
491
509
  if (!onOff.state.onOff) {
492
510
  onOff.state.onOff = true;
493
511
 
494
- // Ensure we move to "on" level before initiating any transition
495
- this.handleOnOffChange(true);
512
+ if (this.state.onLevel !== null) {
513
+ // Ensure we move to "on" level before initiating any transition
514
+ result = this.handleOnOffChange(true);
515
+ this.internal.blockOnOffCouplingOnce = true; // But block the second call by listener
516
+ this.context.transaction.addParticipants({
517
+ postCommit: () => {
518
+ if (this.internal.blockOnOffCouplingOnce) {
519
+ this.internal.blockOnOffCouplingOnce = false;
520
+ }
521
+ },
522
+ });
523
+ }
496
524
  }
497
525
  }
498
526
  }
@@ -513,6 +541,8 @@ export class LevelControlBaseServer extends LevelControlBase {
513
541
  },
514
542
  });
515
543
  }
544
+
545
+ return result;
516
546
  }
517
547
 
518
548
  /**
@@ -526,9 +556,16 @@ export class LevelControlBaseServer extends LevelControlBase {
526
556
  * @param onOff The new onOff state
527
557
  */
528
558
  protected handleOnOffChange(onOff: boolean): MaybePromise {
559
+ if (this.internal.blockOnOffCouplingOnce) {
560
+ this.internal.blockOnOffCouplingOnce = false;
561
+ return;
562
+ }
563
+
529
564
  if (!onOff || this.state.onLevel === null) {
530
565
  return;
531
566
  }
567
+
568
+ logger.debug(`OnOff changed to ON, setting level to onLevel value of ${this.state.onLevel}`);
532
569
  this.state.currentLevel = this.state.onLevel;
533
570
  }
534
571
 
@@ -556,21 +593,6 @@ export class LevelControlBaseServer extends LevelControlBase {
556
593
  );
557
594
  }
558
595
 
559
- #assertLevelValue(level: number) {
560
- if (level < this.minLevel) {
561
- throw new StatusResponseError(
562
- `The level value of ${level} is invalid. It must be greater or equal to ${this.minLevel}.`,
563
- StatusCode.ConstraintError,
564
- );
565
- }
566
- if (level > this.maxLevel) {
567
- throw new StatusResponseError(
568
- `The level value of ${level} is invalid. It must be less or equal to ${this.maxLevel}.`,
569
- StatusCode.ConstraintError,
570
- );
571
- }
572
- }
573
-
574
596
  #getBootReason() {
575
597
  const rootEndpoint = this.env.get(ServerNode);
576
598
  if (rootEndpoint.behaviors.has(GeneralDiagnosticsBehavior)) {
@@ -578,6 +600,26 @@ export class LevelControlBaseServer extends LevelControlBase {
578
600
  }
579
601
  }
580
602
 
603
+ /** Apply Scene values when requested from ScenesManagement cluster */
604
+ #applySceneValues(values: Val.Struct, transitionTime: number): MaybePromise {
605
+ const level = values.currentLevel;
606
+ // If no number (including null), or already current level or outside our range, ignore
607
+ // This is the only supported sceneable attribute in Level Control, so ignore all others for now
608
+ if (
609
+ typeof level !== "number" ||
610
+ this.state.currentLevel === level ||
611
+ cropValueRange(level, this.minLevel, this.maxLevel) !== level
612
+ ) {
613
+ return;
614
+ }
615
+ return this.moveToLevelLogic(level, transitionTime / 100, false, { executeIfOff: true });
616
+ }
617
+
618
+ /** Invalidate all stored scenes manually for this endpoint in the Scenesmanagement cluster because SDK behavior. */
619
+ #invalidateScenes() {
620
+ this.agent.maybeGet(ScenesManagementServer)?.makeAllFabricSceneInfoEntriesInvalid();
621
+ }
622
+
581
623
  override async [Symbol.asyncDispose]() {
582
624
  if (this.internal.transitions) {
583
625
  await this.internal.transitions.close();
@@ -589,10 +631,11 @@ export class LevelControlBaseServer extends LevelControlBase {
589
631
 
590
632
  export namespace LevelControlBaseServer {
591
633
  export class Internal {
592
- /**
593
- * Transition management.
594
- */
634
+ /** Transition management. */
595
635
  transitions?: Transitions<LevelControlBaseServer>;
636
+
637
+ /** Internal flag to avoid on/off coupling during transitions */
638
+ blockOnOffCouplingOnce = false;
596
639
  }
597
640
 
598
641
  export class State extends LevelControlBase.State {
@@ -5,10 +5,12 @@
5
5
  */
6
6
 
7
7
  import { GeneralDiagnosticsBehavior } from "#behaviors/general-diagnostics";
8
+ import { ScenesManagementServer } from "#behaviors/scenes-management";
8
9
  import { GeneralDiagnostics } from "#clusters/general-diagnostics";
9
10
  import { OnOff } from "#clusters/on-off";
10
11
  import { MaybePromise, Millis, Time, Timer } from "#general";
11
12
  import { ServerNode } from "#node/ServerNode.js";
13
+ import { hasRemoteActor, Val } from "#protocol";
12
14
  import { OnOffBehavior } from "./OnOffBehavior.js";
13
15
 
14
16
  const OnOffLogicBase = OnOffBehavior.with(OnOff.Feature.Lighting);
@@ -41,6 +43,11 @@ export class OnOffBaseServer extends OnOffLogicBase {
41
43
  }
42
44
  }
43
45
  }
46
+
47
+ if (this.agent.has(ScenesManagementServer)) {
48
+ this.agent.get(ScenesManagementServer).implementScenes(this, this.#applySceneValues);
49
+ this.reactTo(this.events.onOff$Changed, this.#clearDelayedSceneApplyData);
50
+ }
44
51
  }
45
52
 
46
53
  override async [Symbol.asyncDispose]() {
@@ -50,6 +57,9 @@ export class OnOffBaseServer extends OnOffLogicBase {
50
57
  }
51
58
 
52
59
  override on(): MaybePromise {
60
+ if (!this.state.onOff) {
61
+ this.#invalidateScenes();
62
+ }
53
63
  this.state.onOff = true;
54
64
  if (this.features.lighting) {
55
65
  this.state.globalSceneControl = true;
@@ -62,7 +72,15 @@ export class OnOffBaseServer extends OnOffLogicBase {
62
72
  }
63
73
  }
64
74
 
75
+ /** Invalidate all stored scenes manually for this endpoint in the Scenesmanagement cluster because SDK behavior. */
76
+ #invalidateScenes() {
77
+ this.agent.maybeGet(ScenesManagementServer)?.makeAllFabricSceneInfoEntriesInvalid();
78
+ }
79
+
65
80
  override off(): MaybePromise {
81
+ if (this.state.onOff) {
82
+ this.#invalidateScenes();
83
+ }
66
84
  this.state.onOff = false;
67
85
  if (this.features.lighting) {
68
86
  if (this.timedOnTimer.isRunning) {
@@ -91,30 +109,40 @@ export class OnOffBaseServer extends OnOffLogicBase {
91
109
  /**
92
110
  * Default implementation notes:
93
111
  * * This implementation ignores the effect and just calls off().
94
- * * Global Scene Control is not supported yet.
95
112
  */
96
113
  override offWithEffect(): MaybePromise {
97
114
  if (this.state.globalSceneControl) {
98
- // TODO Store state in global scene
115
+ if (hasRemoteActor(this.context) && this.context.session.fabric !== undefined) {
116
+ this.endpoint
117
+ .agentFor(this.context)
118
+ .maybeGet(ScenesManagementServer)
119
+ ?.storeGlobalScene(this.context.session.fabric.fabricIndex);
120
+ }
99
121
  this.state.globalSceneControl = false;
100
122
  }
101
123
  return this.off();
102
124
  }
103
125
 
104
- /**
105
- * Default implementation notes:
106
- * * Global Scene Control is not supported yet, so the device is just turned on.
107
- */
108
- override onWithRecallGlobalScene(): MaybePromise {
126
+ override async onWithRecallGlobalScene() {
109
127
  if (this.state.globalSceneControl) {
110
128
  return;
111
129
  }
112
- // TODO Recall global scene to set onOff accordingly
130
+ if (
131
+ hasRemoteActor(this.context) &&
132
+ this.context.session.fabric !== undefined &&
133
+ this.agent.has(ScenesManagementServer)
134
+ ) {
135
+ await this.endpoint
136
+ .agentFor(this.context)
137
+ .maybeGet(ScenesManagementServer)
138
+ ?.recallGlobalScene(this.context.session.fabric.fabricIndex);
139
+ }
140
+
113
141
  this.state.globalSceneControl = true;
114
142
  if (this.state.onTime === 0) {
115
143
  this.state.offWaitTime = 0;
116
144
  }
117
- return this.on();
145
+ await this.on();
118
146
  }
119
147
 
120
148
  /**
@@ -177,6 +205,45 @@ export class OnOffBaseServer extends OnOffLogicBase {
177
205
  return timer;
178
206
  }
179
207
 
208
+ /** Apply Scene values when requested from ScenesManagement cluster */
209
+ #applySceneValues(values: Val.Struct, transitionTime: number): MaybePromise {
210
+ this.#clearDelayedSceneApplyData();
211
+
212
+ const onOff = values.onOff;
213
+ // If no number (including null) or outside our range, ignore
214
+ if (typeof onOff !== "boolean" || this.state.onOff === onOff) {
215
+ return;
216
+ }
217
+
218
+ if (transitionTime === 0) {
219
+ this.state.onOff = onOff;
220
+ } else {
221
+ this.internal.applyScenePendingOnOff = onOff;
222
+ this.internal.applySceneDelayTimer = Time.getPeriodicTimer(
223
+ "delayed scene apply",
224
+ Millis(transitionTime),
225
+ this.callback(this.#applyDelayedSceneOnOffValue),
226
+ ).start();
227
+ }
228
+ }
229
+
230
+ #clearDelayedSceneApplyData() {
231
+ if (this.internal.applySceneDelayTimer?.isRunning) {
232
+ this.internal.applySceneDelayTimer.stop();
233
+ }
234
+ this.internal.applySceneDelayTimer = undefined;
235
+ this.internal.applyScenePendingOnOff = undefined;
236
+ }
237
+
238
+ #applyDelayedSceneOnOffValue() {
239
+ const onOff = this.internal.applyScenePendingOnOff;
240
+ this.#clearDelayedSceneApplyData();
241
+ if (onOff === undefined) {
242
+ return;
243
+ }
244
+ this.state.onOff = onOff;
245
+ }
246
+
180
247
  #delayedOffTick() {
181
248
  let time = (this.state.offWaitTime ?? 0) - 1;
182
249
  if (time <= 0) {
@@ -198,6 +265,8 @@ export namespace OnOffBaseServer {
198
265
  export class Internal {
199
266
  timedOnTimer?: Timer;
200
267
  delayedOffTimer?: Timer;
268
+ applySceneDelayTimer?: Timer;
269
+ applyScenePendingOnOff?: boolean;
201
270
  }
202
271
  }
203
272