@ecopoesis/homebridge-dmx 0.1.0 → 0.2.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.
- package/CLAUDE.md +18 -3
- package/dist/config.d.ts +17 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +42 -0
- package/dist/config.js.map +1 -1
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +22 -1
- package/dist/platform.js.map +1 -1
- package/dist/zoneAccessory.d.ts +14 -0
- package/dist/zoneAccessory.d.ts.map +1 -0
- package/dist/zoneAccessory.js +85 -0
- package/dist/zoneAccessory.js.map +1 -0
- package/package.json +1 -1
- package/src/config.ts +61 -1
- package/src/platform.ts +22 -1
- package/src/zoneAccessory.ts +104 -0
package/CLAUDE.md
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
|
-
# homebridge-
|
|
1
|
+
# @ecopoesis/homebridge-dmx
|
|
2
2
|
|
|
3
|
-
Homebridge plugin
|
|
3
|
+
Homebridge platform plugin that exposes DMX fixtures as HomeKit Lightbulb accessories. The DMX controller is abstracted (config has a `controllers[]` section + `type` field) so additional controllers can be added later without rewriting the patch; the supported controller today is the **Nicolaudie Stick-DE3**, reverse-engineered from the ESA2 Hardware Manager.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Published to npm as **`@ecopoesis/homebridge-dmx`** (scoped because the unscoped `homebridge-dmx` is squatted on npm by an abandoned 2019 package). Homebridge alias is `DMX`.
|
|
6
|
+
|
|
7
|
+
## Plugin architecture (2026-05-24)
|
|
8
|
+
|
|
9
|
+
- Top-level platform `DmxPlatform` reads `controllers[]`, `profiles[]`, `patch[]` from config (or external YAML via `yamlPath`)
|
|
10
|
+
- One `StickController` per controller spec; one `StickFixture` (Lightbulb accessory) per patch entry, wired to its controller
|
|
11
|
+
- HomeKit set → render bytes via the fixture's color model → write to universe buffer → debounce (750 ms) → spawn `tools/send_dmx.mjs` as a child process per transaction
|
|
12
|
+
- Subprocess approach: empirical workaround — in-process protocol only works for the FIRST transaction per long-lived plugin process; sessions 2+ silently fail to drive output. Fresh node process per transaction reliably works. Cost: ~1.5-2 s per change.
|
|
13
|
+
- Color models: dimmer, cct, rgb, rgbw, rgbww, rgbaw, hsvcct (the WAC native model)
|
|
14
|
+
- `tools/send_dmx.mjs` is now structurally critical (frozen reference shipped in the npm tarball)
|
|
15
|
+
|
|
16
|
+
## Current deployment
|
|
17
|
+
|
|
18
|
+
Running on `homebridge01.local` (192.168.16.40) in a `homebridge/homebridge:latest` docker container managed by Portainer + Watchtower. Config volume bind-mounted at `/opt/containers/homebridge` → `/homebridge` inside the container. All 18 WAC fixtures patched at universe A, DMX 1/6/11/.../86.
|
|
19
|
+
|
|
20
|
+
## Architecture (RE / hardware context)
|
|
6
21
|
|
|
7
22
|
- 18x WAC DC-WD05 fixtures on a DMX chain
|
|
8
23
|
- Nicolaudie Stick-DE3 wall-mount DMX controller at 192.168.96.2
|
package/dist/config.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChannelDef, ColorModel } from './color/types.js';
|
|
1
|
+
import { ChannelDef, ColorModel, HKCharacteristic } from './color/types.js';
|
|
2
2
|
export interface ControllerSpec {
|
|
3
3
|
id: string;
|
|
4
4
|
type: string;
|
|
@@ -17,12 +17,18 @@ export interface PatchSpec {
|
|
|
17
17
|
universe?: string;
|
|
18
18
|
start: number;
|
|
19
19
|
}
|
|
20
|
+
export interface ZoneSpec {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
members: string[];
|
|
24
|
+
}
|
|
20
25
|
export interface RawConfig {
|
|
21
26
|
name?: string;
|
|
22
27
|
yamlPath?: string;
|
|
23
28
|
controllers?: ControllerSpec[];
|
|
24
29
|
profiles?: ProfileSpec[];
|
|
25
30
|
patch?: PatchSpec[];
|
|
31
|
+
zones?: ZoneSpec[];
|
|
26
32
|
}
|
|
27
33
|
export interface Profile {
|
|
28
34
|
name: string;
|
|
@@ -45,10 +51,20 @@ export interface Fixture {
|
|
|
45
51
|
startCh: number;
|
|
46
52
|
nChannels: number;
|
|
47
53
|
}
|
|
54
|
+
/** A virtual "zone" Lightbulb: appears as a single accessory in HomeKit;
|
|
55
|
+
* setting its state dispatches to every member fixture. The intersection
|
|
56
|
+
* of the members' color-model characteristics is what HomeKit sees. */
|
|
57
|
+
export interface Zone {
|
|
58
|
+
id: string;
|
|
59
|
+
name: string;
|
|
60
|
+
members: Fixture[];
|
|
61
|
+
characteristics: HKCharacteristic[];
|
|
62
|
+
}
|
|
48
63
|
export interface LoadedConfig {
|
|
49
64
|
name: string;
|
|
50
65
|
controllers: Controller[];
|
|
51
66
|
fixtures: Fixture[];
|
|
67
|
+
zones: Zone[];
|
|
52
68
|
}
|
|
53
69
|
export declare const DEFAULT_PLATFORM_NAME = "DMX";
|
|
54
70
|
/** Load + validate the platform config. `rawJson` is the Homebridge
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAuCA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAuCA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAI5E,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;IAC/B,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC;IACpB,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,UAAU,CAAC;IAClB,QAAQ,EAAE,UAAU,EAAE,CAAC;CACxB;AAED,eAAO,MAAM,gBAAgB,uBAAwB,CAAC;AACtD,MAAM,MAAM,cAAc,GAAG,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAE7D,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,cAAc,CAAC;IACrB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;wEAEwE;AACxE,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,EAAE,CAAC;IACnB,eAAe,EAAE,gBAAgB,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AAED,eAAO,MAAM,qBAAqB,QAAQ,CAAC;AAE3C;;;;oEAIoE;AACpE,wBAAgB,UAAU,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,YAAY,CAmLzE"}
|
package/dist/config.js
CHANGED
|
@@ -182,10 +182,52 @@ export function loadConfig(rawJson, cwd) {
|
|
|
182
182
|
nChannels: nch,
|
|
183
183
|
});
|
|
184
184
|
}
|
|
185
|
+
// Zones — virtual Lightbulb accessories. Each zone's members must be
|
|
186
|
+
// existing fixture ids. Zones never reference other zones (no recursion).
|
|
187
|
+
const fixturesById = new Map(fixtures.map((f) => [f.id, f]));
|
|
188
|
+
const zoneSpecs = raw.zones ?? [];
|
|
189
|
+
const zones = [];
|
|
190
|
+
const seenZoneIds = new Set();
|
|
191
|
+
for (const zs of zoneSpecs) {
|
|
192
|
+
if (!zs.id)
|
|
193
|
+
throw new Error('zone: missing id');
|
|
194
|
+
if (seenZoneIds.has(zs.id)) {
|
|
195
|
+
throw new Error(`zone: duplicate id "${zs.id}"`);
|
|
196
|
+
}
|
|
197
|
+
if (fixturesById.has(zs.id)) {
|
|
198
|
+
throw new Error(`zone id "${zs.id}" collides with fixture id`);
|
|
199
|
+
}
|
|
200
|
+
seenZoneIds.add(zs.id);
|
|
201
|
+
if (!Array.isArray(zs.members) || zs.members.length === 0) {
|
|
202
|
+
throw new Error(`zone "${zs.id}": members must be a non-empty array of fixture ids`);
|
|
203
|
+
}
|
|
204
|
+
const members = [];
|
|
205
|
+
for (const m of zs.members) {
|
|
206
|
+
const fx = fixturesById.get(m);
|
|
207
|
+
if (!fx) {
|
|
208
|
+
throw new Error(`zone "${zs.id}": unknown fixture "${m}"`);
|
|
209
|
+
}
|
|
210
|
+
members.push(fx);
|
|
211
|
+
}
|
|
212
|
+
// Intersection of member characteristics — HomeKit only exposes what
|
|
213
|
+
// ALL members can do.
|
|
214
|
+
let chars = new Set(members[0].profile.model.characteristics);
|
|
215
|
+
for (let i = 1; i < members.length; i++) {
|
|
216
|
+
const mc = new Set(members[i].profile.model.characteristics);
|
|
217
|
+
chars = new Set([...chars].filter((c) => mc.has(c)));
|
|
218
|
+
}
|
|
219
|
+
zones.push({
|
|
220
|
+
id: zs.id,
|
|
221
|
+
name: zs.name || zs.id,
|
|
222
|
+
members,
|
|
223
|
+
characteristics: [...chars],
|
|
224
|
+
});
|
|
225
|
+
}
|
|
185
226
|
return {
|
|
186
227
|
name: raw.name ?? DEFAULT_PLATFORM_NAME,
|
|
187
228
|
controllers: [...controllersById.values()],
|
|
188
229
|
fixtures,
|
|
230
|
+
zones,
|
|
189
231
|
};
|
|
190
232
|
}
|
|
191
233
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,EAAE;AACF,UAAU;AACV,mEAAmE;AACnE,uEAAuE;AACvE,oDAAoD;AACpD,mDAAmD;AACnD,EAAE;AACF,SAAS;AACT,MAAM;AACN,yBAAyB;AACzB,qBAAqB;AACrB,gDAAgD;AAChD,uBAAuB;AACvB,mEAAmE;AACnE,SAAS;AACT,oBAAoB;AACpB,iDAAiD;AACjD,6DAA6D;AAC7D,0EAA0E;AAC1E,SAAS;AACT,iBAAiB;AACjB,2DAA2D;AAC3D,8DAA8D;AAC9D,QAAQ;AACR,MAAM;AACN,EAAE;AACF,wEAAwE;AACxE,0DAA0D;AAC1D,2EAA2E;AAC3E,6BAA6B;AAC7B,wEAAwE;AACxE,0EAA0E;AAC1E,qCAAqC;AAErC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,IAAI,MAAM,SAAS,CAAC;AAG3B,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/platform.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
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 { ZoneFixture } from './zoneAccessory.js';
|
|
9
10
|
import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
|
|
10
11
|
export class DmxPlatform {
|
|
11
12
|
log;
|
|
@@ -34,7 +35,8 @@ export class DmxPlatform {
|
|
|
34
35
|
}
|
|
35
36
|
this.log.info(`DMX platform configured: ${this.cfg.controllers.length} controller` +
|
|
36
37
|
`${this.cfg.controllers.length === 1 ? '' : 's'}, ` +
|
|
37
|
-
`${this.cfg.fixtures.length} fixture${this.cfg.fixtures.length === 1 ? '' : 's'}`
|
|
38
|
+
`${this.cfg.fixtures.length} fixture${this.cfg.fixtures.length === 1 ? '' : 's'}, ` +
|
|
39
|
+
`${this.cfg.zones.length} zone${this.cfg.zones.length === 1 ? '' : 's'}`);
|
|
38
40
|
this.api.on('didFinishLaunching', () => this.discoverDevices());
|
|
39
41
|
this.api.on('shutdown', () => {
|
|
40
42
|
for (const c of this.controllers.values())
|
|
@@ -81,6 +83,25 @@ export class DmxPlatform {
|
|
|
81
83
|
this.accessories.push(accessory);
|
|
82
84
|
}
|
|
83
85
|
}
|
|
86
|
+
// Zones — virtual accessories that dispatch to their member fixtures.
|
|
87
|
+
for (const zone of this.cfg.zones) {
|
|
88
|
+
const uuid = this.api.hap.uuid.generate(`dmx-zone:${zone.id}`);
|
|
89
|
+
wantUuids.add(uuid);
|
|
90
|
+
const existing = this.accessories.find((a) => a.UUID === uuid);
|
|
91
|
+
if (existing) {
|
|
92
|
+
this.log.debug(`wiring cached zone ${zone.id}`);
|
|
93
|
+
existing.context.zone = { id: zone.id };
|
|
94
|
+
new ZoneFixture(this, existing, zone, this.controllers);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
this.log.info(`registering new zone: ${zone.name} (${zone.id}, ${zone.members.length} members)`);
|
|
98
|
+
const accessory = new this.api.platformAccessory(zone.name, uuid);
|
|
99
|
+
accessory.context.zone = { id: zone.id };
|
|
100
|
+
new ZoneFixture(this, accessory, zone, this.controllers);
|
|
101
|
+
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
102
|
+
this.accessories.push(accessory);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
84
105
|
const stale = this.accessories.filter((a) => !wantUuids.has(a.UUID));
|
|
85
106
|
if (stale.length) {
|
|
86
107
|
this.log.info(`unregistering ${stale.length} stale accessor${stale.length === 1 ? 'y' : 'ies'}`);
|
package/dist/platform.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,EAAE;AACF,0EAA0E;AAC1E,2EAA2E;AAC3E,kBAAkB;AAYlB,OAAO,EAA4B,UAAU,EAAa,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE3D,MAAM,OAAO,WAAW;IASJ;IACA;IACA;IAVF,OAAO,CAAiB;IACxB,cAAc,CAAwB;IACtC,WAAW,GAAwB,EAAE,CAAC;IAE9C,GAAG,GAAwB,IAAI,CAAC;IAChC,WAAW,GAAG,IAAI,GAAG,EAA2B,CAAC;IAEzD,YACkB,GAAW,EACX,MAAsB,EACtB,GAAQ;QAFR,QAAG,GAAH,GAAG,CAAQ;QACX,WAAM,GAAN,MAAM,CAAgB;QACtB,QAAG,GAAH,GAAG,CAAK;QAExB,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,cAAc,GAAG,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC;QAE7C,IAAI,CAAC;YACH,IAAI,CAAC,GAAG,GAAG,UAAU,CACnB,MAA8B,EAC9B,GAAG,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,IAAI,OAAO,CAAC,GAAG,EAAE,CAC3C,CAAC;QACJ,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,eAAe,EAAG,CAAW,CAAC,OAAO,CAAC,CAAC;YACtD,OAAO;QACT,CAAC;QAED,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;YACrC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;QACrD,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,IAAI,CACX,4BAA4B,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,MAAM,aAAa;YACpE,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI;YACnD,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,WAAW,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,
|
|
1
|
+
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PlatformAccessory } from 'homebridge';
|
|
2
|
+
import { Zone } from './config.js';
|
|
3
|
+
import { StickController } from './controller.js';
|
|
4
|
+
import type { DmxPlatform } from './platform.js';
|
|
5
|
+
export declare class ZoneFixture {
|
|
6
|
+
private readonly platform;
|
|
7
|
+
private readonly zone;
|
|
8
|
+
private readonly controllers;
|
|
9
|
+
private state;
|
|
10
|
+
private pending;
|
|
11
|
+
constructor(platform: DmxPlatform, accessory: PlatformAccessory, zone: Zone, controllers: Map<string, StickController>);
|
|
12
|
+
private schedule;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=zoneAccessory.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
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.
|
|
6
|
+
//
|
|
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.
|
|
10
|
+
import { CHARACTERISTIC_UPDATE_DELAY_MS } from './settings.js';
|
|
11
|
+
export class ZoneFixture {
|
|
12
|
+
platform;
|
|
13
|
+
zone;
|
|
14
|
+
controllers;
|
|
15
|
+
state = { on: false, brightness: 100 };
|
|
16
|
+
pending = null;
|
|
17
|
+
constructor(platform, accessory, zone, controllers) {
|
|
18
|
+
this.platform = platform;
|
|
19
|
+
this.zone = zone;
|
|
20
|
+
this.controllers = controllers;
|
|
21
|
+
const C = platform.Characteristic;
|
|
22
|
+
const info = accessory.getService(platform.Service.AccessoryInformation);
|
|
23
|
+
info?.setCharacteristic(C.Manufacturer, 'DMX')
|
|
24
|
+
?.setCharacteristic(C.Model, `Zone (${zone.members.length} fixtures)`)
|
|
25
|
+
?.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(); });
|
|
33
|
+
const has = (k) => zone.characteristics.includes(k);
|
|
34
|
+
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
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (has('Hue')) {
|
|
43
|
+
bulb.getCharacteristic(C.Hue)
|
|
44
|
+
.onGet(() => this.state.hue ?? 0)
|
|
45
|
+
.onSet((v) => { this.state.hue = Number(v); this.schedule(); });
|
|
46
|
+
}
|
|
47
|
+
if (has('Saturation')) {
|
|
48
|
+
bulb.getCharacteristic(C.Saturation)
|
|
49
|
+
.onGet(() => this.state.saturation ?? 0)
|
|
50
|
+
.onSet((v) => { this.state.saturation = Number(v); this.schedule(); });
|
|
51
|
+
}
|
|
52
|
+
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
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
schedule() {
|
|
63
|
+
if (this.pending)
|
|
64
|
+
clearTimeout(this.pending);
|
|
65
|
+
this.pending = setTimeout(() => {
|
|
66
|
+
this.pending = null;
|
|
67
|
+
const s = this.state;
|
|
68
|
+
this.platform.log.info(`[zone:${this.zone.id}] HK set: on=${s.on} br=${s.brightness}` +
|
|
69
|
+
(s.hue != null ? ` h=${s.hue}` : '') +
|
|
70
|
+
(s.saturation != null ? ` s=${s.saturation}` : '') +
|
|
71
|
+
(s.colorTemperatureMireds != null ? ` ct=${s.colorTemperatureMireds}` : '') +
|
|
72
|
+
` → ${this.zone.members.length} members`);
|
|
73
|
+
// Dispatch the zone's state to each member via that member's controller.
|
|
74
|
+
for (const m of this.zone.members) {
|
|
75
|
+
const c = this.controllers.get(m.controller.id);
|
|
76
|
+
if (!c) {
|
|
77
|
+
this.platform.log.error(`zone "${this.zone.id}": no controller for "${m.id}"`);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
c.setFixture(m, this.state);
|
|
81
|
+
}
|
|
82
|
+
}, CHARACTERISTIC_UPDATE_DELAY_MS);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=zoneAccessory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zoneAccessory.js","sourceRoot":"","sources":["../src/zoneAccessory.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -37,7 +37,7 @@ import fs from 'node:fs';
|
|
|
37
37
|
import path from 'node:path';
|
|
38
38
|
import yaml from 'js-yaml';
|
|
39
39
|
|
|
40
|
-
import { ChannelDef, ColorModel } from './color/types.js';
|
|
40
|
+
import { ChannelDef, ColorModel, HKCharacteristic } from './color/types.js';
|
|
41
41
|
import { getColorModel } from './color/index.js';
|
|
42
42
|
import { parseChannelName } from './color/parsers.js';
|
|
43
43
|
|
|
@@ -62,12 +62,19 @@ export interface PatchSpec {
|
|
|
62
62
|
start: number; // 1-based DMX start address
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
export interface ZoneSpec {
|
|
66
|
+
id: string; // unique handle (kebab-case)
|
|
67
|
+
name: string; // human-friendly HomeKit name
|
|
68
|
+
members: string[]; // fixture ids included in this zone
|
|
69
|
+
}
|
|
70
|
+
|
|
65
71
|
export interface RawConfig {
|
|
66
72
|
name?: string;
|
|
67
73
|
yamlPath?: string;
|
|
68
74
|
controllers?: ControllerSpec[];
|
|
69
75
|
profiles?: ProfileSpec[];
|
|
70
76
|
patch?: PatchSpec[];
|
|
77
|
+
zones?: ZoneSpec[];
|
|
71
78
|
}
|
|
72
79
|
|
|
73
80
|
export interface Profile {
|
|
@@ -95,10 +102,21 @@ export interface Fixture {
|
|
|
95
102
|
nChannels: number; // = channels.length
|
|
96
103
|
}
|
|
97
104
|
|
|
105
|
+
/** A virtual "zone" Lightbulb: appears as a single accessory in HomeKit;
|
|
106
|
+
* setting its state dispatches to every member fixture. The intersection
|
|
107
|
+
* of the members' color-model characteristics is what HomeKit sees. */
|
|
108
|
+
export interface Zone {
|
|
109
|
+
id: string;
|
|
110
|
+
name: string;
|
|
111
|
+
members: Fixture[];
|
|
112
|
+
characteristics: HKCharacteristic[]; // intersection of member characteristics
|
|
113
|
+
}
|
|
114
|
+
|
|
98
115
|
export interface LoadedConfig {
|
|
99
116
|
name: string;
|
|
100
117
|
controllers: Controller[];
|
|
101
118
|
fixtures: Fixture[];
|
|
119
|
+
zones: Zone[];
|
|
102
120
|
}
|
|
103
121
|
|
|
104
122
|
export const DEFAULT_PLATFORM_NAME = 'DMX';
|
|
@@ -240,9 +258,51 @@ export function loadConfig(rawJson: RawConfig, cwd?: string): LoadedConfig {
|
|
|
240
258
|
});
|
|
241
259
|
}
|
|
242
260
|
|
|
261
|
+
// Zones — virtual Lightbulb accessories. Each zone's members must be
|
|
262
|
+
// existing fixture ids. Zones never reference other zones (no recursion).
|
|
263
|
+
const fixturesById = new Map(fixtures.map((f) => [f.id, f]));
|
|
264
|
+
const zoneSpecs = raw.zones ?? [];
|
|
265
|
+
const zones: Zone[] = [];
|
|
266
|
+
const seenZoneIds = new Set<string>();
|
|
267
|
+
for (const zs of zoneSpecs) {
|
|
268
|
+
if (!zs.id) throw new Error('zone: missing id');
|
|
269
|
+
if (seenZoneIds.has(zs.id)) {
|
|
270
|
+
throw new Error(`zone: duplicate id "${zs.id}"`);
|
|
271
|
+
}
|
|
272
|
+
if (fixturesById.has(zs.id)) {
|
|
273
|
+
throw new Error(`zone id "${zs.id}" collides with fixture id`);
|
|
274
|
+
}
|
|
275
|
+
seenZoneIds.add(zs.id);
|
|
276
|
+
if (!Array.isArray(zs.members) || zs.members.length === 0) {
|
|
277
|
+
throw new Error(`zone "${zs.id}": members must be a non-empty array of fixture ids`);
|
|
278
|
+
}
|
|
279
|
+
const members: Fixture[] = [];
|
|
280
|
+
for (const m of zs.members) {
|
|
281
|
+
const fx = fixturesById.get(m);
|
|
282
|
+
if (!fx) {
|
|
283
|
+
throw new Error(`zone "${zs.id}": unknown fixture "${m}"`);
|
|
284
|
+
}
|
|
285
|
+
members.push(fx);
|
|
286
|
+
}
|
|
287
|
+
// Intersection of member characteristics — HomeKit only exposes what
|
|
288
|
+
// ALL members can do.
|
|
289
|
+
let chars = new Set<HKCharacteristic>(members[0].profile.model.characteristics);
|
|
290
|
+
for (let i = 1; i < members.length; i++) {
|
|
291
|
+
const mc = new Set<HKCharacteristic>(members[i].profile.model.characteristics);
|
|
292
|
+
chars = new Set([...chars].filter((c) => mc.has(c)));
|
|
293
|
+
}
|
|
294
|
+
zones.push({
|
|
295
|
+
id: zs.id,
|
|
296
|
+
name: zs.name || zs.id,
|
|
297
|
+
members,
|
|
298
|
+
characteristics: [...chars],
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
243
302
|
return {
|
|
244
303
|
name: raw.name ?? DEFAULT_PLATFORM_NAME,
|
|
245
304
|
controllers: [...controllersById.values()],
|
|
246
305
|
fixtures,
|
|
306
|
+
zones,
|
|
247
307
|
};
|
|
248
308
|
}
|
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 { ZoneFixture } from './zoneAccessory.js';
|
|
20
21
|
import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
|
|
21
22
|
|
|
22
23
|
export class DmxPlatform implements DynamicPlatformPlugin {
|
|
@@ -52,7 +53,8 @@ export class DmxPlatform implements DynamicPlatformPlugin {
|
|
|
52
53
|
this.log.info(
|
|
53
54
|
`DMX platform configured: ${this.cfg.controllers.length} controller` +
|
|
54
55
|
`${this.cfg.controllers.length === 1 ? '' : 's'}, ` +
|
|
55
|
-
`${this.cfg.fixtures.length} fixture${this.cfg.fixtures.length === 1 ? '' : 's'}
|
|
56
|
+
`${this.cfg.fixtures.length} fixture${this.cfg.fixtures.length === 1 ? '' : 's'}, ` +
|
|
57
|
+
`${this.cfg.zones.length} zone${this.cfg.zones.length === 1 ? '' : 's'}`,
|
|
56
58
|
);
|
|
57
59
|
|
|
58
60
|
this.api.on('didFinishLaunching', () => this.discoverDevices());
|
|
@@ -105,6 +107,25 @@ export class DmxPlatform implements DynamicPlatformPlugin {
|
|
|
105
107
|
}
|
|
106
108
|
}
|
|
107
109
|
|
|
110
|
+
// Zones — virtual accessories that dispatch to their member fixtures.
|
|
111
|
+
for (const zone of this.cfg.zones) {
|
|
112
|
+
const uuid = this.api.hap.uuid.generate(`dmx-zone:${zone.id}`);
|
|
113
|
+
wantUuids.add(uuid);
|
|
114
|
+
const existing = this.accessories.find((a) => a.UUID === uuid);
|
|
115
|
+
if (existing) {
|
|
116
|
+
this.log.debug(`wiring cached zone ${zone.id}`);
|
|
117
|
+
existing.context.zone = { id: zone.id };
|
|
118
|
+
new ZoneFixture(this, existing, zone, this.controllers);
|
|
119
|
+
} else {
|
|
120
|
+
this.log.info(`registering new zone: ${zone.name} (${zone.id}, ${zone.members.length} members)`);
|
|
121
|
+
const accessory = new this.api.platformAccessory(zone.name, uuid);
|
|
122
|
+
accessory.context.zone = { id: zone.id };
|
|
123
|
+
new ZoneFixture(this, accessory, zone, this.controllers);
|
|
124
|
+
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
125
|
+
this.accessories.push(accessory);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
108
129
|
const stale = this.accessories.filter((a) => !wantUuids.has(a.UUID));
|
|
109
130
|
if (stale.length) {
|
|
110
131
|
this.log.info(`unregistering ${stale.length} stale accessor${stale.length === 1 ? 'y' : 'ies'}`);
|
|
@@ -0,0 +1,104 @@
|
|
|
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.
|
|
6
|
+
//
|
|
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.
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
CharacteristicValue,
|
|
13
|
+
PlatformAccessory,
|
|
14
|
+
Service,
|
|
15
|
+
} from 'homebridge';
|
|
16
|
+
|
|
17
|
+
import { Zone } from './config.js';
|
|
18
|
+
import { HKCharacteristic, HomeKitLightState } from './color/types.js';
|
|
19
|
+
import { StickController } from './controller.js';
|
|
20
|
+
import type { DmxPlatform } from './platform.js';
|
|
21
|
+
import { CHARACTERISTIC_UPDATE_DELAY_MS } from './settings.js';
|
|
22
|
+
|
|
23
|
+
export class ZoneFixture {
|
|
24
|
+
private state: HomeKitLightState = { on: false, brightness: 100 };
|
|
25
|
+
private pending: NodeJS.Timeout | null = null;
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
private readonly platform: DmxPlatform,
|
|
29
|
+
accessory: PlatformAccessory,
|
|
30
|
+
private readonly zone: Zone,
|
|
31
|
+
private readonly controllers: Map<string, StickController>,
|
|
32
|
+
) {
|
|
33
|
+
const C = platform.Characteristic;
|
|
34
|
+
const info = accessory.getService(platform.Service.AccessoryInformation);
|
|
35
|
+
info?.setCharacteristic(C.Manufacturer, 'DMX')
|
|
36
|
+
?.setCharacteristic(C.Model, `Zone (${zone.members.length} fixtures)`)
|
|
37
|
+
?.setCharacteristic(C.SerialNumber, `zone-${zone.id}`);
|
|
38
|
+
|
|
39
|
+
const bulb: Service =
|
|
40
|
+
accessory.getService(platform.Service.Lightbulb)
|
|
41
|
+
?? accessory.addService(platform.Service.Lightbulb, zone.name);
|
|
42
|
+
|
|
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(); });
|
|
49
|
+
|
|
50
|
+
const has = (k: HKCharacteristic): boolean => zone.characteristics.includes(k);
|
|
51
|
+
|
|
52
|
+
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
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (has('Hue')) {
|
|
61
|
+
bulb.getCharacteristic(C.Hue)
|
|
62
|
+
.onGet(() => this.state.hue ?? 0)
|
|
63
|
+
.onSet((v) => { this.state.hue = Number(v); this.schedule(); });
|
|
64
|
+
}
|
|
65
|
+
if (has('Saturation')) {
|
|
66
|
+
bulb.getCharacteristic(C.Saturation)
|
|
67
|
+
.onGet(() => this.state.saturation ?? 0)
|
|
68
|
+
.onSet((v) => { this.state.saturation = Number(v); this.schedule(); });
|
|
69
|
+
}
|
|
70
|
+
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
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private schedule(): void {
|
|
82
|
+
if (this.pending) clearTimeout(this.pending);
|
|
83
|
+
this.pending = setTimeout(() => {
|
|
84
|
+
this.pending = null;
|
|
85
|
+
const s = this.state;
|
|
86
|
+
this.platform.log.info(
|
|
87
|
+
`[zone:${this.zone.id}] HK set: on=${s.on} br=${s.brightness}` +
|
|
88
|
+
(s.hue != null ? ` h=${s.hue}` : '') +
|
|
89
|
+
(s.saturation != null ? ` s=${s.saturation}` : '') +
|
|
90
|
+
(s.colorTemperatureMireds != null ? ` ct=${s.colorTemperatureMireds}` : '') +
|
|
91
|
+
` → ${this.zone.members.length} members`,
|
|
92
|
+
);
|
|
93
|
+
// Dispatch the zone's state to each member via that member's controller.
|
|
94
|
+
for (const m of this.zone.members) {
|
|
95
|
+
const c = this.controllers.get(m.controller.id);
|
|
96
|
+
if (!c) {
|
|
97
|
+
this.platform.log.error(`zone "${this.zone.id}": no controller for "${m.id}"`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
c.setFixture(m, this.state);
|
|
101
|
+
}
|
|
102
|
+
}, CHARACTERISTIC_UPDATE_DELAY_MS);
|
|
103
|
+
}
|
|
104
|
+
}
|