homebridge-yoto 0.0.34 → 0.0.36
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/config.schema.cjs +8 -0
- package/config.schema.json +81 -0
- package/homebridge-ui/public/client.js +14 -8
- package/lib/accessory.js +252 -142
- package/lib/platform.js +3 -1
- package/package.json +1 -1
package/config.schema.cjs
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/** @type {import('./config.schema.json')} */
|
|
1
2
|
const configSchema = require('./config.schema.json')
|
|
2
3
|
|
|
4
|
+
const serviceSchema = configSchema.schema.properties.services.properties
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {keyof typeof serviceSchema} ServiceSchemaKey
|
|
8
|
+
*/
|
|
9
|
+
|
|
3
10
|
exports.configSchema = configSchema
|
|
11
|
+
exports.serviceSchema = serviceSchema
|
package/config.schema.json
CHANGED
|
@@ -44,6 +44,66 @@
|
|
|
44
44
|
"x-schema-form": {
|
|
45
45
|
"hidden": true
|
|
46
46
|
}
|
|
47
|
+
},
|
|
48
|
+
"services": {
|
|
49
|
+
"title": "Accessory Services",
|
|
50
|
+
"type": "object",
|
|
51
|
+
"properties": {
|
|
52
|
+
"playback": {
|
|
53
|
+
"title": "Playback",
|
|
54
|
+
"type": "boolean",
|
|
55
|
+
"default": true,
|
|
56
|
+
"description": "Expose a playback switch."
|
|
57
|
+
},
|
|
58
|
+
"volume": {
|
|
59
|
+
"title": "Volume",
|
|
60
|
+
"type": "boolean",
|
|
61
|
+
"default": true,
|
|
62
|
+
"description": "Expose volume controls."
|
|
63
|
+
},
|
|
64
|
+
"temperature": {
|
|
65
|
+
"title": "Temperature Sensor",
|
|
66
|
+
"type": "boolean",
|
|
67
|
+
"default": true,
|
|
68
|
+
"description": "Expose temperature sensor when supported."
|
|
69
|
+
},
|
|
70
|
+
"nightlight": {
|
|
71
|
+
"title": "Nightlight",
|
|
72
|
+
"type": "boolean",
|
|
73
|
+
"default": false,
|
|
74
|
+
"description": "Expose day/night nightlight controls and status."
|
|
75
|
+
},
|
|
76
|
+
"cardSlot": {
|
|
77
|
+
"title": "Card Slot",
|
|
78
|
+
"type": "boolean",
|
|
79
|
+
"default": true,
|
|
80
|
+
"description": "Expose card insertion status."
|
|
81
|
+
},
|
|
82
|
+
"nightMode": {
|
|
83
|
+
"title": "Night Mode",
|
|
84
|
+
"type": "boolean",
|
|
85
|
+
"default": true,
|
|
86
|
+
"description": "Expose night mode status."
|
|
87
|
+
},
|
|
88
|
+
"sleepTimer": {
|
|
89
|
+
"title": "Sleep Timer",
|
|
90
|
+
"type": "boolean",
|
|
91
|
+
"default": false,
|
|
92
|
+
"description": "Expose sleep timer switch."
|
|
93
|
+
},
|
|
94
|
+
"bluetooth": {
|
|
95
|
+
"title": "Bluetooth",
|
|
96
|
+
"type": "boolean",
|
|
97
|
+
"default": false,
|
|
98
|
+
"description": "Expose Bluetooth toggle."
|
|
99
|
+
},
|
|
100
|
+
"volumeLimits": {
|
|
101
|
+
"title": "Volume Limits",
|
|
102
|
+
"type": "boolean",
|
|
103
|
+
"default": true,
|
|
104
|
+
"description": "Expose day/night max volume controls."
|
|
105
|
+
}
|
|
106
|
+
}
|
|
47
107
|
}
|
|
48
108
|
}
|
|
49
109
|
},
|
|
@@ -62,6 +122,27 @@
|
|
|
62
122
|
"accessToken",
|
|
63
123
|
"refreshToken"
|
|
64
124
|
]
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"type": "section",
|
|
128
|
+
"title": "Accessory Services",
|
|
129
|
+
"expandable": true,
|
|
130
|
+
"expanded": false,
|
|
131
|
+
"items": [
|
|
132
|
+
{
|
|
133
|
+
"type": "help",
|
|
134
|
+
"helpvalue": "<p>Select which HomeKit services to expose for each Yoto device.</p>"
|
|
135
|
+
},
|
|
136
|
+
"services.playback",
|
|
137
|
+
"services.volume",
|
|
138
|
+
"services.temperature",
|
|
139
|
+
"services.nightlight",
|
|
140
|
+
"services.cardSlot",
|
|
141
|
+
"services.nightMode",
|
|
142
|
+
"services.sleepTimer",
|
|
143
|
+
"services.bluetooth",
|
|
144
|
+
"services.volumeLimits"
|
|
145
|
+
]
|
|
65
146
|
}
|
|
66
147
|
]
|
|
67
148
|
}
|
|
@@ -52,8 +52,7 @@ async function initializeUI () {
|
|
|
52
52
|
if (retryBtn) retryBtn.addEventListener('click', retryAuth)
|
|
53
53
|
if (logoutBtn) logoutBtn.addEventListener('click', logout)
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
homebridge.showSchemaForm()
|
|
55
|
+
homebridge.hideSchemaForm()
|
|
57
56
|
|
|
58
57
|
// Load auth config and check authentication status
|
|
59
58
|
await loadAuthConfig()
|
|
@@ -63,6 +62,17 @@ async function initializeUI () {
|
|
|
63
62
|
// Initialize on ready
|
|
64
63
|
homebridge.addEventListener('ready', initializeUI)
|
|
65
64
|
|
|
65
|
+
/**
|
|
66
|
+
* @param {boolean} shouldShow
|
|
67
|
+
*/
|
|
68
|
+
function setSchemaFormVisibility (shouldShow) {
|
|
69
|
+
if (shouldShow) {
|
|
70
|
+
homebridge.showSchemaForm()
|
|
71
|
+
} else {
|
|
72
|
+
homebridge.hideSchemaForm()
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
66
76
|
/**
|
|
67
77
|
* Show a specific UI section and hide all others
|
|
68
78
|
* @param {string} sectionToShow - ID of section to show
|
|
@@ -90,6 +100,8 @@ function showSection (sectionToShow, options = {}) {
|
|
|
90
100
|
const errorMessageEl = document.getElementById('errorMessage')
|
|
91
101
|
if (errorMessageEl) errorMessageEl.textContent = options.errorMessage
|
|
92
102
|
}
|
|
103
|
+
|
|
104
|
+
setSchemaFormVisibility(sectionToShow === 'authSuccess')
|
|
93
105
|
}
|
|
94
106
|
|
|
95
107
|
/**
|
|
@@ -298,12 +310,6 @@ function startPolling () {
|
|
|
298
310
|
await homebridge.updatePluginConfig(pluginConfig)
|
|
299
311
|
await homebridge.savePluginConfig()
|
|
300
312
|
|
|
301
|
-
// Refresh the schema form to show updated token fields
|
|
302
|
-
homebridge.hideSchemaForm()
|
|
303
|
-
setTimeout(() => {
|
|
304
|
-
homebridge.showSchemaForm()
|
|
305
|
-
}, 100)
|
|
306
|
-
|
|
307
313
|
homebridge.toast.success('Authentication successful!')
|
|
308
314
|
homebridge.toast.info('Please restart the plugin for changes to take effect', 'Restart Required')
|
|
309
315
|
showAuthSuccess()
|
package/lib/accessory.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
/** @import { YotoDeviceModel } from 'yoto-nodejs-client' */
|
|
8
8
|
/** @import { YotoDevice } from 'yoto-nodejs-client/lib/api-endpoints/devices.js' */
|
|
9
9
|
/** @import { YotoAccessoryContext } from './platform.js' */
|
|
10
|
+
/** @import { ServiceSchemaKey } from '../config.schema.cjs' */
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Device capabilities detected from metadata
|
|
@@ -16,6 +17,20 @@
|
|
|
16
17
|
* @property {string | undefined} generation - Device generation (e.g., 'gen3')
|
|
17
18
|
*/
|
|
18
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Accessory service toggles from config.
|
|
22
|
+
* @typedef {Object} YotoServiceToggles
|
|
23
|
+
* @property {boolean} playback
|
|
24
|
+
* @property {boolean} volume
|
|
25
|
+
* @property {boolean} temperature
|
|
26
|
+
* @property {boolean} nightlight
|
|
27
|
+
* @property {boolean} cardSlot
|
|
28
|
+
* @property {boolean} nightMode
|
|
29
|
+
* @property {boolean} sleepTimer
|
|
30
|
+
* @property {boolean} bluetooth
|
|
31
|
+
* @property {boolean} volumeLimits
|
|
32
|
+
*/
|
|
33
|
+
|
|
19
34
|
import convert from 'color-convert'
|
|
20
35
|
import {
|
|
21
36
|
DEFAULT_MANUFACTURER,
|
|
@@ -25,6 +40,31 @@ import {
|
|
|
25
40
|
} from './constants.js'
|
|
26
41
|
import { sanitizeName } from './sanitize-name.js'
|
|
27
42
|
import { syncServiceNames } from './sync-service-names.js'
|
|
43
|
+
import { serviceSchema } from '../config.schema.cjs'
|
|
44
|
+
|
|
45
|
+
// Use syncServiceNames for every visible service so HomeKit labels stay stable.
|
|
46
|
+
// Exceptions: AccessoryInformation (named in platform) and Battery (set Characteristic.Name only).
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {unknown} value
|
|
50
|
+
* @param {boolean} fallback
|
|
51
|
+
* @returns {boolean}
|
|
52
|
+
*/
|
|
53
|
+
function getBooleanSetting (value, fallback) {
|
|
54
|
+
return typeof value === 'boolean' ? value : fallback
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {ServiceSchemaKey} key
|
|
59
|
+
* @returns {boolean}
|
|
60
|
+
*/
|
|
61
|
+
function getServiceDefault (key) {
|
|
62
|
+
const entry = serviceSchema[key]
|
|
63
|
+
if (entry?.default !== undefined && typeof entry.default === 'boolean') {
|
|
64
|
+
return entry.default
|
|
65
|
+
}
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
28
68
|
|
|
29
69
|
/**
|
|
30
70
|
* Yoto Player Accessory Handler
|
|
@@ -39,6 +79,7 @@ export class YotoPlayerAccessory {
|
|
|
39
79
|
/** @type {Service | undefined} */ playbackService
|
|
40
80
|
/** @type {Service | undefined} */ volumeService
|
|
41
81
|
/** @type {Service | undefined} */ batteryService
|
|
82
|
+
/** @type {Service | undefined} */ onlineStatusService
|
|
42
83
|
/** @type {Service | undefined} */ temperatureSensorService
|
|
43
84
|
/** @type {Service | undefined} */ dayNightlightService
|
|
44
85
|
/** @type {Service | undefined} */ nightNightlightService
|
|
@@ -77,6 +118,29 @@ export class YotoPlayerAccessory {
|
|
|
77
118
|
this.#currentServices = new Set()
|
|
78
119
|
}
|
|
79
120
|
|
|
121
|
+
/**
|
|
122
|
+
* @returns {YotoServiceToggles}
|
|
123
|
+
*/
|
|
124
|
+
getServiceToggles () {
|
|
125
|
+
const config = this.#platform.config
|
|
126
|
+
const services = config && typeof config === 'object' ? config['services'] : undefined
|
|
127
|
+
const serviceConfig = typeof services === 'object' && services !== null
|
|
128
|
+
? /** @type {Record<string, unknown>} */ (services)
|
|
129
|
+
: {}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
playback: getBooleanSetting(serviceConfig.playback, getServiceDefault('playback')),
|
|
133
|
+
volume: getBooleanSetting(serviceConfig.volume, getServiceDefault('volume')),
|
|
134
|
+
temperature: getBooleanSetting(serviceConfig.temperature, getServiceDefault('temperature')),
|
|
135
|
+
nightlight: getBooleanSetting(serviceConfig.nightlight, getServiceDefault('nightlight')),
|
|
136
|
+
cardSlot: getBooleanSetting(serviceConfig.cardSlot, getServiceDefault('cardSlot')),
|
|
137
|
+
nightMode: getBooleanSetting(serviceConfig.nightMode, getServiceDefault('nightMode')),
|
|
138
|
+
sleepTimer: getBooleanSetting(serviceConfig.sleepTimer, getServiceDefault('sleepTimer')),
|
|
139
|
+
bluetooth: getBooleanSetting(serviceConfig.bluetooth, getServiceDefault('bluetooth')),
|
|
140
|
+
volumeLimits: getBooleanSetting(serviceConfig.volumeLimits, getServiceDefault('volumeLimits')),
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
80
144
|
/**
|
|
81
145
|
* Setup accessory - create services and setup event listeners
|
|
82
146
|
* @returns {Promise<void>}
|
|
@@ -96,25 +160,46 @@ export class YotoPlayerAccessory {
|
|
|
96
160
|
this.#currentServices.clear()
|
|
97
161
|
|
|
98
162
|
// 1. Setup services
|
|
163
|
+
const serviceToggles = this.getServiceToggles()
|
|
164
|
+
|
|
99
165
|
this.setupAccessoryInformation()
|
|
100
|
-
this.
|
|
166
|
+
this.setupOnlineStatusService()
|
|
167
|
+
|
|
168
|
+
if (serviceToggles.playback) {
|
|
169
|
+
this.setupPlaybackSwitchService()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (serviceToggles.volume) {
|
|
173
|
+
this.setupVolumeService()
|
|
174
|
+
}
|
|
175
|
+
|
|
101
176
|
this.setupBatteryService()
|
|
102
177
|
|
|
103
178
|
// Setup optional services based on device capabilities
|
|
104
|
-
if (this.#deviceModel.capabilities.hasTemperatureSensor) {
|
|
179
|
+
if (serviceToggles.temperature && this.#deviceModel.capabilities.hasTemperatureSensor) {
|
|
105
180
|
this.setupTemperatureSensorService()
|
|
106
181
|
}
|
|
107
182
|
|
|
108
|
-
if (this.#deviceModel.capabilities.hasColoredNightlight) {
|
|
183
|
+
if (serviceToggles.nightlight && this.#deviceModel.capabilities.hasColoredNightlight) {
|
|
109
184
|
this.setupNightlightServices()
|
|
110
185
|
}
|
|
111
186
|
|
|
112
187
|
// Setup universal services (available on all devices)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
188
|
+
if (serviceToggles.cardSlot) {
|
|
189
|
+
this.setupCardSlotService()
|
|
190
|
+
}
|
|
191
|
+
if (serviceToggles.nightMode) {
|
|
192
|
+
this.setupNightModeService()
|
|
193
|
+
}
|
|
194
|
+
if (serviceToggles.sleepTimer) {
|
|
195
|
+
this.setupSleepTimerService()
|
|
196
|
+
}
|
|
197
|
+
if (serviceToggles.bluetooth) {
|
|
198
|
+
this.setupBluetoothService()
|
|
199
|
+
}
|
|
200
|
+
if (serviceToggles.volumeLimits) {
|
|
201
|
+
this.setupVolumeLimitServices()
|
|
202
|
+
}
|
|
118
203
|
|
|
119
204
|
// Remove any services that aren't in our current set
|
|
120
205
|
// (except AccessoryInformation which should always be preserved)
|
|
@@ -178,15 +263,29 @@ export class YotoPlayerAccessory {
|
|
|
178
263
|
}
|
|
179
264
|
|
|
180
265
|
/**
|
|
181
|
-
* Setup
|
|
266
|
+
* Setup online/offline ContactSensor service (PRIMARY)
|
|
182
267
|
*/
|
|
183
|
-
|
|
184
|
-
this
|
|
185
|
-
this.
|
|
268
|
+
setupOnlineStatusService () {
|
|
269
|
+
const { Service, Characteristic } = this.#platform
|
|
270
|
+
const serviceName = this.generateServiceName('Online Status')
|
|
271
|
+
|
|
272
|
+
const service = this.#accessory.getServiceById(Service.ContactSensor, 'OnlineStatus') ||
|
|
273
|
+
this.#accessory.addService(Service.ContactSensor, serviceName, 'OnlineStatus')
|
|
274
|
+
|
|
275
|
+
service.setPrimaryService(true)
|
|
276
|
+
|
|
277
|
+
syncServiceNames({ Characteristic, service, name: serviceName })
|
|
278
|
+
|
|
279
|
+
service
|
|
280
|
+
.getCharacteristic(Characteristic.ContactSensorState)
|
|
281
|
+
.onGet(this.getOnlineStatus.bind(this))
|
|
282
|
+
|
|
283
|
+
this.onlineStatusService = service
|
|
284
|
+
this.#currentServices.add(service)
|
|
186
285
|
}
|
|
187
286
|
|
|
188
287
|
/**
|
|
189
|
-
* Setup play/pause Switch service
|
|
288
|
+
* Setup play/pause Switch service
|
|
190
289
|
*/
|
|
191
290
|
setupPlaybackSwitchService () {
|
|
192
291
|
const { Service, Characteristic } = this.#platform
|
|
@@ -195,15 +294,8 @@ export class YotoPlayerAccessory {
|
|
|
195
294
|
const service = this.#accessory.getServiceById(Service.Switch, 'Playback') ||
|
|
196
295
|
this.#accessory.addService(Service.Switch, serviceName, 'Playback')
|
|
197
296
|
|
|
198
|
-
service.setPrimaryService(true)
|
|
199
|
-
|
|
200
297
|
syncServiceNames({ Characteristic, service, name: serviceName })
|
|
201
298
|
|
|
202
|
-
service.addOptionalCharacteristic(Characteristic.StatusActive)
|
|
203
|
-
service
|
|
204
|
-
.getCharacteristic(Characteristic.StatusActive)
|
|
205
|
-
.onGet(this.getStatusActive.bind(this))
|
|
206
|
-
|
|
207
299
|
service
|
|
208
300
|
.getCharacteristic(Characteristic.On)
|
|
209
301
|
.onGet(this.getPlaybackOn.bind(this))
|
|
@@ -214,28 +306,23 @@ export class YotoPlayerAccessory {
|
|
|
214
306
|
}
|
|
215
307
|
|
|
216
308
|
/**
|
|
217
|
-
* Setup
|
|
309
|
+
* Setup Lightbulb service for volume/mute controls (Speaker service isn't shown in Home)
|
|
218
310
|
*/
|
|
219
311
|
setupVolumeService () {
|
|
220
312
|
const { Service, Characteristic } = this.#platform
|
|
221
313
|
const serviceName = this.generateServiceName('Volume')
|
|
222
314
|
|
|
223
|
-
const service = this.#accessory.getServiceById(Service.
|
|
224
|
-
this.#accessory.addService(Service.
|
|
315
|
+
const service = this.#accessory.getServiceById(Service.Lightbulb, 'Volume') ||
|
|
316
|
+
this.#accessory.addService(Service.Lightbulb, serviceName, 'Volume')
|
|
225
317
|
syncServiceNames({ Characteristic, service, name: serviceName })
|
|
226
318
|
|
|
227
|
-
service.addOptionalCharacteristic(Characteristic.StatusActive)
|
|
228
|
-
service
|
|
229
|
-
.getCharacteristic(Characteristic.StatusActive)
|
|
230
|
-
.onGet(this.getStatusActive.bind(this))
|
|
231
|
-
|
|
232
319
|
service
|
|
233
|
-
.getCharacteristic(Characteristic.
|
|
234
|
-
.onGet(this.
|
|
235
|
-
.onSet(this.
|
|
320
|
+
.getCharacteristic(Characteristic.On)
|
|
321
|
+
.onGet(this.getVolumeOn.bind(this))
|
|
322
|
+
.onSet(this.setVolumeOn.bind(this))
|
|
236
323
|
|
|
237
324
|
service
|
|
238
|
-
.getCharacteristic(Characteristic.
|
|
325
|
+
.getCharacteristic(Characteristic.Brightness)
|
|
239
326
|
.setProps({
|
|
240
327
|
minValue: 0,
|
|
241
328
|
maxValue: 16,
|
|
@@ -256,7 +343,7 @@ export class YotoPlayerAccessory {
|
|
|
256
343
|
const serviceName = this.generateServiceName('Battery')
|
|
257
344
|
const service = this.#accessory.getService(Service.Battery) ||
|
|
258
345
|
this.#accessory.addService(Service.Battery, serviceName)
|
|
259
|
-
|
|
346
|
+
service.setCharacteristic(Characteristic.Name, serviceName)
|
|
260
347
|
|
|
261
348
|
// BatteryLevel (GET only)
|
|
262
349
|
service.getCharacteristic(Characteristic.BatteryLevel)
|
|
@@ -428,18 +515,18 @@ export class YotoPlayerAccessory {
|
|
|
428
515
|
}
|
|
429
516
|
|
|
430
517
|
/**
|
|
431
|
-
* Setup night mode
|
|
518
|
+
* Setup night mode ContactSensor service
|
|
432
519
|
* Shows if device is in night mode (vs day mode)
|
|
433
520
|
*/
|
|
434
521
|
setupNightModeService () {
|
|
435
522
|
const { Service, Characteristic } = this.#platform
|
|
436
523
|
const serviceName = this.generateServiceName('Night Mode')
|
|
437
524
|
|
|
438
|
-
const service = this.#accessory.getServiceById(Service.
|
|
439
|
-
this.#accessory.addService(Service.
|
|
525
|
+
const service = this.#accessory.getServiceById(Service.ContactSensor, 'NightModeStatus') ||
|
|
526
|
+
this.#accessory.addService(Service.ContactSensor, serviceName, 'NightModeStatus')
|
|
440
527
|
syncServiceNames({ Characteristic, service, name: serviceName })
|
|
441
528
|
|
|
442
|
-
service.getCharacteristic(Characteristic.
|
|
529
|
+
service.getCharacteristic(Characteristic.ContactSensorState)
|
|
443
530
|
.onGet(this.getNightModeStatus.bind(this))
|
|
444
531
|
|
|
445
532
|
this.nightModeService = service
|
|
@@ -487,7 +574,7 @@ export class YotoPlayerAccessory {
|
|
|
487
574
|
}
|
|
488
575
|
|
|
489
576
|
/**
|
|
490
|
-
* Setup volume limit
|
|
577
|
+
* Setup volume limit Lightbulb services
|
|
491
578
|
* Control day and night mode max volume limits
|
|
492
579
|
*/
|
|
493
580
|
setupVolumeLimitServices () {
|
|
@@ -495,16 +582,22 @@ export class YotoPlayerAccessory {
|
|
|
495
582
|
|
|
496
583
|
// Day Max Volume
|
|
497
584
|
const dayName = this.generateServiceName('Day Max Volume')
|
|
498
|
-
const dayService = this.#accessory.getServiceById(Service.
|
|
499
|
-
this.#accessory.addService(Service.
|
|
585
|
+
const dayService = this.#accessory.getServiceById(Service.Lightbulb, 'DayMaxVolume') ||
|
|
586
|
+
this.#accessory.addService(Service.Lightbulb, dayName, 'DayMaxVolume')
|
|
500
587
|
syncServiceNames({ Characteristic, service: dayService, name: dayName })
|
|
501
588
|
|
|
502
589
|
dayService
|
|
503
|
-
.getCharacteristic(Characteristic.
|
|
504
|
-
.onGet(() =>
|
|
590
|
+
.getCharacteristic(Characteristic.On)
|
|
591
|
+
.onGet(() => true)
|
|
592
|
+
.onSet((value) => {
|
|
593
|
+
if (!value) {
|
|
594
|
+
dayService.updateCharacteristic(Characteristic.On, true)
|
|
595
|
+
}
|
|
596
|
+
})
|
|
597
|
+
dayService.setCharacteristic(Characteristic.On, true)
|
|
505
598
|
|
|
506
599
|
dayService
|
|
507
|
-
.getCharacteristic(Characteristic.
|
|
600
|
+
.getCharacteristic(Characteristic.Brightness)
|
|
508
601
|
.setProps({ minValue: 0, maxValue: 16, minStep: 1 })
|
|
509
602
|
.onGet(this.getDayMaxVolume.bind(this))
|
|
510
603
|
.onSet(this.setDayMaxVolume.bind(this))
|
|
@@ -513,16 +606,22 @@ export class YotoPlayerAccessory {
|
|
|
513
606
|
|
|
514
607
|
// Night Max Volume
|
|
515
608
|
const nightName = this.generateServiceName('Night Max Volume')
|
|
516
|
-
const nightService = this.#accessory.getServiceById(Service.
|
|
517
|
-
this.#accessory.addService(Service.
|
|
609
|
+
const nightService = this.#accessory.getServiceById(Service.Lightbulb, 'NightMaxVolume') ||
|
|
610
|
+
this.#accessory.addService(Service.Lightbulb, nightName, 'NightMaxVolume')
|
|
518
611
|
syncServiceNames({ Characteristic, service: nightService, name: nightName })
|
|
519
612
|
|
|
520
613
|
nightService
|
|
521
|
-
.getCharacteristic(Characteristic.
|
|
522
|
-
.onGet(() =>
|
|
614
|
+
.getCharacteristic(Characteristic.On)
|
|
615
|
+
.onGet(() => true)
|
|
616
|
+
.onSet((value) => {
|
|
617
|
+
if (!value) {
|
|
618
|
+
nightService.updateCharacteristic(Characteristic.On, true)
|
|
619
|
+
}
|
|
620
|
+
})
|
|
621
|
+
nightService.setCharacteristic(Characteristic.On, true)
|
|
523
622
|
|
|
524
623
|
nightService
|
|
525
|
-
.getCharacteristic(Characteristic.
|
|
624
|
+
.getCharacteristic(Characteristic.Brightness)
|
|
526
625
|
.setProps({ minValue: 0, maxValue: 16, minStep: 1 })
|
|
527
626
|
.onGet(this.getNightMaxVolume.bind(this))
|
|
528
627
|
.onSet(this.setNightMaxVolume.bind(this))
|
|
@@ -582,7 +681,7 @@ export class YotoPlayerAccessory {
|
|
|
582
681
|
break
|
|
583
682
|
|
|
584
683
|
case 'dayMode':
|
|
585
|
-
// Update night mode
|
|
684
|
+
// Update night mode ContactSensor
|
|
586
685
|
this.updateNightModeCharacteristic()
|
|
587
686
|
// Update nightlight status ContactSensors (depends on dayMode)
|
|
588
687
|
if (this.#deviceModel.capabilities.hasColoredNightlight) {
|
|
@@ -861,17 +960,13 @@ export class YotoPlayerAccessory {
|
|
|
861
960
|
if (this.volumeService) {
|
|
862
961
|
const { Characteristic } = this.#platform
|
|
863
962
|
|
|
864
|
-
const active = steps === 0
|
|
865
|
-
? Characteristic.Active.INACTIVE
|
|
866
|
-
: Characteristic.Active.ACTIVE
|
|
867
|
-
|
|
868
963
|
this.volumeService
|
|
869
|
-
.getCharacteristic(Characteristic.
|
|
870
|
-
.updateValue(
|
|
964
|
+
.getCharacteristic(Characteristic.On)
|
|
965
|
+
.updateValue(steps > 0)
|
|
871
966
|
|
|
872
967
|
if (steps !== requestedSteps) {
|
|
873
968
|
this.volumeService
|
|
874
|
-
.getCharacteristic(Characteristic.
|
|
969
|
+
.getCharacteristic(Characteristic.Brightness)
|
|
875
970
|
.updateValue(steps)
|
|
876
971
|
}
|
|
877
972
|
}
|
|
@@ -884,45 +979,34 @@ export class YotoPlayerAccessory {
|
|
|
884
979
|
}
|
|
885
980
|
|
|
886
981
|
/**
|
|
887
|
-
* Get volume
|
|
982
|
+
* Get volume On state (derived from volume > 0)
|
|
888
983
|
* @returns {Promise<CharacteristicValue>}
|
|
889
984
|
*/
|
|
890
|
-
async
|
|
891
|
-
|
|
892
|
-
return this.#deviceModel.status.volume === 0
|
|
893
|
-
? Characteristic.Active.INACTIVE
|
|
894
|
-
: Characteristic.Active.ACTIVE
|
|
985
|
+
async getVolumeOn () {
|
|
986
|
+
return this.#deviceModel.status.volume > 0
|
|
895
987
|
}
|
|
896
988
|
|
|
897
989
|
/**
|
|
898
|
-
* Set volume
|
|
990
|
+
* Set volume On state (mute/unmute)
|
|
899
991
|
* @param {CharacteristicValue} value
|
|
900
992
|
* @returns {Promise<void>}
|
|
901
993
|
*/
|
|
902
|
-
async
|
|
903
|
-
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Set volume
|
|
994
|
+
async setVolumeOn (value) {
|
|
995
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Set volume on:`, value)
|
|
904
996
|
|
|
905
|
-
const
|
|
906
|
-
const
|
|
907
|
-
if (!Number.isFinite(active)) {
|
|
908
|
-
throw new this.#platform.api.hap.HapStatusError(
|
|
909
|
-
this.#platform.api.hap.HAPStatus.INVALID_VALUE_IN_REQUEST
|
|
910
|
-
)
|
|
911
|
-
}
|
|
997
|
+
const isOn = Boolean(value)
|
|
998
|
+
const currentVolume = this.#deviceModel.status.volume
|
|
912
999
|
|
|
913
|
-
if (
|
|
914
|
-
|
|
1000
|
+
if (!isOn) {
|
|
1001
|
+
if (currentVolume !== 0) {
|
|
1002
|
+
await this.setVolume(0)
|
|
1003
|
+
}
|
|
915
1004
|
return
|
|
916
1005
|
}
|
|
917
1006
|
|
|
918
|
-
if (
|
|
1007
|
+
if (currentVolume === 0) {
|
|
919
1008
|
await this.setVolume(this.#lastNonZeroVolume)
|
|
920
|
-
return
|
|
921
1009
|
}
|
|
922
|
-
|
|
923
|
-
throw new this.#platform.api.hap.HapStatusError(
|
|
924
|
-
this.#platform.api.hap.HAPStatus.INVALID_VALUE_IN_REQUEST
|
|
925
|
-
)
|
|
926
1010
|
}
|
|
927
1011
|
|
|
928
1012
|
/**
|
|
@@ -933,6 +1017,17 @@ export class YotoPlayerAccessory {
|
|
|
933
1017
|
return this.#deviceModel.status.isOnline
|
|
934
1018
|
}
|
|
935
1019
|
|
|
1020
|
+
/**
|
|
1021
|
+
* Get online status as a ContactSensorState
|
|
1022
|
+
* @returns {Promise<CharacteristicValue>}
|
|
1023
|
+
*/
|
|
1024
|
+
async getOnlineStatus () {
|
|
1025
|
+
const { Characteristic } = this.#platform
|
|
1026
|
+
return this.#deviceModel.status.isOnline
|
|
1027
|
+
? Characteristic.ContactSensorState.CONTACT_DETECTED
|
|
1028
|
+
: Characteristic.ContactSensorState.CONTACT_NOT_DETECTED
|
|
1029
|
+
}
|
|
1030
|
+
|
|
936
1031
|
// ==================== Battery Characteristic Handlers ====================
|
|
937
1032
|
|
|
938
1033
|
/**
|
|
@@ -940,7 +1035,8 @@ export class YotoPlayerAccessory {
|
|
|
940
1035
|
* @returns {Promise<CharacteristicValue>}
|
|
941
1036
|
*/
|
|
942
1037
|
async getBatteryLevel () {
|
|
943
|
-
|
|
1038
|
+
const battery = this.#deviceModel.status.batteryLevelPercentage
|
|
1039
|
+
return Number.isFinite(battery) ? battery : 100
|
|
944
1040
|
}
|
|
945
1041
|
|
|
946
1042
|
/**
|
|
@@ -959,8 +1055,9 @@ export class YotoPlayerAccessory {
|
|
|
959
1055
|
* @returns {Promise<CharacteristicValue>}
|
|
960
1056
|
*/
|
|
961
1057
|
async getStatusLowBattery () {
|
|
962
|
-
const battery = this.#deviceModel.status.batteryLevelPercentage
|
|
963
|
-
|
|
1058
|
+
const battery = this.#deviceModel.status.batteryLevelPercentage
|
|
1059
|
+
const batteryLevel = Number.isFinite(battery) ? battery : 100
|
|
1060
|
+
return batteryLevel <= LOW_BATTERY_THRESHOLD
|
|
964
1061
|
? this.#platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW
|
|
965
1062
|
: this.#platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL
|
|
966
1063
|
}
|
|
@@ -1077,7 +1174,14 @@ export class YotoPlayerAccessory {
|
|
|
1077
1174
|
* @param {CharacteristicValue} value
|
|
1078
1175
|
*/
|
|
1079
1176
|
async setDayNightlightBrightness (value) {
|
|
1080
|
-
const
|
|
1177
|
+
const rawBrightness = Number(value)
|
|
1178
|
+
if (!Number.isFinite(rawBrightness)) {
|
|
1179
|
+
throw new this.#platform.api.hap.HapStatusError(
|
|
1180
|
+
this.#platform.api.hap.HAPStatus.INVALID_VALUE_IN_REQUEST
|
|
1181
|
+
)
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
const brightnessValue = Math.max(0, Math.min(Math.round(rawBrightness), 100))
|
|
1081
1185
|
this.#log.debug(LOG_PREFIX.ACCESSORY, `Setting day display brightness: ${brightnessValue}`)
|
|
1082
1186
|
await this.#deviceModel.updateConfig({
|
|
1083
1187
|
dayDisplayBrightness: brightnessValue,
|
|
@@ -1104,7 +1208,13 @@ export class YotoPlayerAccessory {
|
|
|
1104
1208
|
* @param {CharacteristicValue} value
|
|
1105
1209
|
*/
|
|
1106
1210
|
async setDayNightlightHue (value) {
|
|
1107
|
-
const
|
|
1211
|
+
const rawHue = Number(value)
|
|
1212
|
+
if (!Number.isFinite(rawHue)) {
|
|
1213
|
+
throw new this.#platform.api.hap.HapStatusError(
|
|
1214
|
+
this.#platform.api.hap.HAPStatus.INVALID_VALUE_IN_REQUEST
|
|
1215
|
+
)
|
|
1216
|
+
}
|
|
1217
|
+
const hue = Math.max(0, Math.min(rawHue, 360))
|
|
1108
1218
|
|
|
1109
1219
|
// Get current saturation to maintain it
|
|
1110
1220
|
const currentColor = this.#deviceModel.config.ambientColour
|
|
@@ -1142,7 +1252,13 @@ export class YotoPlayerAccessory {
|
|
|
1142
1252
|
* @param {CharacteristicValue} value
|
|
1143
1253
|
*/
|
|
1144
1254
|
async setDayNightlightSaturation (value) {
|
|
1145
|
-
const
|
|
1255
|
+
const rawSaturation = Number(value)
|
|
1256
|
+
if (!Number.isFinite(rawSaturation)) {
|
|
1257
|
+
throw new this.#platform.api.hap.HapStatusError(
|
|
1258
|
+
this.#platform.api.hap.HAPStatus.INVALID_VALUE_IN_REQUEST
|
|
1259
|
+
)
|
|
1260
|
+
}
|
|
1261
|
+
const saturation = Math.max(0, Math.min(rawSaturation, 100))
|
|
1146
1262
|
|
|
1147
1263
|
// Get current hue to maintain it
|
|
1148
1264
|
const currentColor = this.#deviceModel.config.ambientColour
|
|
@@ -1210,7 +1326,14 @@ export class YotoPlayerAccessory {
|
|
|
1210
1326
|
* @param {CharacteristicValue} value
|
|
1211
1327
|
*/
|
|
1212
1328
|
async setNightNightlightBrightness (value) {
|
|
1213
|
-
const
|
|
1329
|
+
const rawBrightness = Number(value)
|
|
1330
|
+
if (!Number.isFinite(rawBrightness)) {
|
|
1331
|
+
throw new this.#platform.api.hap.HapStatusError(
|
|
1332
|
+
this.#platform.api.hap.HAPStatus.INVALID_VALUE_IN_REQUEST
|
|
1333
|
+
)
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
const brightnessValue = Math.max(0, Math.min(Math.round(rawBrightness), 100))
|
|
1214
1337
|
this.#log.debug(LOG_PREFIX.ACCESSORY, `Setting night display brightness: ${brightnessValue}`)
|
|
1215
1338
|
await this.#deviceModel.updateConfig({
|
|
1216
1339
|
nightDisplayBrightness: brightnessValue,
|
|
@@ -1237,7 +1360,13 @@ export class YotoPlayerAccessory {
|
|
|
1237
1360
|
* @param {CharacteristicValue} value
|
|
1238
1361
|
*/
|
|
1239
1362
|
async setNightNightlightHue (value) {
|
|
1240
|
-
const
|
|
1363
|
+
const rawHue = Number(value)
|
|
1364
|
+
if (!Number.isFinite(rawHue)) {
|
|
1365
|
+
throw new this.#platform.api.hap.HapStatusError(
|
|
1366
|
+
this.#platform.api.hap.HAPStatus.INVALID_VALUE_IN_REQUEST
|
|
1367
|
+
)
|
|
1368
|
+
}
|
|
1369
|
+
const hue = Math.max(0, Math.min(rawHue, 360))
|
|
1241
1370
|
|
|
1242
1371
|
// Get current saturation to maintain it
|
|
1243
1372
|
const currentColor = this.#deviceModel.config.nightAmbientColour
|
|
@@ -1275,7 +1404,13 @@ export class YotoPlayerAccessory {
|
|
|
1275
1404
|
* @param {CharacteristicValue} value
|
|
1276
1405
|
*/
|
|
1277
1406
|
async setNightNightlightSaturation (value) {
|
|
1278
|
-
const
|
|
1407
|
+
const rawSaturation = Number(value)
|
|
1408
|
+
if (!Number.isFinite(rawSaturation)) {
|
|
1409
|
+
throw new this.#platform.api.hap.HapStatusError(
|
|
1410
|
+
this.#platform.api.hap.HAPStatus.INVALID_VALUE_IN_REQUEST
|
|
1411
|
+
)
|
|
1412
|
+
}
|
|
1413
|
+
const saturation = Math.max(0, Math.min(rawSaturation, 100))
|
|
1279
1414
|
|
|
1280
1415
|
// Get current hue to maintain it
|
|
1281
1416
|
const currentColor = this.#deviceModel.config.nightAmbientColour
|
|
@@ -1346,7 +1481,7 @@ export class YotoPlayerAccessory {
|
|
|
1346
1481
|
return hasCard ? Characteristic.ContactSensorState.CONTACT_DETECTED : Characteristic.ContactSensorState.CONTACT_NOT_DETECTED
|
|
1347
1482
|
}
|
|
1348
1483
|
|
|
1349
|
-
// ==================== Night Mode
|
|
1484
|
+
// ==================== Night Mode ContactSensor Getter ====================
|
|
1350
1485
|
|
|
1351
1486
|
/**
|
|
1352
1487
|
* Get night mode status
|
|
@@ -1356,7 +1491,9 @@ export class YotoPlayerAccessory {
|
|
|
1356
1491
|
const { Characteristic } = this.#platform
|
|
1357
1492
|
const status = this.#deviceModel.status
|
|
1358
1493
|
const isNightMode = status.dayMode === 'night'
|
|
1359
|
-
return isNightMode
|
|
1494
|
+
return isNightMode
|
|
1495
|
+
? Characteristic.ContactSensorState.CONTACT_DETECTED
|
|
1496
|
+
: Characteristic.ContactSensorState.CONTACT_NOT_DETECTED
|
|
1360
1497
|
}
|
|
1361
1498
|
|
|
1362
1499
|
// ==================== Sleep Timer Switch Getter/Setter ====================
|
|
@@ -1402,7 +1539,7 @@ export class YotoPlayerAccessory {
|
|
|
1402
1539
|
* @returns {Promise<CharacteristicValue>}
|
|
1403
1540
|
*/
|
|
1404
1541
|
async getBluetoothState () {
|
|
1405
|
-
return this.#deviceModel.config.bluetoothEnabled
|
|
1542
|
+
return this.#deviceModel.config.bluetoothEnabled ?? false
|
|
1406
1543
|
}
|
|
1407
1544
|
|
|
1408
1545
|
/**
|
|
@@ -1415,7 +1552,7 @@ export class YotoPlayerAccessory {
|
|
|
1415
1552
|
await this.#deviceModel.updateConfig({ bluetoothEnabled: enabled })
|
|
1416
1553
|
}
|
|
1417
1554
|
|
|
1418
|
-
// ==================== Volume Limit
|
|
1555
|
+
// ==================== Volume Limit Lightbulb Getters/Setters ====================
|
|
1419
1556
|
|
|
1420
1557
|
/**
|
|
1421
1558
|
* Get day max volume limit
|
|
@@ -1498,7 +1635,7 @@ export class YotoPlayerAccessory {
|
|
|
1498
1635
|
}
|
|
1499
1636
|
|
|
1500
1637
|
this.volumeService
|
|
1501
|
-
.getCharacteristic(Characteristic.
|
|
1638
|
+
.getCharacteristic(Characteristic.Brightness)
|
|
1502
1639
|
.updateValue(volumeSteps)
|
|
1503
1640
|
}
|
|
1504
1641
|
|
|
@@ -1513,7 +1650,7 @@ export class YotoPlayerAccessory {
|
|
|
1513
1650
|
const maxVolumeSteps = Number.isFinite(maxVolume) ? maxVolume : 16
|
|
1514
1651
|
const clampedMaxVolume = Math.max(0, Math.min(maxVolumeSteps, 16))
|
|
1515
1652
|
this.volumeService
|
|
1516
|
-
.getCharacteristic(Characteristic.
|
|
1653
|
+
.getCharacteristic(Characteristic.Brightness)
|
|
1517
1654
|
.setProps({
|
|
1518
1655
|
minValue: 0,
|
|
1519
1656
|
maxValue: clampedMaxVolume,
|
|
@@ -1531,12 +1668,9 @@ export class YotoPlayerAccessory {
|
|
|
1531
1668
|
if (!this.volumeService) return
|
|
1532
1669
|
|
|
1533
1670
|
const { Characteristic } = this.#platform
|
|
1534
|
-
const active = volume === 0
|
|
1535
|
-
? Characteristic.Active.INACTIVE
|
|
1536
|
-
: Characteristic.Active.ACTIVE
|
|
1537
1671
|
this.volumeService
|
|
1538
|
-
.getCharacteristic(Characteristic.
|
|
1539
|
-
.updateValue(
|
|
1672
|
+
.getCharacteristic(Characteristic.On)
|
|
1673
|
+
.updateValue(volume > 0)
|
|
1540
1674
|
}
|
|
1541
1675
|
|
|
1542
1676
|
/**
|
|
@@ -1590,17 +1724,12 @@ export class YotoPlayerAccessory {
|
|
|
1590
1724
|
* Update online status characteristic for all device-state services
|
|
1591
1725
|
*
|
|
1592
1726
|
* Services that need StatusActive (read device state, unavailable when offline):
|
|
1593
|
-
* - Switch services (playback, seek)
|
|
1594
|
-
* - Speaker (volume, mute)
|
|
1595
|
-
* - Battery (battery level, charging state)
|
|
1596
1727
|
* - TemperatureSensor (temperature reading)
|
|
1597
|
-
* - ContactSensor (card insertion
|
|
1598
|
-
* - OccupancySensor (day/night mode from device) - when implemented
|
|
1599
|
-
* - Switch (Sleep Timer - reads device playback state) - when implemented
|
|
1728
|
+
* - ContactSensor (online status, card insertion, day/night mode, nightlight status)
|
|
1600
1729
|
*
|
|
1601
1730
|
* Services that DON'T need StatusActive (config-based, work offline):
|
|
1602
1731
|
* - Lightbulb services (ambient lights - config only)
|
|
1603
|
-
* -
|
|
1732
|
+
* - Lightbulb services (max volume - config only)
|
|
1604
1733
|
* - Switch (Bluetooth - config only)
|
|
1605
1734
|
* - StatelessProgrammableSwitch (shortcuts - config only)
|
|
1606
1735
|
*
|
|
@@ -1609,26 +1738,14 @@ export class YotoPlayerAccessory {
|
|
|
1609
1738
|
updateOnlineStatusCharacteristic (isOnline) {
|
|
1610
1739
|
const { Characteristic } = this.#platform
|
|
1611
1740
|
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
.
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
if (this.volumeService) {
|
|
1619
|
-
this.volumeService
|
|
1620
|
-
.getCharacteristic(Characteristic.StatusActive)
|
|
1621
|
-
.updateValue(isOnline)
|
|
1741
|
+
if (this.onlineStatusService) {
|
|
1742
|
+
this.onlineStatusService
|
|
1743
|
+
.getCharacteristic(Characteristic.ContactSensorState)
|
|
1744
|
+
.updateValue(isOnline
|
|
1745
|
+
? Characteristic.ContactSensorState.CONTACT_DETECTED
|
|
1746
|
+
: Characteristic.ContactSensorState.CONTACT_NOT_DETECTED)
|
|
1622
1747
|
}
|
|
1623
1748
|
|
|
1624
|
-
// Update Battery (battery level, charging state)
|
|
1625
|
-
// Battery Doesn't support this
|
|
1626
|
-
// if (this.batteryService) {
|
|
1627
|
-
// this.batteryService
|
|
1628
|
-
// .getCharacteristic(Characteristic.StatusActive)
|
|
1629
|
-
// .updateValue(isOnline)
|
|
1630
|
-
// }
|
|
1631
|
-
|
|
1632
1749
|
// Update TemperatureSensor (temperature reading)
|
|
1633
1750
|
if (this.temperatureSensorService) {
|
|
1634
1751
|
this.temperatureSensorService
|
|
@@ -1660,21 +1777,14 @@ export class YotoPlayerAccessory {
|
|
|
1660
1777
|
.updateValue(isOnline)
|
|
1661
1778
|
}
|
|
1662
1779
|
|
|
1663
|
-
// Update night mode
|
|
1780
|
+
// Update night mode ContactSensor (device state)
|
|
1664
1781
|
if (this.nightModeService) {
|
|
1665
1782
|
this.nightModeService
|
|
1666
1783
|
.getCharacteristic(Characteristic.StatusActive)
|
|
1667
1784
|
.updateValue(isOnline)
|
|
1668
1785
|
}
|
|
1669
1786
|
|
|
1670
|
-
//
|
|
1671
|
-
if (this.sleepTimerService) {
|
|
1672
|
-
this.sleepTimerService
|
|
1673
|
-
.getCharacteristic(Characteristic.StatusActive)
|
|
1674
|
-
.updateValue(isOnline)
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
|
-
// Note: Config-based services (Nightlight Lightbulbs, Fanv2, Bluetooth Switch, Shortcuts)
|
|
1787
|
+
// Note: Config-based services (Nightlight Lightbulbs, Volume Limit Lightbulbs, Bluetooth Switch, Shortcuts)
|
|
1678
1788
|
// do NOT get StatusActive updated - they work offline since they only read/write config
|
|
1679
1789
|
|
|
1680
1790
|
// TODO: Add shortcut services when implemented
|
|
@@ -1753,7 +1863,7 @@ export class YotoPlayerAccessory {
|
|
|
1753
1863
|
}
|
|
1754
1864
|
|
|
1755
1865
|
/**
|
|
1756
|
-
* Update night mode
|
|
1866
|
+
* Update night mode ContactSensor characteristic
|
|
1757
1867
|
*/
|
|
1758
1868
|
updateNightModeCharacteristic () {
|
|
1759
1869
|
if (!this.nightModeService) {
|
|
@@ -1765,8 +1875,8 @@ export class YotoPlayerAccessory {
|
|
|
1765
1875
|
const isNightMode = status.dayMode === 'night'
|
|
1766
1876
|
|
|
1767
1877
|
this.nightModeService
|
|
1768
|
-
.getCharacteristic(Characteristic.
|
|
1769
|
-
.updateValue(isNightMode ? Characteristic.
|
|
1878
|
+
.getCharacteristic(Characteristic.ContactSensorState)
|
|
1879
|
+
.updateValue(isNightMode ? Characteristic.ContactSensorState.CONTACT_DETECTED : Characteristic.ContactSensorState.CONTACT_NOT_DETECTED)
|
|
1770
1880
|
}
|
|
1771
1881
|
|
|
1772
1882
|
/**
|
|
@@ -1782,7 +1892,7 @@ export class YotoPlayerAccessory {
|
|
|
1782
1892
|
|
|
1783
1893
|
this.sleepTimerService
|
|
1784
1894
|
.getCharacteristic(Characteristic.On)
|
|
1785
|
-
.updateValue(playback.sleepTimerActive)
|
|
1895
|
+
.updateValue(playback.sleepTimerActive ?? false)
|
|
1786
1896
|
}
|
|
1787
1897
|
|
|
1788
1898
|
/**
|
|
@@ -1794,7 +1904,7 @@ export class YotoPlayerAccessory {
|
|
|
1794
1904
|
}
|
|
1795
1905
|
|
|
1796
1906
|
const { Characteristic } = this.#platform
|
|
1797
|
-
const enabled = this.#deviceModel.config.bluetoothEnabled
|
|
1907
|
+
const enabled = this.#deviceModel.config.bluetoothEnabled ?? false
|
|
1798
1908
|
|
|
1799
1909
|
this.bluetoothService
|
|
1800
1910
|
.getCharacteristic(Characteristic.On)
|
|
@@ -1802,7 +1912,7 @@ export class YotoPlayerAccessory {
|
|
|
1802
1912
|
}
|
|
1803
1913
|
|
|
1804
1914
|
/**
|
|
1805
|
-
* Update volume limit
|
|
1915
|
+
* Update volume limit Lightbulb characteristics
|
|
1806
1916
|
*/
|
|
1807
1917
|
updateVolumeLimitCharacteristics () {
|
|
1808
1918
|
const config = this.#deviceModel.config
|
|
@@ -1811,14 +1921,14 @@ export class YotoPlayerAccessory {
|
|
|
1811
1921
|
if (this.dayMaxVolumeService) {
|
|
1812
1922
|
const limit = Number.isFinite(config.maxVolumeLimit) ? config.maxVolumeLimit : 16
|
|
1813
1923
|
this.dayMaxVolumeService
|
|
1814
|
-
.getCharacteristic(Characteristic.
|
|
1924
|
+
.getCharacteristic(Characteristic.Brightness)
|
|
1815
1925
|
.updateValue(limit)
|
|
1816
1926
|
}
|
|
1817
1927
|
|
|
1818
1928
|
if (this.nightMaxVolumeService) {
|
|
1819
1929
|
const limit = Number.isFinite(config.nightMaxVolumeLimit) ? config.nightMaxVolumeLimit : 10
|
|
1820
1930
|
this.nightMaxVolumeService
|
|
1821
|
-
.getCharacteristic(Characteristic.
|
|
1931
|
+
.getCharacteristic(Characteristic.Brightness)
|
|
1822
1932
|
.updateValue(limit)
|
|
1823
1933
|
}
|
|
1824
1934
|
}
|
package/lib/platform.js
CHANGED
|
@@ -204,7 +204,9 @@ export class YotoPlatform {
|
|
|
204
204
|
})
|
|
205
205
|
|
|
206
206
|
this.yotoAccount.on('mqttConnect', ({ deviceId }) => {
|
|
207
|
-
this.
|
|
207
|
+
const deviceName = this.yotoAccount?.getDevice(deviceId)?.device?.name
|
|
208
|
+
const label = deviceName ? `${deviceName} (${deviceId})` : deviceId
|
|
209
|
+
this.log.debug(`MQTT connected: ${label}`)
|
|
208
210
|
})
|
|
209
211
|
|
|
210
212
|
this.yotoAccount.on('mqttDisconnect', ({ deviceId, metadata }) => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-yoto",
|
|
3
3
|
"description": "Control your Yoto players through Apple HomeKit with real-time MQTT updates",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.36",
|
|
5
5
|
"author": "Bret Comnes <bcomnes@gmail.com> (https://bret.io)",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/bcomnes/homebridge-yoto/issues"
|