homebridge-yoto 0.0.41 → 0.0.42
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/accessory.js +97 -23
- package/lib/card-control-accessory.js +12 -1
- package/lib/platform.js +59 -48
- package/lib/speaker-accessory.js +34 -5
- package/package.json +1 -1
package/lib/accessory.js
CHANGED
|
@@ -243,6 +243,11 @@ export class YotoPlayerAccessory {
|
|
|
243
243
|
const { Service, Characteristic } = this.#platform
|
|
244
244
|
const service = this.#accessory.getService(Service.AccessoryInformation) ||
|
|
245
245
|
this.#accessory.addService(Service.AccessoryInformation)
|
|
246
|
+
const displayName = sanitizeName(this.#accessory.displayName)
|
|
247
|
+
const nameCharacteristic = service.getCharacteristic(Characteristic.Name)
|
|
248
|
+
const configuredCharacteristic = service.getCharacteristic(Characteristic.ConfiguredName)
|
|
249
|
+
const previousName = nameCharacteristic.value
|
|
250
|
+
const configuredName = configuredCharacteristic.value
|
|
246
251
|
|
|
247
252
|
// Build hardware revision from generation and form factor
|
|
248
253
|
const hardwareRevision = [
|
|
@@ -255,11 +260,16 @@ export class YotoPlayerAccessory {
|
|
|
255
260
|
|
|
256
261
|
// Set standard characteristics
|
|
257
262
|
service
|
|
263
|
+
.setCharacteristic(Characteristic.Name, displayName)
|
|
258
264
|
.setCharacteristic(Characteristic.Manufacturer, DEFAULT_MANUFACTURER)
|
|
259
265
|
.setCharacteristic(Characteristic.Model, model)
|
|
260
266
|
.setCharacteristic(Characteristic.SerialNumber, this.#device.deviceId)
|
|
261
267
|
.setCharacteristic(Characteristic.HardwareRevision, hardwareRevision)
|
|
262
268
|
|
|
269
|
+
if (typeof configuredName !== 'string' || configuredName === previousName) {
|
|
270
|
+
service.setCharacteristic(Characteristic.ConfiguredName, displayName)
|
|
271
|
+
}
|
|
272
|
+
|
|
263
273
|
// Set firmware version from live status if available
|
|
264
274
|
if (this.#deviceModel.status.firmwareVersion) {
|
|
265
275
|
service.setCharacteristic(
|
|
@@ -933,7 +943,9 @@ export class YotoPlayerAccessory {
|
|
|
933
943
|
* @returns {Promise<CharacteristicValue>}
|
|
934
944
|
*/
|
|
935
945
|
async getPlaybackOn () {
|
|
936
|
-
|
|
946
|
+
const isOn = this.#deviceModel.playback.playbackStatus === 'playing'
|
|
947
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get playback switch -> ${isOn}`)
|
|
948
|
+
return isOn
|
|
937
949
|
}
|
|
938
950
|
|
|
939
951
|
/**
|
|
@@ -1033,7 +1045,9 @@ export class YotoPlayerAccessory {
|
|
|
1033
1045
|
* @returns {Promise<CharacteristicValue>}
|
|
1034
1046
|
*/
|
|
1035
1047
|
async getVolumeOn () {
|
|
1036
|
-
|
|
1048
|
+
const isOn = this.#deviceModel.status.volume > 0
|
|
1049
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get volume on -> ${isOn}`)
|
|
1050
|
+
return isOn
|
|
1037
1051
|
}
|
|
1038
1052
|
|
|
1039
1053
|
/**
|
|
@@ -1064,7 +1078,9 @@ export class YotoPlayerAccessory {
|
|
|
1064
1078
|
* @returns {Promise<CharacteristicValue>}
|
|
1065
1079
|
*/
|
|
1066
1080
|
async getStatusActive () {
|
|
1067
|
-
|
|
1081
|
+
const isOnline = this.#deviceModel.status.isOnline
|
|
1082
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get status active -> ${isOnline}`)
|
|
1083
|
+
return isOnline
|
|
1068
1084
|
}
|
|
1069
1085
|
|
|
1070
1086
|
/**
|
|
@@ -1073,7 +1089,9 @@ export class YotoPlayerAccessory {
|
|
|
1073
1089
|
*/
|
|
1074
1090
|
async getOnlineStatus () {
|
|
1075
1091
|
const { Characteristic } = this.#platform
|
|
1076
|
-
|
|
1092
|
+
const isOnline = this.#deviceModel.status.isOnline
|
|
1093
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get online status -> ${isOnline}`)
|
|
1094
|
+
return isOnline
|
|
1077
1095
|
? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED
|
|
1078
1096
|
: Characteristic.ContactSensorState.CONTACT_DETECTED
|
|
1079
1097
|
}
|
|
@@ -1086,7 +1104,9 @@ export class YotoPlayerAccessory {
|
|
|
1086
1104
|
*/
|
|
1087
1105
|
async getBatteryLevel () {
|
|
1088
1106
|
const battery = this.#deviceModel.status.batteryLevelPercentage
|
|
1089
|
-
|
|
1107
|
+
const level = Number.isFinite(battery) ? battery : 100
|
|
1108
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get battery level -> ${level}`)
|
|
1109
|
+
return level
|
|
1090
1110
|
}
|
|
1091
1111
|
|
|
1092
1112
|
/**
|
|
@@ -1095,6 +1115,7 @@ export class YotoPlayerAccessory {
|
|
|
1095
1115
|
*/
|
|
1096
1116
|
async getChargingState () {
|
|
1097
1117
|
const isCharging = this.#deviceModel.status.isCharging
|
|
1118
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get charging state -> ${isCharging}`)
|
|
1098
1119
|
return isCharging
|
|
1099
1120
|
? this.#platform.Characteristic.ChargingState.CHARGING
|
|
1100
1121
|
: this.#platform.Characteristic.ChargingState.NOT_CHARGING
|
|
@@ -1107,6 +1128,7 @@ export class YotoPlayerAccessory {
|
|
|
1107
1128
|
async getStatusLowBattery () {
|
|
1108
1129
|
const battery = this.#deviceModel.status.batteryLevelPercentage
|
|
1109
1130
|
const batteryLevel = Number.isFinite(battery) ? battery : 100
|
|
1131
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get low battery -> ${batteryLevel}`)
|
|
1110
1132
|
return batteryLevel <= LOW_BATTERY_THRESHOLD
|
|
1111
1133
|
? this.#platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW
|
|
1112
1134
|
: this.#platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL
|
|
@@ -1123,9 +1145,11 @@ export class YotoPlayerAccessory {
|
|
|
1123
1145
|
|
|
1124
1146
|
// Return a default value if temperature is not available
|
|
1125
1147
|
if (temp === null || temp === 'notSupported') {
|
|
1148
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get temperature -> unavailable`)
|
|
1126
1149
|
return 0
|
|
1127
1150
|
}
|
|
1128
1151
|
|
|
1152
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get temperature -> ${temp}`)
|
|
1129
1153
|
return Number(temp)
|
|
1130
1154
|
}
|
|
1131
1155
|
|
|
@@ -1135,13 +1159,18 @@ export class YotoPlayerAccessory {
|
|
|
1135
1159
|
*/
|
|
1136
1160
|
async getTemperatureSensorFault () {
|
|
1137
1161
|
// Report fault if device is offline or temperature is not available
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1162
|
+
const isOffline = !this.#deviceModel.status.isOnline
|
|
1163
|
+
const temp = this.#deviceModel.status.temperatureCelsius
|
|
1164
|
+
const isUnavailable = temp === null || temp === 'notSupported'
|
|
1165
|
+
const isFault = isOffline || isUnavailable
|
|
1166
|
+
const fault = isFault
|
|
1167
|
+
? this.#platform.Characteristic.StatusFault.GENERAL_FAULT
|
|
1168
|
+
: this.#platform.Characteristic.StatusFault.NO_FAULT
|
|
1169
|
+
this.#log.debug(
|
|
1170
|
+
LOG_PREFIX.ACCESSORY,
|
|
1171
|
+
`[${this.#device.name}] Get temperature sensor fault -> ${isFault} (online=${!isOffline} temp=${temp})`
|
|
1172
|
+
)
|
|
1173
|
+
return fault
|
|
1145
1174
|
}
|
|
1146
1175
|
|
|
1147
1176
|
// ==================== Nightlight Characteristic Handlers ====================
|
|
@@ -1183,7 +1212,9 @@ export class YotoPlayerAccessory {
|
|
|
1183
1212
|
*/
|
|
1184
1213
|
async getDayNightlightOn () {
|
|
1185
1214
|
const color = this.#deviceModel.config.ambientColour
|
|
1186
|
-
|
|
1215
|
+
const isOn = !this.isColorOff(color)
|
|
1216
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get day nightlight on -> ${isOn} (${color})`)
|
|
1217
|
+
return isOn
|
|
1187
1218
|
}
|
|
1188
1219
|
|
|
1189
1220
|
/**
|
|
@@ -1220,10 +1251,16 @@ export class YotoPlayerAccessory {
|
|
|
1220
1251
|
*/
|
|
1221
1252
|
async getDayNightlightBrightness () {
|
|
1222
1253
|
const config = this.#deviceModel.config
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1254
|
+
const isAuto = config.dayDisplayBrightnessAuto
|
|
1255
|
+
const raw = config.dayDisplayBrightness
|
|
1256
|
+
const brightness = isAuto || raw === null
|
|
1257
|
+
? 100
|
|
1258
|
+
: Math.max(0, Math.min(Math.round(raw), 100))
|
|
1259
|
+
this.#log.debug(
|
|
1260
|
+
LOG_PREFIX.ACCESSORY,
|
|
1261
|
+
`[${this.#device.name}] Get day nightlight brightness -> ${brightness} (raw=${raw} auto=${isAuto})`
|
|
1262
|
+
)
|
|
1263
|
+
return brightness
|
|
1227
1264
|
}
|
|
1228
1265
|
|
|
1229
1266
|
/**
|
|
@@ -1260,10 +1297,12 @@ export class YotoPlayerAccessory {
|
|
|
1260
1297
|
async getDayNightlightHue () {
|
|
1261
1298
|
const color = this.#deviceModel.config.ambientColour
|
|
1262
1299
|
if (this.isColorOff(color)) {
|
|
1300
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get day nightlight hue -> 0 (off)`)
|
|
1263
1301
|
return 0
|
|
1264
1302
|
}
|
|
1265
1303
|
const hex = this.parseHexColor(color)
|
|
1266
1304
|
const [h] = convert.hex.hsv(hex)
|
|
1305
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get day nightlight hue -> ${h} (${color})`)
|
|
1267
1306
|
return h
|
|
1268
1307
|
}
|
|
1269
1308
|
|
|
@@ -1311,10 +1350,12 @@ export class YotoPlayerAccessory {
|
|
|
1311
1350
|
async getDayNightlightSaturation () {
|
|
1312
1351
|
const color = this.#deviceModel.config.ambientColour
|
|
1313
1352
|
if (this.isColorOff(color)) {
|
|
1353
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get day nightlight saturation -> 0 (off)`)
|
|
1314
1354
|
return 0
|
|
1315
1355
|
}
|
|
1316
1356
|
const hex = this.parseHexColor(color)
|
|
1317
1357
|
const [, s] = convert.hex.hsv(hex)
|
|
1358
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get day nightlight saturation -> ${s} (${color})`)
|
|
1318
1359
|
return s
|
|
1319
1360
|
}
|
|
1320
1361
|
|
|
@@ -1363,7 +1404,9 @@ export class YotoPlayerAccessory {
|
|
|
1363
1404
|
*/
|
|
1364
1405
|
async getNightNightlightOn () {
|
|
1365
1406
|
const color = this.#deviceModel.config.nightAmbientColour
|
|
1366
|
-
|
|
1407
|
+
const isOn = !this.isColorOff(color)
|
|
1408
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get night nightlight on -> ${isOn} (${color})`)
|
|
1409
|
+
return isOn
|
|
1367
1410
|
}
|
|
1368
1411
|
|
|
1369
1412
|
/**
|
|
@@ -1400,10 +1443,16 @@ export class YotoPlayerAccessory {
|
|
|
1400
1443
|
*/
|
|
1401
1444
|
async getNightNightlightBrightness () {
|
|
1402
1445
|
const config = this.#deviceModel.config
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1446
|
+
const isAuto = config.nightDisplayBrightnessAuto
|
|
1447
|
+
const raw = config.nightDisplayBrightness
|
|
1448
|
+
const brightness = isAuto || raw === null
|
|
1449
|
+
? 100
|
|
1450
|
+
: Math.max(0, Math.min(Math.round(raw), 100))
|
|
1451
|
+
this.#log.debug(
|
|
1452
|
+
LOG_PREFIX.ACCESSORY,
|
|
1453
|
+
`[${this.#device.name}] Get night nightlight brightness -> ${brightness} (raw=${raw} auto=${isAuto})`
|
|
1454
|
+
)
|
|
1455
|
+
return brightness
|
|
1407
1456
|
}
|
|
1408
1457
|
|
|
1409
1458
|
/**
|
|
@@ -1440,10 +1489,12 @@ export class YotoPlayerAccessory {
|
|
|
1440
1489
|
async getNightNightlightHue () {
|
|
1441
1490
|
const color = this.#deviceModel.config.nightAmbientColour
|
|
1442
1491
|
if (this.isColorOff(color)) {
|
|
1492
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get night nightlight hue -> 0 (off)`)
|
|
1443
1493
|
return 0
|
|
1444
1494
|
}
|
|
1445
1495
|
const hex = this.parseHexColor(color)
|
|
1446
1496
|
const [h] = convert.hex.hsv(hex)
|
|
1497
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get night nightlight hue -> ${h} (${color})`)
|
|
1447
1498
|
return h
|
|
1448
1499
|
}
|
|
1449
1500
|
|
|
@@ -1491,10 +1542,12 @@ export class YotoPlayerAccessory {
|
|
|
1491
1542
|
async getNightNightlightSaturation () {
|
|
1492
1543
|
const color = this.#deviceModel.config.nightAmbientColour
|
|
1493
1544
|
if (this.isColorOff(color)) {
|
|
1545
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get night nightlight saturation -> 0 (off)`)
|
|
1494
1546
|
return 0
|
|
1495
1547
|
}
|
|
1496
1548
|
const hex = this.parseHexColor(color)
|
|
1497
1549
|
const [, s] = convert.hex.hsv(hex)
|
|
1550
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get night nightlight saturation -> ${s} (${color})`)
|
|
1498
1551
|
return s
|
|
1499
1552
|
}
|
|
1500
1553
|
|
|
@@ -1545,6 +1598,7 @@ export class YotoPlayerAccessory {
|
|
|
1545
1598
|
const { Characteristic } = this.#platform
|
|
1546
1599
|
const status = this.#deviceModel.status
|
|
1547
1600
|
const isActive = status.nightlightMode !== 'off'
|
|
1601
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get nightlight active -> ${isActive}`)
|
|
1548
1602
|
return isActive ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED
|
|
1549
1603
|
}
|
|
1550
1604
|
|
|
@@ -1558,6 +1612,10 @@ export class YotoPlayerAccessory {
|
|
|
1558
1612
|
const isDay = status.dayMode === 'day'
|
|
1559
1613
|
const isActive = status.nightlightMode !== 'off'
|
|
1560
1614
|
const isShowing = isDay && isActive
|
|
1615
|
+
this.#log.debug(
|
|
1616
|
+
LOG_PREFIX.ACCESSORY,
|
|
1617
|
+
`[${this.#device.name}] Get day nightlight active -> ${isShowing} (day=${isDay} active=${isActive})`
|
|
1618
|
+
)
|
|
1561
1619
|
return isShowing ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED
|
|
1562
1620
|
}
|
|
1563
1621
|
|
|
@@ -1571,6 +1629,10 @@ export class YotoPlayerAccessory {
|
|
|
1571
1629
|
const isNight = status.dayMode === 'night'
|
|
1572
1630
|
const isActive = status.nightlightMode !== 'off'
|
|
1573
1631
|
const isShowing = isNight && isActive
|
|
1632
|
+
this.#log.debug(
|
|
1633
|
+
LOG_PREFIX.ACCESSORY,
|
|
1634
|
+
`[${this.#device.name}] Get night nightlight active -> ${isShowing} (night=${isNight} active=${isActive})`
|
|
1635
|
+
)
|
|
1574
1636
|
return isShowing ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED
|
|
1575
1637
|
}
|
|
1576
1638
|
|
|
@@ -1584,6 +1646,7 @@ export class YotoPlayerAccessory {
|
|
|
1584
1646
|
const { Characteristic } = this.#platform
|
|
1585
1647
|
const status = this.#deviceModel.status
|
|
1586
1648
|
const hasCard = status.cardInsertionState !== 'none'
|
|
1649
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get card slot -> ${hasCard} (${status.cardInsertionState})`)
|
|
1587
1650
|
return hasCard ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED
|
|
1588
1651
|
}
|
|
1589
1652
|
|
|
@@ -1597,6 +1660,7 @@ export class YotoPlayerAccessory {
|
|
|
1597
1660
|
const { Characteristic } = this.#platform
|
|
1598
1661
|
const status = this.#deviceModel.status
|
|
1599
1662
|
const isDayMode = status.dayMode === 'day'
|
|
1663
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get day mode -> ${isDayMode}`)
|
|
1600
1664
|
return isDayMode
|
|
1601
1665
|
? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED
|
|
1602
1666
|
: Characteristic.ContactSensorState.CONTACT_DETECTED
|
|
@@ -1610,6 +1674,10 @@ export class YotoPlayerAccessory {
|
|
|
1610
1674
|
*/
|
|
1611
1675
|
async getSleepTimerState () {
|
|
1612
1676
|
const playback = this.#deviceModel.playback
|
|
1677
|
+
this.#log.debug(
|
|
1678
|
+
LOG_PREFIX.ACCESSORY,
|
|
1679
|
+
`[${this.#device.name}] Get sleep timer -> ${playback.sleepTimerActive ?? false}`
|
|
1680
|
+
)
|
|
1613
1681
|
return playback.sleepTimerActive ?? false
|
|
1614
1682
|
}
|
|
1615
1683
|
|
|
@@ -1645,7 +1713,9 @@ export class YotoPlayerAccessory {
|
|
|
1645
1713
|
* @returns {Promise<CharacteristicValue>}
|
|
1646
1714
|
*/
|
|
1647
1715
|
async getBluetoothState () {
|
|
1648
|
-
|
|
1716
|
+
const enabled = this.#deviceModel.config.bluetoothEnabled ?? false
|
|
1717
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get Bluetooth -> ${enabled}`)
|
|
1718
|
+
return enabled
|
|
1649
1719
|
}
|
|
1650
1720
|
|
|
1651
1721
|
/**
|
|
@@ -1677,6 +1747,10 @@ export class YotoPlayerAccessory {
|
|
|
1677
1747
|
async setCardControl (service, control, value) {
|
|
1678
1748
|
const { Characteristic } = this.#platform
|
|
1679
1749
|
const isOn = Boolean(value)
|
|
1750
|
+
this.#log.debug(
|
|
1751
|
+
LOG_PREFIX.ACCESSORY,
|
|
1752
|
+
`[${this.#device.name}] Set card control: ${control.label} (${control.cardId}) -> ${isOn}`
|
|
1753
|
+
)
|
|
1680
1754
|
|
|
1681
1755
|
if (!isOn) {
|
|
1682
1756
|
service.getCharacteristic(Characteristic.On).updateValue(false)
|
|
@@ -72,13 +72,20 @@ export class YotoCardControlAccessory {
|
|
|
72
72
|
this.#accessory.addService(Service.AccessoryInformation)
|
|
73
73
|
|
|
74
74
|
const displayName = sanitizeName(this.#accessory.displayName)
|
|
75
|
+
const nameCharacteristic = service.getCharacteristic(Characteristic.Name)
|
|
76
|
+
const configuredCharacteristic = service.getCharacteristic(Characteristic.ConfiguredName)
|
|
77
|
+
const previousName = nameCharacteristic.value
|
|
78
|
+
const configuredName = configuredCharacteristic.value
|
|
75
79
|
|
|
76
80
|
service
|
|
77
81
|
.setCharacteristic(Characteristic.Manufacturer, DEFAULT_MANUFACTURER)
|
|
78
82
|
.setCharacteristic(Characteristic.Model, DEFAULT_MODEL)
|
|
79
83
|
.setCharacteristic(Characteristic.SerialNumber, this.#cardControl.id)
|
|
80
84
|
.setCharacteristic(Characteristic.Name, displayName)
|
|
81
|
-
|
|
85
|
+
|
|
86
|
+
if (typeof configuredName !== 'string' || configuredName === previousName) {
|
|
87
|
+
service.setCharacteristic(Characteristic.ConfiguredName, displayName)
|
|
88
|
+
}
|
|
82
89
|
|
|
83
90
|
this.#currentServices.add(service)
|
|
84
91
|
}
|
|
@@ -114,6 +121,10 @@ export class YotoCardControlAccessory {
|
|
|
114
121
|
async setCardControl (value) {
|
|
115
122
|
const { Characteristic } = this.#platform
|
|
116
123
|
const isOn = Boolean(value)
|
|
124
|
+
this.#log.debug(
|
|
125
|
+
LOG_PREFIX.ACCESSORY,
|
|
126
|
+
`Card control toggle requested: ${this.#cardControl.label} (${this.#cardControl.cardId}) -> ${isOn}`
|
|
127
|
+
)
|
|
117
128
|
|
|
118
129
|
if (!isOn) {
|
|
119
130
|
this.switchService?.getCharacteristic(Characteristic.On).updateValue(false)
|
package/lib/platform.js
CHANGED
|
@@ -170,7 +170,7 @@ export class YotoPlatform {
|
|
|
170
170
|
*/
|
|
171
171
|
configureAccessory (accessory) {
|
|
172
172
|
const { log, accessories, cardAccessories } = this
|
|
173
|
-
log.debug('Loading accessory from cache:', accessory.displayName)
|
|
173
|
+
log.debug('Loading accessory from cache:', accessory.displayName, accessory.UUID)
|
|
174
174
|
|
|
175
175
|
const context = accessory.context
|
|
176
176
|
const record = context && typeof context === 'object'
|
|
@@ -179,6 +179,7 @@ export class YotoPlatform {
|
|
|
179
179
|
const accessoryType = record && typeof record['type'] === 'string'
|
|
180
180
|
? record['type']
|
|
181
181
|
: undefined
|
|
182
|
+
log.debug('Cached accessory context type:', accessory.displayName, accessoryType ?? 'device')
|
|
182
183
|
|
|
183
184
|
if (accessoryType === 'card-control' || record?.['cardControl']) {
|
|
184
185
|
cardAccessories.set(accessory.UUID, /** @type {PlatformAccessory<YotoCardAccessoryContext>} */ (accessory))
|
|
@@ -200,6 +201,7 @@ export class YotoPlatform {
|
|
|
200
201
|
|
|
201
202
|
try {
|
|
202
203
|
this.log.debug('Starting Yoto account...')
|
|
204
|
+
this.log.debug('Playback accessory mode:', this.playbackAccessoryConfig.mode)
|
|
203
205
|
|
|
204
206
|
// Listen for devices being added
|
|
205
207
|
this.yotoAccount.on('deviceAdded', async ({ deviceId }) => {
|
|
@@ -212,6 +214,7 @@ export class YotoPlatform {
|
|
|
212
214
|
|
|
213
215
|
const device = deviceModel.device
|
|
214
216
|
this.log.info(`Device discovered: ${device.name} (${deviceId})`)
|
|
217
|
+
this.log.debug('Registering device from account discovery:', device.name, deviceId)
|
|
215
218
|
await this.registerDevice(device, deviceModel)
|
|
216
219
|
})
|
|
217
220
|
|
|
@@ -334,10 +337,15 @@ export class YotoPlatform {
|
|
|
334
337
|
await this.yotoAccount.start()
|
|
335
338
|
|
|
336
339
|
this.log.info(`✓ Yoto account started with ${this.yotoAccount.devices.size} device(s)`)
|
|
340
|
+
this.log.debug(
|
|
341
|
+
'Account devices:',
|
|
342
|
+
Array.from(this.yotoAccount.devices.keys()).join(', ') || 'none'
|
|
343
|
+
)
|
|
337
344
|
|
|
338
345
|
// Remove stale accessories after all devices are registered
|
|
339
346
|
this.removeStaleAccessories()
|
|
340
347
|
|
|
348
|
+
this.log.debug('Registering card control accessories (playOnAll).')
|
|
341
349
|
await this.registerCardControlAccessories()
|
|
342
350
|
} catch (error) {
|
|
343
351
|
this.log.error('Failed to start account:', error instanceof Error ? error.message : String(error))
|
|
@@ -401,28 +409,24 @@ export class YotoPlatform {
|
|
|
401
409
|
const uuid = this.api.hap.uuid.generate(device.deviceId)
|
|
402
410
|
const sanitizedDeviceName = sanitizeName(device.name)
|
|
403
411
|
const accessoryCategory = this.api.hap.Categories.SPEAKER
|
|
412
|
+
this.log.debug(
|
|
413
|
+
'Register device:',
|
|
414
|
+
`${device.name} (${device.deviceId})`,
|
|
415
|
+
`uuid=${uuid}`,
|
|
416
|
+
`category=${accessoryCategory}`
|
|
417
|
+
)
|
|
404
418
|
|
|
405
419
|
// Check if accessory already exists
|
|
406
420
|
const existingAccessory = this.accessories.get(uuid)
|
|
407
421
|
|
|
408
422
|
if (existingAccessory) {
|
|
409
423
|
// Accessory exists - update it
|
|
410
|
-
this.log.debug('Restoring existing accessory from cache:', device.name)
|
|
424
|
+
this.log.debug('Restoring existing accessory from cache:', device.name, existingAccessory.UUID)
|
|
411
425
|
|
|
412
426
|
// Update display name if it has changed
|
|
413
427
|
if (existingAccessory.displayName !== sanitizedDeviceName) {
|
|
428
|
+
this.log.debug('Updating accessory display name:', existingAccessory.displayName, '->', sanitizedDeviceName)
|
|
414
429
|
existingAccessory.updateDisplayName(sanitizedDeviceName)
|
|
415
|
-
const infoService = existingAccessory.getService(this.api.hap.Service.AccessoryInformation)
|
|
416
|
-
if (infoService) {
|
|
417
|
-
// Only update Name, preserve user's ConfiguredName customization
|
|
418
|
-
// ConfiguredName is intentionally NOT updated here because:
|
|
419
|
-
// - It allows users to rename accessories in the Home app
|
|
420
|
-
// - Their custom names should survive Homebridge restarts and Yoto device name changes
|
|
421
|
-
// - Name stays in sync with Yoto's device name for plugin identification
|
|
422
|
-
infoService
|
|
423
|
-
.setCharacteristic(this.api.hap.Characteristic.Name, sanitizedDeviceName)
|
|
424
|
-
.setCharacteristic(this.api.hap.Characteristic.ConfiguredName, sanitizedDeviceName)
|
|
425
|
-
}
|
|
426
430
|
}
|
|
427
431
|
|
|
428
432
|
// Update context with fresh device data
|
|
@@ -439,6 +443,7 @@ export class YotoPlatform {
|
|
|
439
443
|
|
|
440
444
|
// Update accessory information
|
|
441
445
|
this.api.updatePlatformAccessories([existingAccessory])
|
|
446
|
+
this.log.debug('Updated accessory cache entry:', existingAccessory.displayName, existingAccessory.UUID)
|
|
442
447
|
|
|
443
448
|
// Create handler for this accessory with device model
|
|
444
449
|
const handler = new YotoPlayerAccessory({
|
|
@@ -449,9 +454,11 @@ export class YotoPlatform {
|
|
|
449
454
|
|
|
450
455
|
// Track handler
|
|
451
456
|
this.accessoryHandlers.set(uuid, handler)
|
|
457
|
+
this.log.debug('Created accessory handler:', existingAccessory.displayName, uuid)
|
|
452
458
|
|
|
453
459
|
// Initialize accessory (setup services and event listeners)
|
|
454
460
|
await handler.setup()
|
|
461
|
+
this.log.debug('Accessory setup complete:', existingAccessory.displayName)
|
|
455
462
|
|
|
456
463
|
if (this.playbackAccessoryConfig.mode === 'external') {
|
|
457
464
|
await this.registerSpeakerAccessory(device, deviceModel)
|
|
@@ -460,21 +467,13 @@ export class YotoPlatform {
|
|
|
460
467
|
return { success: true }
|
|
461
468
|
} else {
|
|
462
469
|
// Create new accessory
|
|
463
|
-
this.log.debug('Adding new accessory:', device.name)
|
|
470
|
+
this.log.debug('Adding new accessory:', device.name, uuid)
|
|
464
471
|
|
|
465
472
|
// Create platform accessory
|
|
466
473
|
/** @type {PlatformAccessory<YotoAccessoryContext>} */
|
|
467
474
|
// eslint-disable-next-line new-cap
|
|
468
475
|
const accessory = new this.api.platformAccessory(sanitizedDeviceName, uuid, accessoryCategory)
|
|
469
476
|
|
|
470
|
-
// Set Name and ConfiguredName on AccessoryInformation service
|
|
471
|
-
const infoService = accessory.getService(this.api.hap.Service.AccessoryInformation)
|
|
472
|
-
if (infoService) {
|
|
473
|
-
infoService
|
|
474
|
-
.setCharacteristic(this.api.hap.Characteristic.Name, sanitizedDeviceName)
|
|
475
|
-
.setCharacteristic(this.api.hap.Characteristic.ConfiguredName, sanitizedDeviceName)
|
|
476
|
-
}
|
|
477
|
-
|
|
478
477
|
// Set accessory context
|
|
479
478
|
accessory.context = {
|
|
480
479
|
type: 'device',
|
|
@@ -490,13 +489,16 @@ export class YotoPlatform {
|
|
|
490
489
|
|
|
491
490
|
// Track handler
|
|
492
491
|
this.accessoryHandlers.set(uuid, handler)
|
|
492
|
+
this.log.debug('Created accessory handler:', device.name, uuid)
|
|
493
493
|
|
|
494
494
|
// Initialize accessory (setup services and event listeners)
|
|
495
495
|
await handler.setup()
|
|
496
|
+
this.log.debug('Accessory setup complete:', device.name)
|
|
496
497
|
|
|
497
498
|
// Register as a platform accessory (bridged).
|
|
498
499
|
this.log.debug(`Registering new accessory: ${device.name}`)
|
|
499
500
|
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory])
|
|
501
|
+
this.log.debug('Registered platform accessory:', device.name, uuid)
|
|
500
502
|
|
|
501
503
|
if (this.playbackAccessoryConfig.mode === 'external') {
|
|
502
504
|
await this.registerSpeakerAccessory(device, deviceModel)
|
|
@@ -504,6 +506,7 @@ export class YotoPlatform {
|
|
|
504
506
|
|
|
505
507
|
// Add to our tracking map (cast to typed version)
|
|
506
508
|
this.accessories.set(uuid, accessory)
|
|
509
|
+
this.log.debug('Tracked new accessory:', device.name, uuid)
|
|
507
510
|
|
|
508
511
|
return { success: true }
|
|
509
512
|
}
|
|
@@ -519,11 +522,12 @@ export class YotoPlatform {
|
|
|
519
522
|
const uuid = this.getSpeakerAccessoryUuid(device.deviceId)
|
|
520
523
|
const speakerName = this.getSpeakerAccessoryName(device)
|
|
521
524
|
if (this.speakerAccessories.has(uuid)) {
|
|
522
|
-
this.log.debug('SmartSpeaker accessory already published:', speakerName)
|
|
525
|
+
this.log.debug('SmartSpeaker accessory already published:', speakerName, uuid)
|
|
523
526
|
return { success: true }
|
|
524
527
|
}
|
|
525
528
|
|
|
526
529
|
this.log.info('Adding new SmartSpeaker accessory:', speakerName)
|
|
530
|
+
this.log.debug('Creating SmartSpeaker accessory:', speakerName, uuid)
|
|
527
531
|
|
|
528
532
|
/** @type {PlatformAccessory<YotoAccessoryContext>} */
|
|
529
533
|
// eslint-disable-next-line new-cap
|
|
@@ -533,13 +537,6 @@ export class YotoPlatform {
|
|
|
533
537
|
this.api.hap.Categories.SPEAKER
|
|
534
538
|
)
|
|
535
539
|
|
|
536
|
-
const infoService = accessory.getService(this.api.hap.Service.AccessoryInformation)
|
|
537
|
-
if (infoService) {
|
|
538
|
-
infoService
|
|
539
|
-
.setCharacteristic(this.api.hap.Characteristic.Name, speakerName)
|
|
540
|
-
.setCharacteristic(this.api.hap.Characteristic.ConfiguredName, speakerName)
|
|
541
|
-
}
|
|
542
|
-
|
|
543
540
|
accessory.context = {
|
|
544
541
|
device,
|
|
545
542
|
}
|
|
@@ -551,11 +548,14 @@ export class YotoPlatform {
|
|
|
551
548
|
})
|
|
552
549
|
|
|
553
550
|
this.speakerAccessoryHandlers.set(uuid, handler)
|
|
551
|
+
this.log.debug('Created SmartSpeaker handler:', speakerName, uuid)
|
|
554
552
|
|
|
555
553
|
await handler.setup()
|
|
554
|
+
this.log.debug('SmartSpeaker setup complete:', speakerName)
|
|
556
555
|
|
|
557
556
|
this.log.info(`Publishing external SmartSpeaker accessory: ${speakerName}`)
|
|
558
557
|
this.api.publishExternalAccessories(PLUGIN_NAME, [accessory])
|
|
558
|
+
this.log.debug('Published external SmartSpeaker accessory:', speakerName, uuid)
|
|
559
559
|
|
|
560
560
|
this.speakerAccessories.set(uuid, accessory)
|
|
561
561
|
|
|
@@ -569,24 +569,21 @@ export class YotoPlatform {
|
|
|
569
569
|
async registerCardControlAccessories () {
|
|
570
570
|
const cardControls = getCardControlConfigs(this.config).filter(control => control.playOnAll)
|
|
571
571
|
const desiredUuids = new Set()
|
|
572
|
+
this.log.debug('Card control configs (playOnAll):', cardControls.length)
|
|
572
573
|
|
|
573
574
|
for (const control of cardControls) {
|
|
574
575
|
const uuid = this.getCardControlAccessoryUuid(control)
|
|
575
576
|
const accessoryName = this.getCardControlAccessoryName(control)
|
|
576
577
|
desiredUuids.add(uuid)
|
|
578
|
+
this.log.debug('Ensuring card control accessory:', accessoryName, uuid)
|
|
577
579
|
|
|
578
580
|
const existingAccessory = this.cardAccessories.get(uuid)
|
|
579
581
|
if (existingAccessory) {
|
|
580
|
-
this.log.debug('Restoring existing card control accessory from cache:', accessoryName)
|
|
582
|
+
this.log.debug('Restoring existing card control accessory from cache:', accessoryName, uuid)
|
|
581
583
|
|
|
582
584
|
if (existingAccessory.displayName !== accessoryName) {
|
|
585
|
+
this.log.debug('Updating card control display name:', existingAccessory.displayName, '->', accessoryName)
|
|
583
586
|
existingAccessory.updateDisplayName(accessoryName)
|
|
584
|
-
const infoService = existingAccessory.getService(this.api.hap.Service.AccessoryInformation)
|
|
585
|
-
if (infoService) {
|
|
586
|
-
infoService
|
|
587
|
-
.setCharacteristic(this.api.hap.Characteristic.Name, accessoryName)
|
|
588
|
-
.setCharacteristic(this.api.hap.Characteristic.ConfiguredName, accessoryName)
|
|
589
|
-
}
|
|
590
587
|
}
|
|
591
588
|
|
|
592
589
|
existingAccessory.context = {
|
|
@@ -595,6 +592,7 @@ export class YotoPlatform {
|
|
|
595
592
|
}
|
|
596
593
|
|
|
597
594
|
this.api.updatePlatformAccessories([existingAccessory])
|
|
595
|
+
this.log.debug('Updated card control cache entry:', existingAccessory.displayName, uuid)
|
|
598
596
|
|
|
599
597
|
const existingHandler = this.cardAccessoryHandlers.get(uuid)
|
|
600
598
|
if (existingHandler) {
|
|
@@ -612,10 +610,11 @@ export class YotoPlatform {
|
|
|
612
610
|
|
|
613
611
|
this.cardAccessoryHandlers.set(uuid, handler)
|
|
614
612
|
await handler.setup()
|
|
613
|
+
this.log.debug('Card control setup complete:', accessoryName)
|
|
615
614
|
continue
|
|
616
615
|
}
|
|
617
616
|
|
|
618
|
-
this.log.debug('Adding new card control accessory:', accessoryName)
|
|
617
|
+
this.log.debug('Adding new card control accessory:', accessoryName, uuid)
|
|
619
618
|
|
|
620
619
|
/** @type {PlatformAccessory<YotoCardAccessoryContext>} */
|
|
621
620
|
// eslint-disable-next-line new-cap
|
|
@@ -625,13 +624,6 @@ export class YotoPlatform {
|
|
|
625
624
|
this.api.hap.Categories.SWITCH
|
|
626
625
|
)
|
|
627
626
|
|
|
628
|
-
const infoService = accessory.getService(this.api.hap.Service.AccessoryInformation)
|
|
629
|
-
if (infoService) {
|
|
630
|
-
infoService
|
|
631
|
-
.setCharacteristic(this.api.hap.Characteristic.Name, accessoryName)
|
|
632
|
-
.setCharacteristic(this.api.hap.Characteristic.ConfiguredName, accessoryName)
|
|
633
|
-
}
|
|
634
|
-
|
|
635
627
|
accessory.context = {
|
|
636
628
|
type: 'card-control',
|
|
637
629
|
cardControl: control,
|
|
@@ -645,11 +637,14 @@ export class YotoPlatform {
|
|
|
645
637
|
|
|
646
638
|
this.cardAccessoryHandlers.set(uuid, handler)
|
|
647
639
|
await handler.setup()
|
|
640
|
+
this.log.debug('Card control setup complete:', accessoryName)
|
|
648
641
|
|
|
649
642
|
this.log.debug(`Registering card control accessory: ${accessoryName}`)
|
|
650
643
|
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory])
|
|
644
|
+
this.log.debug('Registered card control accessory:', accessoryName, uuid)
|
|
651
645
|
|
|
652
646
|
this.cardAccessories.set(uuid, accessory)
|
|
647
|
+
this.log.debug('Tracked card control accessory:', accessoryName, uuid)
|
|
653
648
|
}
|
|
654
649
|
|
|
655
650
|
for (const [uuid, accessory] of this.cardAccessories) {
|
|
@@ -657,7 +652,7 @@ export class YotoPlatform {
|
|
|
657
652
|
continue
|
|
658
653
|
}
|
|
659
654
|
|
|
660
|
-
this.log.debug('Removing card control accessory from cache:', accessory.displayName)
|
|
655
|
+
this.log.debug('Removing card control accessory from cache:', accessory.displayName, uuid)
|
|
661
656
|
|
|
662
657
|
const handler = this.cardAccessoryHandlers.get(uuid)
|
|
663
658
|
if (handler) {
|
|
@@ -669,6 +664,7 @@ export class YotoPlatform {
|
|
|
669
664
|
|
|
670
665
|
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory])
|
|
671
666
|
this.cardAccessories.delete(uuid)
|
|
667
|
+
this.log.debug('Removed card control accessory:', accessory.displayName, uuid)
|
|
672
668
|
}
|
|
673
669
|
}
|
|
674
670
|
|
|
@@ -683,10 +679,16 @@ export class YotoPlatform {
|
|
|
683
679
|
// Get current device IDs from account
|
|
684
680
|
const currentDeviceIds = this.yotoAccount.getDeviceIds()
|
|
685
681
|
const currentUUIDs = currentDeviceIds.map(id => this.api.hap.uuid.generate(id))
|
|
682
|
+
this.log.debug(
|
|
683
|
+
'Evaluating stale accessories:',
|
|
684
|
+
`accountDevices=${currentDeviceIds.length}`,
|
|
685
|
+
`cachedAccessories=${this.accessories.size}`,
|
|
686
|
+
`externalSpeakers=${this.speakerAccessories.size}`
|
|
687
|
+
)
|
|
686
688
|
|
|
687
689
|
for (const [uuid, accessory] of this.accessories) {
|
|
688
690
|
if (!currentUUIDs.includes(uuid)) {
|
|
689
|
-
this.log.debug('Removing existing accessory from cache:', accessory.displayName)
|
|
691
|
+
this.log.debug('Removing existing accessory from cache:', accessory.displayName, uuid)
|
|
690
692
|
|
|
691
693
|
// Stop handler if it exists
|
|
692
694
|
const handler = this.accessoryHandlers.get(uuid)
|
|
@@ -699,16 +701,18 @@ export class YotoPlatform {
|
|
|
699
701
|
|
|
700
702
|
// Unregister from Homebridge
|
|
701
703
|
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory])
|
|
704
|
+
this.log.debug('Unregistered accessory from Homebridge:', accessory.displayName, uuid)
|
|
702
705
|
|
|
703
706
|
// Remove from our tracking map
|
|
704
707
|
this.accessories.delete(uuid)
|
|
708
|
+
this.log.debug('Removed accessory from tracking map:', accessory.displayName, uuid)
|
|
705
709
|
}
|
|
706
710
|
}
|
|
707
711
|
|
|
708
712
|
for (const [uuid, accessory] of this.speakerAccessories) {
|
|
709
713
|
const deviceId = accessory.context.device?.deviceId
|
|
710
714
|
if (!deviceId || !currentDeviceIds.includes(deviceId)) {
|
|
711
|
-
this.log.debug('Removing external SmartSpeaker accessory from runtime:', accessory.displayName)
|
|
715
|
+
this.log.debug('Removing external SmartSpeaker accessory from runtime:', accessory.displayName, uuid)
|
|
712
716
|
|
|
713
717
|
const handler = this.speakerAccessoryHandlers.get(uuid)
|
|
714
718
|
if (handler) {
|
|
@@ -719,6 +723,7 @@ export class YotoPlatform {
|
|
|
719
723
|
}
|
|
720
724
|
|
|
721
725
|
this.speakerAccessories.delete(uuid)
|
|
726
|
+
this.log.debug('Removed external SmartSpeaker accessory:', accessory.displayName, uuid)
|
|
722
727
|
}
|
|
723
728
|
}
|
|
724
729
|
}
|
|
@@ -728,6 +733,12 @@ export class YotoPlatform {
|
|
|
728
733
|
*/
|
|
729
734
|
async shutdown () {
|
|
730
735
|
this.log.debug('Shutting down Yoto platform...')
|
|
736
|
+
this.log.debug(
|
|
737
|
+
'Handlers to stop:',
|
|
738
|
+
`devices=${this.accessoryHandlers.size}`,
|
|
739
|
+
`speakers=${this.speakerAccessoryHandlers.size}`,
|
|
740
|
+
`cardControls=${this.cardAccessoryHandlers.size}`
|
|
741
|
+
)
|
|
731
742
|
|
|
732
743
|
// Stop all accessory handlers
|
|
733
744
|
const stopPromises = []
|
package/lib/speaker-accessory.js
CHANGED
|
@@ -78,6 +78,11 @@ export class YotoSpeakerAccessory {
|
|
|
78
78
|
const { Service, Characteristic } = this.#platform
|
|
79
79
|
const service = this.#accessory.getService(Service.AccessoryInformation) ||
|
|
80
80
|
this.#accessory.addService(Service.AccessoryInformation)
|
|
81
|
+
const displayName = sanitizeName(this.#accessory.displayName)
|
|
82
|
+
const nameCharacteristic = service.getCharacteristic(Characteristic.Name)
|
|
83
|
+
const configuredCharacteristic = service.getCharacteristic(Characteristic.ConfiguredName)
|
|
84
|
+
const previousName = nameCharacteristic.value
|
|
85
|
+
const configuredName = configuredCharacteristic.value
|
|
81
86
|
|
|
82
87
|
const hardwareRevision = [
|
|
83
88
|
this.#device.generation,
|
|
@@ -87,11 +92,16 @@ export class YotoSpeakerAccessory {
|
|
|
87
92
|
const model = this.#device.deviceFamily || this.#device.deviceType || DEFAULT_MODEL
|
|
88
93
|
|
|
89
94
|
service
|
|
95
|
+
.setCharacteristic(Characteristic.Name, displayName)
|
|
90
96
|
.setCharacteristic(Characteristic.Manufacturer, DEFAULT_MANUFACTURER)
|
|
91
97
|
.setCharacteristic(Characteristic.Model, model)
|
|
92
98
|
.setCharacteristic(Characteristic.SerialNumber, this.#device.deviceId)
|
|
93
99
|
.setCharacteristic(Characteristic.HardwareRevision, hardwareRevision)
|
|
94
100
|
|
|
101
|
+
if (typeof configuredName !== 'string' || configuredName === previousName) {
|
|
102
|
+
service.setCharacteristic(Characteristic.ConfiguredName, displayName)
|
|
103
|
+
}
|
|
104
|
+
|
|
95
105
|
if (this.#deviceModel.status.firmwareVersion) {
|
|
96
106
|
service.setCharacteristic(
|
|
97
107
|
Characteristic.FirmwareRevision,
|
|
@@ -285,7 +295,12 @@ export class YotoSpeakerAccessory {
|
|
|
285
295
|
*/
|
|
286
296
|
async getCurrentMediaState () {
|
|
287
297
|
const playbackStatus = this.#deviceModel.playback.playbackStatus ?? null
|
|
288
|
-
|
|
298
|
+
const current = this.getMediaStateValues(playbackStatus).current
|
|
299
|
+
this.#log.debug(
|
|
300
|
+
LOG_PREFIX.ACCESSORY,
|
|
301
|
+
`[${this.#device.name}] Get current media state -> ${current} (${playbackStatus ?? 'unknown'})`
|
|
302
|
+
)
|
|
303
|
+
return current
|
|
289
304
|
}
|
|
290
305
|
|
|
291
306
|
/**
|
|
@@ -294,7 +309,12 @@ export class YotoSpeakerAccessory {
|
|
|
294
309
|
*/
|
|
295
310
|
async getTargetMediaState () {
|
|
296
311
|
const playbackStatus = this.#deviceModel.playback.playbackStatus ?? null
|
|
297
|
-
|
|
312
|
+
const target = this.getMediaStateValues(playbackStatus).target
|
|
313
|
+
this.#log.debug(
|
|
314
|
+
LOG_PREFIX.ACCESSORY,
|
|
315
|
+
`[${this.#device.name}] Get target media state -> ${target} (${playbackStatus ?? 'unknown'})`
|
|
316
|
+
)
|
|
317
|
+
return target
|
|
298
318
|
}
|
|
299
319
|
|
|
300
320
|
/**
|
|
@@ -337,7 +357,12 @@ export class YotoSpeakerAccessory {
|
|
|
337
357
|
const volumeSteps = this.#deviceModel.status.volume
|
|
338
358
|
const normalizedSteps = Number.isFinite(volumeSteps) ? volumeSteps : 0
|
|
339
359
|
const clampedSteps = Math.max(0, Math.min(normalizedSteps, 16))
|
|
340
|
-
|
|
360
|
+
const percent = Math.round((clampedSteps / 16) * 100)
|
|
361
|
+
this.#log.debug(
|
|
362
|
+
LOG_PREFIX.ACCESSORY,
|
|
363
|
+
`[${this.#device.name}] Get speaker volume -> ${percent} (rawSteps=${volumeSteps})`
|
|
364
|
+
)
|
|
365
|
+
return percent
|
|
341
366
|
}
|
|
342
367
|
|
|
343
368
|
/**
|
|
@@ -380,7 +405,9 @@ export class YotoSpeakerAccessory {
|
|
|
380
405
|
* @returns {Promise<CharacteristicValue>}
|
|
381
406
|
*/
|
|
382
407
|
async getMute () {
|
|
383
|
-
|
|
408
|
+
const isMuted = this.#deviceModel.status.volume === 0
|
|
409
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get speaker mute -> ${isMuted}`)
|
|
410
|
+
return isMuted
|
|
384
411
|
}
|
|
385
412
|
|
|
386
413
|
/**
|
|
@@ -405,7 +432,9 @@ export class YotoSpeakerAccessory {
|
|
|
405
432
|
* @returns {Promise<CharacteristicValue>}
|
|
406
433
|
*/
|
|
407
434
|
async getStatusActive () {
|
|
408
|
-
|
|
435
|
+
const isOnline = this.#deviceModel.status.isOnline
|
|
436
|
+
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Get speaker status active -> ${isOnline}`)
|
|
437
|
+
return isOnline
|
|
409
438
|
}
|
|
410
439
|
|
|
411
440
|
/**
|
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.42",
|
|
5
5
|
"author": "Bret Comnes <bcomnes@gmail.com> (https://bret.io)",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/bcomnes/homebridge-yoto/issues"
|