homebridge-sonos-scenes 0.1.8 → 0.1.9

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/README.md CHANGED
@@ -20,7 +20,7 @@ The goal is not general Sonos control. The goal is a clean way to trigger multi-
20
20
 
21
21
  ## What Is Implemented
22
22
 
23
- - A singular Homebridge dynamic platform plugin with switch accessories for each configured scene.
23
+ - A singular Homebridge dynamic platform plugin with switch accessories for each configured scene plus companion volume controls for quick level adjustments in Apple Home.
24
24
  - A normalized scene model that stores stable Sonos IDs instead of room-name strings.
25
25
  - A local-first `SonosTransport` abstraction with live discovery through the `sonos` package and fixture fallback for UI/testing.
26
26
  - A `SceneRunner` that validates scenes, serializes execution per coordinator, retries transient failures, and emits structured logs.
@@ -7,12 +7,13 @@ export declare class SceneSpeakerAccessory {
7
7
  private service;
8
8
  private scene;
9
9
  private lastKnownVolume;
10
+ private lastKnownActiveVolume;
10
11
  private lastKnownMuted;
11
12
  constructor(platform: SonosScenesPlatform, accessory: PlatformAccessory, scene: SceneDefinition);
12
13
  updateScene(scene: SceneDefinition): void;
13
14
  private displayNameFor;
14
- private handleVolumeGet;
15
- private handleVolumeSet;
16
- private handleMuteGet;
17
- private handleMuteSet;
15
+ private handleBrightnessGet;
16
+ private handleBrightnessSet;
17
+ private handleOnGet;
18
+ private handleOnSet;
18
19
  }
