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 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
- // MQTT cleanup is handled by individual accessories
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
  }
@@ -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
- * Check device online status and reconnect if needed
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 checkDeviceStatus () {
148
- try {
149
- // Fetch fresh device list from API
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
- const wasOnline = this.device.online
159
- const isNowOnline = currentDevice.online
160
-
161
- // Update device info
162
- this.device = currentDevice
163
- this.accessory.context.device = currentDevice
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.11",
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"