homebridge-yoto 0.0.11 → 0.0.12
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/platform.js +93 -2
- package/lib/playerAccessory.js +14 -58
- package/package.json +1 -1
package/lib/platform.js
CHANGED
|
@@ -49,6 +49,13 @@ export class YotoPlatform {
|
|
|
49
49
|
this.storagePath = api.user.storagePath()
|
|
50
50
|
this.tokenFilePath = join(this.storagePath, 'homebridge-yoto-tokens.json')
|
|
51
51
|
|
|
52
|
+
// Track accessory handlers for status updates
|
|
53
|
+
/** @type {Map<string, import('./playerAccessory.js').YotoPlayerAccessory>} */
|
|
54
|
+
this.accessoryHandlers = new Map()
|
|
55
|
+
|
|
56
|
+
// Status polling interval
|
|
57
|
+
this.statusPollInterval = null
|
|
58
|
+
|
|
52
59
|
// Initialize API clients
|
|
53
60
|
this.auth = new YotoAuth(log, this.config.clientId)
|
|
54
61
|
this.yotoApi = new YotoApi(log, this.auth)
|
|
@@ -100,11 +107,71 @@ export class YotoPlatform {
|
|
|
100
107
|
}
|
|
101
108
|
throw error
|
|
102
109
|
}
|
|
110
|
+
|
|
111
|
+
// Start platform-level status polling (every 60 seconds)
|
|
112
|
+
this.startStatusPolling()
|
|
103
113
|
} catch (error) {
|
|
104
114
|
this.log.error(LOG_PREFIX.PLATFORM, 'Initialization failed:', error)
|
|
105
115
|
}
|
|
106
116
|
}
|
|
107
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Start periodic status polling for all devices
|
|
120
|
+
*/
|
|
121
|
+
startStatusPolling () {
|
|
122
|
+
// Poll every 60 seconds
|
|
123
|
+
this.statusPollInterval = setInterval(async () => {
|
|
124
|
+
try {
|
|
125
|
+
await this.checkAllDevicesStatus()
|
|
126
|
+
} catch (error) {
|
|
127
|
+
this.log.error(LOG_PREFIX.PLATFORM, 'Failed to check device status:', error)
|
|
128
|
+
}
|
|
129
|
+
}, 60000)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Stop status polling
|
|
134
|
+
*/
|
|
135
|
+
stopStatusPolling () {
|
|
136
|
+
if (this.statusPollInterval) {
|
|
137
|
+
clearInterval(this.statusPollInterval)
|
|
138
|
+
this.statusPollInterval = null
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Check all devices' online status and notify accessories
|
|
144
|
+
* @returns {Promise<void>}
|
|
145
|
+
*/
|
|
146
|
+
async checkAllDevicesStatus () {
|
|
147
|
+
try {
|
|
148
|
+
// Fetch fresh device list from API (single call for all devices)
|
|
149
|
+
const devices = await this.yotoApi.getDevices()
|
|
150
|
+
|
|
151
|
+
// Update each accessory with fresh device info
|
|
152
|
+
for (const device of devices) {
|
|
153
|
+
const uuid = this.api.hap.uuid.generate(device.deviceId)
|
|
154
|
+
const accessory = this.accessories.get(uuid)
|
|
155
|
+
|
|
156
|
+
if (accessory) {
|
|
157
|
+
const wasOnline = accessory.context.device.online
|
|
158
|
+
const isNowOnline = device.online
|
|
159
|
+
|
|
160
|
+
// Update device info in context
|
|
161
|
+
accessory.context.device = device
|
|
162
|
+
|
|
163
|
+
// Notify accessory handler if status changed
|
|
164
|
+
const handler = this.accessoryHandlers.get(uuid)
|
|
165
|
+
if (handler && wasOnline !== isNowOnline) {
|
|
166
|
+
handler.handleOnlineStatusChange(isNowOnline, wasOnline)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} catch (error) {
|
|
171
|
+
this.log.error(LOG_PREFIX.PLATFORM, 'Error checking device status:', error)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
108
175
|
/**
|
|
109
176
|
* Perform device authorization flow
|
|
110
177
|
* @returns {Promise<void>}
|
|
@@ -286,6 +353,9 @@ export class YotoPlatform {
|
|
|
286
353
|
// Create handler for this accessory
|
|
287
354
|
const handler = new YotoPlayerAccessory(this, typedAccessory)
|
|
288
355
|
|
|
356
|
+
// Track handler for status updates
|
|
357
|
+
this.accessoryHandlers.set(uuid, handler)
|
|
358
|
+
|
|
289
359
|
// Initialize accessory (connect MQTT, etc.)
|
|
290
360
|
handler.initialize().catch(error => {
|
|
291
361
|
this.log.error(LOG_PREFIX.PLATFORM, `Failed to initialize ${device.name}:`, error)
|
|
@@ -312,6 +382,9 @@ export class YotoPlatform {
|
|
|
312
382
|
// Create handler for this accessory
|
|
313
383
|
const handler = new YotoPlayerAccessory(this, typedAccessory)
|
|
314
384
|
|
|
385
|
+
// Track handler for status updates
|
|
386
|
+
this.accessoryHandlers.set(uuid, handler)
|
|
387
|
+
|
|
315
388
|
// Initialize accessory (connect MQTT, etc.)
|
|
316
389
|
handler.initialize().catch(error => {
|
|
317
390
|
this.log.error(LOG_PREFIX.PLATFORM, `Failed to initialize ${device.name}:`, error)
|
|
@@ -341,8 +414,15 @@ export class YotoPlatform {
|
|
|
341
414
|
if (staleAccessories.length > 0) {
|
|
342
415
|
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, staleAccessories)
|
|
343
416
|
|
|
344
|
-
// Remove from tracking map
|
|
417
|
+
// Remove from tracking map and handlers
|
|
345
418
|
for (const accessory of staleAccessories) {
|
|
419
|
+
const handler = this.accessoryHandlers.get(accessory.UUID)
|
|
420
|
+
if (handler) {
|
|
421
|
+
handler.destroy().catch(error => {
|
|
422
|
+
this.log.error(LOG_PREFIX.PLATFORM, 'Error destroying accessory handler:', error)
|
|
423
|
+
})
|
|
424
|
+
this.accessoryHandlers.delete(accessory.UUID)
|
|
425
|
+
}
|
|
346
426
|
this.accessories.delete(accessory.UUID)
|
|
347
427
|
}
|
|
348
428
|
}
|
|
@@ -353,6 +433,17 @@ export class YotoPlatform {
|
|
|
353
433
|
*/
|
|
354
434
|
async shutdown () {
|
|
355
435
|
this.log.info(LOG_PREFIX.PLATFORM, 'Shutting down...')
|
|
356
|
-
|
|
436
|
+
|
|
437
|
+
// Stop status polling
|
|
438
|
+
this.stopStatusPolling()
|
|
439
|
+
|
|
440
|
+
// Cleanup all accessory handlers
|
|
441
|
+
for (const [, handler] of this.accessoryHandlers) {
|
|
442
|
+
try {
|
|
443
|
+
await handler.destroy()
|
|
444
|
+
} catch (error) {
|
|
445
|
+
this.log.error(LOG_PREFIX.PLATFORM, 'Error shutting down accessory:', error)
|
|
446
|
+
}
|
|
447
|
+
}
|
|
357
448
|
}
|
|
358
449
|
}
|
package/lib/playerAccessory.js
CHANGED
|
@@ -111,67 +111,26 @@ export class YotoPlayerAccessory {
|
|
|
111
111
|
*/
|
|
112
112
|
async initialize () {
|
|
113
113
|
await this.connectMqtt()
|
|
114
|
-
|
|
115
|
-
// Start polling for device status updates (every 60 seconds)
|
|
116
|
-
this.startStatusPolling()
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Start periodic status polling to detect online/offline changes
|
|
121
|
-
*/
|
|
122
|
-
startStatusPolling () {
|
|
123
|
-
// Poll every 60 seconds
|
|
124
|
-
this.statusPollInterval = setInterval(async () => {
|
|
125
|
-
try {
|
|
126
|
-
await this.checkDeviceStatus()
|
|
127
|
-
} catch (error) {
|
|
128
|
-
this.log.error(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to check device status:`, error)
|
|
129
|
-
}
|
|
130
|
-
}, 60000)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Stop status polling
|
|
135
|
-
*/
|
|
136
|
-
stopStatusPolling () {
|
|
137
|
-
if (this.statusPollInterval) {
|
|
138
|
-
clearInterval(this.statusPollInterval)
|
|
139
|
-
this.statusPollInterval = null
|
|
140
|
-
}
|
|
114
|
+
// Status polling is handled at platform level
|
|
141
115
|
}
|
|
142
116
|
|
|
143
117
|
/**
|
|
144
|
-
*
|
|
118
|
+
* Handle online status change from platform polling
|
|
119
|
+
* @param {boolean} isNowOnline - Current online status
|
|
120
|
+
* @param {boolean} wasOnline - Previous online status
|
|
145
121
|
* @returns {Promise<void>}
|
|
146
122
|
*/
|
|
147
|
-
async
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const devices = await this.platform.yotoApi.getDevices()
|
|
151
|
-
const currentDevice = devices.find(d => d.deviceId === this.device.deviceId)
|
|
152
|
-
|
|
153
|
-
if (!currentDevice) {
|
|
154
|
-
this.log.warn(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Device no longer found in API`)
|
|
155
|
-
return
|
|
156
|
-
}
|
|
123
|
+
async handleOnlineStatusChange (isNowOnline, wasOnline) {
|
|
124
|
+
// Update device reference from accessory context (platform already updated it)
|
|
125
|
+
this.device = this.accessory.context.device
|
|
157
126
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
this.
|
|
164
|
-
|
|
165
|
-
// Handle state changes
|
|
166
|
-
if (!wasOnline && isNowOnline) {
|
|
167
|
-
this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Device came online, connecting MQTT...`)
|
|
168
|
-
await this.connectMqtt()
|
|
169
|
-
} else if (wasOnline && !isNowOnline) {
|
|
170
|
-
this.log.warn(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Device went offline`)
|
|
171
|
-
await this.disconnectMqtt()
|
|
172
|
-
}
|
|
173
|
-
} catch (error) {
|
|
174
|
-
this.log.error(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Error checking device status:`, error)
|
|
127
|
+
// Handle state changes
|
|
128
|
+
if (!wasOnline && isNowOnline) {
|
|
129
|
+
this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Device came online, connecting MQTT...`)
|
|
130
|
+
await this.connectMqtt()
|
|
131
|
+
} else if (wasOnline && !isNowOnline) {
|
|
132
|
+
this.log.warn(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Device went offline`)
|
|
133
|
+
await this.disconnectMqtt()
|
|
175
134
|
}
|
|
176
135
|
}
|
|
177
136
|
|
|
@@ -485,9 +444,6 @@ export class YotoPlayerAccessory {
|
|
|
485
444
|
async destroy () {
|
|
486
445
|
this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Destroying accessory`)
|
|
487
446
|
|
|
488
|
-
// Stop status polling
|
|
489
|
-
this.stopStatusPolling()
|
|
490
|
-
|
|
491
447
|
// Disconnect MQTT
|
|
492
448
|
await this.disconnectMqtt()
|
|
493
449
|
}
|
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.12",
|
|
5
5
|
"author": "Bret Comnes <bcomnes@gmail.com> (https://bret.io)",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/bcomnes/homebridge-yoto/issues"
|