homebridge-yoto 0.0.37 → 0.0.39
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 +80 -57
- package/lib/platform.js +52 -16
- package/package.json +1 -1
package/lib/accessory.js
CHANGED
|
@@ -92,8 +92,8 @@ export class YotoPlayerAccessory {
|
|
|
92
92
|
/** @type {Service | undefined} */ bluetoothService
|
|
93
93
|
/** @type {Service | undefined} */ dayMaxVolumeService
|
|
94
94
|
/** @type {Service | undefined} */ nightMaxVolumeService
|
|
95
|
-
// Volume state for mute/unmute (0-
|
|
96
|
-
/** @type {number} */ #lastNonZeroVolume =
|
|
95
|
+
// Volume state for mute/unmute (0-100 percent)
|
|
96
|
+
/** @type {number} */ #lastNonZeroVolume = 50
|
|
97
97
|
// Nightlight color state for restore-on-ON
|
|
98
98
|
/** @type {string} */ #lastDayColor = '0xffffff'
|
|
99
99
|
/** @type {string} */ #lastNightColor = '0xffffff'
|
|
@@ -325,7 +325,7 @@ export class YotoPlayerAccessory {
|
|
|
325
325
|
.getCharacteristic(Characteristic.Brightness)
|
|
326
326
|
.setProps({
|
|
327
327
|
minValue: 0,
|
|
328
|
-
maxValue:
|
|
328
|
+
maxValue: 100,
|
|
329
329
|
minStep: 1,
|
|
330
330
|
})
|
|
331
331
|
.onGet(this.getVolume.bind(this))
|
|
@@ -598,7 +598,7 @@ export class YotoPlayerAccessory {
|
|
|
598
598
|
|
|
599
599
|
dayService
|
|
600
600
|
.getCharacteristic(Characteristic.Brightness)
|
|
601
|
-
.setProps({ minValue: 0, maxValue:
|
|
601
|
+
.setProps({ minValue: 0, maxValue: 100, minStep: 1 })
|
|
602
602
|
.onGet(this.getDayMaxVolume.bind(this))
|
|
603
603
|
.onSet(this.setDayMaxVolume.bind(this))
|
|
604
604
|
|
|
@@ -622,7 +622,7 @@ export class YotoPlayerAccessory {
|
|
|
622
622
|
|
|
623
623
|
nightService
|
|
624
624
|
.getCharacteristic(Characteristic.Brightness)
|
|
625
|
-
.setProps({ minValue: 0, maxValue:
|
|
625
|
+
.setProps({ minValue: 0, maxValue: 100, minStep: 1 })
|
|
626
626
|
.onGet(this.getNightMaxVolume.bind(this))
|
|
627
627
|
.onSet(this.setNightMaxVolume.bind(this))
|
|
628
628
|
|
|
@@ -917,16 +917,23 @@ export class YotoPlayerAccessory {
|
|
|
917
917
|
}
|
|
918
918
|
|
|
919
919
|
/**
|
|
920
|
-
* Get volume level (0-16 steps)
|
|
920
|
+
* Get volume level as percentage (mapped from 0-16 steps)
|
|
921
921
|
* @returns {Promise<CharacteristicValue>}
|
|
922
922
|
*/
|
|
923
923
|
async getVolume () {
|
|
924
|
-
this.#
|
|
925
|
-
|
|
924
|
+
const volumeSteps = this.#deviceModel.status.volume
|
|
925
|
+
const normalizedSteps = Number.isFinite(volumeSteps) ? volumeSteps : 0
|
|
926
|
+
const clampedSteps = Math.max(0, Math.min(normalizedSteps, 16))
|
|
927
|
+
const percent = Math.round((clampedSteps / 16) * 100)
|
|
928
|
+
this.#log.debug(
|
|
929
|
+
LOG_PREFIX.ACCESSORY,
|
|
930
|
+
`[${this.#device.name}] Get volume rawSteps=${volumeSteps} steps=${clampedSteps} percent=${percent}`
|
|
931
|
+
)
|
|
932
|
+
return percent
|
|
926
933
|
}
|
|
927
934
|
|
|
928
935
|
/**
|
|
929
|
-
* Set volume level (0-16 steps)
|
|
936
|
+
* Set volume level as percentage (mapped to 0-16 steps)
|
|
930
937
|
* @param {CharacteristicValue} value
|
|
931
938
|
* @returns {Promise<void>}
|
|
932
939
|
*/
|
|
@@ -934,25 +941,25 @@ export class YotoPlayerAccessory {
|
|
|
934
941
|
const deviceModel = this.#deviceModel
|
|
935
942
|
this.#log.debug(LOG_PREFIX.ACCESSORY, `[${this.#device.name}] Set volume:`, value)
|
|
936
943
|
|
|
937
|
-
const
|
|
938
|
-
if (!Number.isFinite(
|
|
944
|
+
const requestedPercent = typeof value === 'number' ? value : Number(value)
|
|
945
|
+
if (!Number.isFinite(requestedPercent)) {
|
|
939
946
|
throw new this.#platform.api.hap.HapStatusError(
|
|
940
947
|
this.#platform.api.hap.HAPStatus.INVALID_VALUE_IN_REQUEST
|
|
941
948
|
)
|
|
942
949
|
}
|
|
943
950
|
|
|
944
|
-
const
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
const
|
|
951
|
+
const normalizedPercent = Math.max(0, Math.min(Math.round(requestedPercent), 100))
|
|
952
|
+
const requestedSteps = Math.round((normalizedPercent / 100) * 16)
|
|
953
|
+
const steps = Math.max(0, Math.min(Math.round(requestedSteps), 16))
|
|
954
|
+
const resultPercent = Math.round((steps / 16) * 100)
|
|
948
955
|
this.#log.debug(
|
|
949
956
|
LOG_PREFIX.ACCESSORY,
|
|
950
|
-
`[${this.#device.name}] Set volume raw=${value}
|
|
957
|
+
`[${this.#device.name}] Set volume raw=${value} normalizedPercent=${normalizedPercent} requestedSteps=${requestedSteps} -> steps=${steps} percent=${resultPercent}`
|
|
951
958
|
)
|
|
952
959
|
|
|
953
960
|
// Track last non-zero volume for unmute
|
|
954
961
|
if (steps > 0) {
|
|
955
|
-
this.#lastNonZeroVolume = steps
|
|
962
|
+
this.#lastNonZeroVolume = Math.round((steps / 16) * 100)
|
|
956
963
|
}
|
|
957
964
|
|
|
958
965
|
try {
|
|
@@ -964,10 +971,11 @@ export class YotoPlayerAccessory {
|
|
|
964
971
|
.getCharacteristic(Characteristic.On)
|
|
965
972
|
.updateValue(steps > 0)
|
|
966
973
|
|
|
967
|
-
if (steps !== requestedSteps) {
|
|
974
|
+
if (steps !== requestedSteps || normalizedPercent !== requestedPercent) {
|
|
975
|
+
const clampedPercent = Math.round((steps / 16) * 100)
|
|
968
976
|
this.volumeService
|
|
969
977
|
.getCharacteristic(Characteristic.Brightness)
|
|
970
|
-
.updateValue(
|
|
978
|
+
.updateValue(clampedPercent)
|
|
971
979
|
}
|
|
972
980
|
}
|
|
973
981
|
} catch (error) {
|
|
@@ -1024,8 +1032,8 @@ export class YotoPlayerAccessory {
|
|
|
1024
1032
|
async getOnlineStatus () {
|
|
1025
1033
|
const { Characteristic } = this.#platform
|
|
1026
1034
|
return this.#deviceModel.status.isOnline
|
|
1027
|
-
? Characteristic.ContactSensorState.
|
|
1028
|
-
: Characteristic.ContactSensorState.
|
|
1035
|
+
? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED
|
|
1036
|
+
: Characteristic.ContactSensorState.CONTACT_DETECTED
|
|
1029
1037
|
}
|
|
1030
1038
|
|
|
1031
1039
|
// ==================== Battery Characteristic Handlers ====================
|
|
@@ -1439,7 +1447,7 @@ export class YotoPlayerAccessory {
|
|
|
1439
1447
|
const { Characteristic } = this.#platform
|
|
1440
1448
|
const status = this.#deviceModel.status
|
|
1441
1449
|
const isActive = status.nightlightMode !== 'off'
|
|
1442
|
-
return isActive ? Characteristic.ContactSensorState.
|
|
1450
|
+
return isActive ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED
|
|
1443
1451
|
}
|
|
1444
1452
|
|
|
1445
1453
|
/**
|
|
@@ -1452,7 +1460,7 @@ export class YotoPlayerAccessory {
|
|
|
1452
1460
|
const isDay = status.dayMode === 'day'
|
|
1453
1461
|
const isActive = status.nightlightMode !== 'off'
|
|
1454
1462
|
const isShowing = isDay && isActive
|
|
1455
|
-
return isShowing ? Characteristic.ContactSensorState.
|
|
1463
|
+
return isShowing ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED
|
|
1456
1464
|
}
|
|
1457
1465
|
|
|
1458
1466
|
/**
|
|
@@ -1465,7 +1473,7 @@ export class YotoPlayerAccessory {
|
|
|
1465
1473
|
const isNight = status.dayMode === 'night'
|
|
1466
1474
|
const isActive = status.nightlightMode !== 'off'
|
|
1467
1475
|
const isShowing = isNight && isActive
|
|
1468
|
-
return isShowing ? Characteristic.ContactSensorState.
|
|
1476
|
+
return isShowing ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED
|
|
1469
1477
|
}
|
|
1470
1478
|
|
|
1471
1479
|
// ==================== Card Slot ContactSensor Getter ====================
|
|
@@ -1478,7 +1486,7 @@ export class YotoPlayerAccessory {
|
|
|
1478
1486
|
const { Characteristic } = this.#platform
|
|
1479
1487
|
const status = this.#deviceModel.status
|
|
1480
1488
|
const hasCard = status.cardInsertionState !== 'none'
|
|
1481
|
-
return hasCard ? Characteristic.ContactSensorState.
|
|
1489
|
+
return hasCard ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED
|
|
1482
1490
|
}
|
|
1483
1491
|
|
|
1484
1492
|
// ==================== Night Mode ContactSensor Getter ====================
|
|
@@ -1492,8 +1500,8 @@ export class YotoPlayerAccessory {
|
|
|
1492
1500
|
const status = this.#deviceModel.status
|
|
1493
1501
|
const isNightMode = status.dayMode === 'night'
|
|
1494
1502
|
return isNightMode
|
|
1495
|
-
? Characteristic.ContactSensorState.
|
|
1496
|
-
: Characteristic.ContactSensorState.
|
|
1503
|
+
? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED
|
|
1504
|
+
: Characteristic.ContactSensorState.CONTACT_DETECTED
|
|
1497
1505
|
}
|
|
1498
1506
|
|
|
1499
1507
|
// ==================== Sleep Timer Switch Getter/Setter ====================
|
|
@@ -1555,67 +1563,79 @@ export class YotoPlayerAccessory {
|
|
|
1555
1563
|
// ==================== Volume Limit Lightbulb Getters/Setters ====================
|
|
1556
1564
|
|
|
1557
1565
|
/**
|
|
1558
|
-
* Get day max volume limit
|
|
1566
|
+
* Get day max volume limit as percentage (mapped from 0-16 steps)
|
|
1559
1567
|
* @returns {Promise<CharacteristicValue>}
|
|
1560
1568
|
*/
|
|
1561
1569
|
async getDayMaxVolume () {
|
|
1562
1570
|
const limit = this.#deviceModel.config.maxVolumeLimit
|
|
1571
|
+
const steps = Number.isFinite(limit) ? limit : 16
|
|
1572
|
+
const clampedSteps = Math.max(0, Math.min(steps, 16))
|
|
1573
|
+
const percent = Math.round((clampedSteps / 16) * 100)
|
|
1563
1574
|
this.#log.debug(
|
|
1564
1575
|
LOG_PREFIX.ACCESSORY,
|
|
1565
|
-
`[${this.#device.name}] Get day max volume limit
|
|
1576
|
+
`[${this.#device.name}] Get day max volume limit rawSteps=${limit} steps=${clampedSteps} percent=${percent}`
|
|
1566
1577
|
)
|
|
1567
|
-
return
|
|
1578
|
+
return percent
|
|
1568
1579
|
}
|
|
1569
1580
|
|
|
1570
1581
|
/**
|
|
1571
|
-
* Set day max volume limit
|
|
1582
|
+
* Set day max volume limit as percentage (mapped to 0-16 steps)
|
|
1572
1583
|
* @param {CharacteristicValue} value
|
|
1573
1584
|
*/
|
|
1574
1585
|
async setDayMaxVolume (value) {
|
|
1575
|
-
const
|
|
1576
|
-
if (!Number.isFinite(
|
|
1586
|
+
const requestedPercent = typeof value === 'number' ? value : Number(value)
|
|
1587
|
+
if (!Number.isFinite(requestedPercent)) {
|
|
1577
1588
|
throw new this.#platform.api.hap.HapStatusError(
|
|
1578
1589
|
this.#platform.api.hap.HAPStatus.INVALID_VALUE_IN_REQUEST
|
|
1579
1590
|
)
|
|
1580
1591
|
}
|
|
1581
1592
|
|
|
1582
|
-
const
|
|
1593
|
+
const normalizedPercent = Math.max(0, Math.min(Math.round(requestedPercent), 100))
|
|
1594
|
+
const requestedSteps = Math.round((normalizedPercent / 100) * 16)
|
|
1595
|
+
const limit = Math.max(0, Math.min(Math.round(requestedSteps), 16))
|
|
1596
|
+
const limitPercent = Math.round((limit / 16) * 100)
|
|
1583
1597
|
this.#log.debug(
|
|
1584
1598
|
LOG_PREFIX.ACCESSORY,
|
|
1585
|
-
`[${this.#device.name}] Set day max volume limit raw=${value}
|
|
1599
|
+
`[${this.#device.name}] Set day max volume limit raw=${value} normalizedPercent=${normalizedPercent} requestedSteps=${requestedSteps} -> steps=${limit} percent=${limitPercent}`
|
|
1586
1600
|
)
|
|
1587
1601
|
await this.#deviceModel.updateConfig({ maxVolumeLimit: limit })
|
|
1588
1602
|
}
|
|
1589
1603
|
|
|
1590
1604
|
/**
|
|
1591
|
-
* Get night max volume limit
|
|
1605
|
+
* Get night max volume limit as percentage (mapped from 0-16 steps)
|
|
1592
1606
|
* @returns {Promise<CharacteristicValue>}
|
|
1593
1607
|
*/
|
|
1594
1608
|
async getNightMaxVolume () {
|
|
1595
1609
|
const limit = this.#deviceModel.config.nightMaxVolumeLimit
|
|
1610
|
+
const steps = Number.isFinite(limit) ? limit : 10
|
|
1611
|
+
const clampedSteps = Math.max(0, Math.min(steps, 16))
|
|
1612
|
+
const percent = Math.round((clampedSteps / 16) * 100)
|
|
1596
1613
|
this.#log.debug(
|
|
1597
1614
|
LOG_PREFIX.ACCESSORY,
|
|
1598
|
-
`[${this.#device.name}] Get night max volume limit
|
|
1615
|
+
`[${this.#device.name}] Get night max volume limit rawSteps=${limit} steps=${clampedSteps} percent=${percent}`
|
|
1599
1616
|
)
|
|
1600
|
-
return
|
|
1617
|
+
return percent
|
|
1601
1618
|
}
|
|
1602
1619
|
|
|
1603
1620
|
/**
|
|
1604
|
-
* Set night max volume limit
|
|
1621
|
+
* Set night max volume limit as percentage (mapped to 0-16 steps)
|
|
1605
1622
|
* @param {CharacteristicValue} value
|
|
1606
1623
|
*/
|
|
1607
1624
|
async setNightMaxVolume (value) {
|
|
1608
|
-
const
|
|
1609
|
-
if (!Number.isFinite(
|
|
1625
|
+
const requestedPercent = typeof value === 'number' ? value : Number(value)
|
|
1626
|
+
if (!Number.isFinite(requestedPercent)) {
|
|
1610
1627
|
throw new this.#platform.api.hap.HapStatusError(
|
|
1611
1628
|
this.#platform.api.hap.HAPStatus.INVALID_VALUE_IN_REQUEST
|
|
1612
1629
|
)
|
|
1613
1630
|
}
|
|
1614
1631
|
|
|
1615
|
-
const
|
|
1632
|
+
const normalizedPercent = Math.max(0, Math.min(Math.round(requestedPercent), 100))
|
|
1633
|
+
const requestedSteps = Math.round((normalizedPercent / 100) * 16)
|
|
1634
|
+
const limit = Math.max(0, Math.min(Math.round(requestedSteps), 16))
|
|
1635
|
+
const limitPercent = Math.round((limit / 16) * 100)
|
|
1616
1636
|
this.#log.debug(
|
|
1617
1637
|
LOG_PREFIX.ACCESSORY,
|
|
1618
|
-
`[${this.#device.name}] Set night max volume limit raw=${value}
|
|
1638
|
+
`[${this.#device.name}] Set night max volume limit raw=${value} normalizedPercent=${normalizedPercent} requestedSteps=${requestedSteps} -> steps=${limit} percent=${limitPercent}`
|
|
1619
1639
|
)
|
|
1620
1640
|
await this.#deviceModel.updateConfig({ nightMaxVolumeLimit: limit })
|
|
1621
1641
|
}
|
|
@@ -1645,18 +1665,19 @@ export class YotoPlayerAccessory {
|
|
|
1645
1665
|
|
|
1646
1666
|
const { Characteristic } = this.#platform
|
|
1647
1667
|
if (volumeSteps > 0) {
|
|
1648
|
-
this.#lastNonZeroVolume = volumeSteps
|
|
1668
|
+
this.#lastNonZeroVolume = Math.round((volumeSteps / 16) * 100)
|
|
1649
1669
|
}
|
|
1650
1670
|
|
|
1651
1671
|
const normalizedVolume = Number.isFinite(volumeSteps) ? volumeSteps : 0
|
|
1652
1672
|
const clampedVolume = Math.max(0, Math.min(normalizedVolume, 16))
|
|
1673
|
+
const percent = Math.round((clampedVolume / 16) * 100)
|
|
1653
1674
|
this.#log.debug(
|
|
1654
1675
|
LOG_PREFIX.ACCESSORY,
|
|
1655
|
-
`[${this.#device.name}] Update volume characteristic
|
|
1676
|
+
`[${this.#device.name}] Update volume characteristic rawSteps=${volumeSteps} steps=${clampedVolume} percent=${percent}`
|
|
1656
1677
|
)
|
|
1657
1678
|
this.volumeService
|
|
1658
1679
|
.getCharacteristic(Characteristic.Brightness)
|
|
1659
|
-
.updateValue(
|
|
1680
|
+
.updateValue(percent)
|
|
1660
1681
|
}
|
|
1661
1682
|
|
|
1662
1683
|
/**
|
|
@@ -1741,8 +1762,8 @@ export class YotoPlayerAccessory {
|
|
|
1741
1762
|
this.onlineStatusService
|
|
1742
1763
|
.getCharacteristic(Characteristic.ContactSensorState)
|
|
1743
1764
|
.updateValue(isOnline
|
|
1744
|
-
? Characteristic.ContactSensorState.
|
|
1745
|
-
: Characteristic.ContactSensorState.
|
|
1765
|
+
? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED
|
|
1766
|
+
: Characteristic.ContactSensorState.CONTACT_DETECTED)
|
|
1746
1767
|
}
|
|
1747
1768
|
|
|
1748
1769
|
// Update TemperatureSensor (temperature reading)
|
|
@@ -1822,7 +1843,7 @@ export class YotoPlayerAccessory {
|
|
|
1822
1843
|
const isActive = status.nightlightMode !== 'off'
|
|
1823
1844
|
this.nightlightActiveService
|
|
1824
1845
|
.getCharacteristic(Characteristic.ContactSensorState)
|
|
1825
|
-
.updateValue(isActive ? Characteristic.ContactSensorState.
|
|
1846
|
+
.updateValue(isActive ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED)
|
|
1826
1847
|
}
|
|
1827
1848
|
|
|
1828
1849
|
if (this.dayNightlightActiveService) {
|
|
@@ -1831,7 +1852,7 @@ export class YotoPlayerAccessory {
|
|
|
1831
1852
|
const isShowing = isDay && isActive
|
|
1832
1853
|
this.dayNightlightActiveService
|
|
1833
1854
|
.getCharacteristic(Characteristic.ContactSensorState)
|
|
1834
|
-
.updateValue(isShowing ? Characteristic.ContactSensorState.
|
|
1855
|
+
.updateValue(isShowing ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED)
|
|
1835
1856
|
}
|
|
1836
1857
|
|
|
1837
1858
|
if (this.nightNightlightActiveService) {
|
|
@@ -1840,7 +1861,7 @@ export class YotoPlayerAccessory {
|
|
|
1840
1861
|
const isShowing = isNight && isActive
|
|
1841
1862
|
this.nightNightlightActiveService
|
|
1842
1863
|
.getCharacteristic(Characteristic.ContactSensorState)
|
|
1843
|
-
.updateValue(isShowing ? Characteristic.ContactSensorState.
|
|
1864
|
+
.updateValue(isShowing ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED)
|
|
1844
1865
|
}
|
|
1845
1866
|
}
|
|
1846
1867
|
|
|
@@ -1858,7 +1879,7 @@ export class YotoPlayerAccessory {
|
|
|
1858
1879
|
|
|
1859
1880
|
this.cardSlotService
|
|
1860
1881
|
.getCharacteristic(Characteristic.ContactSensorState)
|
|
1861
|
-
.updateValue(hasCard ? Characteristic.ContactSensorState.
|
|
1882
|
+
.updateValue(hasCard ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED)
|
|
1862
1883
|
}
|
|
1863
1884
|
|
|
1864
1885
|
/**
|
|
@@ -1875,7 +1896,7 @@ export class YotoPlayerAccessory {
|
|
|
1875
1896
|
|
|
1876
1897
|
this.nightModeService
|
|
1877
1898
|
.getCharacteristic(Characteristic.ContactSensorState)
|
|
1878
|
-
.updateValue(isNightMode ? Characteristic.ContactSensorState.
|
|
1899
|
+
.updateValue(isNightMode ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED)
|
|
1879
1900
|
}
|
|
1880
1901
|
|
|
1881
1902
|
/**
|
|
@@ -1920,25 +1941,27 @@ export class YotoPlayerAccessory {
|
|
|
1920
1941
|
if (this.dayMaxVolumeService) {
|
|
1921
1942
|
const limit = Number.isFinite(config.maxVolumeLimit) ? config.maxVolumeLimit : 16
|
|
1922
1943
|
const clampedLimit = Math.max(0, Math.min(limit, 16))
|
|
1944
|
+
const percent = Math.round((clampedLimit / 16) * 100)
|
|
1923
1945
|
this.#log.debug(
|
|
1924
1946
|
LOG_PREFIX.ACCESSORY,
|
|
1925
|
-
`[${this.#device.name}] Update day max volume characteristic
|
|
1947
|
+
`[${this.#device.name}] Update day max volume characteristic rawSteps=${limit} steps=${clampedLimit} percent=${percent}`
|
|
1926
1948
|
)
|
|
1927
1949
|
this.dayMaxVolumeService
|
|
1928
1950
|
.getCharacteristic(Characteristic.Brightness)
|
|
1929
|
-
.updateValue(
|
|
1951
|
+
.updateValue(percent)
|
|
1930
1952
|
}
|
|
1931
1953
|
|
|
1932
1954
|
if (this.nightMaxVolumeService) {
|
|
1933
1955
|
const limit = Number.isFinite(config.nightMaxVolumeLimit) ? config.nightMaxVolumeLimit : 10
|
|
1934
1956
|
const clampedLimit = Math.max(0, Math.min(limit, 16))
|
|
1957
|
+
const percent = Math.round((clampedLimit / 16) * 100)
|
|
1935
1958
|
this.#log.debug(
|
|
1936
1959
|
LOG_PREFIX.ACCESSORY,
|
|
1937
|
-
`[${this.#device.name}] Update night max volume characteristic
|
|
1960
|
+
`[${this.#device.name}] Update night max volume characteristic rawSteps=${limit} steps=${clampedLimit} percent=${percent}`
|
|
1938
1961
|
)
|
|
1939
1962
|
this.nightMaxVolumeService
|
|
1940
1963
|
.getCharacteristic(Characteristic.Brightness)
|
|
1941
|
-
.updateValue(
|
|
1964
|
+
.updateValue(percent)
|
|
1942
1965
|
}
|
|
1943
1966
|
}
|
|
1944
1967
|
|
package/lib/platform.js
CHANGED
|
@@ -188,68 +188,104 @@ export class YotoPlatform {
|
|
|
188
188
|
this.log.info(`Device offline: ${deviceId}${reason}`)
|
|
189
189
|
})
|
|
190
190
|
|
|
191
|
+
/**
|
|
192
|
+
* @param {string} deviceId
|
|
193
|
+
* @returns {string}
|
|
194
|
+
*/
|
|
195
|
+
const getDeviceLabel = (deviceId) => {
|
|
196
|
+
const deviceName = this.yotoAccount?.getDevice(deviceId)?.device?.name
|
|
197
|
+
return deviceName || deviceId
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @param {{ status?: Record<string, unknown> } | null | undefined} message
|
|
202
|
+
* @returns {string}
|
|
203
|
+
*/
|
|
204
|
+
const formatLegacyStatusFields = (message) => {
|
|
205
|
+
const status = message?.status
|
|
206
|
+
if (!status || typeof status !== 'object') return ''
|
|
207
|
+
const fields = Object.keys(status)
|
|
208
|
+
if (!fields.length) return ''
|
|
209
|
+
const preview = fields.slice(0, 8).join(', ')
|
|
210
|
+
const suffix = fields.length > 8 ? `, +${fields.length - 8} more` : ''
|
|
211
|
+
return ` fields: ${preview}${suffix}`
|
|
212
|
+
}
|
|
213
|
+
|
|
191
214
|
this.yotoAccount.on('statusUpdate', ({ deviceId, source, changedFields }) => {
|
|
215
|
+
const label = getDeviceLabel(deviceId)
|
|
192
216
|
const fields = Array.from(changedFields).join(', ')
|
|
193
|
-
this.log.debug(`Status update [${
|
|
217
|
+
this.log.debug(`Status update [${label} ${source}]: ${fields}`)
|
|
194
218
|
})
|
|
195
219
|
|
|
196
220
|
this.yotoAccount.on('configUpdate', ({ deviceId, changedFields }) => {
|
|
221
|
+
const label = getDeviceLabel(deviceId)
|
|
197
222
|
const fields = Array.from(changedFields).join(', ')
|
|
198
|
-
this.log.debug(`Config update [${
|
|
223
|
+
this.log.debug(`Config update [${label}]: ${fields}`)
|
|
199
224
|
})
|
|
200
225
|
|
|
201
226
|
this.yotoAccount.on('playbackUpdate', ({ deviceId, changedFields }) => {
|
|
227
|
+
const label = getDeviceLabel(deviceId)
|
|
202
228
|
const fields = Array.from(changedFields).join(', ')
|
|
203
|
-
this.log.debug(`Playback update [${
|
|
229
|
+
this.log.debug(`Playback update [${label}]: ${fields}`)
|
|
204
230
|
})
|
|
205
231
|
|
|
206
232
|
this.yotoAccount.on('mqttConnect', ({ deviceId }) => {
|
|
207
|
-
const
|
|
208
|
-
const label = deviceName ? `${deviceName} (${deviceId})` : deviceId
|
|
233
|
+
const label = getDeviceLabel(deviceId)
|
|
209
234
|
this.log.debug(`MQTT connected: ${label}`)
|
|
210
235
|
})
|
|
211
236
|
|
|
212
237
|
this.yotoAccount.on('mqttDisconnect', ({ deviceId, metadata }) => {
|
|
238
|
+
const label = getDeviceLabel(deviceId)
|
|
213
239
|
const reasonCode = metadata?.packet?.reasonCode
|
|
214
240
|
const reason = typeof reasonCode === 'number' ? ` (code ${reasonCode})` : ''
|
|
215
|
-
this.log.warn(`MQTT disconnected: ${
|
|
241
|
+
this.log.warn(`MQTT disconnected: ${label}${reason}`)
|
|
216
242
|
})
|
|
217
243
|
|
|
218
244
|
this.yotoAccount.on('mqttClose', ({ deviceId, metadata }) => {
|
|
245
|
+
const label = getDeviceLabel(deviceId)
|
|
219
246
|
const reason = metadata?.reason ? ` (${metadata.reason})` : ''
|
|
220
|
-
this.log.debug(`MQTT closed: ${
|
|
247
|
+
this.log.debug(`MQTT closed: ${label}${reason}`)
|
|
221
248
|
})
|
|
222
249
|
|
|
223
250
|
this.yotoAccount.on('mqttReconnect', ({ deviceId }) => {
|
|
224
|
-
|
|
251
|
+
const label = getDeviceLabel(deviceId)
|
|
252
|
+
this.log.debug(`MQTT reconnecting: ${label}`)
|
|
225
253
|
})
|
|
226
254
|
|
|
227
255
|
this.yotoAccount.on('mqttOffline', ({ deviceId }) => {
|
|
228
|
-
|
|
256
|
+
const label = getDeviceLabel(deviceId)
|
|
257
|
+
this.log.debug(`MQTT offline: ${label}`)
|
|
229
258
|
})
|
|
230
259
|
|
|
231
260
|
this.yotoAccount.on('mqttEnd', ({ deviceId }) => {
|
|
232
|
-
|
|
261
|
+
const label = getDeviceLabel(deviceId)
|
|
262
|
+
this.log.debug(`MQTT ended: ${label}`)
|
|
233
263
|
})
|
|
234
264
|
|
|
235
265
|
this.yotoAccount.on('mqttStatus', ({ deviceId, topic }) => {
|
|
236
|
-
|
|
266
|
+
const label = getDeviceLabel(deviceId)
|
|
267
|
+
this.log.debug(`MQTT status [${label}]: ${topic}`)
|
|
237
268
|
})
|
|
238
269
|
|
|
239
270
|
this.yotoAccount.on('mqttEvents', ({ deviceId, topic }) => {
|
|
240
|
-
|
|
271
|
+
const label = getDeviceLabel(deviceId)
|
|
272
|
+
this.log.debug(`MQTT events [${label}]: ${topic}`)
|
|
241
273
|
})
|
|
242
274
|
|
|
243
|
-
this.yotoAccount.on('mqttStatusLegacy', ({ deviceId, topic }) => {
|
|
244
|
-
|
|
275
|
+
this.yotoAccount.on('mqttStatusLegacy', ({ deviceId, topic, message }) => {
|
|
276
|
+
const label = getDeviceLabel(deviceId)
|
|
277
|
+
const fields = formatLegacyStatusFields(message)
|
|
278
|
+
this.log.debug(`MQTT legacy status [${label}]: ${topic}${fields}`)
|
|
245
279
|
})
|
|
246
280
|
|
|
247
281
|
this.yotoAccount.on('mqttResponse', ({ deviceId, topic }) => {
|
|
248
|
-
|
|
282
|
+
const label = getDeviceLabel(deviceId)
|
|
283
|
+
this.log.debug(`MQTT response [${label}]: ${topic}`)
|
|
249
284
|
})
|
|
250
285
|
|
|
251
286
|
this.yotoAccount.on('mqttUnknown', ({ deviceId, topic }) => {
|
|
252
|
-
|
|
287
|
+
const label = getDeviceLabel(deviceId)
|
|
288
|
+
this.log.debug(`MQTT unknown [${label}]: ${topic}`)
|
|
253
289
|
})
|
|
254
290
|
|
|
255
291
|
// Start the account (discovers devices, creates device models, starts MQTT)
|
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.39",
|
|
5
5
|
"author": "Bret Comnes <bcomnes@gmail.com> (https://bret.io)",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/bcomnes/homebridge-yoto/issues"
|