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 CHANGED
@@ -35,7 +35,7 @@ export class YotoAuth {
35
35
  * @returns {Promise<YotoApiDeviceCodeResponse>}
36
36
  */
37
37
  async initiateDeviceFlow () {
38
- this.log.info(LOG_PREFIX.AUTH, 'Initiating device authorization flow...')
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.info(LOG_PREFIX.AUTH, 'Waiting for user authorization...')
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.info(LOG_PREFIX.PLATFORM, 'Starting OAuth flow...')
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.info(LOG_PREFIX.PLATFORM, 'Token refreshed')
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.info(LOG_PREFIX.PLATFORM, 'Loading accessory from cache:', accessory.displayName)
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.info(LOG_PREFIX.PLATFORM, 'Discovering Yoto devices...')
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.info(LOG_PREFIX.PLATFORM, 'Restoring existing accessory:', device.name)
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.info(LOG_PREFIX.PLATFORM, 'Adding new accessory:', device.name)
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.info(LOG_PREFIX.PLATFORM, 'Removing stale accessory:', accessory.displayName)
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.info(LOG_PREFIX.PLATFORM, 'Shutting down...')
476
+ this.log.debug(LOG_PREFIX.PLATFORM, 'Shutting down...')
477
477
 
478
478
  // Stop status polling
479
479
  this.stopStatusPolling()
@@ -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.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Raw MQTT status message:`, JSON.stringify(statusMessage, null, 2))
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.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Unwrapped status - batteryLevel:`, status.batteryLevel)
523
- this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Unwrapped status - userVolume:`, status.userVolume)
524
- this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Unwrapped status - volume:`, status.volume)
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.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Raw MQTT events message:`, JSON.stringify(eventsMessage, null, 2))
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.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Unwrapped events - cardId:`, events.cardId)
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.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Command response:`, response)
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.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getVolume - currentEvents:`, this.currentEvents)
803
- this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getVolume - events.volume:`, this.currentEvents?.volume)
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.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getVolume - no volume in events, returning default: 50`)
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.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getVolume - returning volume:`, volume)
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.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getBatteryLevel - currentStatus:`, this.currentStatus)
875
- this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getBatteryLevel - batteryLevel:`, this.currentStatus?.batteryLevel)
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.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getBatteryLevel - returning default: 100`)
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.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getBatteryLevel - returning:`, battery)
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
- // Get current config to update brightness settings
981
- const config = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
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 auto or previous brightness
985
- config.config.dayDisplayBrightness = 'auto'
986
- config.config.nightDisplayBrightness = 'auto'
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
- config.config.dayDisplayBrightness = '0'
990
- config.config.nightDisplayBrightness = '0'
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, config)
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
- const config = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
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
- // Map 0-100 to brightness string
1030
- const brightness = String(Math.round(Number(value)))
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
- config.config.dayDisplayBrightness = brightness
1034
- config.config.nightDisplayBrightness = brightness
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, config)
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 enabled:`, value)
1079
+ this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Set Bluetooth:`, value)
1062
1080
 
1063
1081
  try {
1064
- const config = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
1065
- config.config.bluetoothEnabled = value ? '1' : '0'
1066
- await this.platform.yotoApi.updateDeviceConfig(this.device.deviceId, config)
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
- const config = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
1095
- config.config.repeatAll = Boolean(value)
1096
- await this.platform.yotoApi.updateDeviceConfig(this.device.deviceId, config)
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 enabled:`, value)
1147
+ this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Set BT headphones:`, value)
1122
1148
 
1123
1149
  try {
1124
- const config = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
1125
- config.config.btHeadphonesEnabled = Boolean(value)
1126
- await this.platform.yotoApi.updateDeviceConfig(this.device.deviceId, config)
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 config = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
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.error(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to get day volume limit:`, error)
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
- const config = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
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
- config.config.maxVolumeLimit = String(limit)
1262
- await this.platform.yotoApi.updateDeviceConfig(this.device.deviceId, config)
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 config = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
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.error(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to get night volume limit:`, error)
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
- const config = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
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
- config.config.nightMaxVolumeLimit = String(limit)
1320
- await this.platform.yotoApi.updateDeviceConfig(this.device.deviceId, config)
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 config = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
1336
- const color = config.config.ambientColour || '#000000'
1337
- // Off if color is black (#000000)
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.error(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to get ambient light state:`, error)
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
- // Turn on - set to white or previous color
1355
- await this.mqtt.setAmbientLight(this.device.deviceId, 255, 255, 255)
1406
+ // Restore to a default color (warm white) when turning on
1407
+ await this.updateAmbientLightColor()
1356
1408
  } else {
1357
- // Turn off - set to black
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 config = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
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.error(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to get ambient light hue:`, error)
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 config = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
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.error(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to get ambient light saturation:`, error)
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 config = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
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 100
1500
+ return 0
1440
1501
  }
1441
1502
  return brightness
1442
1503
  } catch (error) {
1443
- this.log.error(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to get ambient brightness:`, error)
1444
- return 100
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
- // Get current config to read existing HSV values
1465
- const config = await this.platform.yotoApi.getDeviceConfig(this.device.deviceId)
1466
- const currentHex = config.config.ambientColour || '#ffffff'
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.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Updated display name`)
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.info(LOG_PREFIX.API, ERROR_MESSAGES.TOKEN_EXPIRED)
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.info(LOG_PREFIX.MQTT, `Connecting to ${this.brokerUrl}...`)
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.info(LOG_PREFIX.MQTT, `Reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts}`)
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.info(LOG_PREFIX.MQTT, 'Disconnecting from MQTT broker...')
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.info(LOG_PREFIX.MQTT, 'Disconnected')
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.info(LOG_PREFIX.MQTT, `Resubscribing to ${this.subscribedDevices.size} device(s)...`)
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.info(LOG_PREFIX.MQTT, `Subscribing to device ${deviceId}...`)
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.info(LOG_PREFIX.MQTT, `Unsubscribing from device ${deviceId}...`)
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.18",
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"