homebridge-yoto 0.0.16 → 0.0.17

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.
@@ -511,13 +511,17 @@ export class YotoPlayerAccessory {
511
511
 
512
512
  /**
513
513
  * Handle status update from MQTT
514
- * @param {YotoDeviceStatus} status - Status data
514
+ * @param {YotoDeviceStatus | {status: YotoDeviceStatus}} statusMessage - Status data or wrapped status
515
515
  */
516
- handleStatusUpdate (status) {
517
- this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Received status update:`, JSON.stringify(status, null, 2))
518
- this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Status - batteryLevel:`, status.batteryLevel)
519
- this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Status - userVolume:`, status.userVolume)
520
- this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Status - volume:`, status.volume)
516
+ handleStatusUpdate (statusMessage) {
517
+ this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Raw MQTT status message:`, JSON.stringify(statusMessage, null, 2))
518
+
519
+ // Unwrap status if it's nested under a 'status' property
520
+ const status = /** @type {YotoDeviceStatus} */ ('status' in statusMessage ? statusMessage.status : statusMessage)
521
+
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)
521
525
 
522
526
  this.currentStatus = status
523
527
  this.lastUpdateTime = Date.now()
@@ -529,10 +533,15 @@ export class YotoPlayerAccessory {
529
533
 
530
534
  /**
531
535
  * Handle playback events update from MQTT
532
- * @param {YotoPlaybackEvents} events - Playback events
536
+ * @param {YotoPlaybackEvents | {events: YotoPlaybackEvents}} eventsMessage - Playback events or wrapped events
533
537
  */
534
- handleEventsUpdate (events) {
535
- this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Events update received`)
538
+ handleEventsUpdate (eventsMessage) {
539
+ this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Raw MQTT events message:`, JSON.stringify(eventsMessage, null, 2))
540
+
541
+ // Unwrap events if it's nested under an 'events' property
542
+ const events = /** @type {YotoPlaybackEvents} */ ('events' in eventsMessage ? eventsMessage.events : eventsMessage)
543
+
544
+ this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Unwrapped events - cardId:`, events.cardId)
536
545
  this.currentEvents = events
537
546
  this.lastUpdateTime = Date.now()
538
547
  this.accessory.context.lastEvents = events
@@ -551,7 +560,7 @@ export class YotoPlayerAccessory {
551
560
  * @param {import('./types.js').MqttCommandResponse} response - Command response
552
561
  */
553
562
  handleCommandResponse (response) {
554
- this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Command response:`, response)
563
+ this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Command response:`, response)
555
564
  }
556
565
 
557
566
  /**
@@ -562,16 +571,17 @@ export class YotoPlayerAccessory {
562
571
  return
563
572
  }
564
573
 
565
- // Update volume
566
- if (this.speakerService) {
574
+ // Update volume from events, not status
575
+ if (this.speakerService && this.currentEvents?.volume !== undefined) {
576
+ const volume = Number(this.currentEvents.volume) || 0
567
577
  this.speakerService.updateCharacteristic(
568
578
  this.platform.Characteristic.Volume,
569
- this.currentStatus.userVolume
579
+ volume
570
580
  )
571
581
  }
572
582
 
573
583
  // Update battery
574
- if (this.batteryService) {
584
+ if (this.batteryService && this.currentStatus.batteryLevel !== undefined) {
575
585
  this.batteryService.updateCharacteristic(
576
586
  this.platform.Characteristic.BatteryLevel,
577
587
  this.currentStatus.batteryLevel
@@ -603,7 +613,7 @@ export class YotoPlayerAccessory {
603
613
  }
604
614
 
605
615
  // Update display brightness
606
- if (this.displayService) {
616
+ if (this.displayService && this.currentStatus.dnowBrightness !== undefined) {
607
617
  const isOn = this.currentStatus.dnowBrightness > 0
608
618
  this.displayService.updateCharacteristic(
609
619
  this.platform.Characteristic.On,
@@ -619,7 +629,7 @@ export class YotoPlayerAccessory {
619
629
  }
620
630
 
621
631
  // Update advanced control switches
622
- if (this.bluetoothSwitch) {
632
+ if (this.bluetoothSwitch && this.currentStatus.bluetoothHp !== undefined) {
623
633
  const bluetoothEnabled = this.currentStatus.bluetoothHp
624
634
  this.bluetoothSwitch.updateCharacteristic(
625
635
  this.platform.Characteristic.On,
@@ -627,7 +637,7 @@ export class YotoPlayerAccessory {
627
637
  )
628
638
  }
629
639
 
630
- if (this.btHeadphonesSwitch) {
640
+ if (this.btHeadphonesSwitch && this.currentStatus.bluetoothHp !== undefined) {
631
641
  const btHeadphonesEnabled = this.currentStatus.bluetoothHp
632
642
  this.btHeadphonesSwitch.updateCharacteristic(
633
643
  this.platform.Characteristic.On,
@@ -789,15 +799,16 @@ export class YotoPlayerAccessory {
789
799
  * @returns {Promise<CharacteristicValue>}
790
800
  */
