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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 applemanj
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # homebridge-sonos-scenes
2
+
3
+ `homebridge-sonos-scenes` is a Homebridge plugin scaffold for Sonos workflow scenes.
4
+
5
+ The goal is not general Sonos control. The goal is a clean way to trigger multi-step Sonos workflows from Apple Home, such as:
6
+
7
+ - grouping rooms around a coordinator,
8
+ - selecting `line_in`, `favorite`, or transport-gated `tv` sources,
9
+ - setting coordinator and per-room volume,
10
+ - optionally ungrouping when the switch turns off.
11
+
12
+ ## What Is Implemented
13
+
14
+ - A singular Homebridge dynamic platform plugin with switch accessories for each configured scene.
15
+ - A normalized scene model that stores stable Sonos IDs instead of room-name strings.
16
+ - A local-first `SonosTransport` abstraction with live discovery through the `sonos` package and fixture fallback for UI/testing.
17
+ - A `SceneRunner` that validates scenes, serializes execution per coordinator, retries transient failures, and emits structured logs.
18
+ - A Homebridge custom UI under `homebridge-ui/` with discovery, validation, test execution, and scene editing.
19
+ - Sample fixture data and config examples for development without a live Sonos system.
20
+ - Unit tests covering config normalization, validation, retry behavior, and off-action execution.
21
+
22
+ ## Project Layout
23
+
24
+ ```text
25
+ src/
26
+ accessories/sceneSwitch.ts
27
+ config.ts
28
+ discoveryService.ts
29
+ index.ts
30
+ logger.ts
31
+ platform.ts
32
+ sampleTopology.ts
33
+ sceneRunner.ts
34
+ transports/
35
+ ui/serverApi.ts
36
+ homebridge-ui/
37
+ public/index.html
38
+ server.js
39
+ examples/
40
+ config.example.json
41
+ sample-topology.json
42
+ test/
43
+ ```
44
+
45
+ ## Running It
46
+
47
+ ```bash
48
+ npm install
49
+ npm run build
50
+ npm test
51
+ ```
52
+
53
+ For Homebridge development, point the plugin at a config shaped like [examples/config.example.json](examples/config.example.json).
54
+
55
+ ## Official Homebridge Test Path
56
+
57
+ If you want to test it the normal Homebridge way, publish it to npm and install it from Homebridge by package name.
58
+
59
+ This repo is set up for that flow now:
60
+
61
+ - `prepare` builds `dist/` for Git-based installs.
62
+ - `prepack` rebuilds `dist/` before `npm publish` so the published tarball contains runnable plugin files.
63
+ - the published package includes `dist/`, `homebridge-ui/`, `config.schema.json`, and the example/docs assets listed in `package.json`.
64
+
65
+ Typical release flow:
66
+
67
+ ```bash
68
+ npm login
69
+ npm test
70
+ npm publish
71
+ ```
72
+
73
+ After that, install `homebridge-sonos-scenes` from the Homebridge UI or via npm in the normal Homebridge plugin workflow.
74
+
75
+ The Homebridge project’s verified-plugin guidance also expects the plugin to be published to npm with source available on GitHub:
76
+
77
+ - Homebridge Verified Plugins: <https://github.com/homebridge/homebridge/wiki/verified-Plugins>
78
+
79
+ ## Transport Notes
80
+
81
+ - The default transport is `local`.
82
+ - Live discovery uses the community `sonos` package for local network discovery and control.
83
+ - If live discovery fails, the plugin falls back to the configured fixture file or the built-in sample topology so the custom UI remains usable during development.
84
+ - `tv` source loading is disabled by default and remains transport-gated.
85
+ - Favorites rely on URIs exposed through the local transport; some complex favorite types may need more transport-specific handling later.
86
+
87
+ ## Official Sonos References
88
+
89
+ These docs informed the product boundaries and future cloud-adapter shape:
90
+
91
+ - Sonos Connected Home: <https://docs.sonos.com/docs/connected-home-get-started>
92
+ - Sonos Authorize: <https://docs.sonos.com/docs/authorize>
93
+ - Sonos Discover: <https://docs.sonos.com/docs/discover>
94
+ - Sonos Control: <https://docs.sonos.com/docs/control>
95
+ - Sonos Subscribe: <https://docs.sonos.com/docs/subscribe>
96
+ - Sonos Features: <https://docs.sonos.com/docs/connected-home-features>
97
+ - Homebridge plugin-ui-utils: <https://github.com/homebridge/plugin-ui-utils>
98
+
99
+ ## Current Gaps
100
+
101
+ - The local transport is MVP-level and best-effort for advanced favorites and TV input handling.
102
+ - Subscription-driven refresh is still represented by the transport abstraction, but not yet fully wired for live event propagation.
103
+ - `npm audit` currently reports a high-severity advisory in the transitive `ip` dependency pulled in by `sonos`.
@@ -0,0 +1,86 @@
1
+ {
2
+ "pluginAlias": "SonosScenes",
3
+ "pluginType": "platform",
4
+ "singular": true,
5
+ "customUi": true,
6
+ "headerDisplay": "Discovery-driven Sonos workflow scenes for Homebridge.",
7
+ "footerDisplay": "Use the custom UI to discover players, validate scenes, and test execution.",
8
+ "schema": {
9
+ "type": "object",
10
+ "properties": {
11
+ "name": {
12
+ "title": "Name",
13
+ "type": "string",
14
+ "default": "Sonos Scenes",
15
+ "required": true
16
+ },
17
+ "platform": {
18
+ "title": "Platform",
19
+ "type": "string",
20
+ "default": "SonosScenes",
21
+ "required": true
22
+ },
23
+ "logLevel": {
24
+ "title": "Log Level",
25
+ "type": "string",
26
+ "default": "info",
27
+ "oneOf": [
28
+ "debug",
29
+ "info",
30
+ "warn",
31
+ "error"
32
+ ]
33
+ },
34
+ "defaultHouseholdId": {
35
+ "title": "Default Household Id",
36
+ "type": "string"
37
+ },
38
+ "transport": {
39
+ "title": "Transport",
40
+ "type": "object",
41
+ "properties": {
42
+ "kind": {
43
+ "title": "Kind",
44
+ "type": "string",
45
+ "default": "local",
46
+ "oneOf": [
47
+ "local"
48
+ ]
49
+ },
50
+ "enableLiveDiscovery": {
51
+ "title": "Enable Live Discovery",
52
+ "type": "boolean",
53
+ "default": true
54
+ },
55
+ "fixturePath": {
56
+ "title": "Fixture Path",
57
+ "type": "string"
58
+ },
59
+ "discoveryTimeoutMs": {
60
+ "title": "Discovery Timeout (ms)",
61
+ "type": "integer",
62
+ "default": 2500
63
+ },
64
+ "requestTimeoutMs": {
65
+ "title": "Request Timeout (ms)",
66
+ "type": "integer",
67
+ "default": 5000
68
+ },
69
+ "allowTvSource": {
70
+ "title": "Allow TV Source",
71
+ "type": "boolean",
72
+ "default": false
73
+ }
74
+ }
75
+ },
76
+ "scenes": {
77
+ "title": "Scenes",
78
+ "type": "array",
79
+ "items": {
80
+ "type": "object"
81
+ },
82
+ "default": []
83
+ }
84
+ }
85
+ }
86
+ }
@@ -0,0 +1,17 @@
1
+ import type { PlatformAccessory } from "homebridge";
2
+ import type { SceneDefinition } from "../types";
3
+ import type { SonosScenesPlatform } from "../platform";
4
+ export declare class SceneSwitchAccessory {
5
+ private readonly platform;
6
+ private readonly accessory;
7
+ private service;
8
+ private scene;
9
+ private onState;
10
+ private resetTimer?;
11
+ constructor(platform: SonosScenesPlatform, accessory: PlatformAccessory, scene: SceneDefinition);
12
+ updateScene(scene: SceneDefinition): void;
13
+ private handleOnGet;
14
+ private handleOnSet;
15
+ private armAutoReset;
16
+ private clearAutoReset;
17
+ }
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SceneSwitchAccessory = void 0;
4
+ class SceneSwitchAccessory {
5
+ platform;
6
+ accessory;
7
+ service;
8
+ scene;
9
+ onState = false;
10
+ resetTimer;
11
+ constructor(platform, accessory, scene) {
12
+ this.platform = platform;
13
+ this.accessory = accessory;
14
+ this.scene = scene;
15
+ this.accessory.context.sceneId = scene.id;
16
+ this.accessory.displayName = scene.name;
17
+ this.service =
18
+ this.accessory.getService(this.platform.Service.Switch) ?? this.accessory.addService(this.platform.Service.Switch);
19
+ this.service.setCharacteristic(this.platform.Characteristic.Name, scene.name);
20
+ this.service.getCharacteristic(this.platform.Characteristic.On)
21
+ .onGet(this.handleOnGet.bind(this))
22
+ .onSet(this.handleOnSet.bind(this));
23
+ const accessoryInformation = this.accessory.getService(this.platform.Service.AccessoryInformation)
24
+ ?? this.accessory.addService(this.platform.Service.AccessoryInformation);
25
+ accessoryInformation
26
+ .setCharacteristic(this.platform.Characteristic.Manufacturer, "homebridge-sonos-scenes")
27
+ .setCharacteristic(this.platform.Characteristic.Model, "Sonos Scene Switch")
28
+ .setCharacteristic(this.platform.Characteristic.SerialNumber, scene.id);
29
+ }
30
+ updateScene(scene) {
31
+ this.scene = scene;
32
+ this.accessory.context.sceneId = scene.id;
33
+ this.accessory.displayName = scene.name;
34
+ this.service.setCharacteristic(this.platform.Characteristic.Name, scene.name);
35
+ }
36
+ handleOnGet() {
37
+ return this.onState;
38
+ }
39
+ async handleOnSet(value) {
40
+ const nextState = value === true;
41
+ if (nextState) {
42
+ const result = await this.platform.runScene(this.scene.id, "on");
43
+ if (!result.ok) {
44
+ this.onState = false;
45
+ this.service.updateCharacteristic(this.platform.Characteristic.On, false);
46
+ throw new Error(result.errors.join(" "));
47
+ }
48
+ this.onState = true;
49
+ this.service.updateCharacteristic(this.platform.Characteristic.On, true);
50
+ this.armAutoReset();
51
+ return;
52
+ }
53
+ const result = await this.platform.runScene(this.scene.id, "off");
54
+ if (!result.ok) {
55
+ throw new Error(result.errors.join(" "));
56
+ }
57
+ this.clearAutoReset();
58
+ this.onState = false;
59
+ this.service.updateCharacteristic(this.platform.Characteristic.On, false);
60
+ }
61
+ armAutoReset() {
62
+ this.clearAutoReset();
63
+ if (this.scene.autoResetMs <= 0) {
64
+ return;
65
+ }
66
+ this.resetTimer = setTimeout(() => {
67
+ this.onState = false;
68
+ this.service.updateCharacteristic(this.platform.Characteristic.On, false);
69
+ this.resetTimer = undefined;
70
+ }, this.scene.autoResetMs);
71
+ }
72
+ clearAutoReset() {
73
+ if (!this.resetTimer) {
74
+ return;
75
+ }
76
+ clearTimeout(this.resetTimer);
77
+ this.resetTimer = undefined;
78
+ }
79
+ }
80
+ exports.SceneSwitchAccessory = SceneSwitchAccessory;
81
+ //# sourceMappingURL=sceneSwitch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sceneSwitch.js","sourceRoot":"","sources":["../../../src/accessories/sceneSwitch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { HouseholdSnapshot, SceneDefinition, SceneSourceKind, ScenesPlatformConfig, SonosTransport, TopologySnapshot, ValidationResult } from "./types";
2
+ export declare function createDefaultPlatformConfig(): ScenesPlatformConfig;
3
+ export declare function normalizeScene(scene: Partial<SceneDefinition>): SceneDefinition;
4
+ export declare function normalizePlatformConfig(config: Partial<ScenesPlatformConfig> | undefined): ScenesPlatformConfig;
5
+ export declare function findHousehold(snapshot: TopologySnapshot, householdId: string): HouseholdSnapshot | undefined;
6
+ export declare function validateSceneDefinition(sceneInput: Partial<SceneDefinition>, snapshot: TopologySnapshot, transport: SonosTransport): ValidationResult;
7
+ export declare function transportSourceKinds(transport: SonosTransport): SceneSourceKind[];
@@ -0,0 +1,256 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createDefaultPlatformConfig = createDefaultPlatformConfig;
4
+ exports.normalizeScene = normalizeScene;
5
+ exports.normalizePlatformConfig = normalizePlatformConfig;
6
+ exports.findHousehold = findHousehold;
7
+ exports.validateSceneDefinition = validateSceneDefinition;
8
+ exports.transportSourceKinds = transportSourceKinds;
9
+ const node_crypto_1 = require("node:crypto");
10
+ const types_1 = require("./types");
11
+ const DEFAULT_TRANSPORT = {
12
+ kind: "local",
13
+ enableLiveDiscovery: true,
14
+ discoveryTimeoutMs: 2500,
15
+ requestTimeoutMs: 5000,
16
+ allowTvSource: false,
17
+ };
18
+ function createDefaultPlatformConfig() {
19
+ return {
20
+ platform: types_1.PLATFORM_NAME,
21
+ name: "Sonos Scenes",
22
+ logLevel: "info",
23
+ transport: { ...DEFAULT_TRANSPORT },
24
+ scenes: [],
25
+ };
26
+ }
27
+ function slugify(value) {
28
+ const slug = value
29
+ .toLowerCase()
30
+ .replace(/[^a-z0-9]+/g, "-")
31
+ .replace(/^-+|-+$/g, "");
32
+ return slug || `scene-${(0, node_crypto_1.randomUUID)().slice(0, 8)}`;
33
+ }
34
+ function asNumber(value, fallback) {
35
+ if (typeof value === "number" && Number.isFinite(value)) {
36
+ return value;
37
+ }
38
+ if (typeof value === "string" && value.trim().length > 0) {
39
+ const parsed = Number(value);
40
+ if (Number.isFinite(parsed)) {
41
+ return parsed;
42
+ }
43
+ }
44
+ return fallback;
45
+ }
46
+ function clampVolume(value) {
47
+ if (value === undefined) {
48
+ return undefined;
49
+ }
50
+ return Math.max(0, Math.min(100, Math.round(value)));
51
+ }
52
+ function normalizeOffBehavior(value) {
53
+ if (typeof value === "object" && value !== null && "kind" in value && value.kind === "ungroup") {
54
+ return { kind: "ungroup" };
55
+ }
56
+ return { kind: "none" };
57
+ }
58
+ function normalizeSource(value) {
59
+ if (!value || typeof value !== "object" || !("kind" in value)) {
60
+ return undefined;
61
+ }
62
+ const input = value;
63
+ if (input.kind === "favorite") {
64
+ const favoriteId = String(input.favoriteId ?? "").trim();
65
+ if (!favoriteId) {
66
+ return undefined;
67
+ }
68
+ return {
69
+ kind: "favorite",
70
+ favoriteId,
71
+ favoriteName: typeof input.favoriteName === "string" ? input.favoriteName : undefined,
72
+ };
73
+ }
74
+ if (input.kind === "line_in") {
75
+ const deviceId = String(input.deviceId ?? "").trim();
76
+ if (!deviceId) {
77
+ return undefined;
78
+ }
79
+ return {
80
+ kind: "line_in",
81
+ deviceId,
82
+ deviceName: typeof input.deviceName === "string" ? input.deviceName : undefined,
83
+ playOnCompletion: input.playOnCompletion !== false,
84
+ };
85
+ }
86
+ if (input.kind === "tv") {
87
+ const deviceId = String(input.deviceId ?? "").trim();
88
+ if (!deviceId) {
89
+ return undefined;
90
+ }
91
+ return {
92
+ kind: "tv",
93
+ deviceId,
94
+ deviceName: typeof input.deviceName === "string" ? input.deviceName : undefined,
95
+ playOnCompletion: input.playOnCompletion !== false,
96
+ };
97
+ }
98
+ return undefined;
99
+ }
100
+ function normalizeScene(scene) {
101
+ const sceneName = String(scene.name ?? "New Scene").trim() || "New Scene";
102
+ return {
103
+ id: String(scene.id ?? "").trim() || slugify(sceneName),
104
+ name: sceneName,
105
+ householdId: String(scene.householdId ?? "").trim(),
106
+ coordinatorPlayerId: String(scene.coordinatorPlayerId ?? "").trim(),
107
+ memberPlayerIds: Array.isArray(scene.memberPlayerIds)
108
+ ? scene.memberPlayerIds.map((playerId) => String(playerId).trim()).filter(Boolean)
109
+ : [],
110
+ source: normalizeSource(scene.source),
111
+ coordinatorVolume: clampVolume(scene.coordinatorVolume === undefined ? undefined : asNumber(scene.coordinatorVolume, 0)),
112
+ playerVolumes: Array.isArray(scene.playerVolumes)
113
+ ? Array.from(new Map(scene.playerVolumes
114
+ .filter((volume) => volume && typeof volume.playerId === "string")
115
+ .map((volume) => [
116
+ volume.playerId,
117
+ {
118
+ playerId: String(volume.playerId).trim(),
119
+ volume: Math.max(0, Math.min(100, Math.round(asNumber(volume.volume, 0)))),
120
+ },
121
+ ])).values()).filter((volume) => volume.playerId.length > 0)
122
+ : [],
123
+ offBehavior: normalizeOffBehavior(scene.offBehavior),
124
+ settleMs: Math.max(0, asNumber(scene.settleMs, 750)),
125
+ retryCount: Math.max(0, asNumber(scene.retryCount, 3)),
126
+ retryDelayMs: Math.max(0, asNumber(scene.retryDelayMs, 750)),
127
+ autoResetMs: Math.max(0, asNumber(scene.autoResetMs, 1000)),
128
+ };
129
+ }
130
+ function normalizePlatformConfig(config) {
131
+ const defaults = createDefaultPlatformConfig();
132
+ const transport = {
133
+ ...DEFAULT_TRANSPORT,
134
+ ...(config?.transport ?? {}),
135
+ kind: "local",
136
+ };
137
+ return {
138
+ ...defaults,
139
+ ...config,
140
+ platform: types_1.PLATFORM_NAME,
141
+ name: String(config?.name ?? defaults.name).trim() || defaults.name,
142
+ logLevel: config?.logLevel === "debug" ||
143
+ config?.logLevel === "info" ||
144
+ config?.logLevel === "warn" ||
145
+ config?.logLevel === "error"
146
+ ? config.logLevel
147
+ : defaults.logLevel,
148
+ defaultHouseholdId: typeof config?.defaultHouseholdId === "string" ? config.defaultHouseholdId : undefined,
149
+ transport,
150
+ scenes: Array.isArray(config?.scenes) ? config.scenes.map((scene) => normalizeScene(scene)) : [],
151
+ };
152
+ }
153
+ function findHousehold(snapshot, householdId) {
154
+ return snapshot.households.find((household) => household.id === householdId);
155
+ }
156
+ function playerName(household, playerId) {
157
+ return household.players.find((player) => player.id === playerId)?.name ?? playerId;
158
+ }
159
+ function validateVolumes(scene, household, result) {
160
+ const referenced = new Set(household.players.map((player) => player.id));
161
+ if (scene.coordinatorVolume !== undefined && (scene.coordinatorVolume < 0 || scene.coordinatorVolume > 100)) {
162
+ result.errors.push("Coordinator volume must be between 0 and 100.");
163
+ }
164
+ for (const item of scene.playerVolumes) {
165
+ if (!referenced.has(item.playerId)) {
166
+ result.errors.push(`Per-room volume references an unknown player: ${item.playerId}.`);
167
+ }
168
+ if (item.volume < 0 || item.volume > 100) {
169
+ result.errors.push(`Volume for ${item.playerId} must be between 0 and 100.`);
170
+ }
171
+ }
172
+ }
173
+ function validateSource(scene, household, transport, result) {
174
+ const source = scene.source;
175
+ if (!source) {
176
+ return;
177
+ }
178
+ if (!transport.supportsSource(source.kind)) {
179
+ result.errors.push(`The active transport does not support source kind "${source.kind}".`);
180
+ return;
181
+ }
182
+ if (source.kind === "favorite") {
183
+ const favorite = household.favorites.find((item) => item.id === source.favoriteId);
184
+ if (!favorite) {
185
+ result.errors.push(`Favorite "${source.favoriteId}" was not found in household "${household.displayName}".`);
186
+ }
187
+ return;
188
+ }
189
+ const device = household.players.find((player) => player.id === source.deviceId);
190
+ if (!device) {
191
+ result.errors.push(`Source device "${source.deviceId}" was not found in household "${household.displayName}".`);
192
+ return;
193
+ }
194
+ if (!device.sourceOptions.includes(source.kind)) {
195
+ result.errors.push(`${device.name} does not advertise ${source.kind} as an available source.`);
196
+ }
197
+ }
198
+ function validateSceneDefinition(sceneInput, snapshot, transport) {
199
+ const scene = normalizeScene(sceneInput);
200
+ const result = {
201
+ valid: true,
202
+ errors: [],
203
+ warnings: [],
204
+ };
205
+ if (!scene.name.trim()) {
206
+ result.errors.push("Scene name is required.");
207
+ }
208
+ if (!scene.householdId.trim()) {
209
+ result.errors.push("Household selection is required.");
210
+ }
211
+ const household = findHousehold(snapshot, scene.householdId);
212
+ if (!household) {
213
+ result.errors.push(`Household "${scene.householdId}" was not found in the current topology.`);
214
+ }
215
+ if (!scene.coordinatorPlayerId.trim()) {
216
+ result.errors.push("Coordinator player selection is required.");
217
+ }
218
+ const duplicateMembers = scene.memberPlayerIds.filter((playerId, index, values) => values.indexOf(playerId) !== index);
219
+ if (duplicateMembers.length > 0) {
220
+ result.errors.push(`Scene members contain duplicates: ${Array.from(new Set(duplicateMembers)).join(", ")}.`);
221
+ }
222
+ if (scene.memberPlayerIds.includes(scene.coordinatorPlayerId)) {
223
+ result.errors.push("The coordinator cannot also appear in the grouped member list.");
224
+ }
225
+ if (household) {
226
+ const coordinator = household.players.find((player) => player.id === scene.coordinatorPlayerId);
227
+ if (!coordinator) {
228
+ result.errors.push(`Coordinator "${scene.coordinatorPlayerId}" was not found in "${household.displayName}".`);
229
+ }
230
+ for (const playerId of scene.memberPlayerIds) {
231
+ if (!household.players.some((player) => player.id === playerId)) {
232
+ result.errors.push(`Member "${playerId}" was not found in "${household.displayName}".`);
233
+ }
234
+ }
235
+ validateVolumes(scene, household, result);
236
+ validateSource(scene, household, transport, result);
237
+ if (scene.memberPlayerIds.length === 0) {
238
+ result.warnings.push("The scene has no member rooms selected, so only the coordinator room will be targeted.");
239
+ }
240
+ if (scene.offBehavior.kind === "ungroup" && scene.memberPlayerIds.length === 0) {
241
+ result.warnings.push("Off behavior is set to ungroup, but the scene does not include any grouped members.");
242
+ }
243
+ if (scene.coordinatorVolume === undefined && scene.playerVolumes.length === 0) {
244
+ result.warnings.push("No volume changes are configured. The scene will only affect grouping and source selection.");
245
+ }
246
+ if (scene.playerVolumes.some((volume) => volume.playerId === scene.coordinatorPlayerId)) {
247
+ result.warnings.push(`Per-room volume includes the coordinator (${playerName(household, scene.coordinatorPlayerId)}). Coordinator volume already has its own field.`);
248
+ }
249
+ }
250
+ result.valid = result.errors.length === 0;
251
+ return result;
252
+ }
253
+ function transportSourceKinds(transport) {
254
+ return ["favorite", "line_in", "tv"].filter((kind) => transport.supportsSource(kind));
255
+ }
256
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { HouseholdSnapshot, SonosHouseholdSummary, SonosTransport, TopologySnapshot } from "./types";
2
+ export declare class DiscoveryService {
3
+ private readonly transport;
4
+ private lastSnapshot?;
5
+ constructor(transport: SonosTransport);
6
+ refresh(): Promise<TopologySnapshot>;
7
+ getSnapshot(): Promise<TopologySnapshot>;
8
+ getHouseholds(): Promise<SonosHouseholdSummary[]>;
9
+ findHousehold(snapshot: TopologySnapshot, householdId: string): HouseholdSnapshot | undefined;
10
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DiscoveryService = void 0;
4
+ class DiscoveryService {
5
+ transport;
6
+ lastSnapshot;
7
+ constructor(transport) {
8
+ this.transport = transport;
9
+ }
10
+ async refresh() {
11
+ this.lastSnapshot = await this.transport.discoverTopology();
12
+ return this.lastSnapshot;
13
+ }
14
+ async getSnapshot() {
15
+ if (!this.lastSnapshot) {
16
+ return this.refresh();
17
+ }
18
+ return this.lastSnapshot;
19
+ }
20
+ async getHouseholds() {
21
+ const snapshot = await this.getSnapshot();
22
+ return snapshot.households.map((household) => ({
23
+ id: household.id,
24
+ displayName: household.displayName,
25
+ }));
26
+ }
27
+ findHousehold(snapshot, householdId) {
28
+ return snapshot.households.find((household) => household.id === householdId);
29
+ }
30
+ }
31
+ exports.DiscoveryService = DiscoveryService;
32
+ //# sourceMappingURL=discoveryService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discoveryService.js","sourceRoot":"","sources":["../../src/discoveryService.ts"],"names":[],"mappings":";;;AAEA,MAAa,gBAAgB;IAGE;IAFrB,YAAY,CAAoB;IAExC,YAA6B,SAAyB;QAAzB,cAAS,GAAT,SAAS,CAAgB;IAAG,CAAC;IAE1D,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,YAAY,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,gBAAgB,EAAE,CAAC;QAC5D,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,WAAW;QACf,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;QACxB,CAAC;QAED,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;QAC1C,OAAO,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;YAC7C,EAAE,EAAE,SAAS,CAAC,EAAE;YAChB,WAAW,EAAE,SAAS,CAAC,WAAW;SACnC,CAAC,CAAC,CAAC;IACN,CAAC;IAED,aAAa,CAAC,QAA0B,EAAE,WAAmB;QAC3D,OAAO,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,WAAW,CAAC,CAAC;IAC/E,CAAC;CACF;AA7BD,4CA6BC"}
@@ -0,0 +1,3 @@
1
+ import type { API } from "homebridge";
2
+ declare const _default: (api: API) => void;
3
+ export = _default;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ const platform_1 = require("./platform");
3
+ module.exports = (api) => {
4
+ api.registerPlatform(platform_1.PLUGIN_NAME, platform_1.PLATFORM_NAME, platform_1.SonosScenesPlatform);
5
+ };
6
+ //# sourceMappingURL=index.js.map