homebridge-yoto 0.0.17 → 0.0.19
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 +43 -35
- 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
|
@@ -514,14 +514,14 @@ export class YotoPlayerAccessory {
|
|
|
514
514
|
* @param {YotoDeviceStatus | {status: YotoDeviceStatus}} statusMessage - Status data or wrapped status
|
|
515
515
|
*/
|
|
516
516
|
handleStatusUpdate (statusMessage) {
|
|
517
|
-
this.log.
|
|
517
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Raw MQTT status message:`, JSON.stringify(statusMessage, null, 2))
|
|
518
518
|
|
|
519
519
|
// Unwrap status if it's nested under a 'status' property
|
|
520
520
|
const status = /** @type {YotoDeviceStatus} */ ('status' in statusMessage ? statusMessage.status : statusMessage)
|
|
521
521
|
|
|
522
|
-
this.log.
|
|
523
|
-
this.log.
|
|
524
|
-
this.log.
|
|
522
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Unwrapped status - batteryLevel:`, status.batteryLevel)
|
|
523
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Unwrapped status - userVolume:`, status.userVolume)
|
|
524
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Unwrapped status - volume:`, status.volume)
|
|
525
525
|
|
|
526
526
|
this.currentStatus = status
|
|
527
527
|
this.lastUpdateTime = Date.now()
|
|
@@ -536,19 +536,19 @@ export class YotoPlayerAccessory {
|
|
|
536
536
|
* @param {YotoPlaybackEvents | {events: YotoPlaybackEvents}} eventsMessage - Playback events or wrapped events
|
|
537
537
|
*/
|
|
538
538
|
handleEventsUpdate (eventsMessage) {
|
|
539
|
-
this.log.
|
|
539
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Raw MQTT events message:`, JSON.stringify(eventsMessage, null, 2))
|
|
540
540
|
|
|
541
541
|
// Unwrap events if it's nested under an 'events' property
|
|
542
542
|
const events = /** @type {YotoPlaybackEvents} */ ('events' in eventsMessage ? eventsMessage.events : eventsMessage)
|
|
543
543
|
|
|
544
|
-
this.log.
|
|
544
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Unwrapped events - cardId:`, events.cardId)
|
|
545
545
|
this.currentEvents = events
|
|
546
546
|
this.lastUpdateTime = Date.now()
|
|
547
547
|
this.accessory.context.lastEvents = events
|
|
548
548
|
|
|
549
|
-
// Track active content changes
|
|
549
|
+
// Track active content changes with event data
|
|
550
550
|
if (this.platform.config.exposeActiveContent !== false && events.cardId) {
|
|
551
|
-
this.handleActiveContentChange(events.cardId)
|
|
551
|
+
this.handleActiveContentChange(events.cardId, events)
|
|
552
552
|
}
|
|
553
553
|
|
|
554
554
|
// Update playback-related characteristics
|
|
@@ -560,7 +560,7 @@ export class YotoPlayerAccessory {
|
|
|
560
560
|
* @param {import('./types.js').MqttCommandResponse} response - Command response
|
|
561
561
|
*/
|
|
562
562
|
handleCommandResponse (response) {
|
|
563
|
-
this.log.
|
|
563
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Command response:`, response)
|
|
564
564
|
}
|
|
565
565
|
|
|
566
566
|
/**
|
|
@@ -799,16 +799,16 @@ export class YotoPlayerAccessory {
|
|
|
799
799
|
* @returns {Promise<CharacteristicValue>}
|
|
800
800
|
*/
|
|
801
801
|
async getVolume () {
|
|
802
|
-
this.log.
|
|
803
|
-
this.log.
|
|
802
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getVolume - currentEvents:`, this.currentEvents)
|
|
803
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getVolume - events.volume:`, this.currentEvents?.volume)
|
|
804
804
|
|
|
805
805
|
// Volume comes from events, not status
|
|
806
806
|
if (!this.currentEvents || this.currentEvents.volume === undefined) {
|
|
807
|
-
this.log.
|
|
807
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getVolume - no volume in events, returning default: 50`)
|
|
808
808
|
return 50
|
|
809
809
|
}
|
|
810
810
|
const volume = Number(this.currentEvents.volume) || 50
|
|
811
|
-
this.log.
|
|
811
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getVolume - returning volume:`, volume)
|
|
812
812
|
return volume
|
|
813
813
|
}
|
|
814
814
|
|
|
@@ -871,15 +871,15 @@ export class YotoPlayerAccessory {
|
|
|
871
871
|
* @returns {Promise<CharacteristicValue>}
|
|
872
872
|
*/
|
|
873
873
|
async getBatteryLevel () {
|
|
874
|
-
this.log.
|
|
875
|
-
this.log.
|
|
874
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getBatteryLevel - currentStatus:`, this.currentStatus)
|
|
875
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getBatteryLevel - batteryLevel:`, this.currentStatus?.batteryLevel)
|
|
876
876
|
|
|
877
877
|
if (!this.currentStatus || this.currentStatus.batteryLevel === undefined) {
|
|
878
|
-
this.log.
|
|
878
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getBatteryLevel - returning default: 100`)
|
|
879
879
|
return 100
|
|
880
880
|
}
|
|
881
881
|
const battery = Number(this.currentStatus.batteryLevel) || 100
|
|
882
|
-
this.log.
|
|
882
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] getBatteryLevel - returning:`, battery)
|
|
883
883
|
return battery
|
|
884
884
|
}
|
|
885
885
|
|
|
@@ -1567,8 +1567,9 @@ export class YotoPlayerAccessory {
|
|
|
1567
1567
|
/**
|
|
1568
1568
|
* Handle active content change
|
|
1569
1569
|
* @param {string} cardId - Card ID
|
|
1570
|
+
* @param {YotoPlaybackEvents} [events] - Optional event data with track/chapter info
|
|
1570
1571
|
*/
|
|
1571
|
-
async handleActiveContentChange (cardId) {
|
|
1572
|
+
async handleActiveContentChange (cardId, events) {
|
|
1572
1573
|
// Skip if same card
|
|
1573
1574
|
if (this.activeContentCardId === cardId) {
|
|
1574
1575
|
return
|
|
@@ -1584,25 +1585,38 @@ export class YotoPlayerAccessory {
|
|
|
1584
1585
|
return
|
|
1585
1586
|
}
|
|
1586
1587
|
|
|
1587
|
-
|
|
1588
|
+
this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Active card changed: ${cardId}`)
|
|
1589
|
+
|
|
1590
|
+
// Use event data if available for immediate content info
|
|
1591
|
+
if (events?.trackTitle || events?.chapterTitle) {
|
|
1592
|
+
const title = events.trackTitle || events.chapterTitle || cardId
|
|
1593
|
+
this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Now playing: "${title}"`)
|
|
1594
|
+
|
|
1595
|
+
if (events.chapterKey && events.chapterKey !== events.chapterTitle) {
|
|
1596
|
+
this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Chapter: ${events.chapterKey}`)
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
// Optionally update accessory display name with current content
|
|
1600
|
+
if (this.platform.config.updateAccessoryName) {
|
|
1601
|
+
this.accessory.displayName = `${this.device.name} - ${title}`
|
|
1602
|
+
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Updated display name`)
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
// Check if we own this card to fetch additional details
|
|
1588
1607
|
if (!this.platform.isCardOwned(cardId)) {
|
|
1589
|
-
this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}]
|
|
1608
|
+
this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Card ${cardId} (not in library - using event data)`)
|
|
1590
1609
|
this.activeContentInfo = null
|
|
1591
1610
|
this.accessory.context.activeContentInfo = null
|
|
1592
1611
|
return
|
|
1593
1612
|
}
|
|
1594
1613
|
|
|
1595
|
-
|
|
1596
|
-
|
|
1614
|
+
// Attempt to fetch full card details for owned cards
|
|
1597
1615
|
try {
|
|
1598
|
-
// Fetch card details for owned cards
|
|
1599
1616
|
const content = await this.platform.yotoApi.getContent(cardId)
|
|
1600
1617
|
this.activeContentInfo = content
|
|
1601
1618
|
|
|
1602
|
-
// Log
|
|
1603
|
-
const title = content.card?.title || 'Unknown'
|
|
1604
|
-
this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Now playing: "${title}"`)
|
|
1605
|
-
|
|
1619
|
+
// Log additional metadata from API
|
|
1606
1620
|
if (content.card?.metadata?.author) {
|
|
1607
1621
|
this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Author: ${content.card.metadata.author}`)
|
|
1608
1622
|
}
|
|
@@ -1611,23 +1625,17 @@ export class YotoPlayerAccessory {
|
|
|
1611
1625
|
this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Category: ${content.card.metadata.category}`)
|
|
1612
1626
|
}
|
|
1613
1627
|
|
|
1614
|
-
// Optionally update accessory display name with current content
|
|
1615
|
-
if (this.platform.config.updateAccessoryName && content.card?.title) {
|
|
1616
|
-
this.accessory.displayName = `${this.device.name} - ${content.card.title}`
|
|
1617
|
-
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Updated display name`)
|
|
1618
|
-
}
|
|
1619
|
-
|
|
1620
1628
|
// Store in context for persistence
|
|
1621
1629
|
this.accessory.context.activeContentInfo = this.activeContentInfo
|
|
1622
1630
|
} catch (error) {
|
|
1623
1631
|
// Handle 403 Forbidden (store-bought cards user doesn't own)
|
|
1624
1632
|
if (error instanceof Error && error.message.includes('403')) {
|
|
1625
|
-
this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}]
|
|
1633
|
+
this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Card ${cardId} API access denied (using event data)`)
|
|
1626
1634
|
this.activeContentInfo = null
|
|
1627
1635
|
this.accessory.context.activeContentInfo = null
|
|
1628
1636
|
} else if (error instanceof Error && error.message.includes('404')) {
|
|
1629
1637
|
// Card not found - might be deleted or invalid
|
|
1630
|
-
this.log.warn(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Card ${cardId} not found`)
|
|
1638
|
+
this.log.warn(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Card ${cardId} not found in API`)
|
|
1631
1639
|
this.activeContentInfo = null
|
|
1632
1640
|
this.accessory.context.activeContentInfo = null
|
|
1633
1641
|
} else {
|
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.19",
|
|
5
5
|
"author": "Bret Comnes <bcomnes@gmail.com> (https://bret.io)",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/bcomnes/homebridge-yoto/issues"
|