homebridge-yoto 0.0.9 → 0.0.10

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.
@@ -41,6 +41,10 @@ export class YotoPlayerAccessory {
41
41
  // Create dedicated MQTT client for this device
42
42
  this.mqtt = new YotoMqtt(this.log)
43
43
 
44
+ // Track online status polling
45
+ this.statusPollInterval = null
46
+ this.mqttConnected = false
47
+
44
48
  this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Initializing accessory`)
45
49
 
46
50
  // Set up services
@@ -107,6 +111,84 @@ export class YotoPlayerAccessory {
107
111
  */
108
112
  async initialize () {
109
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
+ }
141
+ }
142
+
143
+ /**
144
+ * Check device online status and reconnect if needed
145
+ * @returns {Promise<void>}
146
+ */
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
+ }
157
+
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)
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Disconnect MQTT for this device
180
+ * @returns {Promise<void>}
181
+ */
182
+ async disconnectMqtt () {
183
+ if (this.mqtt && this.mqttConnected) {
184
+ try {
185
+ await this.mqtt.disconnect()
186
+ this.mqttConnected = false
187
+ this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] MQTT disconnected`)
188
+ } catch (error) {
189
+ this.log.error(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to disconnect MQTT:`, error)
190
+ }
191
+ }
110
192
  }
111
193
 
112
194
  /**
@@ -397,11 +479,30 @@ export class YotoPlayerAccessory {
397
479
  .onSet(this.setAmbientLightBrightness.bind(this))
398
480
  }
399
481
 
482
+ /**
483
+ * Cleanup and destroy the accessory
484
+ */
485
+ async destroy () {
486
+ this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Destroying accessory`)
487
+
488
+ // Stop status polling
489
+ this.stopStatusPolling()
490
+
491
+ // Disconnect MQTT
492
+ await this.disconnectMqtt()
493
+ }
494
+
400
495
  /**
401
496
  * Connect MQTT for this device
402
497
  */
