@ecopoesis/homebridge-dmx 0.2.0 → 0.3.0

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.
@@ -8,6 +8,7 @@ export declare class DmxPlatform implements DynamicPlatformPlugin {
8
8
  readonly accessories: PlatformAccessory[];
9
9
  private cfg;
10
10
  private controllers;
11
+ private registry;
11
12
  constructor(log: Logger, config: PlatformConfig, api: API);
12
13
  private makeController;
13
14
  /** Called by Homebridge for each cached accessory at startup. */
@@ -1 +1 @@
1
- {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,GAAG,EACH,cAAc,EACd,qBAAqB,EACrB,MAAM,EACN,iBAAiB,EACjB,cAAc,EACd,OAAO,EACR,MAAM,YAAY,CAAC;AAQpB,qBAAa,WAAY,YAAW,qBAAqB;aASrC,GAAG,EAAE,MAAM;aACX,MAAM,EAAE,cAAc;aACtB,GAAG,EAAE,GAAG;IAV1B,SAAgB,OAAO,EAAE,OAAO,OAAO,CAAC;IACxC,SAAgB,cAAc,EAAE,OAAO,cAAc,CAAC;IACtD,SAAgB,WAAW,EAAE,iBAAiB,EAAE,CAAM;IAEtD,OAAO,CAAC,GAAG,CAA6B;IACxC,OAAO,CAAC,WAAW,CAAsC;gBAGvC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,GAAG;IAgC1B,OAAO,CAAC,cAAc;IAStB,iEAAiE;IACjE,kBAAkB,CAAC,SAAS,EAAE,iBAAiB,GAAG,IAAI;IAKtD,OAAO,CAAC,eAAe;CA0DxB"}
1
+ {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,GAAG,EACH,cAAc,EACd,qBAAqB,EACrB,MAAM,EACN,iBAAiB,EACjB,cAAc,EACd,OAAO,EACR,MAAM,YAAY,CAAC;AASpB,qBAAa,WAAY,YAAW,qBAAqB;aAUrC,GAAG,EAAE,MAAM;aACX,MAAM,EAAE,cAAc;aACtB,GAAG,EAAE,GAAG;IAX1B,SAAgB,OAAO,EAAE,OAAO,OAAO,CAAC;IACxC,SAAgB,cAAc,EAAE,OAAO,cAAc,CAAC;IACtD,SAAgB,WAAW,EAAE,iBAAiB,EAAE,CAAM;IAEtD,OAAO,CAAC,GAAG,CAA6B;IACxC,OAAO,CAAC,WAAW,CAAsC;IACzD,OAAO,CAAC,QAAQ,CAAuB;gBAGrB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,GAAG;IAgC1B,OAAO,CAAC,cAAc;IAStB,iEAAiE;IACjE,kBAAkB,CAAC,SAAS,EAAE,iBAAiB,GAAG,IAAI;IAKtD,OAAO,CAAC,eAAe;CA0DxB"}
package/dist/platform.js CHANGED
@@ -6,6 +6,7 @@
6
6
  import { loadConfig } from './config.js';
7
7
  import { StickController } from './controller.js';
8
8
  import { StickFixture } from './platformAccessory.js';
9
+ import { StateRegistry } from './stateRegistry.js';
9
10
  import { ZoneFixture } from './zoneAccessory.js';
10
11
  import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
11
12
  export class DmxPlatform {
@@ -17,6 +18,7 @@ export class DmxPlatform {
17
18
  accessories = [];
18
19
  cfg = null;
19
20
  controllers = new Map();
21
+ registry = new StateRegistry();
20
22
  constructor(log, config, api) {
21
23
  this.log = log;
22
24
  this.config = config;
@@ -72,13 +74,13 @@ export class DmxPlatform {
72
74
  if (existing) {
73
75
  this.log.debug(`wiring cached accessory ${fx.id}`);
74
76
  existing.context.fixture = { id: fx.id, controllerId: fx.controller.id };
75
- new StickFixture(this, existing, fx, controller);
77
+ new StickFixture(this, existing, fx, controller, this.registry);
76
78
  }
77
79
  else {
78
80
  this.log.info(`registering new accessory: ${fx.name} (${fx.id})`);
79
81
  const accessory = new this.api.platformAccessory(fx.name, uuid);
80
82
  accessory.context.fixture = { id: fx.id, controllerId: fx.controller.id };
81
- new StickFixture(this, accessory, fx, controller);
83
+ new StickFixture(this, accessory, fx, controller, this.registry);
82
84
  this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
83
85
  this.accessories.push(accessory);
84
86
  }
@@ -91,13 +93,13 @@ export class DmxPlatform {
91
93
  if (existing) {
92
94
  this.log.debug(`wiring cached zone ${zone.id}`);
93
95
  existing.context.zone = { id: zone.id };
94
- new ZoneFixture(this, existing, zone, this.controllers);
96
+ new ZoneFixture(this, existing, zone, this.controllers, this.registry);
95
97
  }
96
98
  else {
97
99
  this.log.info(`registering new zone: ${zone.name} (${zone.id}, ${zone.members.length} members)`);
98
100
  const accessory = new this.api.platformAccessory(zone.name, uuid);
99
101
  accessory.context.zone = { id: zone.id };
100
- new ZoneFixture(this, accessory, zone, this.controllers);
102
+ new ZoneFixture(this, accessory, zone, this.controllers, this.registry);
101
103
  this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
102
104
  this.accessories.push(accessory);
103
105
  }
@@ -1 +1 @@
1
- {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
@@ -2,17 +2,17 @@ import type { PlatformAccessory } from 'homebridge';
2
2
  import { Fixture } from './config.js';
3
3
  import { StickController } from './controller.js';
4
4
  import type { DmxPlatform } from './platform.js';
5
+ import { StateRegistry } from './stateRegistry.js';
5
6
  export declare class StickFixture {
6
7
  private readonly platform;
7
8
  private readonly accessory;
8
9
  private readonly fixture;
9
10
  private readonly controller;
10
- /** Current HomeKit state we'll send to the Stick on the next push. */
11
- private state;
12
- /** Debounce timer for HomeKit's multi-characteristic dispatch
13
- * (Hue/Saturation/Brightness arrive as 3 separate set calls). */
11
+ private readonly registry;
14
12
  private pending;
15
- constructor(platform: DmxPlatform, accessory: PlatformAccessory, fixture: Fixture, controller: StickController);
13
+ private bulb;
14
+ constructor(platform: DmxPlatform, accessory: PlatformAccessory, fixture: Fixture, controller: StickController, registry: StateRegistry);
15
+ private pushFromRegistry;
16
16
  private schedule;
17
17
  }
18
18
  //# sourceMappingURL=platformAccessory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"platformAccessory.d.ts","sourceRoot":"","sources":["../src/platformAccessory.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,iBAAiB,EAElB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAGjD,qBAAa,YAAY;IASrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAX7B,sEAAsE;IACtE,OAAO,CAAC,KAAK,CAAqD;IAElE;sEACkE;IAClE,OAAO,CAAC,OAAO,CAA+B;gBAG3B,QAAQ,EAAE,WAAW,EACrB,SAAS,EAAE,iBAAiB,EAC5B,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,eAAe;IA6D9C,OAAO,CAAC,QAAQ;CAcjB"}
1
+ {"version":3,"file":"platformAccessory.d.ts","sourceRoot":"","sources":["../src/platformAccessory.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAEV,iBAAiB,EAElB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,qBAAa,YAAY;IAKrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAR3B,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,IAAI,CAAU;gBAGH,QAAQ,EAAE,WAAW,EACrB,SAAS,EAAE,iBAAiB,EAC5B,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,eAAe,EAC3B,QAAQ,EAAE,aAAa;IAoE1C,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,QAAQ;CAcjB"}
@@ -1,80 +1,109 @@
1
1
  // StickFixture — one HomeKit Lightbulb accessory per patched fixture.
2
+ //
3
+ // State is owned by the platform-wide StateRegistry. We read on .onGet,
4
+ // write on .onSet, and subscribe to changes so the HomeKit-displayed
5
+ // values track the registry (e.g. when a zone updates this fixture).
2
6
  import { CHARACTERISTIC_UPDATE_DELAY_MS } from './settings.js';
3
7
  export class StickFixture {
4
8
  platform;
5
9
  accessory;
6
10
  fixture;
7
11
  controller;
8
- /** Current HomeKit state we'll send to the Stick on the next push. */
9
- state = { on: false, brightness: 100 };
10
- /** Debounce timer for HomeKit's multi-characteristic dispatch
11
- * (Hue/Saturation/Brightness arrive as 3 separate set calls). */
12
+ registry;
12
13
  pending = null;
13
- constructor(platform, accessory, fixture, controller) {
14
+ bulb;
15
+ constructor(platform, accessory, fixture, controller, registry) {
14
16
  this.platform = platform;
15
17
  this.accessory = accessory;
16
18
  this.fixture = fixture;
17
19
  this.controller = controller;
20
+ this.registry = registry;
18
21
  const C = platform.Characteristic;
19
22
  const info = accessory.getService(platform.Service.AccessoryInformation);
20
23
  info?.setCharacteristic(C.Manufacturer, 'Stick-DE3 DMX')
21
24
  ?.setCharacteristic(C.Model, fixture.profile.name)
22
25
  ?.setCharacteristic(C.SerialNumber, fixture.id);
23
- const bulb = accessory.getService(platform.Service.Lightbulb)
24
- ?? accessory.addService(platform.Service.Lightbulb, fixture.name);
25
- bulb.setCharacteristic(C.Name, fixture.name);
26
+ this.bulb =
27
+ accessory.getService(platform.Service.Lightbulb)
28
+ ?? accessory.addService(platform.Service.Lightbulb, fixture.name);
29
+ this.bulb.setCharacteristic(C.Name, fixture.name);
26
30
  const chars = fixture.profile.model.characteristics;
27
- bulb.getCharacteristic(C.On)
28
- .onGet(() => this.state.on)
29
- .onSet((v) => { this.state.on = Boolean(v); this.schedule(); });
31
+ this.bulb.getCharacteristic(C.On)
32
+ .onGet(() => registry.get(fixture.id).on)
33
+ .onSet((v) => {
34
+ registry.update(fixture.id, { on: Boolean(v) });
35
+ this.schedule();
36
+ });
30
37
  if (chars.includes('Brightness')) {
31
- bulb.getCharacteristic(C.Brightness)
32
- .onGet(() => this.state.brightness)
38
+ this.bulb.getCharacteristic(C.Brightness)
39
+ .onGet(() => registry.get(fixture.id).brightness)
33
40
  .onSet((v) => {
34
- this.state.brightness = Number(v);
41
+ registry.update(fixture.id, { brightness: Number(v) });
35
42
  this.schedule();
36
43
  });
37
44
  }
38
45
  if (chars.includes('Hue')) {
39
- bulb.getCharacteristic(C.Hue)
40
- .onGet(() => this.state.hue ?? 0)
46
+ this.bulb.getCharacteristic(C.Hue)
47
+ .onGet(() => registry.get(fixture.id).hue ?? 0)
41
48
  .onSet((v) => {
42
- this.state.hue = Number(v);
43
- // setting hue implies color mode (sat>0); leave saturation as-is
49
+ registry.update(fixture.id, { hue: Number(v) });
44
50
  this.schedule();
45
51
  });
46
52
  }
47
53
  if (chars.includes('Saturation')) {
48
- bulb.getCharacteristic(C.Saturation)
49
- .onGet(() => this.state.saturation ?? 0)
54
+ this.bulb.getCharacteristic(C.Saturation)
55
+ .onGet(() => registry.get(fixture.id).saturation ?? 0)
50
56
  .onSet((v) => {
51
- this.state.saturation = Number(v);
57
+ registry.update(fixture.id, { saturation: Number(v) });
52
58
  this.schedule();
53
59
  });
54
60
  }
55
61
  if (chars.includes('ColorTemperature')) {
56
- bulb.getCharacteristic(C.ColorTemperature)
57
- .onGet(() => this.state.colorTemperatureMireds ?? 200)
62
+ this.bulb.getCharacteristic(C.ColorTemperature)
63
+ .onGet(() => registry.get(fixture.id).colorTemperatureMireds ?? 200)
58
64
  .onSet((v) => {
59
- this.state.colorTemperatureMireds = Number(v);
60
- // CCT mode: HomeKit toggles between color and white modes; we mirror
61
- // by zeroing saturation when CCT is changed.
62
- this.state.saturation = 0;
65
+ // CCT change implies white mode; zero saturation.
66
+ registry.update(fixture.id, {
67
+ colorTemperatureMireds: Number(v),
68
+ saturation: 0,
69
+ });
63
70
  this.schedule();
64
71
  });
65
72
  }
73
+ // Refresh HomeKit characteristics whenever this fixture's state changes
74
+ // in the registry (e.g. via a zone set, or another channel of itself).
75
+ registry.onChange((changedId) => {
76
+ if (changedId !== fixture.id)
77
+ return;
78
+ this.pushFromRegistry();
79
+ });
80
+ }
81
+ pushFromRegistry() {
82
+ const s = this.registry.get(this.fixture.id);
83
+ const C = this.platform.Characteristic;
84
+ const chars = this.fixture.profile.model.characteristics;
85
+ this.bulb.updateCharacteristic(C.On, s.on);
86
+ if (chars.includes('Brightness'))
87
+ this.bulb.updateCharacteristic(C.Brightness, s.brightness);
88
+ if (chars.includes('Hue') && s.hue != null)
89
+ this.bulb.updateCharacteristic(C.Hue, s.hue);
90
+ if (chars.includes('Saturation') && s.saturation != null)
91
+ this.bulb.updateCharacteristic(C.Saturation, s.saturation);
92
+ if (chars.includes('ColorTemperature') && s.colorTemperatureMireds != null) {
93
+ this.bulb.updateCharacteristic(C.ColorTemperature, s.colorTemperatureMireds);
94
+ }
66
95
  }
67
96
  schedule() {
68
97
  if (this.pending)
69
98
  clearTimeout(this.pending);
70
99
  this.pending = setTimeout(() => {
71
100
  this.pending = null;
72
- const s = this.state;
101
+ const s = this.registry.get(this.fixture.id);
73
102
  this.platform.log.info(`[${this.fixture.id}] HK set: on=${s.on} br=${s.brightness}` +
74
103
  (s.hue != null ? ` h=${s.hue}` : '') +
75
104
  (s.saturation != null ? ` s=${s.saturation}` : '') +
76
105
  (s.colorTemperatureMireds != null ? ` ct=${s.colorTemperatureMireds}` : ''));
77
- this.controller.setFixture(this.fixture, this.state);
106
+ this.controller.setFixture(this.fixture, s);
78
107
  }, CHARACTERISTIC_UPDATE_DELAY_MS);
79
108
  }
80
109
  }
@@ -1 +1 @@
1
- {"version":3,"file":"platformAccessory.js","sourceRoot":"","sources":["../src/platformAccessory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"platformAccessory.js","sourceRoot":"","sources":["../src/platformAccessory.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,EAAE;AACF,wEAAwE;AACxE,qEAAqE;AACrE,qEAAqE;AAYrE,OAAO,EAAE,8BAA8B,EAAE,MAAM,eAAe,CAAC;AAG/D,MAAM,OAAO,YAAY;IAKJ;IACA;IACA;IACA;IACA;IARX,OAAO,GAA0B,IAAI,CAAC;IACtC,IAAI,CAAU;IAEtB,YACmB,QAAqB,EACrB,SAA4B,EAC5B,OAAgB,EAChB,UAA2B,EAC3B,QAAuB;QAJvB,aAAQ,GAAR,QAAQ,CAAa;QACrB,cAAS,GAAT,SAAS,CAAmB;QAC5B,YAAO,GAAP,OAAO,CAAS;QAChB,eAAU,GAAV,UAAU,CAAiB;QAC3B,aAAQ,GAAR,QAAQ,CAAe;QAExC,MAAM,CAAC,GAAG,QAAQ,CAAC,cAAc,CAAC;QAClC,MAAM,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;QACzE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,YAAY,EAAE,eAAe,CAAC;YACpD,EAAE,iBAAiB,CAAC,CAAC,CAAC,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;YAClD,EAAE,iBAAiB,CAAC,CAAC,CAAC,YAAY,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;QAEpD,IAAI,CAAC,IAAI;YACP,SAAS,CAAC,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC;mBAC7C,SAAS,CAAC,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QAEpE,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QAElD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC;QAEpD,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC;aAC9B,KAAK,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;aACxC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;YACX,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAChD,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,CAAC,CAAC,CAAC;QAEL,IAAI,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YACjC,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,UAAU,CAAC;iBACtC,KAAK,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC;iBAChD,KAAK,CAAC,CAAC,CAAsB,EAAE,EAAE;gBAChC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBACvD,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,CAAC,CAAC,CAAC;QACP,CAAC;QACD,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC;iBAC/B,KAAK,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;iBAC9C,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;gBACX,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBAChD,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,CAAC,CAAC,CAAC;QACP,CAAC;QACD,IAAI,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YACjC,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,UAAU,CAAC;iBACtC,KAAK,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC;iBACrD,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;gBACX,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBACvD,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,CAAC,CAAC,CAAC;QACP,CAAC;QACD,IAAI,KAAK,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;YACvC,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,gBAAgB,CAAC;iBAC5C,KAAK,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,sBAAsB,IAAI,GAAG,CAAC;iBACnE,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;gBACX,kDAAkD;gBAClD,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE;oBAC1B,sBAAsB,EAAE,MAAM,CAAC,CAAC,CAAC;oBACjC,UAAU,EAAE,CAAC;iBACd,CAAC,CAAC;gBACH,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,CAAC,CAAC,CAAC;QACP,CAAC;QAED,wEAAwE;QACxE,uEAAuE;QACvE,QAAQ,CAAC,QAAQ,CAAC,CAAC,SAAS,EAAE,EAAE;YAC9B,IAAI,SAAS,KAAK,OAAO,CAAC,EAAE;gBAAE,OAAO;YACrC,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,gBAAgB;QACtB,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC7C,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC;QACvC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC;QACzD,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAC3C,IAAI,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC;YAAE,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC;QAC7F,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,IAAI;YAAE,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;QACzF,IAAI,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,UAAU,IAAI,IAAI;YAAE,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC;QACrH,IAAI,KAAK,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,sBAAsB,IAAI,IAAI,EAAE,CAAC;YAC3E,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC,gBAAgB,EAAE,CAAC,CAAC,sBAAsB,CAAC,CAAC;QAC/E,CAAC;IACH,CAAC;IAEO,QAAQ;QACd,IAAI,IAAI,CAAC,OAAO;YAAE,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC7C,IAAI,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YAC7B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAC7C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CACpB,IAAI,IAAI,CAAC,OAAO,CAAC,EAAE,gBAAgB,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,UAAU,EAAE;gBAC5D,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACpC,CAAC,CAAC,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAClD,CAAC,CAAC,CAAC,sBAAsB,IAAI,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,sBAAsB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAC5E,CAAC;YACF,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAC9C,CAAC,EAAE,8BAA8B,CAAC,CAAC;IACrC,CAAC;CACF"}
@@ -0,0 +1,21 @@
1
+ import { HomeKitLightState } from './color/types.js';
2
+ export type StateChangeListener = (fixtureId: string) => void;
3
+ export declare class StateRegistry {
4
+ private states;
5
+ private listeners;
6
+ get(id: string): HomeKitLightState;
7
+ /** Replace a fixture's state and fire change listeners. */
8
+ set(id: string, state: HomeKitLightState): void;
9
+ /** Merge a partial update into a fixture's state, returning the new full
10
+ * state. Fires listeners. */
11
+ update(id: string, patch: Partial<HomeKitLightState>): HomeKitLightState;
12
+ onChange(cb: StateChangeListener): () => void;
13
+ }
14
+ /** Bucket members' values; return the value with the largest bucket. Ties
15
+ * break in favour of the value held by the first member in
16
+ * alphabetical-id order. */
17
+ export declare function pickMajority<T>(items: Array<{
18
+ id: string;
19
+ v: T;
20
+ }>): T | undefined;
21
+ //# sourceMappingURL=stateRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stateRegistry.d.ts","sourceRoot":"","sources":["../src/stateRegistry.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,MAAM,mBAAmB,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;AAI9D,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAwC;IACtD,OAAO,CAAC,SAAS,CAAkC;IAEnD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,iBAAiB;IAIlC,2DAA2D;IAC3D,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,IAAI;IAK/C;kCAC8B;IAC9B,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,iBAAiB;IAMxE,QAAQ,CAAC,EAAE,EAAE,mBAAmB,GAAG,MAAM,IAAI;CAI9C;AAED;;6BAE6B;AAC7B,wBAAgB,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,CAAC,CAAA;CAAE,CAAC,GAAG,CAAC,GAAG,SAAS,CAoBjF"}
@@ -0,0 +1,65 @@
1
+ // StateRegistry — central store of every fixture's HomeKit-visible state.
2
+ //
3
+ // Every StickFixture and ZoneFixture reads/writes through here. Whenever a
4
+ // fixture's state changes, listeners fire so zones can refresh their own
5
+ // HomeKit characteristics (and vice versa: setting a zone updates each
6
+ // member's state in the registry, which fires events, which prompt the
7
+ // per-fixture accessories to update their HomeKit-displayed state).
8
+ //
9
+ // Majority rule for zone state: see pickMajority() — count occurrences of
10
+ // each value across (alphabetically-sorted) members; largest count wins;
11
+ // ties are broken in favour of the value held by the alphabetically-first
12
+ // member.
13
+ const DEFAULT_STATE = { on: false, brightness: 100 };
14
+ export class StateRegistry {
15
+ states = new Map();
16
+ listeners = new Set();
17
+ get(id) {
18
+ return this.states.get(id) ?? { ...DEFAULT_STATE };
19
+ }
20
+ /** Replace a fixture's state and fire change listeners. */
21
+ set(id, state) {
22
+ this.states.set(id, { ...state });
23
+ for (const l of this.listeners)
24
+ l(id);
25
+ }
26
+ /** Merge a partial update into a fixture's state, returning the new full
27
+ * state. Fires listeners. */
28
+ update(id, patch) {
29
+ const next = { ...this.get(id), ...patch };
30
+ this.set(id, next);
31
+ return next;
32
+ }
33
+ onChange(cb) {
34
+ this.listeners.add(cb);
35
+ return () => this.listeners.delete(cb);
36
+ }
37
+ }
38
+ /** Bucket members' values; return the value with the largest bucket. Ties
39
+ * break in favour of the value held by the first member in
40
+ * alphabetical-id order. */
41
+ export function pickMajority(items) {
42
+ if (items.length === 0)
43
+ return undefined;
44
+ const sorted = items.slice().sort((a, b) => a.id.localeCompare(b.id));
45
+ const counts = new Map();
46
+ for (let i = 0; i < sorted.length; i++) {
47
+ const v = sorted[i].v;
48
+ const rec = counts.get(v);
49
+ if (rec)
50
+ rec.count++;
51
+ else
52
+ counts.set(v, { count: 1, firstIdx: i });
53
+ }
54
+ let bestV = sorted[0].v;
55
+ let bestRec = counts.get(bestV);
56
+ for (const [v, rec] of counts) {
57
+ if (rec.count > bestRec.count ||
58
+ (rec.count === bestRec.count && rec.firstIdx < bestRec.firstIdx)) {
59
+ bestV = v;
60
+ bestRec = rec;
61
+ }
62
+ }
63
+ return bestV;
64
+ }
65
+ //# sourceMappingURL=stateRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stateRegistry.js","sourceRoot":"","sources":["../src/stateRegistry.ts"],"names":[],"mappings":"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"}
@@ -1,14 +1,30 @@
1
1
  import type { PlatformAccessory } from 'homebridge';
2
- import { Zone } from './config.js';
2
+ import { Fixture, Zone } from './config.js';
3
3
  import { StickController } from './controller.js';
4
4
  import type { DmxPlatform } from './platform.js';
5
+ import { StateRegistry } from './stateRegistry.js';
5
6
  export declare class ZoneFixture {
6
7
  private readonly platform;
7
8
  private readonly zone;
8
9
  private readonly controllers;
9
- private state;
10
+ private readonly registry;
10
11
  private pending;
11
- constructor(platform: DmxPlatform, accessory: PlatformAccessory, zone: Zone, controllers: Map<string, StickController>);
12
- private schedule;
12
+ private refreshPending;
13
+ private bulb;
14
+ constructor(platform: DmxPlatform, accessory: PlatformAccessory, zone: Zone, controllers: Map<string, StickController>, registry: StateRegistry);
15
+ /** Compute majority state across members, per characteristic. */
16
+ private majorityState;
17
+ /** A HomeKit set on the zone: build the new state from the current
18
+ * majority + the patch the user is applying, write it into every
19
+ * member, and trigger the controller after a small debounce so
20
+ * multi-characteristic dispatch coalesces. */
21
+ private applyToMembers;
22
+ /** Debounced controller dispatch. Sends current registry state for each
23
+ * member to the appropriate StickController. */
24
+ private scheduleDispatch;
25
+ /** Debounced refresh: when members change, push the new majority to
26
+ * the zone's HomeKit characteristics. */
27
+ private scheduleRefresh;
13
28
  }
29
+ export type _MemberRef = Fixture;
14
30
  //# sourceMappingURL=zoneAccessory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"zoneAccessory.d.ts","sourceRoot":"","sources":["../src/zoneAccessory.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAEV,iBAAiB,EAElB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEnC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAGjD,qBAAa,WAAW;IAKpB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAEzB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAP9B,OAAO,CAAC,KAAK,CAAqD;IAClE,OAAO,CAAC,OAAO,CAA+B;gBAG3B,QAAQ,EAAE,WAAW,EACtC,SAAS,EAAE,iBAAiB,EACX,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC;IAkD5D,OAAO,CAAC,QAAQ;CAuBjB"}
1
+ {"version":3,"file":"zoneAccessory.d.ts","sourceRoot":"","sources":["../src/zoneAccessory.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAEV,iBAAiB,EAElB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAE,aAAa,EAAgB,MAAM,oBAAoB,CAAC;AAEjE,qBAAa,WAAW;IAMpB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAEzB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAT3B,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,IAAI,CAAU;gBAGH,QAAQ,EAAE,WAAW,EACtC,SAAS,EAAE,iBAAiB,EACX,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,EACzC,QAAQ,EAAE,aAAa;IAmD1C,iEAAiE;IACjE,OAAO,CAAC,aAAa;IAWrB;;;mDAG+C;IAC/C,OAAO,CAAC,cAAc;IAWtB;qDACiD;IACjD,OAAO,CAAC,gBAAgB;IAuBxB;8CAC0C;IAC1C,OAAO,CAAC,eAAe;CAqBxB;AAGD,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC"}
@@ -1,83 +1,146 @@
1
- // ZoneFixture — virtual HomeKit Lightbulb that dispatches to a set of
2
- // member fixtures. Setting any characteristic of the zone updates the
3
- // zone's local state, then calls controller.setFixture for each member
4
- // using the zone's current state. The single controller debounce
5
- // coalesces all member updates into one subprocess transaction.
1
+ // ZoneFixture — virtual HomeKit Lightbulb that aggregates a set of member
2
+ // fixtures.
6
3
  //
7
- // Per-member HomeKit accessories are NOT updated automatically. The user
8
- // has accepted this trade-off: last command wins, and partial overrides
9
- // of group commands are fine.
4
+ // Reads: each .onGet returns the MAJORITY value of that characteristic
5
+ // across the zone's members. Ties are broken in favour of the value held
6
+ // by the alphabetically-first member. This gives a deterministic display
7
+ // even when members are in split states.
8
+ //
9
+ // Writes: each .onSet updates every member's state in the registry (which
10
+ // fires per-member change events so the individual fixture accessories
11
+ // refresh their own HomeKit displays), then triggers the controller for
12
+ // each member so the DMX wire reflects the change.
13
+ //
14
+ // When ANY member's state changes (by any path), the zone's HomeKit
15
+ // characteristics push the new majority — so zones containing overlapping
16
+ // members stay coherent with each other.
10
17
  import { CHARACTERISTIC_UPDATE_DELAY_MS } from './settings.js';
18
+ import { pickMajority } from './stateRegistry.js';
11
19
  export class ZoneFixture {
12
20
  platform;
13
21
  zone;
14
22
  controllers;
15
- state = { on: false, brightness: 100 };
23
+ registry;
16
24
  pending = null;
17
- constructor(platform, accessory, zone, controllers) {
25
+ refreshPending = null;
26
+ bulb;
27
+ constructor(platform, accessory, zone, controllers, registry) {
18
28
  this.platform = platform;
19
29
  this.zone = zone;
20
30
  this.controllers = controllers;
31
+ this.registry = registry;
21
32
  const C = platform.Characteristic;
22
33
  const info = accessory.getService(platform.Service.AccessoryInformation);
23
34
  info?.setCharacteristic(C.Manufacturer, 'DMX')
24
35
  ?.setCharacteristic(C.Model, `Zone (${zone.members.length} fixtures)`)
25
36
  ?.setCharacteristic(C.SerialNumber, `zone-${zone.id}`);
26
- const bulb = accessory.getService(platform.Service.Lightbulb)
27
- ?? accessory.addService(platform.Service.Lightbulb, zone.name);
28
- bulb.setCharacteristic(C.Name, zone.name);
29
- // Always expose On.
30
- bulb.getCharacteristic(C.On)
31
- .onGet(() => this.state.on)
32
- .onSet((v) => { this.state.on = Boolean(v); this.schedule(); });
37
+ this.bulb =
38
+ accessory.getService(platform.Service.Lightbulb)
39
+ ?? accessory.addService(platform.Service.Lightbulb, zone.name);
40
+ this.bulb.setCharacteristic(C.Name, zone.name);
33
41
  const has = (k) => zone.characteristics.includes(k);
42
+ this.bulb.getCharacteristic(C.On)
43
+ .onGet(() => this.majorityState().on)
44
+ .onSet((v) => this.applyToMembers({ on: Boolean(v) }));
34
45
  if (has('Brightness')) {
35
- bulb.getCharacteristic(C.Brightness)
36
- .onGet(() => this.state.brightness)
37
- .onSet((v) => {
38
- this.state.brightness = Number(v);
39
- this.schedule();
40
- });
46
+ this.bulb.getCharacteristic(C.Brightness)
47
+ .onGet(() => this.majorityState().brightness)
48
+ .onSet((v) => this.applyToMembers({ brightness: Number(v) }));
41
49
  }
42
50
  if (has('Hue')) {
43
- bulb.getCharacteristic(C.Hue)
44
- .onGet(() => this.state.hue ?? 0)
45
- .onSet((v) => { this.state.hue = Number(v); this.schedule(); });
51
+ this.bulb.getCharacteristic(C.Hue)
52
+ .onGet(() => this.majorityState().hue ?? 0)
53
+ .onSet((v) => this.applyToMembers({ hue: Number(v) }));
46
54
  }
47
55
  if (has('Saturation')) {
48
- bulb.getCharacteristic(C.Saturation)
49
- .onGet(() => this.state.saturation ?? 0)
50
- .onSet((v) => { this.state.saturation = Number(v); this.schedule(); });
56
+ this.bulb.getCharacteristic(C.Saturation)
57
+ .onGet(() => this.majorityState().saturation ?? 0)
58
+ .onSet((v) => this.applyToMembers({ saturation: Number(v) }));
51
59
  }
52
60
  if (has('ColorTemperature')) {
53
- bulb.getCharacteristic(C.ColorTemperature)
54
- .onGet(() => this.state.colorTemperatureMireds ?? 200)
55
- .onSet((v) => {
56
- this.state.colorTemperatureMireds = Number(v);
57
- this.state.saturation = 0;
58
- this.schedule();
59
- });
61
+ this.bulb.getCharacteristic(C.ColorTemperature)
62
+ .onGet(() => this.majorityState().colorTemperatureMireds ?? 200)
63
+ .onSet((v) => this.applyToMembers({
64
+ colorTemperatureMireds: Number(v),
65
+ saturation: 0,
66
+ }));
67
+ }
68
+ // Refresh on any member change.
69
+ const memberIds = new Set(zone.members.map((m) => m.id));
70
+ registry.onChange((id) => {
71
+ if (memberIds.has(id))
72
+ this.scheduleRefresh();
73
+ });
74
+ }
75
+ /** Compute majority state across members, per characteristic. */
76
+ majorityState() {
77
+ const ss = this.zone.members.map((m) => ({ id: m.id, state: this.registry.get(m.id) }));
78
+ return {
79
+ on: pickMajority(ss.map((s) => ({ id: s.id, v: s.state.on }))) ?? false,
80
+ brightness: pickMajority(ss.map((s) => ({ id: s.id, v: s.state.brightness }))) ?? 100,
81
+ hue: pickMajority(ss.map((s) => ({ id: s.id, v: s.state.hue }))),
82
+ saturation: pickMajority(ss.map((s) => ({ id: s.id, v: s.state.saturation }))),
83
+ colorTemperatureMireds: pickMajority(ss.map((s) => ({ id: s.id, v: s.state.colorTemperatureMireds }))),
84
+ };
85
+ }
86
+ /** A HomeKit set on the zone: build the new state from the current
87
+ * majority + the patch the user is applying, write it into every
88
+ * member, and trigger the controller after a small debounce so
89
+ * multi-characteristic dispatch coalesces. */
90
+ applyToMembers(patch) {
91
+ const next = { ...this.majorityState(), ...patch };
92
+ // Push the next state into every member's registry entry. This fires
93
+ // per-fixture change events so the individual StickFixture accessories
94
+ // refresh their own HomeKit displays.
95
+ for (const m of this.zone.members) {
96
+ this.registry.update(m.id, next);
60
97
  }
98
+ this.scheduleDispatch();
61
99
  }
62
- schedule() {
100
+ /** Debounced controller dispatch. Sends current registry state for each
101
+ * member to the appropriate StickController. */
102
+ scheduleDispatch() {
63
103
  if (this.pending)
64
104
  clearTimeout(this.pending);
65
105
  this.pending = setTimeout(() => {
66
106
  this.pending = null;
67
- const s = this.state;
107
+ const s = this.majorityState();
68
108
  this.platform.log.info(`[zone:${this.zone.id}] HK set: on=${s.on} br=${s.brightness}` +
69
109
  (s.hue != null ? ` h=${s.hue}` : '') +
70
110
  (s.saturation != null ? ` s=${s.saturation}` : '') +
71
111
  (s.colorTemperatureMireds != null ? ` ct=${s.colorTemperatureMireds}` : '') +
72
112
  ` → ${this.zone.members.length} members`);
73
- // Dispatch the zone's state to each member via that member's controller.
74
113
  for (const m of this.zone.members) {
75
114
  const c = this.controllers.get(m.controller.id);
76
115
  if (!c) {
77
116
  this.platform.log.error(`zone "${this.zone.id}": no controller for "${m.id}"`);
78
117
  continue;
79
118
  }
80
- c.setFixture(m, this.state);
119
+ c.setFixture(m, this.registry.get(m.id));
120
+ }
121
+ }, CHARACTERISTIC_UPDATE_DELAY_MS);
122
+ }
123
+ /** Debounced refresh: when members change, push the new majority to
124
+ * the zone's HomeKit characteristics. */
125
+ scheduleRefresh() {
126
+ if (this.refreshPending)
127
+ clearTimeout(this.refreshPending);
128
+ this.refreshPending = setTimeout(() => {
129
+ this.refreshPending = null;
130
+ const s = this.majorityState();
131
+ const C = this.platform.Characteristic;
132
+ this.bulb.updateCharacteristic(C.On, s.on);
133
+ if (this.zone.characteristics.includes('Brightness')) {
134
+ this.bulb.updateCharacteristic(C.Brightness, s.brightness);
135
+ }
136
+ if (this.zone.characteristics.includes('Hue') && s.hue != null) {
137
+ this.bulb.updateCharacteristic(C.Hue, s.hue);
138
+ }
139
+ if (this.zone.characteristics.includes('Saturation') && s.saturation != null) {
140
+ this.bulb.updateCharacteristic(C.Saturation, s.saturation);
141
+ }
142
+ if (this.zone.characteristics.includes('ColorTemperature') && s.colorTemperatureMireds != null) {
143
+ this.bulb.updateCharacteristic(C.ColorTemperature, s.colorTemperatureMireds);
81
144
  }
82
145
  }, CHARACTERISTIC_UPDATE_DELAY_MS);
83
146
  }
@@ -1 +1 @@
1
- {"version":3,"file":"zoneAccessory.js","sourceRoot":"","sources":["../src/zoneAccessory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"zoneAccessory.js","sourceRoot":"","sources":["../src/zoneAccessory.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ecopoesis/homebridge-dmx",
3
3
  "displayName": "DMX",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "description": "Homebridge platform for DMX controllers (currently Nicolaudie Stick-DE3)",
6
6
  "license": "Apache-2.0",
7
7
  "type": "module",
package/src/platform.ts CHANGED
@@ -17,6 +17,7 @@ import type {
17
17
  import { Controller, LoadedConfig, loadConfig, RawConfig } from './config.js';
18
18
  import { StickController } from './controller.js';
19
19
  import { StickFixture } from './platformAccessory.js';
20
+ import { StateRegistry } from './stateRegistry.js';
20
21
  import { ZoneFixture } from './zoneAccessory.js';
21
22
  import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
22
23
 
@@ -27,6 +28,7 @@ export class DmxPlatform implements DynamicPlatformPlugin {
27
28
 
28
29
  private cfg: LoadedConfig | null = null;
29
30
  private controllers = new Map<string, StickController>();
31
+ private registry = new StateRegistry();
30
32
 
31
33
  constructor(
32
34
  public readonly log: Logger,
@@ -96,12 +98,12 @@ export class DmxPlatform implements DynamicPlatformPlugin {
96
98
  if (existing) {
97
99
  this.log.debug(`wiring cached accessory ${fx.id}`);
98
100
  existing.context.fixture = { id: fx.id, controllerId: fx.controller.id };
99
- new StickFixture(this, existing, fx, controller);
101
+ new StickFixture(this, existing, fx, controller, this.registry);
100
102
  } else {
101
103
  this.log.info(`registering new accessory: ${fx.name} (${fx.id})`);
102
104
  const accessory = new this.api.platformAccessory(fx.name, uuid);
103
105
  accessory.context.fixture = { id: fx.id, controllerId: fx.controller.id };
104
- new StickFixture(this, accessory, fx, controller);
106
+ new StickFixture(this, accessory, fx, controller, this.registry);
105
107
  this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
106
108
  this.accessories.push(accessory);
107
109
  }
@@ -115,12 +117,12 @@ export class DmxPlatform implements DynamicPlatformPlugin {
115
117
  if (existing) {
116
118
  this.log.debug(`wiring cached zone ${zone.id}`);
117
119
  existing.context.zone = { id: zone.id };
118
- new ZoneFixture(this, existing, zone, this.controllers);
120
+ new ZoneFixture(this, existing, zone, this.controllers, this.registry);
119
121
  } else {
120
122
  this.log.info(`registering new zone: ${zone.name} (${zone.id}, ${zone.members.length} members)`);
121
123
  const accessory = new this.api.platformAccessory(zone.name, uuid);
122
124
  accessory.context.zone = { id: zone.id };
123
- new ZoneFixture(this, accessory, zone, this.controllers);
125
+ new ZoneFixture(this, accessory, zone, this.controllers, this.registry);
124
126
  this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
125
127
  this.accessories.push(accessory);
126
128
  }
@@ -1,4 +1,8 @@
1
1
  // StickFixture — one HomeKit Lightbulb accessory per patched fixture.
2
+ //
3
+ // State is owned by the platform-wide StateRegistry. We read on .onGet,
4
+ // write on .onSet, and subscribe to changes so the HomeKit-displayed
5
+ // values track the registry (e.g. when a zone updates this fixture).
2
6
 
3
7
  import type {
4
8
  CharacteristicValue,
@@ -11,20 +15,18 @@ import { HomeKitLightState } from './color/types.js';
11
15
  import { StickController } from './controller.js';
12
16
  import type { DmxPlatform } from './platform.js';
13
17
  import { CHARACTERISTIC_UPDATE_DELAY_MS } from './settings.js';
18
+ import { StateRegistry } from './stateRegistry.js';
14
19
 
15
20
  export class StickFixture {
16
- /** Current HomeKit state we'll send to the Stick on the next push. */
17
- private state: HomeKitLightState = { on: false, brightness: 100 };
18
-
19
- /** Debounce timer for HomeKit's multi-characteristic dispatch
20
- * (Hue/Saturation/Brightness arrive as 3 separate set calls). */
21
21
  private pending: NodeJS.Timeout | null = null;
22
+ private bulb: Service;
22
23
 
23
24
  constructor(
24
25
  private readonly platform: DmxPlatform,
25
26
  private readonly accessory: PlatformAccessory,
26
27
  private readonly fixture: Fixture,
27
28
  private readonly controller: StickController,
29
+ private readonly registry: StateRegistry,
28
30
  ) {
29
31
  const C = platform.Characteristic;
30
32
  const info = accessory.getService(platform.Service.AccessoryInformation);
@@ -32,71 +34,91 @@ export class StickFixture {
32
34
  ?.setCharacteristic(C.Model, fixture.profile.name)
33
35
  ?.setCharacteristic(C.SerialNumber, fixture.id);
34
36
 
35
- const bulb: Service =
37
+ this.bulb =
36
38
  accessory.getService(platform.Service.Lightbulb)
37
39
  ?? accessory.addService(platform.Service.Lightbulb, fixture.name);
38
40
 
39
- bulb.setCharacteristic(C.Name, fixture.name);
41
+ this.bulb.setCharacteristic(C.Name, fixture.name);
40
42
 
41
43
  const chars = fixture.profile.model.characteristics;
42
44
 
43
- bulb.getCharacteristic(C.On)
44
- .onGet(() => this.state.on)
45
- .onSet((v) => { this.state.on = Boolean(v); this.schedule(); });
45
+ this.bulb.getCharacteristic(C.On)
46
+ .onGet(() => registry.get(fixture.id).on)
47
+ .onSet((v) => {
48
+ registry.update(fixture.id, { on: Boolean(v) });
49
+ this.schedule();
50
+ });
46
51
 
47
52
  if (chars.includes('Brightness')) {
48
- bulb.getCharacteristic(C.Brightness)
49
- .onGet(() => this.state.brightness)
53
+ this.bulb.getCharacteristic(C.Brightness)
54
+ .onGet(() => registry.get(fixture.id).brightness)
50
55
  .onSet((v: CharacteristicValue) => {
51
- this.state.brightness = Number(v);
56
+ registry.update(fixture.id, { brightness: Number(v) });
52
57
  this.schedule();
53
58
  });
54
59
  }
55
-
56
60
  if (chars.includes('Hue')) {
57
- bulb.getCharacteristic(C.Hue)
58
- .onGet(() => this.state.hue ?? 0)
61
+ this.bulb.getCharacteristic(C.Hue)
62
+ .onGet(() => registry.get(fixture.id).hue ?? 0)
59
63
  .onSet((v) => {
60
- this.state.hue = Number(v);
61
- // setting hue implies color mode (sat>0); leave saturation as-is
64
+ registry.update(fixture.id, { hue: Number(v) });
62
65
  this.schedule();
63
66
  });
64
67
  }
65
-
66
68
  if (chars.includes('Saturation')) {
67
- bulb.getCharacteristic(C.Saturation)
68
- .onGet(() => this.state.saturation ?? 0)
69
+ this.bulb.getCharacteristic(C.Saturation)
70
+ .onGet(() => registry.get(fixture.id).saturation ?? 0)
69
71
  .onSet((v) => {
70
- this.state.saturation = Number(v);
72
+ registry.update(fixture.id, { saturation: Number(v) });
71
73
  this.schedule();
72
74
  });
73
75
  }
74
-
75
76
  if (chars.includes('ColorTemperature')) {
76
- bulb.getCharacteristic(C.ColorTemperature)
77
- .onGet(() => this.state.colorTemperatureMireds ?? 200)
77
+ this.bulb.getCharacteristic(C.ColorTemperature)
78
+ .onGet(() => registry.get(fixture.id).colorTemperatureMireds ?? 200)
78
79
  .onSet((v) => {
79
- this.state.colorTemperatureMireds = Number(v);
80
- // CCT mode: HomeKit toggles between color and white modes; we mirror
81
- // by zeroing saturation when CCT is changed.
82
- this.state.saturation = 0;
80
+ // CCT change implies white mode; zero saturation.
81
+ registry.update(fixture.id, {
82
+ colorTemperatureMireds: Number(v),
83
+ saturation: 0,
84
+ });
83
85
  this.schedule();
84
86
  });
85
87
  }
88
+
89
+ // Refresh HomeKit characteristics whenever this fixture's state changes
90
+ // in the registry (e.g. via a zone set, or another channel of itself).
91
+ registry.onChange((changedId) => {
92
+ if (changedId !== fixture.id) return;
93
+ this.pushFromRegistry();
94
+ });
95
+ }
96
+
97
+ private pushFromRegistry(): void {
98
+ const s = this.registry.get(this.fixture.id);
99
+ const C = this.platform.Characteristic;
100
+ const chars = this.fixture.profile.model.characteristics;
101
+ this.bulb.updateCharacteristic(C.On, s.on);
102
+ if (chars.includes('Brightness')) this.bulb.updateCharacteristic(C.Brightness, s.brightness);
103
+ if (chars.includes('Hue') && s.hue != null) this.bulb.updateCharacteristic(C.Hue, s.hue);
104
+ if (chars.includes('Saturation') && s.saturation != null) this.bulb.updateCharacteristic(C.Saturation, s.saturation);
105
+ if (chars.includes('ColorTemperature') && s.colorTemperatureMireds != null) {
106
+ this.bulb.updateCharacteristic(C.ColorTemperature, s.colorTemperatureMireds);
107
+ }
86
108
  }
87
109
 
88
110
  private schedule(): void {
89
111
  if (this.pending) clearTimeout(this.pending);
90
112
  this.pending = setTimeout(() => {
91
113
  this.pending = null;
92
- const s = this.state;
114
+ const s = this.registry.get(this.fixture.id);
93
115
  this.platform.log.info(
94
116
  `[${this.fixture.id}] HK set: on=${s.on} br=${s.brightness}` +
95
117
  (s.hue != null ? ` h=${s.hue}` : '') +
96
118
  (s.saturation != null ? ` s=${s.saturation}` : '') +
97
119
  (s.colorTemperatureMireds != null ? ` ct=${s.colorTemperatureMireds}` : ''),
98
120
  );
99
- this.controller.setFixture(this.fixture, this.state);
121
+ this.controller.setFixture(this.fixture, s);
100
122
  }, CHARACTERISTIC_UPDATE_DELAY_MS);
101
123
  }
102
124
  }
@@ -0,0 +1,71 @@
1
+ // StateRegistry — central store of every fixture's HomeKit-visible state.
2
+ //
3
+ // Every StickFixture and ZoneFixture reads/writes through here. Whenever a
4
+ // fixture's state changes, listeners fire so zones can refresh their own
5
+ // HomeKit characteristics (and vice versa: setting a zone updates each
6
+ // member's state in the registry, which fires events, which prompt the
7
+ // per-fixture accessories to update their HomeKit-displayed state).
8
+ //
9
+ // Majority rule for zone state: see pickMajority() — count occurrences of
10
+ // each value across (alphabetically-sorted) members; largest count wins;
11
+ // ties are broken in favour of the value held by the alphabetically-first
12
+ // member.
13
+
14
+ import { HomeKitLightState } from './color/types.js';
15
+
16
+ export type StateChangeListener = (fixtureId: string) => void;
17
+
18
+ const DEFAULT_STATE: HomeKitLightState = { on: false, brightness: 100 };
19
+
20
+ export class StateRegistry {
21
+ private states = new Map<string, HomeKitLightState>();
22
+ private listeners = new Set<StateChangeListener>();
23
+
24
+ get(id: string): HomeKitLightState {
25
+ return this.states.get(id) ?? { ...DEFAULT_STATE };
26
+ }
27
+
28
+ /** Replace a fixture's state and fire change listeners. */
29
+ set(id: string, state: HomeKitLightState): void {
30
+ this.states.set(id, { ...state });
31
+ for (const l of this.listeners) l(id);
32
+ }
33
+
34
+ /** Merge a partial update into a fixture's state, returning the new full
35
+ * state. Fires listeners. */
36
+ update(id: string, patch: Partial<HomeKitLightState>): HomeKitLightState {
37
+ const next: HomeKitLightState = { ...this.get(id), ...patch };
38
+ this.set(id, next);
39
+ return next;
40
+ }
41
+
42
+ onChange(cb: StateChangeListener): () => void {
43
+ this.listeners.add(cb);
44
+ return () => this.listeners.delete(cb);
45
+ }
46
+ }
47
+
48
+ /** Bucket members' values; return the value with the largest bucket. Ties
49
+ * break in favour of the value held by the first member in
50
+ * alphabetical-id order. */
51
+ export function pickMajority<T>(items: Array<{ id: string; v: T }>): T | undefined {
52
+ if (items.length === 0) return undefined;
53
+ const sorted = items.slice().sort((a, b) => a.id.localeCompare(b.id));
54
+ const counts = new Map<T, { count: number; firstIdx: number }>();
55
+ for (let i = 0; i < sorted.length; i++) {
56
+ const v = sorted[i].v;
57
+ const rec = counts.get(v);
58
+ if (rec) rec.count++;
59
+ else counts.set(v, { count: 1, firstIdx: i });
60
+ }
61
+ let bestV: T = sorted[0].v;
62
+ let bestRec = counts.get(bestV)!;
63
+ for (const [v, rec] of counts) {
64
+ if (rec.count > bestRec.count ||
65
+ (rec.count === bestRec.count && rec.firstIdx < bestRec.firstIdx)) {
66
+ bestV = v;
67
+ bestRec = rec;
68
+ }
69
+ }
70
+ return bestV;
71
+ }
@@ -1,12 +1,19 @@
1
- // ZoneFixture — virtual HomeKit Lightbulb that dispatches to a set of
2
- // member fixtures. Setting any characteristic of the zone updates the
3
- // zone's local state, then calls controller.setFixture for each member
4
- // using the zone's current state. The single controller debounce
5
- // coalesces all member updates into one subprocess transaction.
1
+ // ZoneFixture — virtual HomeKit Lightbulb that aggregates a set of member
2
+ // fixtures.
6
3
  //
7
- // Per-member HomeKit accessories are NOT updated automatically. The user
8
- // has accepted this trade-off: last command wins, and partial overrides
9
- // of group commands are fine.
4
+ // Reads: each .onGet returns the MAJORITY value of that characteristic
5
+ // across the zone's members. Ties are broken in favour of the value held
6
+ // by the alphabetically-first member. This gives a deterministic display
7
+ // even when members are in split states.
8
+ //
9
+ // Writes: each .onSet updates every member's state in the registry (which
10
+ // fires per-member change events so the individual fixture accessories
11
+ // refresh their own HomeKit displays), then triggers the controller for
12
+ // each member so the DMX wire reflects the change.
13
+ //
14
+ // When ANY member's state changes (by any path), the zone's HomeKit
15
+ // characteristics push the new majority — so zones containing overlapping
16
+ // members stay coherent with each other.
10
17
 
11
18
  import type {
12
19
  CharacteristicValue,
@@ -14,21 +21,24 @@ import type {
14
21
  Service,
15
22
  } from 'homebridge';
16
23
 
17
- import { Zone } from './config.js';
24
+ import { Fixture, Zone } from './config.js';
18
25
  import { HKCharacteristic, HomeKitLightState } from './color/types.js';
19
26
  import { StickController } from './controller.js';
20
27
  import type { DmxPlatform } from './platform.js';
21
28
  import { CHARACTERISTIC_UPDATE_DELAY_MS } from './settings.js';
29
+ import { StateRegistry, pickMajority } from './stateRegistry.js';
22
30
 
23
31
  export class ZoneFixture {
24
- private state: HomeKitLightState = { on: false, brightness: 100 };
25
32
  private pending: NodeJS.Timeout | null = null;
33
+ private refreshPending: NodeJS.Timeout | null = null;
34
+ private bulb: Service;
26
35
 
27
36
  constructor(
28
37
  private readonly platform: DmxPlatform,
29
38
  accessory: PlatformAccessory,
30
39
  private readonly zone: Zone,
31
40
  private readonly controllers: Map<string, StickController>,
41
+ private readonly registry: StateRegistry,
32
42
  ) {
33
43
  const C = platform.Characteristic;
34
44
  const info = accessory.getService(platform.Service.AccessoryInformation);
@@ -36,53 +46,83 @@ export class ZoneFixture {
36
46
  ?.setCharacteristic(C.Model, `Zone (${zone.members.length} fixtures)`)
37
47
  ?.setCharacteristic(C.SerialNumber, `zone-${zone.id}`);
38
48
 
39
- const bulb: Service =
49
+ this.bulb =
40
50
  accessory.getService(platform.Service.Lightbulb)
41
51
  ?? accessory.addService(platform.Service.Lightbulb, zone.name);
42
52
 
43
- bulb.setCharacteristic(C.Name, zone.name);
44
-
45
- // Always expose On.
46
- bulb.getCharacteristic(C.On)
47
- .onGet(() => this.state.on)
48
- .onSet((v) => { this.state.on = Boolean(v); this.schedule(); });
53
+ this.bulb.setCharacteristic(C.Name, zone.name);
49
54
 
50
55
  const has = (k: HKCharacteristic): boolean => zone.characteristics.includes(k);
51
56
 
57
+ this.bulb.getCharacteristic(C.On)
58
+ .onGet(() => this.majorityState().on)
59
+ .onSet((v) => this.applyToMembers({ on: Boolean(v) }));
60
+
52
61
  if (has('Brightness')) {
53
- bulb.getCharacteristic(C.Brightness)
54
- .onGet(() => this.state.brightness)
55
- .onSet((v: CharacteristicValue) => {
56
- this.state.brightness = Number(v);
57
- this.schedule();
58
- });
62
+ this.bulb.getCharacteristic(C.Brightness)
63
+ .onGet(() => this.majorityState().brightness)
64
+ .onSet((v: CharacteristicValue) => this.applyToMembers({ brightness: Number(v) }));
59
65
  }
60
66
  if (has('Hue')) {
61
- bulb.getCharacteristic(C.Hue)
62
- .onGet(() => this.state.hue ?? 0)
63
- .onSet((v) => { this.state.hue = Number(v); this.schedule(); });
67
+ this.bulb.getCharacteristic(C.Hue)
68
+ .onGet(() => this.majorityState().hue ?? 0)
69
+ .onSet((v) => this.applyToMembers({ hue: Number(v) }));
64
70
  }
65
71
  if (has('Saturation')) {
66
- bulb.getCharacteristic(C.Saturation)
67
- .onGet(() => this.state.saturation ?? 0)
68
- .onSet((v) => { this.state.saturation = Number(v); this.schedule(); });
72
+ this.bulb.getCharacteristic(C.Saturation)
73
+ .onGet(() => this.majorityState().saturation ?? 0)
74
+ .onSet((v) => this.applyToMembers({ saturation: Number(v) }));
69
75
  }
70
76
  if (has('ColorTemperature')) {
71
- bulb.getCharacteristic(C.ColorTemperature)
72
- .onGet(() => this.state.colorTemperatureMireds ?? 200)
73
- .onSet((v) => {
74
- this.state.colorTemperatureMireds = Number(v);
75
- this.state.saturation = 0;
76
- this.schedule();
77
- });
77
+ this.bulb.getCharacteristic(C.ColorTemperature)
78
+ .onGet(() => this.majorityState().colorTemperatureMireds ?? 200)
79
+ .onSet((v) => this.applyToMembers({
80
+ colorTemperatureMireds: Number(v),
81
+ saturation: 0,
82
+ }));
83
+ }
84
+
85
+ // Refresh on any member change.
86
+ const memberIds = new Set(zone.members.map((m) => m.id));
87
+ registry.onChange((id) => {
88
+ if (memberIds.has(id)) this.scheduleRefresh();
89
+ });
90
+ }
91
+
92
+ /** Compute majority state across members, per characteristic. */
93
+ private majorityState(): HomeKitLightState {
94
+ const ss = this.zone.members.map((m) => ({ id: m.id, state: this.registry.get(m.id) }));
95
+ return {
96
+ on: pickMajority(ss.map((s) => ({ id: s.id, v: s.state.on }))) ?? false,
97
+ brightness: pickMajority(ss.map((s) => ({ id: s.id, v: s.state.brightness }))) ?? 100,
98
+ hue: pickMajority(ss.map((s) => ({ id: s.id, v: s.state.hue }))),
99
+ saturation: pickMajority(ss.map((s) => ({ id: s.id, v: s.state.saturation }))),
100
+ colorTemperatureMireds: pickMajority(ss.map((s) => ({ id: s.id, v: s.state.colorTemperatureMireds }))),
101
+ };
102
+ }
103
+
104
+ /** A HomeKit set on the zone: build the new state from the current
105
+ * majority + the patch the user is applying, write it into every
106
+ * member, and trigger the controller after a small debounce so
107
+ * multi-characteristic dispatch coalesces. */
108
+ private applyToMembers(patch: Partial<HomeKitLightState>): void {
109
+ const next = { ...this.majorityState(), ...patch };
110
+ // Push the next state into every member's registry entry. This fires
111
+ // per-fixture change events so the individual StickFixture accessories
112
+ // refresh their own HomeKit displays.
113
+ for (const m of this.zone.members) {
114
+ this.registry.update(m.id, next);
78
115
  }
116
+ this.scheduleDispatch();
79
117
  }
80
118
 
81
- private schedule(): void {
119
+ /** Debounced controller dispatch. Sends current registry state for each
120
+ * member to the appropriate StickController. */
121
+ private scheduleDispatch(): void {
82
122
  if (this.pending) clearTimeout(this.pending);
83
123
  this.pending = setTimeout(() => {
84
124
  this.pending = null;
85
- const s = this.state;
125
+ const s = this.majorityState();
86
126
  this.platform.log.info(
87
127
  `[zone:${this.zone.id}] HK set: on=${s.on} br=${s.brightness}` +
88
128
  (s.hue != null ? ` h=${s.hue}` : '') +
@@ -90,15 +130,41 @@ export class ZoneFixture {
90
130
  (s.colorTemperatureMireds != null ? ` ct=${s.colorTemperatureMireds}` : '') +
91
131
  ` → ${this.zone.members.length} members`,
92
132
  );
93
- // Dispatch the zone's state to each member via that member's controller.
94
133
  for (const m of this.zone.members) {
95
134
  const c = this.controllers.get(m.controller.id);
96
135
  if (!c) {
97
136
  this.platform.log.error(`zone "${this.zone.id}": no controller for "${m.id}"`);
98
137
  continue;
99
138
  }
100
- c.setFixture(m, this.state);
139
+ c.setFixture(m, this.registry.get(m.id));
140
+ }
141
+ }, CHARACTERISTIC_UPDATE_DELAY_MS);
142
+ }
143
+
144
+ /** Debounced refresh: when members change, push the new majority to
145
+ * the zone's HomeKit characteristics. */
146
+ private scheduleRefresh(): void {
147
+ if (this.refreshPending) clearTimeout(this.refreshPending);
148
+ this.refreshPending = setTimeout(() => {
149
+ this.refreshPending = null;
150
+ const s = this.majorityState();
151
+ const C = this.platform.Characteristic;
152
+ this.bulb.updateCharacteristic(C.On, s.on);
153
+ if (this.zone.characteristics.includes('Brightness')) {
154
+ this.bulb.updateCharacteristic(C.Brightness, s.brightness);
155
+ }
156
+ if (this.zone.characteristics.includes('Hue') && s.hue != null) {
157
+ this.bulb.updateCharacteristic(C.Hue, s.hue);
158
+ }
159
+ if (this.zone.characteristics.includes('Saturation') && s.saturation != null) {
160
+ this.bulb.updateCharacteristic(C.Saturation, s.saturation);
161
+ }
162
+ if (this.zone.characteristics.includes('ColorTemperature') && s.colorTemperatureMireds != null) {
163
+ this.bulb.updateCharacteristic(C.ColorTemperature, s.colorTemperatureMireds);
101
164
  }
102
165
  }, CHARACTERISTIC_UPDATE_DELAY_MS);
103
166
  }
104
167
  }
168
+
169
+ // Silence "Fixture imported but unused" — kept for potential future use.
170
+ export type _MemberRef = Fixture;