homebridge-sonos-scenes 0.1.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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +103 -0
  3. package/config.schema.json +86 -0
  4. package/dist/src/accessories/sceneSwitch.d.ts +17 -0
  5. package/dist/src/accessories/sceneSwitch.js +81 -0
  6. package/dist/src/accessories/sceneSwitch.js.map +1 -0
  7. package/dist/src/config.d.ts +7 -0
  8. package/dist/src/config.js +256 -0
  9. package/dist/src/config.js.map +1 -0
  10. package/dist/src/discoveryService.d.ts +10 -0
  11. package/dist/src/discoveryService.js +32 -0
  12. package/dist/src/discoveryService.js.map +1 -0
  13. package/dist/src/index.d.ts +3 -0
  14. package/dist/src/index.js +6 -0
  15. package/dist/src/index.js.map +1 -0
  16. package/dist/src/logger.d.ts +23 -0
  17. package/dist/src/logger.js +70 -0
  18. package/dist/src/logger.js.map +1 -0
  19. package/dist/src/platform.d.ts +23 -0
  20. package/dist/src/platform.js +111 -0
  21. package/dist/src/platform.js.map +1 -0
  22. package/dist/src/sampleTopology.d.ts +2 -0
  23. package/dist/src/sampleTopology.js +66 -0
  24. package/dist/src/sampleTopology.js.map +1 -0
  25. package/dist/src/sceneRunner.d.ts +20 -0
  26. package/dist/src/sceneRunner.js +186 -0
  27. package/dist/src/sceneRunner.js.map +1 -0
  28. package/dist/src/transports/index.d.ts +2 -0
  29. package/dist/src/transports/index.js +13 -0
  30. package/dist/src/transports/index.js.map +1 -0
  31. package/dist/src/transports/localTransport.d.ts +33 -0
  32. package/dist/src/transports/localTransport.js +544 -0
  33. package/dist/src/transports/localTransport.js.map +1 -0
  34. package/dist/src/types.d.ts +141 -0
  35. package/dist/src/types.js +6 -0
  36. package/dist/src/types.js.map +1 -0
  37. package/dist/src/ui/serverApi.d.ts +11 -0
  38. package/dist/src/ui/serverApi.js +52 -0
  39. package/dist/src/ui/serverApi.js.map +1 -0
  40. package/examples/config.example.json +48 -0
  41. package/examples/sample-topology.json +77 -0
  42. package/homebridge-ui/public/index.html +810 -0
  43. package/homebridge-ui/server.js +67 -0
  44. package/package.json +52 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AACA,yCAA6E;AAE7E,iBAAS,CAAC,GAAQ,EAAE,EAAE;IACpB,GAAG,CAAC,gBAAgB,CAAC,sBAAW,EAAE,wBAAa,EAAE,8BAAmB,CAAC,CAAC;AACxE,CAAC,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { Logger as HomebridgeLogger } from "homebridge";
