homebridge-yoto 0.0.18 → 0.0.20
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/lib/auth.js +2 -2
- package/lib/platform.js +8 -8
- package/lib/playerAccessory.js +140 -76
- package/lib/yotoApi.js +1 -1
- package/lib/yotoMqtt.js +7 -7
- package/package.json +1 -1
package/lib/auth.js
CHANGED
|
@@ -35,7 +35,7 @@ export class YotoAuth {
|
|
|
35
35
|
* @returns {Promise<YotoApiDeviceCodeResponse>}
|
|
36
36
|
*/
|
|
37
37
|
async initiateDeviceFlow () {
|
|
38
|
-
this.log.
|
|
38
|
+
this.log.debug(LOG_PREFIX.AUTH, 'Initiating device authorization flow...')
|
|
39
39
|
this.log.debug(LOG_PREFIX.AUTH, `Client ID: ${this.clientId}`)
|
|
40
40
|
this.log.debug(LOG_PREFIX.AUTH, `Endpoint: ${YOTO_OAUTH_DEVICE_CODE_URL}`)
|
|
41
41
|
this.log.debug(LOG_PREFIX.AUTH, `Scope: ${OAUTH_SCOPE}`)
|
|
@@ -97,7 +97,7 @@ export class YotoAuth {
|
|
|
97
97
|
const startTime = Date.now()
|
|
98
98
|
const timeout = OAUTH_DEVICE_CODE_TIMEOUT
|
|
99
99
|
|
|
100
|
-
this.log.
|
|
100
|
+
this.log.debug(LOG_PREFIX.AUTH, 'Waiting for user authorization...')
|
|
101
101
|
|
|
102
102
|
while (Date.now() - startTime < timeout) {
|
|
103
103
|
try {
|
package/lib/platform.js
CHANGED
|
@@ -219,7 +219,7 @@ export class YotoPlatform {
|
|
|
219
219
|
*/
|
|
220
220
|
async performDeviceFlow () {
|
|
221
221
|
this.log.warn(LOG_PREFIX.PLATFORM, ERROR_MESSAGES.NO_AUTH)
|
|
222
|
-
this.log.
|
|
222
|
+
this.log.debug(LOG_PREFIX.PLATFORM, 'Starting OAuth flow...')
|
|
223
223
|
|
|
224
224
|
const tokenResponse = await this.auth.authorize()
|
|
225
225
|
|
|
@@ -258,7 +258,7 @@ export class YotoPlatform {
|
|
|
258
258
|
* @param {number} expiresAt - New expiration timestamp
|
|
259
259
|
*/
|
|
260
260
|
handleTokenRefresh (accessToken, refreshToken, expiresAt) {
|
|
261
|
-
this.log.
|
|
261
|
+
this.log.debug(LOG_PREFIX.PLATFORM, 'Token refreshed')
|
|
262
262
|
this.config.accessToken = accessToken
|
|
263
263
|
this.config.refreshToken = refreshToken
|
|
264
264
|
this.config.tokenExpiresAt = expiresAt
|
|
@@ -329,7 +329,7 @@ export class YotoPlatform {
|
|
|
329
329
|
* @param {PlatformAccessory<YotoAccessoryContext>} accessory - Cached accessory
|
|
330
330
|
*/
|
|
331
331
|
configureAccessory (accessory) {
|
|
332
|
-
this.log.
|
|
332
|
+
this.log.debug(LOG_PREFIX.PLATFORM, 'Loading accessory from cache:', accessory.displayName)
|
|
333
333
|
|
|
334
334
|
// Add to our tracking map
|
|
335
335
|
this.accessories.set(accessory.UUID, accessory)
|
|
@@ -341,7 +341,7 @@ export class YotoPlatform {
|
|
|
341
341
|
*/
|
|
342
342
|
async discoverDevices () {
|
|
343
343
|
try {
|
|
344
|
-
this.log.
|
|
344
|
+
this.log.debug(LOG_PREFIX.PLATFORM, 'Discovering Yoto devices...')
|
|
345
345
|
|
|
346
346
|
// Fetch devices from API
|
|
347
347
|
const devices = await this.yotoApi.getDevices()
|
|
@@ -381,7 +381,7 @@ export class YotoPlatform {
|
|
|
381
381
|
|
|
382
382
|
if (existingAccessory) {
|
|
383
383
|
// Accessory exists - update it
|
|
384
|
-
this.log.
|
|
384
|
+
this.log.debug(LOG_PREFIX.PLATFORM, 'Restoring existing accessory:', device.name)
|
|
385
385
|
|
|
386
386
|
// Update context with fresh device data
|
|
387
387
|
const typedAccessory = /** @type {PlatformAccessory<YotoAccessoryContext>} */ (existingAccessory)
|
|
@@ -403,7 +403,7 @@ export class YotoPlatform {
|
|
|
403
403
|
})
|
|
404
404
|
} else {
|
|
405
405
|
// Create new accessory
|
|
406
|
-
this.log.
|
|
406
|
+
this.log.debug(LOG_PREFIX.PLATFORM, 'Adding new accessory:', device.name)
|
|
407
407
|
|
|
408
408
|
// Create platform accessory
|
|
409
409
|
// eslint-disable-next-line new-cap
|
|
@@ -447,7 +447,7 @@ export class YotoPlatform {
|
|
|
447
447
|
|
|
448
448
|
for (const [uuid, accessory] of this.accessories) {
|
|
449
449
|
if (!this.discoveredUUIDs.includes(uuid)) {
|
|
450
|
-
this.log.
|
|
450
|
+
this.log.debug(LOG_PREFIX.PLATFORM, 'Removing stale accessory:', accessory.displayName)
|
|
451
451
|
staleAccessories.push(accessory)
|
|
452
452
|
}
|
|
453
453
|
}
|
|
@@ -473,7 +473,7 @@ export class YotoPlatform {
|
|
|
473
473
|
* Shutdown handler - cleanup connections
|
|
474
474
|
*/
|
|
475
475
|
async shutdown () {
|
|
476
|
-
this.log.
|
|
476
|
+
this.log.debug(LOG_PREFIX.PLATFORM, 'Shutting down...')
|
|
477
477
|
|
|
478
478
|
// Stop status polling
|
|
479
479
|
this.stopStatusPolling()
|
package/lib/playerAccessory.js
CHANGED
|
@@ -38,6 +38,9 @@ export class YotoPlayerAccessory {
|
|
|
38
38
|
this.currentEvents = accessory.context.lastEvents || null
|
|
39
39
|
this.lastUpdateTime = Date.now()
|
|
40
40
|
|
|
41
|
+
// Cache for device config
|
|
42
|
+
this.deviceConfig = null
|
|
43
|
+
|
|
41
44
|
// Create dedicated MQTT client for this device
|
|
42
45
|
this.mqtt = new YotoMqtt(this.log)
|
|
43
46
|
|
|
@@ -110,6 +113,15 @@ export class YotoPlayerAccessory {
|
|
|
110
113
|
* @returns {Promise<void>}
|
|
111
114
|
*/
|
|
112
115
|
async initialize () {
|
|
116
|
+
// Fetch device config for cached access
|
|
117
|
+
try {
|
|
118
|
+
this.deviceConfig = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
|
|
119
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Device config loaded`)
|
|
120
|
+
} catch (error) {
|
|
121
|
+
this.log.warn(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to load device config:`, error)
|
|
122
|
+
// Continue without config - getters will return defaults
|
|
123
|
+
}
|
|
124
|
+
|
|
113
125
|
await this.connectMqtt()
|
|
114
126
|
// Status polling is handled at platform level
|
|
115
127
|
}
|
|
@@ -514,14 +526,14 @@ export class YotoPlayerAccessory {
|
|
|
514
526
|
* @param {YotoDeviceStatus | {status: YotoDeviceStatus}} statusMessage - Status data or wrapped status
|
|
515
527
|
*/
|
|
516
528
|
handleStatusUpdate (statusMessage) {
|
|
517
|
-
this.log.
|
|
529
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Raw MQTT status message:`, JSON.stringify(statusMessage, null, 2))
|
|
518
530
|
|
|
519
531
|
// Unwrap status if it's nested under a 'status' property
|
|
520
532
|
const status = /** @type {YotoDeviceStatus} */ ('status' in statusMessage ? statusMessage.status : statusMessage)
|
|
521
533
|
|
|
522
|
-
this.log.
|
|
523
|
-
this.log.
|
|
524
|
-
this.log.
|
|
534
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Unwrapped status - batteryLevel:`, status.batteryLevel)
|
|
535
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Unwrapped status - userVolume:`, status.userVolume)
|
|
536
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Unwrapped status - volume:`, status.volume)
|
|
525
537
|
|
|
526
538
|
this.currentStatus = status
|
|
527
539
|
this.lastUpdateTime = Date.now()
|
|
@@ -536,12 +548,12 @@ export class YotoPlayerAccessory {
|
|
|
536
548
|
* @param {YotoPlaybackEvents | {events: YotoPlaybackEvents}} eventsMessage - Playback events or wrapped events
|
|
537
549
|
*/
|
|
538
550
|
handleEventsUpdate (eventsMessage) {
|
|
539
|
-
this.log.
|
|
551
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Raw MQTT events message:`, JSON.stringify(eventsMessage, null, 2))
|
|
540
552
|
|
|
541
553
|
// Unwrap events if it's nested under an 'events' property
|
|
542
554
|
const events = /** @type {YotoPlaybackEvents} */ ('events' in eventsMessage ? eventsMessage.events : eventsMessage)
|
|
543
555
|
|
|
544
|
-
this.log.
|
|
556
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Unwrapped events - cardId:`, events.cardId)
|
|
545
557
|
this.currentEvents = events
|
|
546
558
|
this.lastUpdateTime = Date.now()
|
|
547
559
|
this.accessory.context.lastEvents = events
|
|
@@ -560,7 +572,7 @@ export class YotoPlayerAccessory {
|
|
|
560
572
|
* @param {import('./types.js').MqttCommandResponse} response - Command response
|
|
561
573
|
*/
|
|
562
574
|
handleCommandResponse (response) {
|
|
563
|
-
this.log.
|
|
575
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Command response:`, response)
|
|
564
576
|
}
|
|
565
577
|
|
|
566
578
|
/**
|
|
@@ -799,16 +811,16 @@ export class YotoPlayerAccessory {
|
|
|
799
811
|
* @returns {Promise<CharacteristicValue>}
|
|
800
812
|
*/
|
|
801
813
|
async getVolume () {
|
|
802
|
-
this.log.
|
|
803
|
-
this.log.
|
|
814
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getVolume - currentEvents:`, this.currentEvents)
|
|
815
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getVolume - events.volume:`, this.currentEvents?.volume)
|
|
804
816
|
|
|
805
817
|
// Volume comes from events, not status
|
|
806
818
|
if (!this.currentEvents || this.currentEvents.volume === undefined) {
|
|
807
|
-
this.log.
|
|
819
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getVolume - no volume in events, returning default: 50`)
|
|
808
820
|
return 50
|
|
809
821
|
}
|
|
810
822
|
const volume = Number(this.currentEvents.volume) || 50
|
|
811
|
-
this.log.
|
|
823
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getVolume - returning volume:`, volume)
|
|
812
824
|
return volume
|
|
813
825
|
}
|
|
814
826
|
|
|
@@ -871,15 +883,15 @@ export class YotoPlayerAccessory {
|
|
|
871
883
|
* @returns {Promise<CharacteristicValue>}
|
|
872
884
|
*/
|
|
873
885
|
async getBatteryLevel () {
|
|
874
|
-
this.log.
|
|
875
|
-
this.log.
|
|
886
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getBatteryLevel - currentStatus:`, this.currentStatus)
|
|
887
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getBatteryLevel - batteryLevel:`, this.currentStatus?.batteryLevel)
|
|
876
888
|
|
|
877
889
|
if (!this.currentStatus || this.currentStatus.batteryLevel === undefined) {
|
|
878
|
-
this.log.
|
|
890
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getBatteryLevel - returning default: 100`)
|
|
879
891
|
return 100
|
|
880
892
|
}
|
|
881
893
|
const battery = Number(this.currentStatus.batteryLevel) || 100
|
|
882
|
-
this.log.
|
|
894
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getBatteryLevel - returning:`, battery)
|
|
883
895
|
return battery
|
|
884
896
|
}
|
|
885
897
|
|
|
@@ -977,20 +989,23 @@ export class YotoPlayerAccessory {
|
|
|
977
989
|
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Set display on:`, value)
|
|
978
990
|
|
|
979
991
|
try {
|
|
980
|
-
//
|
|
981
|
-
|
|
992
|
+
// Refresh config if not cached
|
|
993
|
+
if (!this.deviceConfig) {
|
|
994
|
+
this.deviceConfig = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
|
|
995
|
+
}
|
|
982
996
|
|
|
983
997
|
if (value) {
|
|
984
|
-
// Turn on - restore to
|
|
985
|
-
|
|
986
|
-
|
|
998
|
+
// Turn on - restore to previous brightness or default
|
|
999
|
+
const brightness = this.currentStatus?.dnowBrightness || 100
|
|
1000
|
+
this.deviceConfig.config.dayDisplayBrightness = String(brightness)
|
|
1001
|
+
this.deviceConfig.config.nightDisplayBrightness = String(brightness)
|
|
987
1002
|
} else {
|
|
988
1003
|
// Turn off - set brightness to 0
|
|
989
|
-
|
|
990
|
-
|
|
1004
|
+
this.deviceConfig.config.dayDisplayBrightness = '0'
|
|
1005
|
+
this.deviceConfig.config.nightDisplayBrightness = '0'
|
|
991
1006
|
}
|
|
992
1007
|
|
|
993
|
-
await this.platform.yotoApi.updateDeviceConfig(this.device.deviceId,
|
|
1008
|
+
await this.platform.yotoApi.updateDeviceConfig(this.device.deviceId, this.deviceConfig)
|
|
994
1009
|
} catch (error) {
|
|
995
1010
|
this.log.error(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to set display on:`, error)
|
|
996
1011
|
throw new this.platform.api.hap.HapStatusError(
|
|
@@ -1024,16 +1039,19 @@ export class YotoPlayerAccessory {
|
|
|
1024
1039
|
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Set display brightness:`, value)
|
|
1025
1040
|
|
|
1026
1041
|
try {
|
|
1027
|
-
|
|
1042
|
+
// Refresh config if not cached
|
|
1043
|
+
if (!this.deviceConfig) {
|
|
1044
|
+
this.deviceConfig = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
|
|
1045
|
+
}
|
|
1028
1046
|
|
|
1029
|
-
//
|
|
1030
|
-
const brightness =
|
|
1047
|
+
// Clamp brightness to 0-100
|
|
1048
|
+
const brightness = Math.max(0, Math.min(100, Number(value)))
|
|
1031
1049
|
|
|
1032
1050
|
// Update both day and night brightness
|
|
1033
|
-
|
|
1034
|
-
|
|
1051
|
+
this.deviceConfig.config.dayDisplayBrightness = String(brightness)
|
|
1052
|
+
this.deviceConfig.config.nightDisplayBrightness = String(brightness)
|
|
1035
1053
|
|
|
1036
|
-
await this.platform.yotoApi.updateDeviceConfig(this.device.deviceId,
|
|
1054
|
+
await this.platform.yotoApi.updateDeviceConfig(this.device.deviceId, this.deviceConfig)
|
|
1037
1055
|
} catch (error) {
|
|
1038
1056
|
this.log.error(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to set display brightness:`, error)
|
|
1039
1057
|
throw new this.platform.api.hap.HapStatusError(
|
|
@@ -1058,12 +1076,16 @@ export class YotoPlayerAccessory {
|
|
|
1058
1076
|
* @param {CharacteristicValue} value - Enabled state
|
|
1059
1077
|
*/
|
|
1060
1078
|
async setBluetoothEnabled (value) {
|
|
1061
|
-
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Set Bluetooth
|
|
1079
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Set Bluetooth:`, value)
|
|
1062
1080
|
|
|
1063
1081
|
try {
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1082
|
+
// Refresh config if not cached
|
|
1083
|
+
if (!this.deviceConfig) {
|
|
1084
|
+
this.deviceConfig = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
this.deviceConfig.config.bluetoothEnabled = value ? 'true' : 'false'
|
|
1088
|
+
await this.platform.yotoApi.updateDeviceConfig(this.device.deviceId, this.deviceConfig)
|
|
1067
1089
|
} catch (error) {
|
|
1068
1090
|
this.log.error(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to set Bluetooth:`, error)
|
|
1069
1091
|
throw new this.platform.api.hap.HapStatusError(
|
|
@@ -1091,11 +1113,15 @@ export class YotoPlayerAccessory {
|
|
|
1091
1113
|
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Set repeat all:`, value)
|
|
1092
1114
|
|
|
1093
1115
|
try {
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1116
|
+
// Refresh config if not cached
|
|
1117
|
+
if (!this.deviceConfig) {
|
|
1118
|
+
this.deviceConfig = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
this.deviceConfig.config.repeatAll = Boolean(value)
|
|
1122
|
+
await this.platform.yotoApi.updateDeviceConfig(this.device.deviceId, this.deviceConfig)
|
|
1097
1123
|
} catch (error) {
|
|
1098
|
-
this.log.error(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to set repeat:`, error)
|
|
1124
|
+
this.log.error(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to set repeat all:`, error)
|
|
1099
1125
|
throw new this.platform.api.hap.HapStatusError(
|
|
1100
1126
|
this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE
|
|
1101
1127
|
)
|
|
@@ -1118,12 +1144,16 @@ export class YotoPlayerAccessory {
|
|
|
1118
1144
|
* @param {CharacteristicValue} value - Enabled state
|
|
1119
1145
|
*/
|
|
1120
1146
|
async setBtHeadphonesEnabled (value) {
|
|
1121
|
-
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Set BT headphones
|
|
1147
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Set BT headphones:`, value)
|
|
1122
1148
|
|
|
1123
1149
|
try {
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1150
|
+
// Refresh config if not cached
|
|
1151
|
+
if (!this.deviceConfig) {
|
|
1152
|
+
this.deviceConfig = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
this.deviceConfig.config.btHeadphonesEnabled = Boolean(value)
|
|
1156
|
+
await this.platform.yotoApi.updateDeviceConfig(this.device.deviceId, this.deviceConfig)
|
|
1127
1157
|
} catch (error) {
|
|
1128
1158
|
this.log.error(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to set BT headphones:`, error)
|
|
1129
1159
|
throw new this.platform.api.hap.HapStatusError(
|
|
@@ -1233,16 +1263,19 @@ export class YotoPlayerAccessory {
|
|
|
1233
1263
|
* @returns {Promise<CharacteristicValue>}
|
|
1234
1264
|
*/
|
|
1235
1265
|
async getDayVolumeLimit () {
|
|
1266
|
+
if (!this.deviceConfig) {
|
|
1267
|
+
return 100 // Default to max
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1236
1270
|
try {
|
|
1237
|
-
const
|
|
1238
|
-
const limit = parseInt(config.config.maxVolumeLimit || '16')
|
|
1271
|
+
const limit = parseInt(this.deviceConfig.config.maxVolumeLimit || '16')
|
|
1239
1272
|
if (isNaN(limit)) {
|
|
1240
1273
|
return 100
|
|
1241
1274
|
}
|
|
1242
1275
|
// Map 0-16 to 0-100
|
|
1243
1276
|
return Math.round((limit / 16) * 100)
|
|
1244
1277
|
} catch (error) {
|
|
1245
|
-
this.log.
|
|
1278
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to parse day volume limit:`, error)
|
|
1246
1279
|
return 100
|
|
1247
1280
|
}
|
|
1248
1281
|
}
|
|
@@ -1255,11 +1288,15 @@ export class YotoPlayerAccessory {
|
|
|
1255
1288
|
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Set day volume limit:`, value)
|
|
1256
1289
|
|
|
1257
1290
|
try {
|
|
1258
|
-
|
|
1291
|
+
// Refresh config if not cached
|
|
1292
|
+
if (!this.deviceConfig) {
|
|
1293
|
+
this.deviceConfig = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1259
1296
|
// Map 0-100 to 0-16
|
|
1260
1297
|
const limit = Math.round((Number(value) / 100) * 16)
|
|
1261
|
-
|
|
1262
|
-
await this.platform.yotoApi.updateDeviceConfig(this.device.deviceId,
|
|
1298
|
+
this.deviceConfig.config.maxVolumeLimit = String(limit)
|
|
1299
|
+
await this.platform.yotoApi.updateDeviceConfig(this.device.deviceId, this.deviceConfig)
|
|
1263
1300
|
} catch (error) {
|
|
1264
1301
|
this.log.error(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to set day volume limit:`, error)
|
|
1265
1302
|
throw new this.platform.api.hap.HapStatusError(
|
|
@@ -1291,16 +1328,19 @@ export class YotoPlayerAccessory {
|
|
|
1291
1328
|
* @returns {Promise<CharacteristicValue>}
|
|
1292
1329
|
*/
|
|
1293
1330
|
async getNightVolumeLimit () {
|
|
1331
|
+
if (!this.deviceConfig) {
|
|
1332
|
+
return 100 // Default to max
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1294
1335
|
try {
|
|
1295
|
-
const
|
|
1296
|
-
const limit = parseInt(config.config.nightMaxVolumeLimit || '16')
|
|
1336
|
+
const limit = parseInt(this.deviceConfig.config.nightMaxVolumeLimit || '16')
|
|
1297
1337
|
if (isNaN(limit)) {
|
|
1298
1338
|
return 100
|
|
1299
1339
|
}
|
|
1300
1340
|
// Map 0-16 to 0-100
|
|
1301
1341
|
return Math.round((limit / 16) * 100)
|
|
1302
1342
|
} catch (error) {
|
|
1303
|
-
this.log.
|
|
1343
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to parse night volume limit:`, error)
|
|
1304
1344
|
return 100
|
|
1305
1345
|
}
|
|
1306
1346
|
}
|
|
@@ -1313,11 +1353,15 @@ export class YotoPlayerAccessory {
|
|
|
1313
1353
|
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Set night volume limit:`, value)
|
|
1314
1354
|
|
|
1315
1355
|
try {
|
|
1316
|
-
|
|
1356
|
+
// Refresh config if not cached
|
|
1357
|
+
if (!this.deviceConfig) {
|
|
1358
|
+
this.deviceConfig = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1317
1361
|
// Map 0-100 to 0-16
|
|
1318
1362
|
const limit = Math.round((Number(value) / 100) * 16)
|
|
1319
|
-
|
|
1320
|
-
await this.platform.yotoApi.updateDeviceConfig(this.device.deviceId,
|
|
1363
|
+
this.deviceConfig.config.nightMaxVolumeLimit = String(limit)
|
|
1364
|
+
await this.platform.yotoApi.updateDeviceConfig(this.device.deviceId, this.deviceConfig)
|
|
1321
1365
|
} catch (error) {
|
|
1322
1366
|
this.log.error(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to set night volume limit:`, error)
|
|
1323
1367
|
throw new this.platform.api.hap.HapStatusError(
|
|
@@ -1331,13 +1375,16 @@ export class YotoPlayerAccessory {
|
|
|
1331
1375
|
* @returns {Promise<CharacteristicValue>}
|
|
1332
1376
|
*/
|
|
1333
1377
|
async getAmbientLightOn () {
|
|
1378
|
+
if (!this.deviceConfig) {
|
|
1379
|
+
return false // Default to off
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1334
1382
|
try {
|
|
1335
|
-
const
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
return color !== '#000000' && color !== 'off'
|
|
1383
|
+
const color = this.deviceConfig.config.ambientColour || '000000'
|
|
1384
|
+
// Consider light "on" if not pure black
|
|
1385
|
+
return color !== '000000'
|
|
1339
1386
|
} catch (error) {
|
|
1340
|
-
this.log.
|
|
1387
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to parse ambient light state:`, error)
|
|
1341
1388
|
return false
|
|
1342
1389
|
}
|
|
1343
1390
|
}
|
|
@@ -1350,11 +1397,16 @@ export class YotoPlayerAccessory {
|
|
|
1350
1397
|
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Set ambient light on:`, value)
|
|
1351
1398
|
|
|
1352
1399
|
try {
|
|
1400
|
+
// Refresh config if not cached
|
|
1401
|
+
if (!this.deviceConfig) {
|
|
1402
|
+
this.deviceConfig = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1353
1405
|
if (value) {
|
|
1354
|
-
//
|
|
1355
|
-
await this.
|
|
1406
|
+
// Restore to a default color (warm white) when turning on
|
|
1407
|
+
await this.updateAmbientLightColor()
|
|
1356
1408
|
} else {
|
|
1357
|
-
// Turn off
|
|
1409
|
+
// Turn off by setting to black
|
|
1358
1410
|
await this.mqtt.setAmbientLight(this.device.deviceId, 0, 0, 0)
|
|
1359
1411
|
}
|
|
1360
1412
|
} catch (error) {
|
|
@@ -1370,16 +1422,19 @@ export class YotoPlayerAccessory {
|
|
|
1370
1422
|
* @returns {Promise<CharacteristicValue>}
|
|
1371
1423
|
*/
|
|
1372
1424
|
async getAmbientLightHue () {
|
|
1425
|
+
if (!this.deviceConfig) {
|
|
1426
|
+
return 0 // Default hue
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1373
1429
|
try {
|
|
1374
|
-
const
|
|
1375
|
-
const hex = config.config.ambientColour || '#000000'
|
|
1430
|
+
const hex = this.deviceConfig.config.ambientColour || '000000'
|
|
1376
1431
|
const { h } = this.hexToHsv(hex)
|
|
1377
1432
|
if (isNaN(h)) {
|
|
1378
1433
|
return 0
|
|
1379
1434
|
}
|
|
1380
1435
|
return h
|
|
1381
1436
|
} catch (error) {
|
|
1382
|
-
this.log.
|
|
1437
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to parse ambient light hue:`, error)
|
|
1383
1438
|
return 0
|
|
1384
1439
|
}
|
|
1385
1440
|
}
|
|
@@ -1400,16 +1455,19 @@ export class YotoPlayerAccessory {
|
|
|
1400
1455
|
* @returns {Promise<CharacteristicValue>}
|
|
1401
1456
|
*/
|
|
1402
1457
|
async getAmbientLightSaturation () {
|
|
1458
|
+
if (!this.deviceConfig) {
|
|
1459
|
+
return 0 // Default saturation
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1403
1462
|
try {
|
|
1404
|
-
const
|
|
1405
|
-
const hex = config.config.ambientColour || '#000000'
|
|
1463
|
+
const hex = this.deviceConfig.config.ambientColour || '000000'
|
|
1406
1464
|
const { s } = this.hexToHsv(hex)
|
|
1407
1465
|
if (isNaN(s)) {
|
|
1408
1466
|
return 0
|
|
1409
1467
|
}
|
|
1410
1468
|
return s
|
|
1411
1469
|
} catch (error) {
|
|
1412
|
-
this.log.
|
|
1470
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to parse ambient light saturation:`, error)
|
|
1413
1471
|
return 0
|
|
1414
1472
|
}
|
|
1415
1473
|
}
|
|
@@ -1430,18 +1488,21 @@ export class YotoPlayerAccessory {
|
|
|
1430
1488
|
* @returns {Promise<CharacteristicValue>}
|
|
1431
1489
|
*/
|
|
1432
1490
|
async getAmbientLightBrightness () {
|
|
1491
|
+
if (!this.deviceConfig) {
|
|
1492
|
+
return 0 // Default brightness
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1433
1495
|
try {
|
|
1434
|
-
const
|
|
1435
|
-
const hex = config.config.ambientColour
|
|
1496
|
+
const hex = this.deviceConfig.config.ambientColour || '000000'
|
|
1436
1497
|
const { v } = this.hexToHsv(hex)
|
|
1437
1498
|
const brightness = Math.round(v)
|
|
1438
1499
|
if (isNaN(brightness)) {
|
|
1439
|
-
return
|
|
1500
|
+
return 0
|
|
1440
1501
|
}
|
|
1441
1502
|
return brightness
|
|
1442
1503
|
} catch (error) {
|
|
1443
|
-
this.log.
|
|
1444
|
-
return
|
|
1504
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to parse ambient brightness:`, error)
|
|
1505
|
+
return 0
|
|
1445
1506
|
}
|
|
1446
1507
|
}
|
|
1447
1508
|
|
|
@@ -1461,9 +1522,12 @@ export class YotoPlayerAccessory {
|
|
|
1461
1522
|
*/
|
|
1462
1523
|
async updateAmbientLightColor () {
|
|
1463
1524
|
try {
|
|
1464
|
-
//
|
|
1465
|
-
|
|
1466
|
-
|
|
1525
|
+
// Refresh config if not cached
|
|
1526
|
+
if (!this.deviceConfig) {
|
|
1527
|
+
this.deviceConfig = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
const currentHex = this.deviceConfig.config.ambientColour || '000000'
|
|
1467
1531
|
const currentHsv = this.hexToHsv(currentHex)
|
|
1468
1532
|
|
|
1469
1533
|
// Use pending values or current values
|
|
@@ -1599,7 +1663,7 @@ export class YotoPlayerAccessory {
|
|
|
1599
1663
|
// Optionally update accessory display name with current content
|
|
1600
1664
|
if (this.platform.config.updateAccessoryName) {
|
|
1601
1665
|
this.accessory.displayName = `${this.device.name} - ${title}`
|
|
1602
|
-
this.log.
|
|
1666
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Updated display name`)
|
|
1603
1667
|
}
|
|
1604
1668
|
}
|
|
1605
1669
|
|
package/lib/yotoApi.js
CHANGED
|
@@ -59,7 +59,7 @@ export class YotoApi {
|
|
|
59
59
|
|
|
60
60
|
// Check if token needs refresh
|
|
61
61
|
if (this.auth.isTokenExpired(this.tokenExpiresAt)) {
|
|
62
|
-
this.log.
|
|
62
|
+
this.log.debug(LOG_PREFIX.API, ERROR_MESSAGES.TOKEN_EXPIRED)
|
|
63
63
|
|
|
64
64
|
if (!this.refreshToken) {
|
|
65
65
|
throw new Error(ERROR_MESSAGES.TOKEN_REFRESH_FAILED)
|
package/lib/yotoMqtt.js
CHANGED
|
@@ -65,7 +65,7 @@ export class YotoMqtt extends EventEmitter {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
return new Promise((resolve, reject) => {
|
|
68
|
-
this.log.
|
|
68
|
+
this.log.debug(LOG_PREFIX.MQTT, `Connecting to ${this.brokerUrl}...`)
|
|
69
69
|
|
|
70
70
|
const clientId = `DASH${deviceId}`
|
|
71
71
|
const username = `${deviceId}?x-amz-customauthorizer-name=${YOTO_MQTT_AUTH_NAME}`
|
|
@@ -144,7 +144,7 @@ export class YotoMqtt extends EventEmitter {
|
|
|
144
144
|
|
|
145
145
|
this.client.on('reconnect', () => {
|
|
146
146
|
this.reconnectAttempts++
|
|
147
|
-
this.log.
|
|
147
|
+
this.log.debug(LOG_PREFIX.MQTT, `Reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts}`)
|
|
148
148
|
|
|
149
149
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
150
150
|
this.log.error(LOG_PREFIX.MQTT, 'Max reconnection attempts reached, stopping reconnection')
|
|
@@ -201,14 +201,14 @@ export class YotoMqtt extends EventEmitter {
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
return new Promise((resolve) => {
|
|
204
|
-
this.log.
|
|
204
|
+
this.log.debug(LOG_PREFIX.MQTT, 'Disconnecting from MQTT broker...')
|
|
205
205
|
|
|
206
206
|
if (this.client) {
|
|
207
207
|
this.client.end(false, {}, () => {
|
|
208
208
|
this.connected = false
|
|
209
209
|
this.client = null
|
|
210
210
|
this.subscribedDevices.clear()
|
|
211
|
-
this.log.
|
|
211
|
+
this.log.debug(LOG_PREFIX.MQTT, 'Disconnected')
|
|
212
212
|
resolve()
|
|
213
213
|
})
|
|
214
214
|
} else {
|
|
@@ -246,7 +246,7 @@ export class YotoMqtt extends EventEmitter {
|
|
|
246
246
|
return
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
-
this.log.
|
|
249
|
+
this.log.debug(LOG_PREFIX.MQTT, `Resubscribing to ${this.subscribedDevices.size} device(s)...`)
|
|
250
250
|
|
|
251
251
|
for (const deviceId of this.subscribedDevices) {
|
|
252
252
|
const callbacks = this.deviceCallbacks.get(deviceId)
|
|
@@ -281,7 +281,7 @@ export class YotoMqtt extends EventEmitter {
|
|
|
281
281
|
return
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
-
this.log.
|
|
284
|
+
this.log.debug(LOG_PREFIX.MQTT, `Subscribing to device ${deviceId}...`)
|
|
285
285
|
|
|
286
286
|
const topics = [
|
|
287
287
|
this.buildTopic(MQTT_TOPIC_DATA_STATUS, deviceId),
|
|
@@ -331,7 +331,7 @@ export class YotoMqtt extends EventEmitter {
|
|
|
331
331
|
return
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
-
this.log.
|
|
334
|
+
this.log.debug(LOG_PREFIX.MQTT, `Unsubscribing from device ${deviceId}...`)
|
|
335
335
|
|
|
336
336
|
const topics = [
|
|
337
337
|
this.buildTopic(MQTT_TOPIC_DATA_STATUS, deviceId),
|
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.20",
|
|
5
5
|
"author": "Bret Comnes <bcomnes@gmail.com> (https://bret.io)",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/bcomnes/homebridge-yoto/issues"
|