@@ -7,76 +7,122 @@ class SceneSpeakerAccessory {
7
7
  service;
8
8
  scene;
9
9
  lastKnownVolume;
10
+ lastKnownActiveVolume;
10
11
  lastKnownMuted = false;
11
12
  constructor(platform, accessory, scene) {
12
13
  this.platform = platform;
13
14
  this.accessory = accessory;
14
15
  this.scene = scene;
15
- this.lastKnownVolume = scene.coordinatorVolume ?? 0;
16
+ this.lastKnownVolume = scene.coordinatorVolume ?? 30;
17
+ this.lastKnownActiveVolume = this.lastKnownVolume > 0 ? this.lastKnownVolume : 30;
16
18
  this.accessory.context.sceneId = scene.id;
17
- this.accessory.context.kind = "speaker";
19
+ this.accessory.context.kind = "volume";
18
20
  this.accessory.displayName = this.displayNameFor(scene);
19
- this.accessory.category = 26 /* this.platform.api.hap.Categories.SPEAKER */;
21
+ this.accessory.category = 5 /* this.platform.api.hap.Categories.LIGHTBULB */;
22
+ const legacySpeakerService = this.accessory.getService(this.platform.Service.Speaker);
23
+ if (legacySpeakerService) {
24
+ this.accessory.removeService(legacySpeakerService);
25
+ }
20
26
  this.service =
21
- this.accessory.getService(this.platform.Service.Speaker)
22
- ?? this.accessory.addService(this.platform.Service.Speaker);
23
- if (!this.service.testCharacteristic(this.platform.Characteristic.Volume)) {
24
- this.service.addCharacteristic(this.platform.Characteristic.Volume);
27
+ this.accessory.getService(this.platform.Service.Lightbulb)
28
+ ?? this.accessory.addService(this.platform.Service.Lightbulb);
29
+ if (!this.service.testCharacteristic(this.platform.Characteristic.Brightness)) {
30
+ this.service.addCharacteristic(this.platform.Characteristic.Brightness);
25
31
  }
26
32
  this.service.setCharacteristic(this.platform.Characteristic.Name, this.displayNameFor(scene));
27
- this.service.getCharacteristic(this.platform.Characteristic.Mute)
28
- .onGet(this.handleMuteGet.bind(this))
29
- .onSet(this.handleMuteSet.bind(this));
30
- this.service.getCharacteristic(this.platform.Characteristic.Volume)
31
- .onGet(this.handleVolumeGet.bind(this))
32
- .onSet(this.handleVolumeSet.bind(this));
33
+ this.service.getCharacteristic(this.platform.Characteristic.On)
34
+ .onGet(this.handleOnGet.bind(this))
35
+ .onSet(this.handleOnSet.bind(this));
36
+ this.service.getCharacteristic(this.platform.Characteristic.Brightness)
37
+ .onGet(this.handleBrightnessGet.bind(this))
38
+ .onSet(this.handleBrightnessSet.bind(this));
33
39
  const accessoryInformation = this.accessory.getService(this.platform.Service.AccessoryInformation)
34
40
  ?? this.accessory.addService(this.platform.Service.AccessoryInformation);
35
41
  accessoryInformation
36
42
  .setCharacteristic(this.platform.Characteristic.Manufacturer, "homebridge-sonos-scenes")
37
- .setCharacteristic(this.platform.Characteristic.Model, "Sonos Scene Speaker")
38
- .setCharacteristic(this.platform.Characteristic.SerialNumber, `${scene.id}:speaker`);
43
+ .setCharacteristic(this.platform.Characteristic.Model, "Sonos Scene Volume Dimmer")
44
+ .setCharacteristic(this.platform.Characteristic.SerialNumber, `${scene.id}:volume`);
39
45
  }
40
46
  updateScene(scene) {
41
47
  this.scene = scene;
42
- this.lastKnownVolume = scene.coordinatorVolume ?? this.lastKnownVolume;
48
+ const configuredVolume = scene.coordinatorVolume;
49
+ if (configuredVolume !== undefined) {
50
+ this.lastKnownVolume = configuredVolume;
51
+ if (configuredVolume > 0) {
52
+ this.lastKnownActiveVolume = configuredVolume;
53
+ }
54
+ }
43
55
  this.accessory.context.sceneId = scene.id;
44
- this.accessory.context.kind = "speaker";
56
+ this.accessory.context.kind = "volume";
45
57
  this.accessory.displayName = this.displayNameFor(scene);
46
58
  this.service.setCharacteristic(this.platform.Characteristic.Name, this.displayNameFor(scene));
47
59
  }
48
60
  displayNameFor(scene) {
49
61
  return `${scene.name} Volume`;
50
62
  }
51
- async handleVolumeGet() {
63
+ async handleBrightnessGet() {
52
64
  try {
53
65
  this.lastKnownVolume = await this.platform.getSceneVolume(this.scene.id);
66
+ if (this.lastKnownVolume > 0) {
67
+ this.lastKnownActiveVolume = this.lastKnownVolume;
68
+ }
54
69
  }
55
70
  catch {
56
71
  void 0;
57
72
  }
58
73
  return this.lastKnownVolume;
59
74
  }
60
- async handleVolumeSet(value) {
75
+ async handleBrightnessSet(value) {
61
76
  const nextVolume = Math.max(0, Math.min(100, Math.round(Number(value))));
62
77
  await this.platform.setSceneVolume(this.scene.id, nextVolume);
63
78
  this.lastKnownVolume = nextVolume;
64
- this.service.updateCharacteristic(this.platform.Characteristic.Volume, nextVolume);
79
+ this.service.updateCharacteristic(this.platform.Characteristic.Brightness, nextVolume);
80
+ if (nextVolume > 0) {
81
+ this.lastKnownActiveVolume = nextVolume;
82
+ if (this.lastKnownMuted) {
83
+ await this.platform.setSceneMuted(this.scene.id, false);
84
+ this.lastKnownMuted = false;
85
+ }
86
+ this.service.updateCharacteristic(this.platform.Characteristic.On, true);
87
+ return;
88
+ }
89
+ await this.platform.setSceneMuted(this.scene.id, true);
90
+ this.lastKnownMuted = true;
91
+ this.service.updateCharacteristic(this.platform.Characteristic.On, false);
65
92
  }
66
- async handleMuteGet() {
93
+ async handleOnGet() {
67
94
  try {
68
95
  this.lastKnownMuted = await this.platform.getSceneMuted(this.scene.id);
96
+ this.lastKnownVolume = await this.platform.getSceneVolume(this.scene.id);
97
+ if (this.lastKnownVolume > 0) {
98
+ this.lastKnownActiveVolume = this.lastKnownVolume;
99
+ }
69
100
  }
70
101
  catch {
71
102
  void 0;
72
103
  }
73
- return this.lastKnownMuted;
104
+ return !this.lastKnownMuted && this.lastKnownVolume > 0;
74
105
  }
75
- async handleMuteSet(value) {
76
- const nextMuted = value === true;
77
- await this.platform.setSceneMuted(this.scene.id, nextMuted);
78
- this.lastKnownMuted = nextMuted;
79
- this.service.updateCharacteristic(this.platform.Characteristic.Mute, nextMuted);
106
+ async handleOnSet(value) {
107
+ const nextOn = value === true;
108
+ if (nextOn) {
109
+ const restoredVolume = this.lastKnownVolume > 0 ? this.lastKnownVolume : this.lastKnownActiveVolume;
110
+ if (this.lastKnownVolume <= 0 && restoredVolume > 0) {
111
+ await this.platform.setSceneVolume(this.scene.id, restoredVolume);
112
+ this.lastKnownVolume = restoredVolume;
113
+ this.service.updateCharacteristic(this.platform.Characteristic.Brightness, restoredVolume);
114
+ }
115
+ await this.platform.setSceneMuted(this.scene.id, false);
116
+ this.lastKnownMuted = false;
117
+ this.service.updateCharacteristic(this.platform.Characteristic.On, true);
118
+ return;
119
+ }
120
+ if (this.lastKnownVolume > 0) {
121
+ this.lastKnownActiveVolume = this.lastKnownVolume;
122
+ }
123
+ await this.platform.setSceneMuted(this.scene.id, true);
124
+ this.lastKnownMuted = true;
125
+ this.service.updateCharacteristic(this.platform.Characteristic.On, false);
80
126
  }
81
127
  }
82
128
  exports.SceneSpeakerAccessory = SceneSpeakerAccessory;
@@ -1 +1 @@
1
- {"version":3,"file":"sceneSpeaker.js","sourceRoot":"","sources":["../../../src/accessories/sceneSpeaker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sceneSpeaker.js","sourceRoot":"","sources":["../../../src/accessories/sceneSpeaker.ts"],"names":[],"mappings":"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"}
@@ -619,7 +619,7 @@
619
619
  <div class="card-header scene-section-header">
620
620
  <div class="scene-section-header-copy">
621
621
  <strong>Scenes</strong>
622
- <div class="scene-help">Each saved scene becomes a HomeKit switch plus a companion volume speaker.</div>
622
+ <div class="scene-help">Each saved scene becomes a HomeKit switch plus a companion volume control.</div>
623
623
  <div class="scene-inline-note mt-2">Save scene changes in the editor, then use Homebridge&apos;s footer Save button when you&apos;re ready to write everything to `config.json`.</div>
624
624
  </div>
625
625
  <div class="scene-section-actions">
@@ -651,7 +651,7 @@
651
651
  <span>Scene Name</span>
652
652
  <span class="scene-info-tag" tabindex="0" role="button" aria-label="Explain Scene Name">
653
653
  ?
654
- <span class="scene-tooltip">The friendly name shown for this scene in HomeKit. The companion volume speaker uses the same name with "Volume" appended.</span>
654
+ <span class="scene-tooltip">The friendly name shown for this scene in HomeKit. The companion volume control uses the same name with "Volume" appended.</span>
655
655
  </span>
656
656
  </span>
657
657
  </label>
@@ -706,19 +706,7 @@
706
706
  </label>
707
707
  <select class="form-select" id="source-target"></select>
708
708
  </div>
709
- <div class="col-md-3">
710
- <label class="form-label" for="coordinator-volume">
711
- <span class="scene-label-row">
712
- <span>Lead Room Volume</span>
713
- <span class="scene-info-tag" tabindex="0" role="button" aria-label="Explain Coordinator Volume">
714
- ?
715
- <span class="scene-tooltip">Optional starting volume for the scene's lead room. Leave this blank to keep whatever volume that room already has.</span>
716
- </span>
717
- </span>
718
- </label>
719
- <input class="form-control" id="coordinator-volume" type="number" min="0" max="100">
720
- </div>
721
- <div class="col-md-3">
709
+ <div class="col-md-4">
722
710
  <label class="form-label" for="settle-ms">
723
711
  <span class="scene-label-row">
724
712
  <span>Settle Delay (ms)</span>
@@ -730,7 +718,7 @@
730
718
  </label>
731
719
  <input class="form-control" id="settle-ms" type="number" min="0">
732
720
  </div>
733
- <div class="col-md-3">
721
+ <div class="col-md-4">
734
722
  <label class="form-label" for="retry-count">
735
723
  <span class="scene-label-row">
736
724
  <span>Retry Count</span>
@@ -742,7 +730,7 @@
742
730
  </label>
743
731
  <input class="form-control" id="retry-count" type="number" min="0">
744
732
  </div>
745
- <div class="col-md-3">
733
+ <div class="col-md-4">
746
734
  <label class="form-label" for="retry-delay-ms">
747
735
  <span class="scene-label-row">
748
736
  <span>Retry Delay (ms)</span>
@@ -754,7 +742,7 @@
754
742
  </label>
755
743
  <input class="form-control" id="retry-delay-ms" type="number" min="0">
756
744
  </div>
757
- <div class="col-md-4">
745
+ <div class="col-md-6">
758
746
  <label class="form-label" for="auto-reset-ms">
759
747
  <span class="scene-label-row">
760
748
  <span>Auto Reset (ms)</span>
@@ -766,7 +754,7 @@
766
754
  </label>
767
755
  <input class="form-control" id="auto-reset-ms" type="number" min="0">
768
756
  </div>
769
- <div class="col-md-4">
757
+ <div class="col-md-6">
770
758
  <label class="form-label" for="off-behavior">
771
759
  <span class="scene-label-row">
772
760
  <span>Off Behavior</span>
@@ -829,7 +817,6 @@
829
817
  memberList: document.getElementById("member-list"),
830
818
  sourceKind: document.getElementById("source-kind"),
831
819
  sourceTarget: document.getElementById("source-target"),
832
- coordinatorVolume: document.getElementById("coordinator-volume"),
833
820
  settleMs: document.getElementById("settle-ms"),
834
821
  retryCount: document.getElementById("retry-count"),
835
822
  retryDelayMs: document.getElementById("retry-delay-ms"),
@@ -1009,7 +996,8 @@
1009
996
  selectedRoomIds.unshift(draft.source.deviceId);
1010
997
  }
1011
998
  draft.memberPlayerIds = selectedRoomIds.filter((playerId) => playerId !== draft.coordinatorPlayerId);
1012
- draft.coordinatorVolume = elements.coordinatorVolume.value === "" ? undefined : Number(elements.coordinatorVolume.value);
999
+ const leadVolumeInput = elements.memberList.querySelector("input[data-coordinator-volume]");
1000
+ draft.coordinatorVolume = leadVolumeInput && leadVolumeInput.value !== "" ? Number(leadVolumeInput.value) : undefined;
1013
1001
  draft.settleMs = Number(elements.settleMs.value || 0);
1014
1002
  draft.retryCount = Number(elements.retryCount.value || 0);
1015
1003
  draft.retryDelayMs = Number(elements.retryDelayMs.value || 0);
@@ -1204,6 +1192,20 @@
1204
1192
  ${flags ? `<span class="scene-member-flags">${flags}</span>` : ""}
1205
1193
  </span>
1206
1194
  </label>
1195
+ ${isSelected && coordinatorId === player.id ? `
1196
+ <div class="scene-member-volume">
1197
+ <label class="scene-member-volume-label" for="lead-volume-${player.id}">Lead Room Volume</label>
1198
+ <input
1199
+ class="form-control form-control-sm scene-member-volume-input"
1200
+ id="lead-volume-${player.id}"
1201
+ data-coordinator-volume="true"
1202
+ type="number"
1203
+ min="0"
1204
+ max="100"
1205
+ placeholder="Keep current"
1206
+ value="${state.draft.coordinatorVolume ?? ""}">
1207
+ </div>
1208
+ ` : ""}
1207
1209
  ${isSelected && coordinatorId !== player.id ? `
1208
1210
  <div class="scene-member-volume">
1209
1211
  <label class="scene-member-volume-label" for="member-volume-${player.id}">Volume Override</label>
@@ -1397,7 +1399,6 @@
1397
1399
  renderHouseholdOptions();
1398
1400
  renderSourceControls();
1399
1401
  renderMemberOptions();
1400
- elements.coordinatorVolume.value = state.draft.coordinatorVolume === "" ? "" : state.draft.coordinatorVolume ?? "";
1401
1402
  elements.settleMs.value = state.draft.settleMs ?? 750;
1402
1403
  elements.retryCount.value = state.draft.retryCount ?? 3;
1403
1404
  elements.retryDelayMs.value = state.draft.retryDelayMs ?? 750;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-sonos-scenes",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Homebridge plugin for Sonos workflow scenes and orchestration.",
5
5
  "author": "applemanj",
6
6
  "homepage": "https://github.com/applemanj/homebridge-sonos-scenes#readme",