791
801
  async getVolume () {
792
- this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getVolume - currentStatus:`, this.currentStatus)
793
- this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getVolume - userVolume:`, this.currentStatus?.userVolume)
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)
794
804
 
795
- if (!this.currentStatus || this.currentStatus.userVolume === undefined) {
796
- this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getVolume - returning default: 50`)
805
+ // Volume comes from events, not status
806
+ 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`)
797
808
  return 50
798
809
  }
799
- const volume = Number(this.currentStatus.userVolume) || 50
800
- this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getVolume - returning:`, volume)
810
+ const volume = Number(this.currentEvents.volume) || 50
811
+ this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getVolume - returning volume:`, volume)
801
812
  return volume
802
813
  }
803
814
 
@@ -823,10 +834,11 @@ export class YotoPlayerAccessory {
823
834
  * @returns {Promise<CharacteristicValue>}
824
835
  */
825
836
  async getMute () {
826
- if (!this.currentStatus || this.currentStatus.userVolume === undefined) {
837
+ // Volume comes from events, not status
838
+ if (!this.currentEvents || this.currentEvents.volume === undefined) {
827
839
  return false
828
840
  }
829
- return Number(this.currentStatus.userVolume) === 0
841
+ return Number(this.currentEvents.volume) === 0
830
842
  }
831
843
 
832
844
  /**
@@ -842,7 +854,7 @@ export class YotoPlayerAccessory {
842
854
  await this.mqtt.setVolume(this.device.deviceId, 0)
843
855
  } else {
844
856
  // Unmute - restore to a reasonable volume if currently 0
845
- const currentVolume = this.currentStatus?.userVolume || 0
857
+ const currentVolume = this.currentEvents?.volume ? Number(this.currentEvents.volume) : 0
846
858
  const targetVolume = currentVolume === 0 ? 50 : currentVolume
847
859
  await this.mqtt.setVolume(this.device.deviceId, targetVolume)
848
860
  }
@@ -951,7 +963,7 @@ export class YotoPlayerAccessory {
951
963
  * @returns {Promise<CharacteristicValue>}
952
964
  */
953
965
  async getDisplayOn () {
954
- if (!this.currentStatus) {
966
+ if (!this.currentStatus || this.currentStatus.dnowBrightness === undefined) {
955
967
  return true
956
968
  }
957
969
  return this.currentStatus.dnowBrightness > 0
@@ -1035,7 +1047,7 @@ export class YotoPlayerAccessory {
1035
1047
  * @returns {Promise<CharacteristicValue>}
1036
1048
  */
1037
1049
  async getBluetoothEnabled () {
1038
- if (!this.currentStatus) {
1050
+ if (!this.currentStatus || this.currentStatus.bluetoothHp === undefined) {
1039
1051
  return false
1040
1052
  }
1041
1053
  return this.currentStatus.bluetoothHp
@@ -1095,7 +1107,7 @@ export class YotoPlayerAccessory {
1095
1107
  * @returns {Promise<CharacteristicValue>}
1096
1108
  */
1097
1109
  async getBtHeadphonesEnabled () {
1098
- if (!this.currentStatus) {
1110
+ if (!this.currentStatus || this.currentStatus.bluetoothHp === undefined) {
1099
1111
  return false
1100
1112
  }
1101
1113
  return this.currentStatus.bluetoothHp
package/lib/types.js CHANGED
@@ -15,43 +15,63 @@
15
15
  */
16
16
 
17
17
  /**
18
+ * YotoDeviceStatus - ACTUAL structure from MQTT /device/{id}/data/status
19
+ * Note: This differs from the documented API schema
18
20
  * @typedef {Object} YotoDeviceStatus
19
- * @property {number} statusVersion - Status data version
20
- * @property {string} fwVersion - Firmware version
21
- * @property {string} productType - Product type identifier
22
- * @property {number} batteryLevel - Battery level (0-100)
23
- * @property {number} als - Ambient light sensor reading
24
- * @property {number} freeDisk - Free disk space in bytes
25
- * @property {number} shutdownTimeout - Auto-shutdown timeout in seconds
26
- * @property {number} dbatTimeout - Display battery timeout
27
- * @property {number} charging - Charging state (0=not charging, 1=charging)
28
- * @property {string | null} activeCard - Currently active card ID or null
29
- * @property {number} cardInserted - Card insertion state (0=none, 1=physical, 2=remote)
30
- * @property {number} playingStatus - Playing status code
31
- * @property {boolean} headphones - Whether headphones are connected
32
- * @property {number} dnowBrightness - Current display brightness
33
- * @property {number} dayBright - Day mode brightness setting
34
- * @property {number} nightBright - Night mode brightness setting
35
- * @property {boolean} bluetoothHp - Bluetooth headphones enabled
36
- * @property {number} volume - System volume level
37
- * @property {number} userVolume - User volume level (0-100)
38
- * @property {'12' | '24'} timeFormat - Time format preference
39
- * @property {string} nightlightMode - Nightlight mode setting
40
- * @property {string} temp - Temperature reading
41
- * @property {number} day - Day mode (0=night, 1=day, -1=unknown)
42
- */
43
-
44
- /**
21
+ * @property {number} battery - Raw battery voltage (e.g., 3693)
22
+ * @property {string} [powerCaps] - Power capability flags (e.g., '0x02')
23
+ * @property {number} batteryLevel - Battery level percentage (0-100)
24
+ * @property {number} batteryTemp - Battery temperature
25
+ * @property {string} batteryData - Battery data string (e.g., '0:0:0')
26
+ * @property {number} batteryLevelRaw - Raw battery level value
27
+ * @property {number} free - Free memory
28
+ * @property {number} freeDMA - Free DMA memory
29
+ * @property {number} free32 - Free 32-bit memory
30
+ * @property {number} upTime - Device uptime in seconds
31
+ * @property {number} utcTime - Current UTC time (Unix timestamp)
32
+ * @property {number} aliveTime - Time device has been alive
33
+ * @property {number} accelTemp - Accelerometer temperature in Celsius
34
+ * @property {number} [qiOtp] - Qi charging related field
35
+ * @property {number} [errorsLogged] - Number of errors logged
36
+ * @property {string} nightlightMode - Nightlight mode (e.g., 'off')
37
+ * @property {string} temp - Temperature data string (e.g., '1014:23:318')
38
+ *
39
+ * Note: Volume information comes from events, not status!
40
+ * The following documented fields may exist but haven't been observed in v3 players:
41
+ * @property {number} [statusVersion] - Status data version
42
+ * @property {string} [fwVersion] - Firmware version
43
+ * @property {string} [productType] - Product type identifier
44
+ * @property {number} [als] - Ambient light sensor reading
45
+ * @property {number} [freeDisk] - Free disk space in bytes
46
+ * @property {number} [shutdownTimeout] - Auto-shutdown timeout in seconds
47
+ * @property {number} [dbatTimeout] - Display battery timeout
48
+ * @property {number} [charging] - Charging state (0=not charging, 1=charging)
49
+ * @property {string | null} [activeCard] - Currently active card ID or null
50
+ * @property {number} [cardInserted] - Card insertion state (0=none, 1=physical, 2=remote)
51
+ * @property {number} [playingStatus] - Playing status code
52
+ * @property {boolean} [headphones] - Whether headphones are connected
53
+ * @property {number} [dnowBrightness] - Current display brightness
54
+ * @property {number} [dayBright] - Day mode brightness setting
55
+ * @property {number} [nightBright] - Night mode brightness setting
56
+ * @property {boolean} [bluetoothHp] - Bluetooth headphones enabled
57
+ * @property {number} [volume] - System volume level (may be in events instead)
58
+ * @property {number} [userVolume] - User volume level 0-100 (may be in events instead)
59
+ * @property {'12' | '24'} [timeFormat] - Time format preference
60
+ * @property {number} [day] - Day mode (0=night, 1=day, -1=unknown)
61
+ */
62
+
63
+ /**
64
+ * YotoPlaybackEvents - From MQTT /device/{id}/data/events
45
65
  * @typedef {Object} YotoPlaybackEvents
46
66
  * @property {string} repeatAll - Repeat all setting ("true" or "false")
47
67
  * @property {string} streaming - Streaming active ("true" or "false")
48
- * @property {string} volume - Current volume level
49
- * @property {string} volumeMax - Maximum volume level
68
+ * @property {string} volume - Current volume level (0-100 as string)
69
+ * @property {string} volumeMax - Maximum volume level (0-100 as string)
50
70
  * @property {string} playbackWait - Playback wait state ("true" or "false")
51
71
  * @property {string} sleepTimerActive - Sleep timer active ("true" or "false")
52
- * @property {string} eventUtc - Event timestamp (Unix timestamp)
53
- * @property {string} trackLength - Track duration in seconds
54
- * @property {string} position - Current playback position in seconds
72
+ * @property {string} eventUtc - Event timestamp (Unix timestamp as string)
73
+ * @property {string} trackLength - Track duration in seconds (as string)
74
+ * @property {string} position - Current playback position in seconds (as string)
55
75
  * @property {string} cardId - Active card ID
56
76
  * @property {string} source - Playback source (e.g., "card", "remote", "MQTT")
57
77
  * @property {string} cardUpdatedAt - Card last updated timestamp (ISO8601)
@@ -59,8 +79,8 @@
59
79
  * @property {string} chapterKey - Current chapter key
60
80
  * @property {string} trackTitle - Current track title
61
81
  * @property {string} trackKey - Current track key
62
- * @property {string} playbackStatus - Playback status ("playing", "paused", "stopped")
63
- * @property {string} sleepTimerSeconds - Sleep timer remaining seconds
82
+ * @property {string} playbackStatus - Playback status (e.g., "playing", "paused", "stopped")
83
+ * @property {string} sleepTimerSeconds - Remaining sleep timer seconds (as string)
64
84
  */
65
85
 
66
86
  /**
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.16",
4
+ "version": "0.0.17",
5
5
  "author": "Bret Comnes <bcomnes@gmail.com> (https://bret.io)",
6
6
  "bugs": {
7
7
  "url": "https://github.com/bcomnes/homebridge-yoto/issues"