403
498
  async connectMqtt () {
404
499
  try {
500
+ // Check if device is online first
501
+ if (!this.device.online) {
502
+ this.log.warn(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Device is offline, skipping MQTT connection`)
503
+ return
504
+ }
505
+
405
506
  // Ensure we have an access token
406
507
  if (!this.platform.config.accessToken) {
407
508
  this.log.warn(LOG_PREFIX.ACCESSORY, `[${this.device.name}] No access token available for MQTT connection`)
@@ -410,6 +511,7 @@ export class YotoPlayerAccessory {
410
511
 
411
512
  // TEMPORARY: Debug logging for MQTT troubleshooting
412
513
  this.log.warn(LOG_PREFIX.ACCESSORY, `[${this.device.name}] MQTT Connection Details:`)
514
+ this.log.warn(LOG_PREFIX.ACCESSORY, ` Device Online: ${this.device.online}`)
413
515
  this.log.warn(LOG_PREFIX.ACCESSORY, ` Device ID: ${this.device.deviceId}`)
414
516
  this.log.warn(LOG_PREFIX.ACCESSORY, ` Access Token: ${this.platform.config.accessToken}`)
415
517
  this.log.warn(LOG_PREFIX.ACCESSORY, ` Token Length: ${this.platform.config.accessToken.length}`)
@@ -427,8 +529,26 @@ export class YotoPlayerAccessory {
427
529
  onResponse: this.handleCommandResponse.bind(this)
428
530
  })
429
531
 
532
+ this.mqttConnected = true
430
533
  this.log.debug(LOG_PREFIX.ACCESSORY, `[${this.device.name}] MQTT connected and subscribed`)
534
+
535
+ // Set up MQTT event listeners
536
+ this.mqtt.on('disconnected', () => {
537
+ this.mqttConnected = false
538
+ this.log.warn(LOG_PREFIX.ACCESSORY, `[${this.device.name}] MQTT disconnected`)
539
+ })
540
+
541
+ this.mqtt.on('offline', () => {
542
+ this.mqttConnected = false
543
+ this.log.warn(LOG_PREFIX.ACCESSORY, `[${this.device.name}] MQTT offline`)
544
+ })
545
+
546
+ this.mqtt.on('connected', () => {
547
+ this.mqttConnected = true
548
+ this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] MQTT reconnected`)
549
+ })
431
550
  } catch (error) {
551
+ this.mqttConnected = false
432
552
  this.log.error(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to connect MQTT:`, error)
433
553
  }
434
554
  }
package/lib/yotoMqtt.js CHANGED
@@ -6,6 +6,7 @@
6
6
  /** @import { YotoDeviceStatus, YotoPlaybackEvents, MqttVolumeCommand, MqttAmbientCommand, MqttSleepTimerCommand, MqttCardStartCommand, MqttCommandResponse } from './types.js' */
7
7
 
8
8
  import mqtt from 'mqtt'
9
+ import { EventEmitter } from 'events'
9
10
  import {
10
11
  YOTO_MQTT_BROKER_URL,
11
12
  YOTO_MQTT_AUTH_NAME,
@@ -30,14 +31,16 @@ import {
30
31
 
31
32
  /**
32
33
  * MQTT client for Yoto device communication
34
+ * @extends EventEmitter
33
35
  */
34
- export class YotoMqtt {
36
+ export class YotoMqtt extends EventEmitter {
35
37
  /**
36
38
  * @param {Logger} log - Homebridge logger
37
39
  * @param {Object} [options] - MQTT options
38
40
  * @param {string} [options.brokerUrl] - MQTT broker URL
39
41
  */
40
42
  constructor (log, options = {}) {
43
+ super()
41
44
  this.log = log
42
45
  this.brokerUrl = options.brokerUrl || YOTO_MQTT_BROKER_URL
43
46
  this.client = null
@@ -64,7 +67,7 @@ export class YotoMqtt {
64
67
  return new Promise((resolve, reject) => {
65
68
  this.log.info(LOG_PREFIX.MQTT, `Connecting to ${this.brokerUrl}...`)
66
69
 
67
- const clientId = `homebridge-yoto-${deviceId}-${Date.now()}`
70
+ const clientId = `DASH${deviceId}`
68
71
  const username = `${deviceId}?x-amz-customauthorizer-name=${YOTO_MQTT_AUTH_NAME}`
69
72
 
70
73
  // TEMPORARY: Detailed debug logging for MQTT troubleshooting
@@ -92,7 +95,6 @@ export class YotoMqtt {
92
95
  reconnectPeriod: 0, // Disable auto-reconnect - we'll handle reconnection manually
93
96
  connectTimeout: MQTT_CONNECT_TIMEOUT,
94
97
  clientId,
95
- clean: true,
96
98
  ALPNProtocols: ['x-amzn-mqtt-ca']
97
99
  })
98
100
 
@@ -102,6 +104,9 @@ export class YotoMqtt {
102
104
  this.reconnectDelay = MQTT_RECONNECT_PERIOD
103
105
  this.log.info(LOG_PREFIX.MQTT, '✓ Connected to MQTT broker')
104
106
 
107
+ // Emit connected event
108
+ this.emit('connected')
109
+
105
110
  // Resubscribe to all devices after reconnection
106
111
  this.resubscribeDevices()
107
112
 
@@ -110,7 +115,13 @@ export class YotoMqtt {
110
115
 
111
116
  this.client.on('error', (error) => {
112
117
  this.log.error(LOG_PREFIX.MQTT, 'Connection error:', error)
113
- this.log.error(LOG_PREFIX.MQTT, 'Error details:', JSON.stringify(error, null, 2))
118
+ this.log.error(LOG_PREFIX.MQTT, 'Error message:', error.message)
119
+ const errorWithCode = /** @type {any} */ (error)
120
+ this.log.error(LOG_PREFIX.MQTT, 'Error code:', errorWithCode.code)
121
+ this.log.error(LOG_PREFIX.MQTT, 'Error stack:', error.stack)
122
+ if (errorWithCode.code) {
123
+ this.log.error(LOG_PREFIX.MQTT, `AWS IoT error code: ${errorWithCode.code}`)
124
+ }
114
125
  if (!this.connected) {
115
126
  reject(error)
116
127
  }
@@ -120,9 +131,14 @@ export class YotoMqtt {
120
131
  const wasConnected = this.connected
121
132
  this.connected = false
122
133
 
134
+ // Emit disconnected event
135
+ this.emit('disconnected')
136
+
123
137
  if (wasConnected) {
124
138
  this.log.warn(LOG_PREFIX.MQTT, ERROR_MESSAGES.MQTT_DISCONNECTED)
125
139
  this.handleReconnect()
140
+ } else {
141
+ this.log.error(LOG_PREFIX.MQTT, 'Connection closed before establishing connection')
126
142
  }
127
143
  })
128
144
 
@@ -140,7 +156,26 @@ export class YotoMqtt {
140
156
 
141
157
  this.client.on('offline', () => {
142
158
  this.connected = false
143
- this.log.warn(LOG_PREFIX.MQTT, 'MQTT client offline')
159
+ this.log.warn(LOG_PREFIX.MQTT, 'MQTT client offline - connection failed or lost')
160
+
161
+ // Emit offline event
162
+ this.emit('offline')
163
+ })
164
+
165
+ this.client.on('end', () => {
166
+ this.log.warn(LOG_PREFIX.MQTT, 'MQTT client ended')
167
+ })
168
+
169
+ this.client.on('disconnect', (packet) => {
170
+ this.log.warn(LOG_PREFIX.MQTT, 'MQTT client disconnected:', packet)
171
+ })
172
+
173
+ this.client.on('packetreceive', (packet) => {
174
+ this.log.debug(LOG_PREFIX.MQTT, 'Packet received:', packet.cmd)
175
+ })
176
+
177
+ this.client.on('packetsend', (packet) => {
178
+ this.log.debug(LOG_PREFIX.MQTT, 'Packet sent:', packet.cmd)
144
179
  })
145
180
 
146
181
  this.client.on('message', (topic, message) => {
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.9",
4
+ "version": "0.0.10",
5
5
  "author": "Bret Comnes <bcomnes@gmail.com> (https://bret.io)",
6
6
  "bugs": {
7
7
  "url": "https://github.com/bcomnes/homebridge-yoto/issues"