2
+ import type { LogLevel, SceneLogEntry } from "./types";
3
+ export interface LogCollector {
4
+ entries: SceneLogEntry[];
5
+ push(entry: SceneLogEntry): void;
6
+ }
7
+ export declare class MemoryLogCollector implements LogCollector {
8
+ readonly entries: SceneLogEntry[];
9
+ push(entry: SceneLogEntry): void;
10
+ }
11
+ export declare class StructuredLogger {
12
+ private readonly scope;
13
+ private readonly level;
14
+ private readonly homebridgeLog?;
15
+ private readonly collector?;
16
+ constructor(scope: string, level: LogLevel, homebridgeLog?: HomebridgeLogger | undefined, collector?: LogCollector | undefined);
17
+ child(scope: string): StructuredLogger;
18
+ debug(message: string): void;
19
+ info(message: string): void;
20
+ warn(message: string): void;
21
+ error(message: string): void;
22
+ private log;
23
+ }
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StructuredLogger = exports.MemoryLogCollector = void 0;
4
+ const severity = {
5
+ debug: 10,
6
+ info: 20,
7
+ warn: 30,
8
+ error: 40,
9
+ };
10
+ class MemoryLogCollector {
11
+ entries = [];
12
+ push(entry) {
13
+ this.entries.push(entry);
14
+ }
15
+ }
16
+ exports.MemoryLogCollector = MemoryLogCollector;
17
+ class StructuredLogger {
18
+ scope;
19
+ level;
20
+ homebridgeLog;
21
+ collector;
22
+ constructor(scope, level, homebridgeLog, collector) {
23
+ this.scope = scope;
24
+ this.level = level;
25
+ this.homebridgeLog = homebridgeLog;
26
+ this.collector = collector;
27
+ }
28
+ child(scope) {
29
+ return new StructuredLogger(`${this.scope}:${scope}`, this.level, this.homebridgeLog, this.collector);
30
+ }
31
+ debug(message) {
32
+ this.log("debug", message);
33
+ }
34
+ info(message) {
35
+ this.log("info", message);
36
+ }
37
+ warn(message) {
38
+ this.log("warn", message);
39
+ }
40
+ error(message) {
41
+ this.log("error", message);
42
+ }
43
+ log(level, message) {
44
+ const entry = {
45
+ timestamp: new Date().toISOString(),
46
+ level,
47
+ scope: this.scope,
48
+ message,
49
+ };
50
+ this.collector?.push(entry);
51
+ if (severity[level] < severity[this.level] || !this.homebridgeLog) {
52
+ return;
53
+ }
54
+ if (level === "debug") {
55
+ this.homebridgeLog.debug(`[${this.scope}] ${message}`);
56
+ return;
57
+ }
58
+ if (level === "info") {
59
+ this.homebridgeLog.info(`[${this.scope}] ${message}`);
60
+ return;
61
+ }
62
+ if (level === "warn") {
63
+ this.homebridgeLog.warn(`[${this.scope}] ${message}`);
64
+ return;
65
+ }
66
+ this.homebridgeLog.error(`[${this.scope}] ${message}`);
67
+ }
68
+ }
69
+ exports.StructuredLogger = StructuredLogger;
70
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":";;;AAGA,MAAM,QAAQ,GAA6B;IACzC,KAAK,EAAE,EAAE;IACT,IAAI,EAAE,EAAE;IACR,IAAI,EAAE,EAAE;IACR,KAAK,EAAE,EAAE;CACV,CAAC;AAOF,MAAa,kBAAkB;IACb,OAAO,GAAoB,EAAE,CAAC;IAE9C,IAAI,CAAC,KAAoB;QACvB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;CACF;AAND,gDAMC;AAED,MAAa,gBAAgB;IAER;IACA;IACA;IACA;IAJnB,YACmB,KAAa,EACb,KAAe,EACf,aAAgC,EAChC,SAAwB;QAHxB,UAAK,GAAL,KAAK,CAAQ;QACb,UAAK,GAAL,KAAK,CAAU;QACf,kBAAa,GAAb,aAAa,CAAmB;QAChC,cAAS,GAAT,SAAS,CAAe;IACxC,CAAC;IAEJ,KAAK,CAAC,KAAa;QACjB,OAAO,IAAI,gBAAgB,CAAC,GAAG,IAAI,CAAC,KAAK,IAAI,KAAK,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IACxG,CAAC;IAED,KAAK,CAAC,OAAe;QACnB,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC7B,CAAC;IAED,IAAI,CAAC,OAAe;QAClB,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5B,CAAC;IAED,IAAI,CAAC,OAAe;QAClB,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,OAAe;QACnB,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC7B,CAAC;IAEO,GAAG,CAAC,KAAe,EAAE,OAAe;QAC1C,MAAM,KAAK,GAAkB;YAC3B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,KAAK;YACL,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,OAAO;SACR,CAAC;QAEF,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAE5B,IAAI,QAAQ,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YAClE,OAAO;QACT,CAAC;QAED,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;YACtB,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC,CAAC;YACvD,OAAO;QACT,CAAC;QAED,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;YACrB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC,CAAC;YACtD,OAAO;QACT,CAAC;QAED,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;YACrB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC,CAAC;YACtD,OAAO;QACT,CAAC;QAED,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC,CAAC;IACzD,CAAC;CACF;AA3DD,4CA2DC"}
@@ -0,0 +1,23 @@
1
+ import type { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig } from "homebridge";
2
+ import type { SceneDefinition, SceneRunResult, SceneTrigger } from "./types";
3
+ import { PLATFORM_NAME, PLUGIN_NAME } from "./types";
4
+ export { PLATFORM_NAME, PLUGIN_NAME };
5
+ export declare class SonosScenesPlatform implements DynamicPlatformPlugin {
6
+ readonly log: Logger;
7
+ private readonly rawConfig;
8
+ readonly api: API;
9
+ readonly Service: typeof import("homebridge").Service;
10
+ readonly Characteristic: typeof import("homebridge").Characteristic;
11
+ private readonly config;
12
+ private readonly logger;
13
+ private readonly discoveryService;
14
+ private readonly sceneRunner;
15
+ private readonly transport;
16
+ private readonly cachedAccessories;
17
+ private readonly sceneAccessories;
18
+ constructor(log: Logger, rawConfig: PlatformConfig, api: API);
19
+ configureAccessory(accessory: PlatformAccessory): void;
20
+ runScene(sceneId: string, trigger: SceneTrigger): Promise<SceneRunResult>;
21
+ getScene(sceneId: string): SceneDefinition | undefined;
22
+ private syncAccessories;
23
+ }
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SonosScenesPlatform = exports.PLUGIN_NAME = exports.PLATFORM_NAME = void 0;
4
+ const config_1 = require("./config");
5
+ const discoveryService_1 = require("./discoveryService");
6
+ const logger_1 = require("./logger");
7
+ const sceneRunner_1 = require("./sceneRunner");
8
+ const sceneSwitch_1 = require("./accessories/sceneSwitch");
9
+ const transports_1 = require("./transports");
10
+ const types_1 = require("./types");
11
+ Object.defineProperty(exports, "PLATFORM_NAME", { enumerable: true, get: function () { return types_1.PLATFORM_NAME; } });
12
+ Object.defineProperty(exports, "PLUGIN_NAME", { enumerable: true, get: function () { return types_1.PLUGIN_NAME; } });
13
+ class SonosScenesPlatform {
14
+ log;
15
+ rawConfig;
16
+ api;
17
+ Service;
18
+ Characteristic;
19
+ config;
20
+ logger;
21
+ discoveryService;
22
+ sceneRunner;
23
+ transport;
24
+ cachedAccessories = new Map();
25
+ sceneAccessories = new Map();
26
+ constructor(log, rawConfig, api) {
27
+ this.log = log;
28
+ this.rawConfig = rawConfig;
29
+ this.api = api;
30
+ this.Service = this.api.hap.Service;
31
+ this.Characteristic = this.api.hap.Characteristic;
32
+ this.config = (0, config_1.normalizePlatformConfig)(rawConfig);
33
+ this.transport = (0, transports_1.createTransport)(this.config);
34
+ this.logger = new logger_1.StructuredLogger("platform", this.config.logLevel, this.log);
35
+ this.discoveryService = new discoveryService_1.DiscoveryService(this.transport);
36
+ this.sceneRunner = new sceneRunner_1.SceneRunner(this.discoveryService, this.transport, this.logger);
37
+ this.api.on("didFinishLaunching", () => {
38
+ void this.syncAccessories();
39
+ });
40
+ }
41
+ configureAccessory(accessory) {
42
+ this.cachedAccessories.set(accessory.UUID, accessory);
43
+ }
44
+ async runScene(sceneId, trigger) {
45
+ const scene = this.config.scenes.find((item) => item.id === sceneId);
46
+ if (!scene) {
47
+ return {
48
+ ok: false,
49
+ sceneId,
50
+ trigger,
51
+ logs: [],
52
+ errors: [`Scene "${sceneId}" is not configured.`],
53
+ };
54
+ }
55
+ if (trigger === "off") {
56
+ return this.sceneRunner.runOff(scene);
57
+ }
58
+ if (trigger === "test") {
59
+ return this.sceneRunner.runTest(scene);
60
+ }
61
+ return this.sceneRunner.runOn(scene);
62
+ }
63
+ getScene(sceneId) {
64
+ return this.config.scenes.find((scene) => scene.id === sceneId);
65
+ }
66
+ async syncAccessories() {
67
+ this.logger.info(`Syncing ${this.config.scenes.length} configured Sonos scene accessory(s).`);
68
+ const activeAccessoryIds = new Set();
69
+ for (const scene of this.config.scenes) {
70
+ const uuid = this.api.hap.uuid.generate(`${types_1.PLUGIN_NAME}:${scene.id}`);
71
+ activeAccessoryIds.add(uuid);
72
+ let accessory = this.cachedAccessories.get(uuid);
73
+ if (!accessory) {
74
+ accessory = new this.api.platformAccessory(scene.name, uuid);
75
+ this.cachedAccessories.set(uuid, accessory);
76
+ const wrapper = new sceneSwitch_1.SceneSwitchAccessory(this, accessory, scene);
77
+ this.sceneAccessories.set(scene.id, wrapper);
78
+ this.api.registerPlatformAccessories(types_1.PLUGIN_NAME, types_1.PLATFORM_NAME, [accessory]);
79
+ continue;
80
+ }
81
+ const wrapper = this.sceneAccessories.get(scene.id) ?? new sceneSwitch_1.SceneSwitchAccessory(this, accessory, scene);
82
+ wrapper.updateScene(scene);
83
+ this.sceneAccessories.set(scene.id, wrapper);
84
+ this.api.updatePlatformAccessories([accessory]);
85
+ }
86
+ const staleAccessories = Array.from(this.cachedAccessories.entries())
87
+ .filter(([uuid]) => !activeAccessoryIds.has(uuid))
88
+ .map(([, accessory]) => accessory);
89
+ if (staleAccessories.length > 0) {
90
+ this.api.unregisterPlatformAccessories(types_1.PLUGIN_NAME, types_1.PLATFORM_NAME, staleAccessories);
91
+ for (const accessory of staleAccessories) {
92
+ this.cachedAccessories.delete(accessory.UUID);
93
+ }
94
+ for (const [sceneId, wrapper] of this.sceneAccessories.entries()) {
95
+ if (!this.config.scenes.some((scene) => scene.id === sceneId)) {
96
+ void wrapper;
97
+ this.sceneAccessories.delete(sceneId);
98
+ }
99
+ }
100
+ }
101
+ try {
102
+ const snapshot = await this.discoveryService.refresh();
103
+ this.logger.info(`Discovery complete: ${snapshot.households.length} household(s) available.`);
104
+ }
105
+ catch (error) {
106
+ this.logger.warn(`Initial discovery failed: ${error instanceof Error ? error.message : String(error)}`);
107
+ }
108
+ }
109
+ }
110
+ exports.SonosScenesPlatform = SonosScenesPlatform;
111
+ //# sourceMappingURL=platform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../../src/platform.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { TopologySnapshot } from "./types";
2
+ export declare const sampleTopology: TopologySnapshot;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sampleTopology = void 0;
4
+ exports.sampleTopology = {
5
+ capturedAt: "2026-04-18T00:00:00.000Z",
6
+ origin: "fixture",
7
+ households: [
8
+ {
9
+ id: "local-household",
10
+ displayName: "Local Sonos",
11
+ players: [
12
+ {
13
+ id: "RINCON_UPPER_LEVEL",
14
+ name: "Upper Level",
15
+ model: "Sonos Amp",
16
+ capabilities: ["PLAYBACK", "LINE_IN", "AIRPLAY"],
17
+ deviceIds: ["RINCON_UPPER_LEVEL"],
18
+ groupId: "GROUP_UPPER_LEVEL",
19
+ isCoordinator: true,
20
+ fixedVolume: false,
21
+ sourceOptions: ["line_in", "favorite"],
22
+ },
23
+ {
24
+ id: "RINCON_PRIMARY_BEDROOM",
25
+ name: "Primary Bedroom",
26
+ model: "Sonos Beam",
27
+ capabilities: ["PLAYBACK", "AIRPLAY"],
28
+ deviceIds: ["RINCON_PRIMARY_BEDROOM"],
29
+ groupId: "GROUP_PRIMARY_BEDROOM",
30
+ isCoordinator: true,
31
+ fixedVolume: false,
32
+ sourceOptions: ["favorite"],
33
+ },
34
+ ],
35
+ groups: [
36
+ {
37
+ id: "GROUP_UPPER_LEVEL",
38
+ name: "Upper Level",
39
+ coordinatorId: "RINCON_UPPER_LEVEL",
40
+ playerIds: ["RINCON_UPPER_LEVEL"],
41
+ playbackState: "PLAYBACK_STATE_IDLE",
42
+ },
43
+ {
44
+ id: "GROUP_PRIMARY_BEDROOM",
45
+ name: "Primary Bedroom",
46
+ coordinatorId: "RINCON_PRIMARY_BEDROOM",
47
+ playerIds: ["RINCON_PRIMARY_BEDROOM"],
48
+ playbackState: "PLAYBACK_STATE_IDLE",
49
+ },
50
+ ],
51
+ favorites: [
52
+ {
53
+ id: "favorite-line-in-demo",
54
+ name: "Line-In Demo",
55
+ uri: "x-sonosapi-stream:s123?sid=254&flags=8224&sn=0",
56
+ },
57
+ {
58
+ id: "favorite-kexp",
59
+ name: "KEXP",
60
+ uri: "x-sonosapi-stream:s21606?sid=254&flags=8224&sn=0",
61
+ },
62
+ ],
63
+ },
64
+ ],
65
+ };
66
+ //# sourceMappingURL=sampleTopology.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sampleTopology.js","sourceRoot":"","sources":["../../src/sampleTopology.ts"],"names":[],"mappings":";;;AAEa,QAAA,cAAc,GAAqB;IAC9C,UAAU,EAAE,0BAA0B;IACtC,MAAM,EAAE,SAAS;IACjB,UAAU,EAAE;QACV;YACE,EAAE,EAAE,iBAAiB;YACrB,WAAW,EAAE,aAAa;YAC1B,OAAO,EAAE;gBACP;oBACE,EAAE,EAAE,oBAAoB;oBACxB,IAAI,EAAE,aAAa;oBACnB,KAAK,EAAE,WAAW;oBAClB,YAAY,EAAE,CAAC,UAAU,EAAE,SAAS,EAAE,SAAS,CAAC;oBAChD,SAAS,EAAE,CAAC,oBAAoB,CAAC;oBACjC,OAAO,EAAE,mBAAmB;oBAC5B,aAAa,EAAE,IAAI;oBACnB,WAAW,EAAE,KAAK;oBAClB,aAAa,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;iBACvC;gBACD;oBACE,EAAE,EAAE,wBAAwB;oBAC5B,IAAI,EAAE,iBAAiB;oBACvB,KAAK,EAAE,YAAY;oBACnB,YAAY,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC;oBACrC,SAAS,EAAE,CAAC,wBAAwB,CAAC;oBACrC,OAAO,EAAE,uBAAuB;oBAChC,aAAa,EAAE,IAAI;oBACnB,WAAW,EAAE,KAAK;oBAClB,aAAa,EAAE,CAAC,UAAU,CAAC;iBAC5B;aACF;YACD,MAAM,EAAE;gBACN;oBACE,EAAE,EAAE,mBAAmB;oBACvB,IAAI,EAAE,aAAa;oBACnB,aAAa,EAAE,oBAAoB;oBACnC,SAAS,EAAE,CAAC,oBAAoB,CAAC;oBACjC,aAAa,EAAE,qBAAqB;iBACrC;gBACD;oBACE,EAAE,EAAE,uBAAuB;oBAC3B,IAAI,EAAE,iBAAiB;oBACvB,aAAa,EAAE,wBAAwB;oBACvC,SAAS,EAAE,CAAC,wBAAwB,CAAC;oBACrC,aAAa,EAAE,qBAAqB;iBACrC;aACF;YACD,SAAS,EAAE;gBACT;oBACE,EAAE,EAAE,uBAAuB;oBAC3B,IAAI,EAAE,cAAc;oBACpB,GAAG,EAAE,gDAAgD;iBACtD;gBACD;oBACE,EAAE,EAAE,eAAe;oBACnB,IAAI,EAAE,MAAM;oBACZ,GAAG,EAAE,kDAAkD;iBACxD;aACF;SACF;KACF;CACF,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { DiscoveryService } from "./discoveryService";
2
+ import { StructuredLogger } from "./logger";
3
+ import type { SceneDefinition, SceneRunResult, SonosTransport } from "./types";
4
+ export declare class SceneRunner {
5
+ private readonly discoveryService;
6
+ private readonly transport;
7
+ private readonly logger;
8
+ private queues;
9
+ constructor(discoveryService: DiscoveryService, transport: SonosTransport, logger: StructuredLogger);
10
+ runOn(scene: SceneDefinition): Promise<SceneRunResult>;
11
+ runOff(scene: SceneDefinition): Promise<SceneRunResult>;
12
+ runTest(scene: SceneDefinition): Promise<SceneRunResult>;
13
+ private enqueue;
14
+ private execute;
15
+ private result;
16
+ private executeOn;
17
+ private executeOff;
18
+ private verifyGrouping;
19
+ private withRetry;
20
+ }
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SceneRunner = void 0;
4
+ const config_1 = require("./config");
5
+ const logger_1 = require("./logger");
6
+ function sleep(ms) {
7
+ if (ms <= 0) {
8
+ return Promise.resolve();
9
+ }
10
+ return new Promise((resolve) => setTimeout(resolve, ms));
11
+ }
12
+ class SceneRunner {
13
+ discoveryService;
14
+ transport;
15
+ logger;
16
+ queues = new Map();
17
+ constructor(discoveryService, transport, logger) {
18
+ this.discoveryService = discoveryService;
19
+ this.transport = transport;
20
+ this.logger = logger;
21
+ }
22
+ runOn(scene) {
23
+ return this.enqueue(scene, "on");
24
+ }
25
+ runOff(scene) {
26
+ return this.enqueue(scene, "off");
27
+ }
28
+ runTest(scene) {
29
+ return this.enqueue(scene, "test");
30
+ }
31
+ enqueue(scene, trigger) {
32
+ const queueKey = `${scene.householdId}:${scene.coordinatorPlayerId || scene.id}`;
33
+ const previous = this.queues.get(queueKey) ?? Promise.resolve({
34
+ ok: true,
35
+ sceneId: scene.id,
36
+ trigger,
37
+ logs: [],
38
+ errors: [],
39
+ });
40
+ const next = previous
41
+ .catch(() => undefined)
42
+ .then(() => this.execute(scene, trigger))
43
+ .finally(() => {
44
+ if (this.queues.get(queueKey) === next) {
45
+ this.queues.delete(queueKey);
46
+ }
47
+ });
48
+ this.queues.set(queueKey, next);
49
+ return next;
50
+ }
51
+ async execute(scene, trigger) {
52
+ const collector = new logger_1.MemoryLogCollector();
53
+ const logger = this.logger.child(scene.id);
54
+ const scopedLogger = new logger_1.StructuredLogger(`scene:${scene.id}`, "debug", undefined, collector);
55
+ const errors = [];
56
+ const log = (level, message) => {
57
+ scopedLogger[level](message);
58
+ logger[level](message);
59
+ };
60
+ log("info", `Running scene "${scene.name}" (${trigger}).`);
61
+ try {
62
+ let snapshot = await this.discoveryService.refresh();
63
+ const validation = (0, config_1.validateSceneDefinition)(scene, snapshot, this.transport);
64
+ if (!validation.valid) {
65
+ throw new Error(validation.errors.join(" "));
66
+ }
67
+ if (validation.warnings.length > 0) {
68
+ for (const warning of validation.warnings) {
69
+ log("warn", warning);
70
+ }
71
+ }
72
+ if (trigger === "off") {
73
+ await this.executeOff(scene, log);
74
+ snapshot = await this.discoveryService.refresh();
75
+ log("info", `Scene "${scene.name}" off action complete.`);
76
+ return this.result(true, scene.id, trigger, collector.entries, errors, snapshot);
77
+ }
78
+ await this.executeOn(scene, log);
79
+ snapshot = await this.discoveryService.refresh();
80
+ this.verifyGrouping(scene, snapshot, log);
81
+ log("info", `Scene "${scene.name}" complete.`);
82
+ return this.result(true, scene.id, trigger, collector.entries, errors, snapshot);
83
+ }
84
+ catch (error) {
85
+ const message = error instanceof Error ? error.message : String(error);
86
+ errors.push(message);
87
+ log("error", message);
88
+ return this.result(false, scene.id, trigger, collector.entries, errors);
89
+ }
90
+ }
91
+ result(ok, sceneId, trigger, logs, errors, snapshot) {
92
+ return {
93
+ ok,
94
+ sceneId,
95
+ trigger,
96
+ logs,
97
+ errors,
98
+ snapshot,
99
+ };
100
+ }
101
+ async executeOn(scene, log) {
102
+ log("info", `Resolved coordinator: ${scene.coordinatorPlayerId}`);
103
+ await this.withRetry(scene, "group members", () => this.transport.setGroupMembers(scene.householdId, scene.coordinatorPlayerId, scene.memberPlayerIds));
104
+ log("info", `Grouped members: ${scene.memberPlayerIds.join(", ") || "none"}`);
105
+ await sleep(scene.settleMs);
106
+ if (scene.source?.kind === "favorite") {
107
+ const source = scene.source;
108
+ await this.withRetry(scene, `load favorite ${source.favoriteId}`, () => this.transport.loadFavorite(scene.householdId, scene.coordinatorPlayerId, source.favoriteId));
109
+ log("info", `Selected favorite: ${source.favoriteId}`);
110
+ await sleep(scene.settleMs);
111
+ }
112
+ if (scene.source?.kind === "line_in") {
113
+ const source = scene.source;
114
+ await this.withRetry(scene, `load line-in ${scene.source.deviceId}`, () => this.transport.loadLineIn(scene.householdId, scene.coordinatorPlayerId, source.deviceId, source.playOnCompletion));
115
+ log("info", `Selected source: line_in (${source.deviceId})`);
116
+ await sleep(scene.settleMs);
117
+ }
118
+ if (scene.source?.kind === "tv") {
119
+ const source = scene.source;
120
+ if (!this.transport.loadTv) {
121
+ throw new Error("The active transport does not implement TV source loading.");
122
+ }
123
+ await this.withRetry(scene, `load TV ${scene.source.deviceId}`, () => this.transport.loadTv(scene.householdId, scene.coordinatorPlayerId, source.deviceId, source.playOnCompletion));
124
+ log("info", `Selected source: tv (${source.deviceId})`);
125
+ await sleep(scene.settleMs);
126
+ }
127
+ if (scene.coordinatorVolume !== undefined) {
128
+ await this.withRetry(scene, `set coordinator volume ${scene.coordinatorVolume}`, () => this.transport.setPlayerVolume(scene.householdId, scene.coordinatorPlayerId, scene.coordinatorVolume));
129
+ log("info", `Set coordinator volume: ${scene.coordinatorPlayerId}=${scene.coordinatorVolume}`);
130
+ }
131
+ for (const volume of scene.playerVolumes) {
132
+ await this.withRetry(scene, `set room volume ${volume.playerId}=${volume.volume}`, () => this.transport.setPlayerVolume(scene.householdId, volume.playerId, volume.volume));
133
+ log("info", `Set volume: ${volume.playerId}=${volume.volume}`);
134
+ }
135
+ }
136
+ async executeOff(scene, log) {
137
+ if (scene.offBehavior.kind === "none") {
138
+ log("info", "Off behavior is set to none; nothing to do.");
139
+ return;
140
+ }
141
+ if (scene.offBehavior.kind === "ungroup") {
142
+ await this.withRetry(scene, "ungroup members", () => this.transport.ungroup(scene.householdId, scene.coordinatorPlayerId, scene.memberPlayerIds));
143
+ log("info", `Ungrouped members: ${scene.memberPlayerIds.join(", ") || "none"}`);
144
+ }
145
+ }
146
+ verifyGrouping(scene, snapshot, log) {
147
+ const household = snapshot.households.find((item) => item.id === scene.householdId);
148
+ if (!household) {
149
+ log("warn", "Verification skipped because the household is no longer present.");
150
+ return;
151
+ }
152
+ const group = household.groups.find((item) => item.coordinatorId === scene.coordinatorPlayerId)
153
+ ?? household.groups.find((item) => item.playerIds.includes(scene.coordinatorPlayerId));
154
+ if (!group) {
155
+ log("warn", "Verification could not find the coordinator group after execution.");
156
+ return;
157
+ }
158
+ const expectedMembers = new Set([scene.coordinatorPlayerId, ...scene.memberPlayerIds]);
159
+ const actualMembers = new Set(group.playerIds);
160
+ const missing = Array.from(expectedMembers).filter((playerId) => !actualMembers.has(playerId));
161
+ if (missing.length > 0) {
162
+ log("warn", `Post-run verification is missing grouped members: ${missing.join(", ")}`);
163
+ return;
164
+ }
165
+ log("debug", `Post-run verification passed for group ${group.id}.`);
166
+ }
167
+ async withRetry(scene, label, action) {
168
+ let lastError;
169
+ for (let attempt = 0; attempt <= scene.retryCount; attempt++) {
170
+ try {
171
+ await action();
172
+ return;
173
+ }
174
+ catch (error) {
175
+ lastError = error;
176
+ if (attempt >= scene.retryCount) {
177
+ break;
178
+ }
179
+ await sleep(scene.retryDelayMs);
180
+ }
181
+ }
182
+ throw new Error(`${label} failed after ${scene.retryCount + 1} attempt(s): ${lastError instanceof Error ? lastError.message : String(lastError)}`);
183
+ }
184
+ }
185
+ exports.SceneRunner = SceneRunner;
186
+ //# sourceMappingURL=sceneRunner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sceneRunner.js","sourceRoot":"","sources":["../../src/sceneRunner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { ScenesPlatformConfig, SonosTransport } from "../types";
2
+ export declare function createTransport(configInput: Partial<ScenesPlatformConfig> | undefined): SonosTransport;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTransport = createTransport;
4
+ const config_1 = require("../config");
5
+ const localTransport_1 = require("./localTransport");
6
+ function createTransport(configInput) {
7
+ const config = (0, config_1.normalizePlatformConfig)(configInput);
8
+ if (config.transport.kind === "local") {
9
+ return new localTransport_1.LocalSonosTransport(config.transport);
10
+ }
11
+ throw new Error(`Unsupported transport kind: ${config.transport.kind ?? "unknown"}`);
12
+ }
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/transports/index.ts"],"names":[],"mappings":";;AAIA,0CAQC;AAZD,sCAAoD;AAEpD,qDAAuD;AAEvD,SAAgB,eAAe,CAAC,WAAsD;IACpF,MAAM,MAAM,GAAG,IAAA,gCAAuB,EAAC,WAAW,CAAC,CAAC;IAEpD,IAAI,MAAM,CAAC,SAAS,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACtC,OAAO,IAAI,oCAAmB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,+BAAgC,MAAM,CAAC,SAA+B,CAAC,IAAI,IAAI,SAAS,EAAE,CAAC,CAAC;AAC9G,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { LocalTransportConfig, SceneSourceKind, SonosTransport, TopologySnapshot } from "../types";
2
+ export declare class LocalSonosTransport implements SonosTransport {
3
+ private readonly config;
4
+ readonly kind = "local";
5
+ private livePlayers;
6
+ private fixtureState;
7
+ private fixtureLoaded;
8
+ private householdRoots;
9
+ private lastSnapshot;
10
+ constructor(config: LocalTransportConfig);
11
+ supportsSource(kind: SceneSourceKind): boolean;
12
+ discoverHouseholds(): Promise<{
13
+ id: string;
14
+ displayName: string;
15
+ }[]>;
16
+ discoverTopology(): Promise<TopologySnapshot>;
17
+ setGroupMembers(householdId: string, coordinatorPlayerId: string, memberPlayerIds: string[]): Promise<void>;
18
+ modifyGroupMembers(householdId: string, coordinatorPlayerId: string, membersToAdd: string[], membersToRemove: string[]): Promise<void>;
19
+ loadLineIn(householdId: string, coordinatorPlayerId: string, deviceId: string, playOnCompletion?: boolean): Promise<void>;
20
+ loadFavorite(householdId: string, coordinatorPlayerId: string, favoriteId: string): Promise<void>;
21
+ loadTv(householdId: string, coordinatorPlayerId: string, deviceId: string, playOnCompletion?: boolean): Promise<void>;
22
+ setGroupVolume(householdId: string, coordinatorPlayerId: string, volume: number): Promise<void>;
23
+ setPlayerVolume(householdId: string, playerId: string, volume: number): Promise<void>;
24
+ ungroup(householdId: string, coordinatorPlayerId: string, memberPlayerIds?: string[]): Promise<void>;
25
+ private tryLiveDiscovery;
26
+ private loadFixtureSnapshot;
27
+ private requireHousehold;
28
+ private requirePlayer;
29
+ private requireLiveRecord;
30
+ private findFavorite;
31
+ private setFixtureGroupMembers;
32
+ private touchFixture;
33
+ }