@switchbot/homebridge-switchbot 5.0.0-beta.43 → 5.0.0-beta.44
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/config.schema.json +61 -9
- package/dist/devices-hap/airpurifier.d.ts.map +1 -1
- package/dist/devices-hap/airpurifier.js +12 -6
- package/dist/devices-hap/airpurifier.js.map +1 -1
- package/dist/devices-hap/blindtilt.js +3 -3
- package/dist/devices-hap/bot.d.ts.map +1 -1
- package/dist/devices-hap/bot.js +16 -5
- package/dist/devices-hap/bot.js.map +1 -1
- package/dist/devices-hap/ceilinglight.d.ts.map +1 -1
- package/dist/devices-hap/ceilinglight.js +13 -7
- package/dist/devices-hap/ceilinglight.js.map +1 -1
- package/dist/devices-hap/colorbulb.d.ts.map +1 -1
- package/dist/devices-hap/colorbulb.js +49 -9
- package/dist/devices-hap/colorbulb.js.map +1 -1
- package/dist/devices-hap/contact.js +3 -3
- package/dist/devices-hap/curtain.js +2 -2
- package/dist/devices-hap/curtain.js.map +1 -1
- package/dist/devices-hap/device.d.ts.map +1 -1
- package/dist/devices-hap/device.js +20 -1
- package/dist/devices-hap/device.js.map +1 -1
- package/dist/devices-hap/fan.d.ts.map +1 -1
- package/dist/devices-hap/fan.js +12 -6
- package/dist/devices-hap/fan.js.map +1 -1
- package/dist/devices-hap/hub.d.ts.map +1 -1
- package/dist/devices-hap/hub.js +6 -5
- package/dist/devices-hap/hub.js.map +1 -1
- package/dist/devices-hap/humidifier.d.ts +5 -0
- package/dist/devices-hap/humidifier.d.ts.map +1 -1
- package/dist/devices-hap/humidifier.js +92 -4
- package/dist/devices-hap/humidifier.js.map +1 -1
- package/dist/devices-hap/iosensor.d.ts.map +1 -1
- package/dist/devices-hap/iosensor.js +36 -21
- package/dist/devices-hap/iosensor.js.map +1 -1
- package/dist/devices-hap/lightstrip.d.ts.map +1 -1
- package/dist/devices-hap/lightstrip.js +38 -8
- package/dist/devices-hap/lightstrip.js.map +1 -1
- package/dist/devices-hap/lock.d.ts.map +1 -1
- package/dist/devices-hap/lock.js +14 -6
- package/dist/devices-hap/lock.js.map +1 -1
- package/dist/devices-hap/meter.d.ts.map +1 -1
- package/dist/devices-hap/meter.js +6 -5
- package/dist/devices-hap/meter.js.map +1 -1
- package/dist/devices-hap/meterplus.d.ts.map +1 -1
- package/dist/devices-hap/meterplus.js +6 -5
- package/dist/devices-hap/meterplus.js.map +1 -1
- package/dist/devices-hap/meterpro.d.ts.map +1 -1
- package/dist/devices-hap/meterpro.js +7 -6
- package/dist/devices-hap/meterpro.js.map +1 -1
- package/dist/devices-hap/motion.js +3 -3
- package/dist/devices-hap/plug.d.ts.map +1 -1
- package/dist/devices-hap/plug.js +11 -6
- package/dist/devices-hap/plug.js.map +1 -1
- package/dist/devices-hap/relayswitch.js +3 -3
- package/dist/devices-hap/robotvacuumcleaner.d.ts.map +1 -1
- package/dist/devices-hap/robotvacuumcleaner.js +13 -6
- package/dist/devices-hap/robotvacuumcleaner.js.map +1 -1
- package/dist/devices-hap/waterdetector.js +3 -3
- package/dist/homebridge-ui/public/index.html +13 -1
- package/dist/platform-hap.d.ts.map +1 -1
- package/dist/platform-hap.js +38 -2
- package/dist/platform-hap.js.map +1 -1
- package/dist/platform-matter.d.ts.map +1 -1
- package/dist/platform-matter.js +69 -2
- package/dist/platform-matter.js.map +1 -1
- package/dist/settings.d.ts +12 -1
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js.map +1 -1
- package/dist/test/hap/device-webhook-context.test.d.ts +2 -0
- package/dist/test/hap/device-webhook-context.test.d.ts.map +1 -0
- package/dist/test/hap/device-webhook-context.test.js +128 -0
- package/dist/test/hap/device-webhook-context.test.js.map +1 -0
- package/dist/test/matter/platform-matter.webhook.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.webhook.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.webhook.test.js +46 -0
- package/dist/test/matter/platform-matter.webhook.test.js.map +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +10 -4
- package/dist/utils.js.map +1 -1
- package/docs/variables/default.html +1 -1
- package/package.json +2 -2
- package/src/devices-hap/airpurifier.ts +11 -6
- package/src/devices-hap/blindtilt.ts +3 -3
- package/src/devices-hap/bot.ts +15 -5
- package/src/devices-hap/ceilinglight.ts +12 -7
- package/src/devices-hap/colorbulb.ts +46 -10
- package/src/devices-hap/contact.ts +3 -3
- package/src/devices-hap/curtain.ts +2 -2
- package/src/devices-hap/device.ts +20 -1
- package/src/devices-hap/fan.ts +11 -6
- package/src/devices-hap/hub.ts +6 -5
- package/src/devices-hap/humidifier.ts +97 -4
- package/src/devices-hap/iosensor.ts +36 -21
- package/src/devices-hap/lightstrip.ts +35 -8
- package/src/devices-hap/lock.ts +13 -6
- package/src/devices-hap/meter.ts +6 -5
- package/src/devices-hap/meterplus.ts +6 -5
- package/src/devices-hap/meterpro.ts +7 -6
- package/src/devices-hap/motion.ts +3 -3
- package/src/devices-hap/plug.ts +10 -6
- package/src/devices-hap/relayswitch.ts +3 -3
- package/src/devices-hap/robotvacuumcleaner.ts +12 -6
- package/src/devices-hap/waterdetector.ts +3 -3
- package/src/homebridge-ui/public/index.html +13 -1
- package/src/platform-hap.ts +38 -2
- package/src/platform-matter.ts +70 -2
- package/src/settings.ts +12 -1
- package/src/test/hap/device-webhook-context.test.ts +136 -0
- package/src/test/matter/platform-matter.webhook.test.ts +54 -0
- package/src/utils.ts +14 -4
package/src/platform-hap.ts
CHANGED
|
@@ -606,6 +606,7 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
606
606
|
'Plug Mini (JP)': Plug,
|
|
607
607
|
'Smart Lock': Lock,
|
|
608
608
|
'Smart Lock Pro': Lock,
|
|
609
|
+
'Smart Lock Ultra': Lock,
|
|
609
610
|
'Color Bulb': ColorBulb,
|
|
610
611
|
'K10+': RobotVacuumCleaner,
|
|
611
612
|
'K10+ Pro': RobotVacuumCleaner,
|
|
@@ -728,12 +729,29 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
728
729
|
const allowConfigOnly = Boolean(this.config.options?.allowConfigOnlyDevices)
|
|
729
730
|
const devices = mergeByDeviceId(this.config.options.devices ?? [], devicesWithTemplates ?? [], allowConfigOnly)
|
|
730
731
|
|
|
732
|
+
// Apply global webhook option to devices that don't have their own webhook setting
|
|
733
|
+
if (this.config.options?.webhook === true) {
|
|
734
|
+
for (const device of devices) {
|
|
735
|
+
if (device.webhook === undefined) {
|
|
736
|
+
device.webhook = true
|
|
737
|
+
this.debugLog(`Applying global webhook option to device: ${device.deviceName ?? device.deviceId}`)
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
731
742
|
this.debugLog(`SwitchBot Devices: ${JSON.stringify(devices)}`)
|
|
732
743
|
|
|
733
744
|
for (const device of devices) {
|
|
734
745
|
if (device.configDeviceName) {
|
|
735
746
|
device.deviceName = device.configDeviceName
|
|
736
747
|
}
|
|
748
|
+
// Log effective webhook setting for diagnostics
|
|
749
|
+
try {
|
|
750
|
+
const effectiveWebhook = device.webhook !== undefined ? device.webhook : (this.config.options?.webhook === true ? true : undefined)
|
|
751
|
+
this.debugLog(`Effective webhook for device ${device.deviceName ?? device.deviceId}: ${String(effectiveWebhook)}`)
|
|
752
|
+
} catch (e: any) {
|
|
753
|
+
this.debugLog(`Failed logging effective webhook for ${device.deviceName ?? device.deviceId}: ${e?.message ?? e}`)
|
|
754
|
+
}
|
|
737
755
|
await this.createDevice(device)
|
|
738
756
|
}
|
|
739
757
|
}
|
|
@@ -773,11 +791,28 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
773
791
|
const allowConfigOnly = Boolean(this.config.options?.allowConfigOnlyDevices)
|
|
774
792
|
const devices = mergeByDeviceId(this.config.options.irdevices ?? [], devicesWithTemplates ?? [], allowConfigOnly)
|
|
775
793
|
|
|
794
|
+
// Apply global webhook option to IR devices that don't have their own webhook setting
|
|
795
|
+
if (this.config.options?.webhook === true) {
|
|
796
|
+
for (const device of devices) {
|
|
797
|
+
if (device.webhook === undefined) {
|
|
798
|
+
device.webhook = true
|
|
799
|
+
this.debugLog(`Applying global webhook option to IR device: ${device.deviceName ?? device.deviceId}`)
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
776
804
|
this.debugLog(`IR Devices: ${JSON.stringify(devices)}`)
|
|
777
805
|
for (const device of devices) {
|
|
778
806
|
if (device.configDeviceName) {
|
|
779
807
|
device.deviceName = device.configDeviceName
|
|
780
808
|
}
|
|
809
|
+
// Log effective webhook setting for diagnostics (IR devices)
|
|
810
|
+
try {
|
|
811
|
+
const effectiveWebhook = device.webhook !== undefined ? device.webhook : (this.config.options?.webhook === true ? true : undefined)
|
|
812
|
+
this.debugLog(`Effective webhook for IR device ${device.deviceName ?? device.deviceId}: ${String(effectiveWebhook)}`)
|
|
813
|
+
} catch (e: any) {
|
|
814
|
+
this.debugLog(`Failed logging effective webhook for IR ${device.deviceName ?? device.deviceId}: ${e?.message ?? e}`)
|
|
815
|
+
}
|
|
781
816
|
await this.createIRDevice(device)
|
|
782
817
|
}
|
|
783
818
|
}
|
|
@@ -819,6 +854,7 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
819
854
|
'Plug Mini (JP)': this.createPlug.bind(this),
|
|
820
855
|
'Smart Lock': this.createLock.bind(this),
|
|
821
856
|
'Smart Lock Pro': this.createLock.bind(this),
|
|
857
|
+
'Smart Lock Ultra': this.createLock.bind(this),
|
|
822
858
|
'Color Bulb': this.createColorBulb.bind(this),
|
|
823
859
|
'K10+': this.createRobotVacuumCleaner.bind(this),
|
|
824
860
|
'K10+ Pro': this.createRobotVacuumCleaner.bind(this),
|
|
@@ -1814,7 +1850,7 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
1814
1850
|
existingAccessory.context.device = device
|
|
1815
1851
|
existingAccessory.context.deviceId = device.deviceId
|
|
1816
1852
|
existingAccessory.context.deviceType = device.deviceType
|
|
1817
|
-
existingAccessory.context.model = device.deviceType === 'Smart Lock Pro' ? SwitchBotModel.LockPro : SwitchBotModel.Lock
|
|
1853
|
+
existingAccessory.context.model = (device.deviceType === 'Smart Lock Pro' || device.deviceType === 'Smart Lock Ultra') ? SwitchBotModel.LockPro : SwitchBotModel.Lock
|
|
1818
1854
|
existingAccessory.displayName = device.configDeviceName
|
|
1819
1855
|
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
|
|
1820
1856
|
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName)
|
|
@@ -1840,7 +1876,7 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
1840
1876
|
accessory.context.device = device
|
|
1841
1877
|
accessory.context.deviceId = device.deviceId
|
|
1842
1878
|
accessory.context.deviceType = device.deviceType
|
|
1843
|
-
accessory.context.model = device.deviceType === 'Smart Lock Pro' ? SwitchBotModel.LockPro : SwitchBotModel.Lock
|
|
1879
|
+
accessory.context.model = (device.deviceType === 'Smart Lock Pro' || device.deviceType === 'Smart Lock Ultra') ? SwitchBotModel.LockPro : SwitchBotModel.Lock
|
|
1844
1880
|
accessory.displayName = device.configDeviceName
|
|
1845
1881
|
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
|
|
1846
1882
|
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName)
|
package/src/platform-matter.ts
CHANGED
|
@@ -445,6 +445,16 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
445
445
|
const allowConfigOnly = Boolean(this.config.options?.allowConfigOnlyDevices)
|
|
446
446
|
const merged = mergeByDeviceId(this.config.options?.devices ?? [], devicesWithTemplates ?? [], allowConfigOnly)
|
|
447
447
|
|
|
448
|
+
// Apply global webhook setting if not explicitly set on individual devices
|
|
449
|
+
if (this.config.options?.webhook === true) {
|
|
450
|
+
merged.forEach((device: any) => {
|
|
451
|
+
if (device.webhook === undefined) {
|
|
452
|
+
device.webhook = true
|
|
453
|
+
this.debugLog(`Applied global webhook setting to Matter device: ${device.deviceId}`)
|
|
454
|
+
}
|
|
455
|
+
})
|
|
456
|
+
}
|
|
457
|
+
|
|
448
458
|
return merged
|
|
449
459
|
}
|
|
450
460
|
|
|
@@ -483,6 +493,16 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
483
493
|
type IRMerged = irdevice & irDevicesConfig
|
|
484
494
|
const merged = mergeByDeviceId(this.config.options?.irdevices ?? [], devicesWithTemplates ?? [], allowConfigOnly) as IRMerged[]
|
|
485
495
|
|
|
496
|
+
// Apply global webhook setting if not explicitly set on individual IR devices
|
|
497
|
+
if (this.config.options?.webhook === true) {
|
|
498
|
+
merged.forEach((device: any) => {
|
|
499
|
+
if (device.webhook === undefined) {
|
|
500
|
+
device.webhook = true
|
|
501
|
+
this.debugLog(`Applied global webhook setting to Matter IR device: ${device.deviceId}`)
|
|
502
|
+
}
|
|
503
|
+
})
|
|
504
|
+
}
|
|
505
|
+
|
|
486
506
|
return merged as unknown as irdevice[]
|
|
487
507
|
}
|
|
488
508
|
|
|
@@ -534,9 +554,20 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
534
554
|
// so accessories can decide how verbose they should be.
|
|
535
555
|
deviceLogging: (dev as any)?.logging,
|
|
536
556
|
platformLogging: this.platformLogging,
|
|
557
|
+
// Expose effective webhook setting for parity with HAP base device logic
|
|
558
|
+
// Prefer explicit per-device setting; otherwise fall back to global option
|
|
559
|
+
webhook: (dev as any)?.webhook !== undefined ? (dev as any).webhook : (this.config.options?.webhook === true ? true : undefined),
|
|
537
560
|
},
|
|
538
561
|
}
|
|
539
562
|
|
|
563
|
+
// Log effective webhook for diagnostics
|
|
564
|
+
try {
|
|
565
|
+
const effectiveWebhook = (dev as any)?.webhook !== undefined ? (dev as any).webhook : (this.config.options?.webhook === true ? true : undefined)
|
|
566
|
+
this.debugLog(`Effective webhook for Matter device ${displayName} (${dev.deviceId}): ${String(effectiveWebhook)}`)
|
|
567
|
+
} catch (e: any) {
|
|
568
|
+
this.debugLog(`Failed logging effective webhook for Matter device ${displayName} (${dev.deviceId}): ${e?.message ?? e}`)
|
|
569
|
+
}
|
|
570
|
+
|
|
540
571
|
// Build platform-side helpers using shared factories so they can be reused/tested
|
|
541
572
|
const sendOpenAPI = makeOpenAPISender(this.retryCommand.bind(this), dev, { maxRetries: this.config.options?.maxRetries ?? 1, delayBetweenRetries: this.config.options?.delayBetweenRetries ?? 1000 })
|
|
542
573
|
const sendBLE = makeBLESender(this.switchBotBLE, dev, { bleRetries: (this.config.options as any)?.bleRetries ?? 2, bleRetryDelay: (this.config.options as any)?.bleRetryDelay ?? 500 })
|
|
@@ -612,6 +643,7 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
612
643
|
// Locks
|
|
613
644
|
'Smart Lock': DoorLockAccessory,
|
|
614
645
|
'Smart Lock Pro': DoorLockAccessory,
|
|
646
|
+
'Smart Lock Ultra': DoorLockAccessory,
|
|
615
647
|
|
|
616
648
|
// Sensors
|
|
617
649
|
'Motion Sensor': OccupancySensorAccessory,
|
|
@@ -1512,6 +1544,38 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
1512
1544
|
}
|
|
1513
1545
|
}
|
|
1514
1546
|
|
|
1547
|
+
// Robot vacuum: some firmwares expose 'taskType' or 'task' with similar semantics
|
|
1548
|
+
// to runState (e.g., 'cleaning', 'mapping', 'idle'). Map to rvcRunMode accordingly.
|
|
1549
|
+
if (status?.taskType !== undefined || status?.task_type !== undefined || status?.task !== undefined) {
|
|
1550
|
+
try {
|
|
1551
|
+
const raw = status?.taskType ?? status?.task_type ?? status?.task
|
|
1552
|
+
let mode: number | undefined
|
|
1553
|
+
if (typeof raw === 'number') {
|
|
1554
|
+
mode = Number(raw)
|
|
1555
|
+
} else if (typeof raw === 'string') {
|
|
1556
|
+
const s = raw.toLowerCase()
|
|
1557
|
+
if (s.includes('clean')) {
|
|
1558
|
+
mode = 1 // Cleaning
|
|
1559
|
+
} else if (s.includes('map')) {
|
|
1560
|
+
mode = 2 // Mapping
|
|
1561
|
+
} else if (s.includes('idle') || s.includes('stop') || s.includes('dock') || s.includes('charge') || s.includes('docked')) {
|
|
1562
|
+
mode = 0 // Idle
|
|
1563
|
+
} else {
|
|
1564
|
+
const n = Number(raw)
|
|
1565
|
+
if (!Number.isNaN(n)) {
|
|
1566
|
+
mode = n
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
if (mode !== undefined) {
|
|
1572
|
+
await safeUpdate('rvcRunMode', { currentMode: Number(mode) }, 'updateRunMode')
|
|
1573
|
+
}
|
|
1574
|
+
} catch (e: any) {
|
|
1575
|
+
this.debugLog(`Failed to apply taskType for ${dev.deviceId}: ${e?.message ?? e}`)
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1515
1579
|
// Brightness
|
|
1516
1580
|
if (status?.brightness !== undefined) {
|
|
1517
1581
|
const rawBrightness = Number(status.brightness)
|
|
@@ -1572,7 +1636,8 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
1572
1636
|
|
|
1573
1637
|
// Battery/powerSource (support many possible field names)
|
|
1574
1638
|
// Note: Some device types like WindowBlind don't support PowerSource cluster
|
|
1575
|
-
if (status?.battery !== undefined || status?.batt !== undefined || status?.batteryLevel !== undefined || status?.batteryPercentage !== undefined || status?.battery_level !== undefined
|
|
1639
|
+
if (status?.battery !== undefined || status?.batt !== undefined || status?.batteryLevel !== undefined || status?.batteryPercentage !== undefined || status?.battery_level !== undefined
|
|
1640
|
+
|| status?.baseBattery !== undefined || status?.base_battery !== undefined || status?.stationBattery !== undefined || status?.waterBaseBattery !== undefined || status?.dockBattery !== undefined) {
|
|
1576
1641
|
// Skip battery updates for device types that don't support PowerSource cluster
|
|
1577
1642
|
const deviceType = String(status?.deviceType ?? dev?.deviceType ?? '')
|
|
1578
1643
|
const unsupportedTypes = ['Curtain', 'Curtain2', 'Curtain3', 'Curtain 2', 'Blind Tilt']
|
|
@@ -1581,7 +1646,10 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
1581
1646
|
this.debugLog(`Device ${dev.deviceId} type ${deviceType} does not support PowerSource cluster, skipping battery update`)
|
|
1582
1647
|
} else {
|
|
1583
1648
|
try {
|
|
1584
|
-
const percentage = Number(
|
|
1649
|
+
const percentage = Number(
|
|
1650
|
+
status?.battery ?? status?.batt ?? status?.batteryPercentage ?? status?.batteryLevel ?? status?.battery_level
|
|
1651
|
+
?? status?.baseBattery ?? status?.base_battery ?? status?.stationBattery ?? status?.waterBaseBattery ?? status?.dockBattery,
|
|
1652
|
+
)
|
|
1585
1653
|
const batPercentRemaining = Math.max(0, Math.min(200, Math.round(percentage * 2)))
|
|
1586
1654
|
let batChargeLevel = 0
|
|
1587
1655
|
if (percentage < 20) {
|
package/src/settings.ts
CHANGED
|
@@ -53,6 +53,12 @@ export interface options {
|
|
|
53
53
|
disableLogsforOpenAPI?: boolean
|
|
54
54
|
hostname?: string
|
|
55
55
|
webhookURL?: string
|
|
56
|
+
/**
|
|
57
|
+
* When true, enables webhook support for all devices by default.
|
|
58
|
+
* Individual devices can override this with their own webhook setting.
|
|
59
|
+
* Requires webhookURL to be configured.
|
|
60
|
+
*/
|
|
61
|
+
webhook?: boolean
|
|
56
62
|
maxRetries?: number
|
|
57
63
|
delayBetweenRetries?: number
|
|
58
64
|
refreshRate?: number
|
|
@@ -178,6 +184,11 @@ export interface humidifierConfig extends BaseDeviceConfig {
|
|
|
178
184
|
hide_temperature?: boolean
|
|
179
185
|
convertUnitTo?: string
|
|
180
186
|
set_minStep?: number
|
|
187
|
+
/**
|
|
188
|
+
* When true (Humidifier2 only), exposes a Switch service in HomeKit
|
|
189
|
+
* to trigger the built-in Drying Filter mode via OpenAPI (setMode 8).
|
|
190
|
+
*/
|
|
191
|
+
activate_dryingfilter?: boolean
|
|
181
192
|
};
|
|
182
193
|
|
|
183
194
|
export interface curtainConfig extends BaseDeviceConfig {
|
|
@@ -253,7 +264,7 @@ export interface ceilingLightConfig extends BaseDeviceConfig {
|
|
|
253
264
|
};
|
|
254
265
|
|
|
255
266
|
export interface lockConfig extends BaseDeviceConfig {
|
|
256
|
-
configDeviceType: 'Smart Lock' | 'Smart Lock Pro'
|
|
267
|
+
configDeviceType: 'Smart Lock' | 'Smart Lock Pro' | 'Smart Lock Ultra'
|
|
257
268
|
hide_contactsensor?: boolean
|
|
258
269
|
activate_latchbutton?: boolean
|
|
259
270
|
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/* eslint-disable import/first */
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
// Mock modules used by HAP device base occasionally via platform
|
|
4
|
+
vi.mock('fakegato-history', () => ({ default: () => ({}) }))
|
|
5
|
+
vi.mock('homebridge-lib/EveHomeKitTypes', () => ({ EveHomeKitTypes: class { constructor() {} } }))
|
|
6
|
+
|
|
7
|
+
import { deviceBase } from '../../devices-hap/device.js'
|
|
8
|
+
|
|
9
|
+
// Minimal HAP stub to satisfy deviceBase constructor operations
|
|
10
|
+
function makeHapStub() {
|
|
11
|
+
class Service {
|
|
12
|
+
private _chars: Record<string, any> = {}
|
|
13
|
+
setCharacteristic(k: any, v: any) {
|
|
14
|
+
this._chars[k] = v
|
|
15
|
+
return this
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getCharacteristic(k: any) {
|
|
19
|
+
return {
|
|
20
|
+
updateValue: (v: any) => {
|
|
21
|
+
this._chars[k] = v
|
|
22
|
+
return this
|
|
23
|
+
},
|
|
24
|
+
} as any
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const Characteristic: any = {
|
|
28
|
+
Manufacturer: 'Manufacturer',
|
|
29
|
+
AppMatchingIdentifier: 'AppMatchingIdentifier',
|
|
30
|
+
Name: 'Name',
|
|
31
|
+
ConfiguredName: 'ConfiguredName',
|
|
32
|
+
Model: 'Model',
|
|
33
|
+
ProductData: 'ProductData',
|
|
34
|
+
SerialNumber: 'SerialNumber',
|
|
35
|
+
HardwareRevision: 'HardwareRevision',
|
|
36
|
+
SoftwareRevision: 'SoftwareRevision',
|
|
37
|
+
FirmwareRevision: 'FirmwareRevision',
|
|
38
|
+
On: 'On',
|
|
39
|
+
}
|
|
40
|
+
;(Service as any).AccessoryInformation = class extends Service {}
|
|
41
|
+
;(Service as any).Outlet = class extends Service {}
|
|
42
|
+
return { Service, Characteristic, Categories: { OUTLET: 7 } }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Minimal PlatformAccessory stub
|
|
46
|
+
function makeAccessoryStub(hap: any, name = 'Test Accessory') {
|
|
47
|
+
const services: any[] = []
|
|
48
|
+
return {
|
|
49
|
+
displayName: name,
|
|
50
|
+
category: 0,
|
|
51
|
+
context: {},
|
|
52
|
+
getService(cls: any) {
|
|
53
|
+
// find existing or create
|
|
54
|
+
const svc = services.find(s => s instanceof cls)
|
|
55
|
+
if (svc) {
|
|
56
|
+
return svc
|
|
57
|
+
}
|
|
58
|
+
const s = new cls()
|
|
59
|
+
services.push(s)
|
|
60
|
+
return s
|
|
61
|
+
},
|
|
62
|
+
addService(cls: any) {
|
|
63
|
+
const s = new cls()
|
|
64
|
+
services.push(s)
|
|
65
|
+
return s
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Minimal SwitchBotHAPPlatform-like stub with required surface
|
|
71
|
+
function makePlatformStub(options: any, hap: any) {
|
|
72
|
+
const log = { info: vi.fn(), warn: vi.fn(), debug: vi.fn(), error: vi.fn(), success: vi.fn() }
|
|
73
|
+
return {
|
|
74
|
+
api: { hap },
|
|
75
|
+
log,
|
|
76
|
+
config: { options, credentials: {} },
|
|
77
|
+
debugMode: false,
|
|
78
|
+
// logging helpers used by deviceBase
|
|
79
|
+
infoLog: () => {},
|
|
80
|
+
successLog: () => {},
|
|
81
|
+
debugSuccessLog: () => {},
|
|
82
|
+
warnLog: () => {},
|
|
83
|
+
debugWarnLog: () => {},
|
|
84
|
+
errorLog: () => {},
|
|
85
|
+
debugErrorLog: () => {},
|
|
86
|
+
debugLog: () => {},
|
|
87
|
+
loggingIsDebug: async () => false,
|
|
88
|
+
enablingPlatformLogging: async () => true,
|
|
89
|
+
connectBLE: vi.fn(),
|
|
90
|
+
bleEventHandler: {},
|
|
91
|
+
webhookEventHandler: {},
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Create a tiny concrete subclass to instantiate deviceBase
|
|
96
|
+
class TestHAPDevice extends deviceBase {
|
|
97
|
+
// Override any methods that may be invoked by tests if needed
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
describe('hap device base webhook context', () => {
|
|
101
|
+
it('sets accessory.context.webhook=true when global webhook is enabled and device.webhook is undefined', async () => {
|
|
102
|
+
const hap = makeHapStub()
|
|
103
|
+
const accessory: any = makeAccessoryStub(hap, 'Plug Device')
|
|
104
|
+
const platform: any = makePlatformStub({ webhook: true, logging: 'debug' }, hap)
|
|
105
|
+
|
|
106
|
+
const dev: any = {
|
|
107
|
+
deviceId: 'DEV-HAP-1',
|
|
108
|
+
deviceType: 'Plug',
|
|
109
|
+
connectionType: 'OpenAPI',
|
|
110
|
+
// webhook intentionally undefined
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const d = new TestHAPDevice(platform, accessory, dev)
|
|
114
|
+
// Assert context
|
|
115
|
+
expect(accessory.context.webhook).toBe(true)
|
|
116
|
+
// ensure no unused var warning
|
|
117
|
+
expect(d).toBeDefined()
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('keeps accessory.context.webhook=false when device.webhook=false even if global is true', async () => {
|
|
121
|
+
const hap = makeHapStub()
|
|
122
|
+
const accessory: any = makeAccessoryStub(hap, 'Plug Device 2')
|
|
123
|
+
const platform: any = makePlatformStub({ webhook: true, logging: 'debug' }, hap)
|
|
124
|
+
|
|
125
|
+
const dev: any = {
|
|
126
|
+
deviceId: 'DEV-HAP-2',
|
|
127
|
+
deviceType: 'Plug',
|
|
128
|
+
connectionType: 'OpenAPI',
|
|
129
|
+
webhook: false,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const d = new TestHAPDevice(platform, accessory, dev)
|
|
133
|
+
expect(accessory.context.webhook).toBe(false)
|
|
134
|
+
expect(d).toBeDefined()
|
|
135
|
+
})
|
|
136
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { SwitchBotMatterPlatform } from '../../platform-matter.js'
|
|
4
|
+
import { makeApiStub, makeLogStub } from '../helpers/platform-fixtures.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Verify that Matter accessories receive the effective webhook flag
|
|
8
|
+
* in their context when a global webhook option is enabled and the
|
|
9
|
+
* per-device webhook setting is undefined.
|
|
10
|
+
*/
|
|
11
|
+
describe('platform-matter webhook context propagation', () => {
|
|
12
|
+
it('sets context.webhook=true when global webhook is enabled and device.webhook is undefined', async () => {
|
|
13
|
+
const api: any = makeApiStub()
|
|
14
|
+
const log: any = makeLogStub()
|
|
15
|
+
|
|
16
|
+
const platform = new SwitchBotMatterPlatform(log as any, {
|
|
17
|
+
name: 'SwitchBot',
|
|
18
|
+
options: { webhook: true },
|
|
19
|
+
} as any, api)
|
|
20
|
+
|
|
21
|
+
// Minimal device that maps to a known Matter accessory constructor
|
|
22
|
+
const dev: any = {
|
|
23
|
+
deviceId: 'DEV-WH-1',
|
|
24
|
+
deviceName: 'Webhook Plug',
|
|
25
|
+
deviceType: 'Plug',
|
|
26
|
+
// webhook intentionally undefined to test fallback
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const acc = await (platform as any).createAccessoryFromDevice(dev)
|
|
30
|
+
expect(acc).toBeDefined()
|
|
31
|
+
expect((acc as any).context?.webhook).toBe(true)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('keeps explicit device.webhook=false even when global webhook is true', async () => {
|
|
35
|
+
const api: any = makeApiStub()
|
|
36
|
+
const log: any = makeLogStub()
|
|
37
|
+
|
|
38
|
+
const platform = new SwitchBotMatterPlatform(log as any, {
|
|
39
|
+
name: 'SwitchBot',
|
|
40
|
+
options: { webhook: true },
|
|
41
|
+
} as any, api)
|
|
42
|
+
|
|
43
|
+
const dev: any = {
|
|
44
|
+
deviceId: 'DEV-WH-2',
|
|
45
|
+
deviceName: 'No Webhook Plug',
|
|
46
|
+
deviceType: 'Plug',
|
|
47
|
+
webhook: false,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const acc = await (platform as any).createAccessoryFromDevice(dev)
|
|
51
|
+
expect(acc).toBeDefined()
|
|
52
|
+
expect((acc as any).context?.webhook).toBe(false)
|
|
53
|
+
})
|
|
54
|
+
})
|
package/src/utils.ts
CHANGED
|
@@ -49,12 +49,22 @@ export function validHumidity(humidity: number, min?: number, max?: number): num
|
|
|
49
49
|
* Converts the value to celsius if the temperature units are in Fahrenheit
|
|
50
50
|
*/
|
|
51
51
|
export function convertUnits(value: number, unit: string, convert?: string): number {
|
|
52
|
-
|
|
52
|
+
// Convert only when source unit differs from target unit.
|
|
53
|
+
// Supported values for unit/convert: 'CELSIUS' | 'FAHRENHEIT'
|
|
54
|
+
if (!convert || unit === convert) {
|
|
55
|
+
return value
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (unit === 'CELSIUS' && convert === 'FAHRENHEIT') {
|
|
53
59
|
return Math.round((value * 9) / 5 + 32)
|
|
54
|
-
} else if (unit === 'FAHRENHEIT' && convert === 'FAHRENHEIT') {
|
|
55
|
-
// celsius should be to the nearest 0.5 degree
|
|
56
|
-
return Math.round((5 / 9) * (value - 32) * 2) / 2
|
|
57
60
|
}
|
|
61
|
+
|
|
62
|
+
if (unit === 'FAHRENHEIT' && convert === 'CELSIUS') {
|
|
63
|
+
// Celsius should be to the nearest 0.5 degree
|
|
64
|
+
return Math.round(((value - 32) * 5) / 9 * 2) / 2
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Unknown unit combination: return as-is
|
|
58
68
|
return value
|
|
59
69
|
}
|
|
60
70
|
|