homebridge-yoto 0.0.39 → 0.0.40
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 +50 -0
- package/config.schema.json +63 -17
- package/lib/accessory.js +128 -43
- package/lib/card-control-accessory.js +200 -0
- package/lib/card-controls.js +80 -0
- package/lib/platform.js +322 -31
- package/lib/service-config.js +63 -0
- package/lib/speaker-accessory.js +490 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -25,6 +25,56 @@
|
|
|
25
25
|
|
|
26
26
|
THIS PLUGIN IS A WIP. DO NOT USE YET.
|
|
27
27
|
|
|
28
|
+
Homebridge plugin that exposes Yoto players to HomeKit with optional playback controls, device status, and nightlight settings.
|
|
29
|
+
|
|
30
|
+
## Settings
|
|
31
|
+
|
|
32
|
+
**Playback Controls** (`services.playbackAccessory`)
|
|
33
|
+
- **Bridged (Switch + Dimmer)**: Adds Playback and Volume services on the main accessory.
|
|
34
|
+
- **External Smart Speaker**: Publishes a separate Smart Speaker accessory for playback and volume. Requires pairing the extra accessory in the Home app.
|
|
35
|
+
- **None**: Disables playback and volume services entirely.
|
|
36
|
+
|
|
37
|
+
**Card Controls** (`services.cardControls`)
|
|
38
|
+
- Adds a per-device switch that plays the configured card ID.
|
|
39
|
+
- Optional "Play on All Yotos" accessory per card control.
|
|
40
|
+
|
|
41
|
+
**Service toggles**
|
|
42
|
+
- **Temperature Sensor**: Adds a temperature sensor when supported by the device.
|
|
43
|
+
- **Nightlight**: Adds day/night nightlight controls and status sensors.
|
|
44
|
+
- **Card Slot**: Adds a card insertion sensor.
|
|
45
|
+
- **Day Mode**: Adds a day/night mode sensor.
|
|
46
|
+
- **Sleep Timer**: Adds a sleep timer switch.
|
|
47
|
+
- **Bluetooth**: Adds a Bluetooth toggle switch.
|
|
48
|
+
- **Volume Limits**: Adds day/night max volume controls.
|
|
49
|
+
|
|
50
|
+
## HomeKit Services
|
|
51
|
+
|
|
52
|
+
**Playback (bridged)**
|
|
53
|
+
- **Playback**: Switch; On resumes, Off pauses.
|
|
54
|
+
- **Volume**: Lightbulb; On unmutes, Off mutes, Brightness maps 0-100% to device volume steps.
|
|
55
|
+
|
|
56
|
+
**Smart Speaker (external)**
|
|
57
|
+
- **Smart Speaker**: Current/Target Media State, Volume, Mute, and StatusActive (online state).
|
|
58
|
+
|
|
59
|
+
**Card Controls**
|
|
60
|
+
- **Card Control**: Switch on each device that plays the configured card ID.
|
|
61
|
+
- **Card Control (All Yotos)**: Optional switch accessory that plays the card on every Yoto.
|
|
62
|
+
|
|
63
|
+
**Device status**
|
|
64
|
+
- **Online Status**: Contact sensor; Contact Not Detected = online.
|
|
65
|
+
- **Battery**: Battery level, charging state, and low battery.
|
|
66
|
+
- **Temperature**: Temperature sensor with fault status when offline/unavailable.
|
|
67
|
+
|
|
68
|
+
**Nightlight**
|
|
69
|
+
- **Day Nightlight / Night Nightlight**: Lightbulbs with On/Off, Brightness, Hue, and Saturation.
|
|
70
|
+
- **Nightlight Active / Day Nightlight Active / Night Nightlight Active**: Contact sensors for live nightlight state.
|
|
71
|
+
|
|
72
|
+
**Other controls**
|
|
73
|
+
- **Card Slot**: Contact sensor for card insertion.
|
|
74
|
+
- **Day Mode**: Contact sensor; Contact Not Detected = day mode.
|
|
75
|
+
- **Sleep Timer**: Switch to enable/disable sleep timer.
|
|
76
|
+
- **Bluetooth**: Switch to toggle Bluetooth.
|
|
77
|
+
- **Day/Night Max Volume**: Lightbulb brightness sets max volume limits.
|
|
28
78
|
|
|
29
79
|
## License
|
|
30
80
|
|
package/config.schema.json
CHANGED
|
@@ -49,17 +49,17 @@
|
|
|
49
49
|
"title": "Accessory Services",
|
|
50
50
|
"type": "object",
|
|
51
51
|
"properties": {
|
|
52
|
-
"
|
|
53
|
-
"title": "Playback",
|
|
54
|
-
"type": "
|
|
55
|
-
"default":
|
|
56
|
-
"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"description": "
|
|
52
|
+
"playbackAccessory": {
|
|
53
|
+
"title": "Playback Controls",
|
|
54
|
+
"type": "string",
|
|
55
|
+
"default": "none",
|
|
56
|
+
"enum": ["bridged", "external", "none"],
|
|
57
|
+
"enumNames": [
|
|
58
|
+
"Bridged (Switch + Dimmer)",
|
|
59
|
+
"External Smart Speaker",
|
|
60
|
+
"None"
|
|
61
|
+
],
|
|
62
|
+
"description": "Choose how playback and volume controls are exposed. External Smart Speaker requires additional pairing steps and appears as a separate accessory."
|
|
63
63
|
},
|
|
64
64
|
"temperature": {
|
|
65
65
|
"title": "Temperature Sensor",
|
|
@@ -79,11 +79,40 @@
|
|
|
79
79
|
"default": true,
|
|
80
80
|
"description": "Expose card insertion status."
|
|
81
81
|
},
|
|
82
|
-
"
|
|
83
|
-
"title": "
|
|
82
|
+
"cardControls": {
|
|
83
|
+
"title": "Card Controls",
|
|
84
|
+
"type": "array",
|
|
85
|
+
"description": "Add switches that play a specific card ID on each Yoto device. Optionally add an accessory that plays the card on all Yotos.",
|
|
86
|
+
"items": {
|
|
87
|
+
"title": "Card Control",
|
|
88
|
+
"type": "object",
|
|
89
|
+
"properties": {
|
|
90
|
+
"label": {
|
|
91
|
+
"title": "Label",
|
|
92
|
+
"type": "string",
|
|
93
|
+
"required": true,
|
|
94
|
+
"description": "Name shown in HomeKit for this card control."
|
|
95
|
+
},
|
|
96
|
+
"cardId": {
|
|
97
|
+
"title": "Card ID",
|
|
98
|
+
"type": "string",
|
|
99
|
+
"required": true,
|
|
100
|
+
"description": "The Yoto card ID to play."
|
|
101
|
+
},
|
|
102
|
+
"playOnAll": {
|
|
103
|
+
"title": "Play on All Yotos",
|
|
104
|
+
"type": "boolean",
|
|
105
|
+
"default": false,
|
|
106
|
+
"description": "Create a separate accessory that plays this card on every Yoto."
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
"dayMode": {
|
|
112
|
+
"title": "Day Mode",
|
|
84
113
|
"type": "boolean",
|
|
85
114
|
"default": true,
|
|
86
|
-
"description": "Expose
|
|
115
|
+
"description": "Expose day mode status."
|
|
87
116
|
},
|
|
88
117
|
"sleepTimer": {
|
|
89
118
|
"title": "Sleep Timer",
|
|
@@ -133,12 +162,29 @@
|
|
|
133
162
|
"type": "help",
|
|
134
163
|
"helpvalue": "<p>Select which HomeKit services to expose for each Yoto device.</p>"
|
|
135
164
|
},
|
|
136
|
-
|
|
137
|
-
|
|
165
|
+
{
|
|
166
|
+
"type": "help",
|
|
167
|
+
"helpvalue": "<p><strong>Playback Controls:</strong> External Smart Speaker requires additional pairing steps in the Home app and will appear as a separate accessory.</p>"
|
|
168
|
+
},
|
|
169
|
+
"services.playbackAccessory",
|
|
138
170
|
"services.temperature",
|
|
139
171
|
"services.nightlight",
|
|
140
172
|
"services.cardSlot",
|
|
141
|
-
|
|
173
|
+
{
|
|
174
|
+
"type": "help",
|
|
175
|
+
"helpvalue": "<p><strong>Card Controls:</strong> Add switches that play a specific card ID on each Yoto. Enable \"Play on All Yotos\" to create a separate accessory.</p>"
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"key": "services.cardControls",
|
|
179
|
+
"type": "array",
|
|
180
|
+
"buttonText": "Add Card Control",
|
|
181
|
+
"items": [
|
|
182
|
+
"services.cardControls[].label",
|
|
183
|
+
"services.cardControls[].cardId",
|
|
184
|
+
"services.cardControls[].playOnAll"
|
|
185
|
+
]
|
|
186
|
+
},
|
|
187
|
+
"services.dayMode",
|
|
142
188
|
"services.sleepTimer",
|
|
143
189
|
"services.bluetooth",
|
|
144
190
|
"services.volumeLimits"
|
package/lib/accessory.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
/** @import { YotoDevice } from 'yoto-nodejs-client/lib/api-endpoints/devices.js' */
|
|
9
9
|
/** @import { YotoAccessoryContext } from './platform.js' */
|
|
10
10
|
/** @import { ServiceSchemaKey } from '../config.schema.cjs' */
|
|
11
|
+
/** @import { CardControlConfig } from './card-controls.js' */
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Device capabilities detected from metadata
|
|
@@ -25,7 +26,7 @@
|
|
|
25
26
|
* @property {boolean} temperature
|
|
26
27
|
* @property {boolean} nightlight
|
|
27
28
|
* @property {boolean} cardSlot
|
|
28
|
-
* @property {boolean}
|
|
29
|
+
* @property {boolean} dayMode
|
|
29
30
|
* @property {boolean} sleepTimer
|
|
30
31
|
* @property {boolean} bluetooth
|
|
31
32
|
* @property {boolean} volumeLimits
|
|
@@ -41,6 +42,8 @@ import {
|
|
|
41
42
|
import { sanitizeName } from './sanitize-name.js'
|
|
42
43
|
import { syncServiceNames } from './sync-service-names.js'
|
|
43
44
|
import { serviceSchema } from '../config.schema.cjs'
|
|
45
|
+
import { getPlaybackAccessoryConfig } from './service-config.js'
|
|
46
|
+
import { getCardControlConfigs } from './card-controls.js'
|
|
44
47
|
|
|
45
48
|
// Use syncServiceNames for every visible service so HomeKit labels stay stable.
|
|
46
49
|
// Exceptions: AccessoryInformation (named in platform) and Battery (set Characteristic.Name only).
|
|
@@ -60,7 +63,7 @@ function getBooleanSetting (value, fallback) {
|
|
|
60
63
|
*/
|
|
61
64
|
function getServiceDefault (key) {
|
|
62
65
|
const entry = serviceSchema[key]
|
|
63
|
-
if (entry
|
|
66
|
+
if (entry && 'default' in entry && typeof entry.default === 'boolean') {
|
|
64
67
|
return entry.default
|
|
65
68
|
}
|
|
66
69
|
return false
|
|
@@ -87,7 +90,7 @@ export class YotoPlayerAccessory {
|
|
|
87
90
|
/** @type {Service | undefined} */ dayNightlightActiveService
|
|
88
91
|
/** @type {Service | undefined} */ nightNightlightActiveService
|
|
89
92
|
/** @type {Service | undefined} */ cardSlotService
|
|
90
|
-
/** @type {Service | undefined} */
|
|
93
|
+
/** @type {Service | undefined} */ dayModeService
|
|
91
94
|
/** @type {Service | undefined} */ sleepTimerService
|
|
92
95
|
/** @type {Service | undefined} */ bluetoothService
|
|
93
96
|
/** @type {Service | undefined} */ dayMaxVolumeService
|
|
@@ -127,14 +130,15 @@ export class YotoPlayerAccessory {
|
|
|
127
130
|
const serviceConfig = typeof services === 'object' && services !== null
|
|
128
131
|
? /** @type {Record<string, unknown>} */ (services)
|
|
129
132
|
: {}
|
|
133
|
+
const playbackConfig = getPlaybackAccessoryConfig(this.#platform.config)
|
|
130
134
|
|
|
131
135
|
return {
|
|
132
|
-
playback:
|
|
133
|
-
volume:
|
|
136
|
+
playback: playbackConfig.playbackEnabled,
|
|
137
|
+
volume: playbackConfig.volumeEnabled,
|
|
134
138
|
temperature: getBooleanSetting(serviceConfig['temperature'], getServiceDefault('temperature')),
|
|
135
139
|
nightlight: getBooleanSetting(serviceConfig['nightlight'], getServiceDefault('nightlight')),
|
|
136
140
|
cardSlot: getBooleanSetting(serviceConfig['cardSlot'], getServiceDefault('cardSlot')),
|
|
137
|
-
|
|
141
|
+
dayMode: getBooleanSetting(serviceConfig['dayMode'], getServiceDefault('dayMode')),
|
|
138
142
|
sleepTimer: getBooleanSetting(serviceConfig['sleepTimer'], getServiceDefault('sleepTimer')),
|
|
139
143
|
bluetooth: getBooleanSetting(serviceConfig['bluetooth'], getServiceDefault('bluetooth')),
|
|
140
144
|
volumeLimits: getBooleanSetting(serviceConfig['volumeLimits'], getServiceDefault('volumeLimits')),
|
|
@@ -188,8 +192,8 @@ export class YotoPlayerAccessory {
|
|
|
188
192
|
if (serviceToggles.cardSlot) {
|
|
189
193
|
this.setupCardSlotService()
|
|
190
194
|
}
|
|
191
|
-
if (serviceToggles.
|
|
192
|
-
this.
|
|
195
|
+
if (serviceToggles.dayMode) {
|
|
196
|
+
this.setupDayModeService()
|
|
193
197
|
}
|
|
194
198
|
if (serviceToggles.sleepTimer) {
|
|
195
199
|
this.setupSleepTimerService()
|
|
@@ -200,6 +204,7 @@ export class YotoPlayerAccessory {
|
|
|
200
204
|
if (serviceToggles.volumeLimits) {
|
|
201
205
|
this.setupVolumeLimitServices()
|
|
202
206
|
}
|
|
207
|
+
this.setupCardControlServices()
|
|
203
208
|
|
|
204
209
|
// Remove any services that aren't in our current set
|
|
205
210
|
// (except AccessoryInformation which should always be preserved)
|
|
@@ -515,21 +520,21 @@ export class YotoPlayerAccessory {
|
|
|
515
520
|
}
|
|
516
521
|
|
|
517
522
|
/**
|
|
518
|
-
* Setup
|
|
519
|
-
* Shows if device is in
|
|
523
|
+
* Setup day mode ContactSensor service
|
|
524
|
+
* Shows if device is in day mode (vs night mode)
|
|
520
525
|
*/
|
|
521
|
-
|
|
526
|
+
setupDayModeService () {
|
|
522
527
|
const { Service, Characteristic } = this.#platform
|
|
523
|
-
const serviceName = this.generateServiceName('
|
|
528
|
+
const serviceName = this.generateServiceName('Day Mode')
|
|
524
529
|
|
|
525
|
-
const service = this.#accessory.getServiceById(Service.ContactSensor, '
|
|
526
|
-
this.#accessory.addService(Service.ContactSensor, serviceName, '
|
|
530
|
+
const service = this.#accessory.getServiceById(Service.ContactSensor, 'DayModeStatus') ||
|
|
531
|
+
this.#accessory.addService(Service.ContactSensor, serviceName, 'DayModeStatus')
|
|
527
532
|
syncServiceNames({ Characteristic, service, name: serviceName })
|
|
528
533
|
|
|
529
534
|
service.getCharacteristic(Characteristic.ContactSensorState)
|
|
530
|
-
.onGet(this.
|
|
535
|
+
.onGet(this.getDayModeStatus.bind(this))
|
|
531
536
|
|
|
532
|
-
this.
|
|
537
|
+
this.dayModeService = service
|
|
533
538
|
this.#currentServices.add(service)
|
|
534
539
|
}
|
|
535
540
|
|
|
@@ -632,6 +637,39 @@ export class YotoPlayerAccessory {
|
|
|
632
637
|
this.#currentServices.add(nightService)
|
|
633
638
|
}
|
|
634
639
|
|
|
640
|
+
/**
|
|
641
|
+
* Setup card control Switch services
|
|
642
|
+
*/
|
|
643
|
+
setupCardControlServices () {
|
|
644
|
+
const cardControls = getCardControlConfigs(this.#platform.config)
|
|
645
|
+
if (cardControls.length === 0) {
|
|
646
|
+
return
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const { Service, Characteristic } = this.#platform
|
|
650
|
+
|
|
651
|
+
for (const control of cardControls) {
|
|
652
|
+
const serviceName = this.generateServiceName(control.label)
|
|
653
|
+
const subtype = `CardControl:${control.id}`
|
|
654
|
+
|
|
655
|
+
const service = this.#accessory.getServiceById(Service.Switch, subtype) ||
|
|
656
|
+
this.#accessory.addService(Service.Switch, serviceName, subtype)
|
|
657
|
+
|
|
658
|
+
syncServiceNames({ Characteristic, service, name: serviceName })
|
|
659
|
+
|
|
660
|
+
service
|
|
661
|
+
.getCharacteristic(Characteristic.On)
|
|
662
|
+
.onGet(() => false)
|
|
663
|
+
.onSet(async (value) => {
|
|
664
|
+
await this.setCardControl(service, control, value)
|
|
665
|
+
})
|
|
666
|
+
|
|
667
|
+
service.updateCharacteristic(Characteristic.On, false)
|
|
668
|
+
|
|
669
|
+
this.#currentServices.add(service)
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
635
673
|
/**
|
|
636
674
|
* Setup event listeners for device model updates
|
|
637
675
|
* Uses exhaustive switch pattern for type safety
|
|
@@ -677,8 +715,8 @@ export class YotoPlayerAccessory {
|
|
|
677
715
|
break
|
|
678
716
|
|
|
679
717
|
case 'dayMode':
|
|
680
|
-
// Update
|
|
681
|
-
this.
|
|
718
|
+
// Update day mode ContactSensor
|
|
719
|
+
this.updateDayModeCharacteristic()
|
|
682
720
|
// Update nightlight status ContactSensors (depends on dayMode)
|
|
683
721
|
if (this.#deviceModel.capabilities.hasColoredNightlight) {
|
|
684
722
|
this.updateNightlightStatusCharacteristics()
|
|
@@ -927,7 +965,7 @@ export class YotoPlayerAccessory {
|
|
|
927
965
|
const percent = Math.round((clampedSteps / 16) * 100)
|
|
928
966
|
this.#log.debug(
|
|
929
967
|
LOG_PREFIX.ACCESSORY,
|
|
930
|
-
`[${this.#device.name}] Get volume rawSteps=${volumeSteps}
|
|
968
|
+
`[${this.#device.name}] Get volume rawSteps=${volumeSteps} percent=${percent}`
|
|
931
969
|
)
|
|
932
970
|
return percent
|
|
933
971
|
}
|
|
@@ -966,13 +1004,12 @@ export class YotoPlayerAccessory {
|
|
|
966
1004
|
await deviceModel.setVolume(steps)
|
|
967
1005
|
if (this.volumeService) {
|
|
968
1006
|
const { Characteristic } = this.#platform
|
|
969
|
-
|
|
1007
|
+
const clampedPercent = Math.round((steps / 16) * 100)
|
|
970
1008
|
this.volumeService
|
|
971
1009
|
.getCharacteristic(Characteristic.On)
|
|
972
1010
|
.updateValue(steps > 0)
|
|
973
1011
|
|
|
974
1012
|
if (steps !== requestedSteps || normalizedPercent !== requestedPercent) {
|
|
975
|
-
const clampedPercent = Math.round((steps / 16) * 100)
|
|
976
1013
|
this.volumeService
|
|
977
1014
|
.getCharacteristic(Characteristic.Brightness)
|
|
978
1015
|
.updateValue(clampedPercent)
|
|
@@ -1489,17 +1526,17 @@ export class YotoPlayerAccessory {
|
|
|
1489
1526
|
return hasCard ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED
|
|
1490
1527
|
}
|
|
1491
1528
|
|
|
1492
|
-
// ====================
|
|
1529
|
+
// ==================== Day Mode ContactSensor Getter ====================
|
|
1493
1530
|
|
|
1494
1531
|
/**
|
|
1495
|
-
* Get
|
|
1532
|
+
* Get day mode status
|
|
1496
1533
|
* @returns {Promise<CharacteristicValue>}
|
|
1497
1534
|
*/
|
|
1498
|
-
async
|
|
1535
|
+
async getDayModeStatus () {
|
|
1499
1536
|
const { Characteristic } = this.#platform
|
|
1500
1537
|
const status = this.#deviceModel.status
|
|
1501
|
-
const
|
|
1502
|
-
return
|
|
1538
|
+
const isDayMode = status.dayMode === 'day'
|
|
1539
|
+
return isDayMode
|
|
1503
1540
|
? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED
|
|
1504
1541
|
: Characteristic.ContactSensorState.CONTACT_DETECTED
|
|
1505
1542
|
}
|
|
@@ -1560,6 +1597,54 @@ export class YotoPlayerAccessory {
|
|
|
1560
1597
|
await this.#deviceModel.updateConfig({ bluetoothEnabled: enabled })
|
|
1561
1598
|
}
|
|
1562
1599
|
|
|
1600
|
+
// ==================== Card Control Switch Setter ====================
|
|
1601
|
+
|
|
1602
|
+
/**
|
|
1603
|
+
* Trigger card playback for a configured card control.
|
|
1604
|
+
* @param {Service} service
|
|
1605
|
+
* @param {CardControlConfig} control
|
|
1606
|
+
* @param {CharacteristicValue} value
|
|
1607
|
+
* @returns {Promise<void>}
|
|
1608
|
+
*/
|
|
1609
|
+
async setCardControl (service, control, value) {
|
|
1610
|
+
const { Characteristic } = this.#platform
|
|
1611
|
+
const isOn = Boolean(value)
|
|
1612
|
+
|
|
1613
|
+
if (!isOn) {
|
|
1614
|
+
service.getCharacteristic(Characteristic.On).updateValue(false)
|
|
1615
|
+
return
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
if (!this.#deviceModel.status.isOnline) {
|
|
1619
|
+
this.#log.warn(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Card control skipped (offline): ${control.label}`)
|
|
1620
|
+
service.getCharacteristic(Characteristic.On).updateValue(false)
|
|
1621
|
+
throw new this.#platform.api.hap.HapStatusError(
|
|
1622
|
+
this.#platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE
|
|
1623
|
+
)
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
this.#log.debug(
|
|
1627
|
+
LOG_PREFIX.ACCESSORY,
|
|
1628
|
+
`[${this.#device.name}] Play card control: ${control.label} (${control.cardId})`
|
|
1629
|
+
)
|
|
1630
|
+
|
|
1631
|
+
try {
|
|
1632
|
+
await this.#deviceModel.startCard({ cardId: control.cardId })
|
|
1633
|
+
} catch (error) {
|
|
1634
|
+
this.#log.error(
|
|
1635
|
+
LOG_PREFIX.ACCESSORY,
|
|
1636
|
+
`[${this.#device.name}] Failed to play card ${control.cardId}:`,
|
|
1637
|
+
error
|
|
1638
|
+
)
|
|
1639
|
+
service.getCharacteristic(Characteristic.On).updateValue(false)
|
|
1640
|
+
throw new this.#platform.api.hap.HapStatusError(
|
|
1641
|
+
this.#platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE
|
|
1642
|
+
)
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
service.getCharacteristic(Characteristic.On).updateValue(false)
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1563
1648
|
// ==================== Volume Limit Lightbulb Getters/Setters ====================
|
|
1564
1649
|
|
|
1565
1650
|
/**
|
|
@@ -1573,7 +1658,7 @@ export class YotoPlayerAccessory {
|
|
|
1573
1658
|
const percent = Math.round((clampedSteps / 16) * 100)
|
|
1574
1659
|
this.#log.debug(
|
|
1575
1660
|
LOG_PREFIX.ACCESSORY,
|
|
1576
|
-
`[${this.#device.name}] Get day max volume limit rawSteps=${limit}
|
|
1661
|
+
`[${this.#device.name}] Get day max volume limit rawSteps=${limit} percent=${percent}`
|
|
1577
1662
|
)
|
|
1578
1663
|
return percent
|
|
1579
1664
|
}
|
|
@@ -1612,7 +1697,7 @@ export class YotoPlayerAccessory {
|
|
|
1612
1697
|
const percent = Math.round((clampedSteps / 16) * 100)
|
|
1613
1698
|
this.#log.debug(
|
|
1614
1699
|
LOG_PREFIX.ACCESSORY,
|
|
1615
|
-
`[${this.#device.name}] Get night max volume limit rawSteps=${limit}
|
|
1700
|
+
`[${this.#device.name}] Get night max volume limit rawSteps=${limit} percent=${percent}`
|
|
1616
1701
|
)
|
|
1617
1702
|
return percent
|
|
1618
1703
|
}
|
|
@@ -1661,9 +1746,6 @@ export class YotoPlayerAccessory {
|
|
|
1661
1746
|
* @param {number} volumeSteps - Volume level (0-16)
|
|
1662
1747
|
*/
|
|
1663
1748
|
updateVolumeCharacteristic (volumeSteps) {
|
|
1664
|
-
if (!this.volumeService) return
|
|
1665
|
-
|
|
1666
|
-
const { Characteristic } = this.#platform
|
|
1667
1749
|
if (volumeSteps > 0) {
|
|
1668
1750
|
this.#lastNonZeroVolume = Math.round((volumeSteps / 16) * 100)
|
|
1669
1751
|
}
|
|
@@ -1673,8 +1755,11 @@ export class YotoPlayerAccessory {
|
|
|
1673
1755
|
const percent = Math.round((clampedVolume / 16) * 100)
|
|
1674
1756
|
this.#log.debug(
|
|
1675
1757
|
LOG_PREFIX.ACCESSORY,
|
|
1676
|
-
`[${this.#device.name}] Update volume characteristic rawSteps=${volumeSteps}
|
|
1758
|
+
`[${this.#device.name}] Update volume characteristic rawSteps=${volumeSteps} percent=${percent}`
|
|
1677
1759
|
)
|
|
1760
|
+
if (!this.volumeService) return
|
|
1761
|
+
|
|
1762
|
+
const { Characteristic } = this.#platform
|
|
1678
1763
|
this.volumeService
|
|
1679
1764
|
.getCharacteristic(Characteristic.Brightness)
|
|
1680
1765
|
.updateValue(percent)
|
|
@@ -1797,9 +1882,9 @@ export class YotoPlayerAccessory {
|
|
|
1797
1882
|
.updateValue(isOnline)
|
|
1798
1883
|
}
|
|
1799
1884
|
|
|
1800
|
-
// Update
|
|
1801
|
-
if (this.
|
|
1802
|
-
this.
|
|
1885
|
+
// Update day mode ContactSensor (device state)
|
|
1886
|
+
if (this.dayModeService) {
|
|
1887
|
+
this.dayModeService
|
|
1803
1888
|
.getCharacteristic(Characteristic.StatusActive)
|
|
1804
1889
|
.updateValue(isOnline)
|
|
1805
1890
|
}
|
|
@@ -1883,20 +1968,20 @@ export class YotoPlayerAccessory {
|
|
|
1883
1968
|
}
|
|
1884
1969
|
|
|
1885
1970
|
/**
|
|
1886
|
-
* Update
|
|
1971
|
+
* Update day mode ContactSensor characteristic
|
|
1887
1972
|
*/
|
|
1888
|
-
|
|
1889
|
-
if (!this.
|
|
1973
|
+
updateDayModeCharacteristic () {
|
|
1974
|
+
if (!this.dayModeService) {
|
|
1890
1975
|
return
|
|
1891
1976
|
}
|
|
1892
1977
|
|
|
1893
1978
|
const { Characteristic } = this.#platform
|
|
1894
1979
|
const status = this.#deviceModel.status
|
|
1895
|
-
const
|
|
1980
|
+
const isDayMode = status.dayMode === 'day'
|
|
1896
1981
|
|
|
1897
|
-
this.
|
|
1982
|
+
this.dayModeService
|
|
1898
1983
|
.getCharacteristic(Characteristic.ContactSensorState)
|
|
1899
|
-
.updateValue(
|
|
1984
|
+
.updateValue(isDayMode ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED)
|
|
1900
1985
|
}
|
|
1901
1986
|
|
|
1902
1987
|
/**
|
|
@@ -1944,7 +2029,7 @@ export class YotoPlayerAccessory {
|
|
|
1944
2029
|
const percent = Math.round((clampedLimit / 16) * 100)
|
|
1945
2030
|
this.#log.debug(
|
|
1946
2031
|
LOG_PREFIX.ACCESSORY,
|
|
1947
|
-
`[${this.#device.name}] Update day max volume characteristic rawSteps=${limit}
|
|
2032
|
+
`[${this.#device.name}] Update day max volume characteristic rawSteps=${limit} percent=${percent}`
|
|
1948
2033
|
)
|
|
1949
2034
|
this.dayMaxVolumeService
|
|
1950
2035
|
.getCharacteristic(Characteristic.Brightness)
|
|
@@ -1957,7 +2042,7 @@ export class YotoPlayerAccessory {
|
|
|
1957
2042
|
const percent = Math.round((clampedLimit / 16) * 100)
|
|
1958
2043
|
this.#log.debug(
|
|
1959
2044
|
LOG_PREFIX.ACCESSORY,
|
|
1960
|
-
`[${this.#device.name}] Update night max volume characteristic rawSteps=${limit}
|
|
2045
|
+
`[${this.#device.name}] Update night max volume characteristic rawSteps=${limit} percent=${percent}`
|
|
1961
2046
|
)
|
|
1962
2047
|
this.nightMaxVolumeService
|
|
1963
2048
|
.getCharacteristic(Characteristic.Brightness)
|