@switchbot/homebridge-switchbot 5.0.0-beta.42 → 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.
Files changed (116) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/README.md +22 -0
  3. package/config.schema.json +71 -9
  4. package/dist/devices-hap/airpurifier.d.ts.map +1 -1
  5. package/dist/devices-hap/airpurifier.js +12 -6
  6. package/dist/devices-hap/airpurifier.js.map +1 -1
  7. package/dist/devices-hap/blindtilt.js +3 -3
  8. package/dist/devices-hap/bot.d.ts.map +1 -1
  9. package/dist/devices-hap/bot.js +16 -5
  10. package/dist/devices-hap/bot.js.map +1 -1
  11. package/dist/devices-hap/ceilinglight.d.ts.map +1 -1
  12. package/dist/devices-hap/ceilinglight.js +13 -7
  13. package/dist/devices-hap/ceilinglight.js.map +1 -1
  14. package/dist/devices-hap/colorbulb.d.ts.map +1 -1
  15. package/dist/devices-hap/colorbulb.js +49 -9
  16. package/dist/devices-hap/colorbulb.js.map +1 -1
  17. package/dist/devices-hap/contact.js +3 -3
  18. package/dist/devices-hap/curtain.js +2 -2
  19. package/dist/devices-hap/curtain.js.map +1 -1
  20. package/dist/devices-hap/device.d.ts.map +1 -1
  21. package/dist/devices-hap/device.js +20 -1
  22. package/dist/devices-hap/device.js.map +1 -1
  23. package/dist/devices-hap/fan.d.ts.map +1 -1
  24. package/dist/devices-hap/fan.js +12 -6
  25. package/dist/devices-hap/fan.js.map +1 -1
  26. package/dist/devices-hap/hub.d.ts.map +1 -1
  27. package/dist/devices-hap/hub.js +6 -5
  28. package/dist/devices-hap/hub.js.map +1 -1
  29. package/dist/devices-hap/humidifier.d.ts +5 -0
  30. package/dist/devices-hap/humidifier.d.ts.map +1 -1
  31. package/dist/devices-hap/humidifier.js +92 -4
  32. package/dist/devices-hap/humidifier.js.map +1 -1
  33. package/dist/devices-hap/iosensor.d.ts.map +1 -1
  34. package/dist/devices-hap/iosensor.js +36 -21
  35. package/dist/devices-hap/iosensor.js.map +1 -1
  36. package/dist/devices-hap/lightstrip.d.ts.map +1 -1
  37. package/dist/devices-hap/lightstrip.js +38 -8
  38. package/dist/devices-hap/lightstrip.js.map +1 -1
  39. package/dist/devices-hap/lock.d.ts.map +1 -1
  40. package/dist/devices-hap/lock.js +14 -6
  41. package/dist/devices-hap/lock.js.map +1 -1
  42. package/dist/devices-hap/meter.d.ts.map +1 -1
  43. package/dist/devices-hap/meter.js +6 -5
  44. package/dist/devices-hap/meter.js.map +1 -1
  45. package/dist/devices-hap/meterplus.d.ts.map +1 -1
  46. package/dist/devices-hap/meterplus.js +6 -5
  47. package/dist/devices-hap/meterplus.js.map +1 -1
  48. package/dist/devices-hap/meterpro.d.ts.map +1 -1
  49. package/dist/devices-hap/meterpro.js +7 -6
  50. package/dist/devices-hap/meterpro.js.map +1 -1
  51. package/dist/devices-hap/motion.js +3 -3
  52. package/dist/devices-hap/plug.d.ts.map +1 -1
  53. package/dist/devices-hap/plug.js +11 -6
  54. package/dist/devices-hap/plug.js.map +1 -1
  55. package/dist/devices-hap/relayswitch.js +3 -3
  56. package/dist/devices-hap/robotvacuumcleaner.d.ts.map +1 -1
  57. package/dist/devices-hap/robotvacuumcleaner.js +13 -6
  58. package/dist/devices-hap/robotvacuumcleaner.js.map +1 -1
  59. package/dist/devices-hap/waterdetector.js +3 -3
  60. package/dist/homebridge-ui/public/index.html +13 -1
  61. package/dist/platform-hap.d.ts +5 -0
  62. package/dist/platform-hap.d.ts.map +1 -1
  63. package/dist/platform-hap.js +106 -16
  64. package/dist/platform-hap.js.map +1 -1
  65. package/dist/platform-matter.d.ts +5 -0
  66. package/dist/platform-matter.d.ts.map +1 -1
  67. package/dist/platform-matter.js +129 -8
  68. package/dist/platform-matter.js.map +1 -1
  69. package/dist/settings.d.ts +17 -1
  70. package/dist/settings.d.ts.map +1 -1
  71. package/dist/settings.js.map +1 -1
  72. package/dist/test/hap/device-webhook-context.test.d.ts +2 -0
  73. package/dist/test/hap/device-webhook-context.test.d.ts.map +1 -0
  74. package/dist/test/hap/device-webhook-context.test.js +128 -0
  75. package/dist/test/hap/device-webhook-context.test.js.map +1 -0
  76. package/dist/test/matter/platform-matter.webhook.test.d.ts +2 -0
  77. package/dist/test/matter/platform-matter.webhook.test.d.ts.map +1 -0
  78. package/dist/test/matter/platform-matter.webhook.test.js +46 -0
  79. package/dist/test/matter/platform-matter.webhook.test.js.map +1 -0
  80. package/dist/utils.d.ts +8 -0
  81. package/dist/utils.d.ts.map +1 -1
  82. package/dist/utils.js +74 -20
  83. package/dist/utils.js.map +1 -1
  84. package/docs/assets/highlight.css +14 -0
  85. package/docs/index.html +12 -1
  86. package/docs/variables/default.html +1 -1
  87. package/package.json +2 -2
  88. package/src/devices-hap/airpurifier.ts +11 -6
  89. package/src/devices-hap/blindtilt.ts +3 -3
  90. package/src/devices-hap/bot.ts +15 -5
  91. package/src/devices-hap/ceilinglight.ts +12 -7
  92. package/src/devices-hap/colorbulb.ts +46 -10
  93. package/src/devices-hap/contact.ts +3 -3
  94. package/src/devices-hap/curtain.ts +2 -2
  95. package/src/devices-hap/device.ts +20 -1
  96. package/src/devices-hap/fan.ts +11 -6
  97. package/src/devices-hap/hub.ts +6 -5
  98. package/src/devices-hap/humidifier.ts +97 -4
  99. package/src/devices-hap/iosensor.ts +36 -21
  100. package/src/devices-hap/lightstrip.ts +35 -8
  101. package/src/devices-hap/lock.ts +13 -6
  102. package/src/devices-hap/meter.ts +6 -5
  103. package/src/devices-hap/meterplus.ts +6 -5
  104. package/src/devices-hap/meterpro.ts +7 -6
  105. package/src/devices-hap/motion.ts +3 -3
  106. package/src/devices-hap/plug.ts +10 -6
  107. package/src/devices-hap/relayswitch.ts +3 -3
  108. package/src/devices-hap/robotvacuumcleaner.ts +12 -6
  109. package/src/devices-hap/waterdetector.ts +3 -3
  110. package/src/homebridge-ui/public/index.html +13 -1
  111. package/src/platform-hap.ts +110 -16
  112. package/src/platform-matter.ts +137 -8
  113. package/src/settings.ts +17 -1
  114. package/src/test/hap/device-webhook-context.test.ts +136 -0
  115. package/src/test/matter/platform-matter.webhook.test.ts +54 -0
  116. package/src/utils.ts +88 -29
@@ -326,7 +326,7 @@ export class ColorBulb extends deviceBase {
326
326
  // Update HomeKit
327
327
  if (serviceData.model === SwitchBotBLEModel.ColorBulb && serviceData.modelName === SwitchBotBLEModelName.ColorBulb) {
328
328
  this.serviceData = serviceData
329
- if (serviceData !== undefined || serviceData !== null) {
329
+ if (serviceData !== undefined && serviceData !== null) {
330
330
  await this.BLEparseStatus()
331
331
  await this.updateHomeKitCharacteristics()
332
332
  } else {
@@ -352,7 +352,7 @@ export class ColorBulb extends deviceBase {
352
352
  this.platform.bleEventHandler[this.device.bleMac] = async (context: colorBulbServiceData) => {
353
353
  try {
354
354
  this.serviceData = context
355
- if (context !== undefined || context !== null) {
355
+ if (context !== undefined && context !== null) {
356
356
  this.debugLog(`received BLE: ${JSON.stringify(context)}`)
357
357
  await this.BLEparseStatus()
358
358
  await this.updateHomeKitCharacteristics()
@@ -397,7 +397,7 @@ export class ColorBulb extends deviceBase {
397
397
  this.platform.webhookEventHandler[this.device.deviceId] = async (context: colorBulbWebhookContext) => {
398
398
  try {
399
399
  this.webhookContext = context
400
- if (context !== undefined || context !== null) {
400
+ if (context !== undefined && context !== null) {
401
401
  this.debugLog(`received Webhook: ${JSON.stringify(context)}`)
402
402
  await this.parseStatusWebhook()
403
403
  await this.updateHomeKitCharacteristics()
@@ -480,15 +480,21 @@ export class ColorBulb extends deviceBase {
480
480
  if (switchBotBLE !== false) {
481
481
  switchBotBLE
482
482
  .discover({ model: this.device.bleModel, id: this.device.bleMac })
483
- .then(async (device_list: WoBulb[]) => {
483
+ .then(async (device_list: SwitchbotDevice[]) => {
484
+ const deviceList = device_list as WoBulb[]
484
485
  this.infoLog(`On: ${this.LightBulb.On}`)
486
+ this.debugLog(`device_list: ${JSON.stringify(device_list)}`)
485
487
  return await this.retryBLE({
486
488
  max: this.maxRetryBLE(),
487
489
  fn: async () => {
488
- if (this.LightBulb.On) {
489
- return await device_list[0].turnOn()
490
+ if (deviceList.length > 0) {
491
+ if (this.LightBulb.On) {
492
+ return await deviceList[0].turnOn()
493
+ } else {
494
+ return await deviceList[0].turnOff()
495
+ }
490
496
  } else {
491
- return await device_list[0].turnOff()
497
+ throw new Error('No devices found during discovery.')
492
498
  }
493
499
  },
494
500
  })
@@ -528,7 +534,17 @@ export class ColorBulb extends deviceBase {
528
534
  .then(async (device_list: SwitchbotDevice[]) => {
529
535
  const deviceList = device_list as WoBulb[]
530
536
  this.infoLog(`Target Brightness: ${this.LightBulb.Brightness}`)
531
- return await deviceList[0].setBrightness(Number(this.LightBulb.Brightness))
537
+ this.debugLog(`device_list: ${JSON.stringify(device_list)}`)
538
+ return await this.retryBLE({
539
+ max: this.maxRetryBLE(),
540
+ fn: async () => {
541
+ if (deviceList.length > 0) {
542
+ return await deviceList[0].setBrightness(Number(this.LightBulb.Brightness))
543
+ } else {
544
+ throw new Error('No devices found during discovery.')
545
+ }
546
+ },
547
+ })
532
548
  })
533
549
  .then(async () => {
534
550
  this.successLog(`Brightness: ${this.LightBulb.Brightness} sent over SwitchBot BLE, sent successfully`)
@@ -567,7 +583,17 @@ export class ColorBulb extends deviceBase {
567
583
  .then(async (device_list: SwitchbotDevice[]) => {
568
584
  const deviceList = device_list as WoBulb[]
569
585
  this.infoLog(`ColorTemperature: ${this.LightBulb.ColorTemperature}`)
570
- return await deviceList[0].setColorTemperature(kelvin)
586
+ this.debugLog(`device_list: ${JSON.stringify(device_list)}`)
587
+ return await this.retryBLE({
588
+ max: this.maxRetryBLE(),
589
+ fn: async () => {
590
+ if (deviceList.length > 0) {
591
+ return await deviceList[0].setColorTemperature(kelvin)
592
+ } else {
593
+ throw new Error('No devices found during discovery.')
594
+ }
595
+ },
596
+ })
571
597
  })
572
598
  .then(async () => {
573
599
  this.successLog(`ColorTemperature: ${this.LightBulb.ColorTemperature} sent over SwitchBot BLE, sent successfully`)
@@ -607,7 +633,17 @@ export class ColorBulb extends deviceBase {
607
633
  .then(async (device_list: SwitchbotDevice[]) => {
608
634
  const deviceList = device_list as WoBulb[]
609
635
  this.infoLog(`RGB: ${(this.LightBulb.Brightness, red, green, blue)}`)
610
- return await deviceList[0].setRGB(Number(this.LightBulb.Brightness), red, green, blue)
636
+ this.debugLog(`device_list: ${JSON.stringify(device_list)}`)
637
+ return await this.retryBLE({
638
+ max: this.maxRetryBLE(),
639
+ fn: async () => {
640
+ if (deviceList.length > 0) {
641
+ return await deviceList[0].setRGB(Number(this.LightBulb.Brightness), red, green, blue)
642
+ } else {
643
+ throw new Error('No devices found during discovery.')
644
+ }
645
+ },
646
+ })
611
647
  })
612
648
  .then(async () => {
613
649
  this.successLog(`RGB: ${(this.LightBulb.Brightness, red, green, blue)} sent over SwitchBot BLE, sent successfully`)
@@ -307,7 +307,7 @@ export class Contact extends deviceBase {
307
307
  // Update HomeKit
308
308
  if (serviceData.model === SwitchBotBLEModel.ContactSensor && serviceData.modelName === SwitchBotBLEModelName.ContactSensor) {
309
309
  this.serviceData = serviceData
310
- if (serviceData !== undefined || serviceData !== null) {
310
+ if (serviceData !== undefined && serviceData !== null) {
311
311
  await this.BLEparseStatus()
312
312
  await this.updateHomeKitCharacteristics()
313
313
  } else {
@@ -333,7 +333,7 @@ export class Contact extends deviceBase {
333
333
  this.platform.bleEventHandler[this.device.bleMac] = async (context: contactSensorServiceData) => {
334
334
  try {
335
335
  this.serviceData = context
336
- if (context !== undefined || context !== null) {
336
+ if (context !== undefined && context !== null) {
337
337
  this.debugLog(`received BLE: ${JSON.stringify(context)}`)
338
338
  await this.BLEparseStatus()
339
339
  await this.updateHomeKitCharacteristics()
@@ -378,7 +378,7 @@ export class Contact extends deviceBase {
378
378
  this.platform.webhookEventHandler[this.device.deviceId] = async (context: contactSensorWebhookContext) => {
379
379
  try {
380
380
  this.webhookContext = context
381
- if (context !== undefined || context !== null) {
381
+ if (context !== undefined && context !== null) {
382
382
  this.debugLog(`received Webhook: ${JSON.stringify(context)}`)
383
383
  await this.parseStatusWebhook()
384
384
  await this.updateHomeKitCharacteristics()
@@ -534,7 +534,7 @@ export class Curtain extends deviceBase {
534
534
  this.platform.webhookEventHandler[this.device.deviceId] = async (context: curtainWebhookContext | curtain3WebhookContext) => {
535
535
  try {
536
536
  this.webhookContext = context
537
- if (context !== undefined || context !== null) {
537
+ if (context !== undefined && context !== null) {
538
538
  this.debugLog(`received Webhook: ${JSON.stringify(context)}`)
539
539
  await this.parseStatusWebhook()
540
540
  await this.updateHomeKitCharacteristics()
@@ -657,7 +657,7 @@ export class Curtain extends deviceBase {
657
657
  const adjustedTargetPosition = 100 - Number(this.WindowCovering.TargetPosition)
658
658
  const { setPositionMode, Mode }: { setPositionMode: number, Mode: string } = await this.setPerformance()
659
659
  this.debugLog(`Mode: ${Mode}, setPositionMode: ${setPositionMode}`)
660
- const adjustedMode = setPositionMode || 'ff'
660
+ const adjustedMode = setPositionMode === 1 ? '01' : '00'
661
661
  let bodyChange: bodyChange
662
662
  if (this.WindowCovering.HoldPosition) {
663
663
  bodyChange = {
@@ -147,7 +147,9 @@ export abstract class deviceBase {
147
147
  device.scanDuration !== 0 && { scanDuration: device.scanDuration },
148
148
  device.offline === true && { offline: device.offline },
149
149
  device.maxRetry !== 0 && { maxRetry: device.maxRetry },
150
- device.webhook === true && { webhook: device.webhook },
150
+ (device.webhook === true || (device.webhook === undefined && this.config.options?.webhook === true)) && {
151
+ webhook: device.webhook !== undefined ? device.webhook : this.config.options?.webhook,
152
+ },
151
153
  device.connectionType !== '' && { connectionType: device.connectionType },
152
154
  device.disablePlatformBLE !== false && { disablePlatformBLE: device.disablePlatformBLE },
153
155
  device.external === true && { external: device.external },
@@ -584,6 +586,12 @@ export abstract class deviceBase {
584
586
  bleModelName: SwitchBotBLEModelName.LockPro,
585
587
  bleModelFriendlyName: SwitchBotBLEModelFriendlyName.LockPro,
586
588
  },
589
+ 'Smart Lock Ultra': {
590
+ model: SwitchBotModel.LockPro,
591
+ bleModel: SwitchBotBLEModel.LockPro,
592
+ bleModelName: SwitchBotBLEModelName.LockPro,
593
+ bleModelFriendlyName: SwitchBotBLEModelFriendlyName.LockPro,
594
+ },
587
595
  'Color Bulb': {
588
596
  model: SwitchBotModel.ColorBulb,
589
597
  bleModel: SwitchBotBLEModel.ColorBulb,
@@ -709,6 +717,17 @@ export abstract class deviceBase {
709
717
  .updateValue(deviceVersion)
710
718
  accessory.context.version = deviceVersion
711
719
  this.debugSuccessLog(`version: ${accessory.context.version}`)
720
+
721
+ // Expose effective webhook setting on accessory context (parity with Matter)
722
+ try {
723
+ const effectiveWebhook = device.webhook !== undefined ? device.webhook : (this.config.options?.webhook === true ? true : undefined)
724
+ if (effectiveWebhook !== undefined) {
725
+ accessory.context.webhook = effectiveWebhook
726
+ this.debugLog(`Effective webhook for ${device.deviceId}: ${String(effectiveWebhook)}`)
727
+ }
728
+ } catch (e: any) {
729
+ this.debugLog(`Failed to set webhook context for ${device.deviceId}: ${e?.message ?? e}`)
730
+ }
712
731
  }
713
732
 
714
733
  async statusCode(statusCode: number): Promise<void> {
@@ -329,7 +329,7 @@ export class Fan extends deviceBase {
329
329
  // Update HomeKit
330
330
  if (serviceData.model === SwitchBotBLEModel.Unknown && SwitchBotBLEModelName.Unknown) {
331
331
  this.serviceData = serviceData
332
- if (serviceData !== undefined || serviceData !== null) {
332
+ if (serviceData !== undefined && serviceData !== null) {
333
333
  await this.BLEparseStatus()
334
334
  await this.updateHomeKitCharacteristics()
335
335
  } else {
@@ -355,7 +355,7 @@ export class Fan extends deviceBase {
355
355
  this.platform.bleEventHandler[this.device.bleMac] = async (context: batteryCirculatorFanServiceData) => {
356
356
  try {
357
357
  this.serviceData = context
358
- if (context !== undefined || context !== null) {
358
+ if (context !== undefined && context !== null) {
359
359
  this.debugLog(`received BLE: ${JSON.stringify(context)}`)
360
360
  await this.BLEparseStatus()
361
361
  await this.updateHomeKitCharacteristics()
@@ -400,7 +400,7 @@ export class Fan extends deviceBase {
400
400
  this.platform.webhookEventHandler[this.device.deviceId] = async (context: batteryCirculatorFanWebhookContext) => {
401
401
  try {
402
402
  this.webhookContext = context
403
- if (context !== undefined || context !== null) {
403
+ if (context !== undefined && context !== null) {
404
404
  this.debugLog(`received Webhook: ${JSON.stringify(context)}`)
405
405
  await this.parseStatusWebhook()
406
406
  await this.updateHomeKitCharacteristics()
@@ -468,13 +468,18 @@ export class Fan extends deviceBase {
468
468
  switchBotBLE
469
469
  .discover({ model: this.device.bleModel, id: this.device.bleMac })
470
470
  .then(async (device_list: SwitchbotDevice[]) => {
471
+ this.debugLog(`device_list: ${JSON.stringify(device_list)}`)
471
472
  return await this.retryBLE({
472
473
  max: this.maxRetryBLE(),
473
474
  fn: async () => {
474
- if (this.Fan.Active) {
475
- return await (device_list[0] as any).turnOn()
475
+ if (Array.isArray(device_list) && device_list.length > 0) {
476
+ if (this.Fan.Active) {
477
+ return await (device_list[0] as any).turnOn()
478
+ } else {
479
+ return await (device_list[0] as any).turnOff()
480
+ }
476
481
  } else {
477
- return await (device_list[0] as any).turnOff()
482
+ throw new Error('No devices found during discovery.')
478
483
  }
479
484
  },
480
485
  })
@@ -211,8 +211,9 @@ export class Hub extends deviceBase {
211
211
 
212
212
  // CurrentTemperature
213
213
  if (!(this.device as hubConfig).hide_temperature && this.TemperatureSensor?.Service) {
214
- this.TemperatureSensor.CurrentTemperature = this.deviceStatus.temperature
215
- this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`)
214
+ // OpenAPI returns Celsius; convert if user configured a different unit
215
+ this.TemperatureSensor.CurrentTemperature = convertUnits(this.deviceStatus.temperature, 'CELSIUS', (this.device as hubConfig).convertUnitTo)
216
+ this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}`)
216
217
  }
217
218
 
218
219
  // LightSensor
@@ -297,7 +298,7 @@ export class Hub extends deviceBase {
297
298
  if ((serviceData.model === SwitchBotBLEModel.Hub2 && serviceData.modelName === SwitchBotBLEModelName.Hub2)
298
299
  || (serviceData.model === SwitchBotBLEModel.Hub3 && serviceData.modelName === SwitchBotBLEModelName.Hub3)) {
299
300
  this.serviceData = serviceData
300
- if (serviceData !== undefined || serviceData !== null) {
301
+ if (serviceData !== undefined && serviceData !== null) {
301
302
  await this.BLEparseStatus()
302
303
  await this.updateHomeKitCharacteristics()
303
304
  } else {
@@ -323,7 +324,7 @@ export class Hub extends deviceBase {
323
324
  this.platform.bleEventHandler[this.device.bleMac] = async (context: hub2ServiceData) => {
324
325
  try {
325
326
  this.serviceData = context
326
- if (context !== undefined || context !== null) {
327
+ if (context !== undefined && context !== null) {
327
328
  this.debugLog(`received BLE: ${JSON.stringify(context)}`)
328
329
  await this.BLEparseStatus()
329
330
  await this.updateHomeKitCharacteristics()
@@ -368,7 +369,7 @@ export class Hub extends deviceBase {
368
369
  this.platform.webhookEventHandler[this.device.deviceId] = async (context: hub2WebhookContext) => {
369
370
  try {
370
371
  this.webhookContext = context
371
- if (context !== undefined || context !== null) {
372
+ if (context !== undefined && context !== null) {
372
373
  this.debugLog(`received Webhook: ${JSON.stringify(context)}`)
373
374
  await this.parseStatusWebhook()
374
375
  await this.updateHomeKitCharacteristics()
@@ -42,6 +42,12 @@ export class Humidifier extends deviceBase {
42
42
  CurrentTemperature: CharacteristicValue
43
43
  }
44
44
 
45
+ private DryingFilterSwitch?: {
46
+ Name: CharacteristicValue
47
+ Service: Service
48
+ On: CharacteristicValue
49
+ }
50
+
45
51
  // OpenAPI
46
52
  deviceStatus!: humidifierStatus | humidifier2Status
47
53
 
@@ -132,6 +138,31 @@ export class Humidifier extends deviceBase {
132
138
  })
133
139
  }
134
140
 
141
+ // Initialize optional Drying Filter Switch (Humidifier2 only)
142
+ if ((device as humidifierConfig).configDeviceType === 'Humidifier2' && (device as humidifierConfig).activate_dryingfilter) {
143
+ accessory.context.DryingFilterSwitch = accessory.context.DryingFilterSwitch ?? {}
144
+ this.DryingFilterSwitch = {
145
+ Name: `${accessory.displayName} Drying Filter`,
146
+ Service: accessory.getService(`${accessory.displayName} Drying Filter`) ?? accessory.addService(this.hap.Service.Switch, `${accessory.displayName} Drying Filter`, 'DryingFilterSwitch') as Service,
147
+ On: false,
148
+ }
149
+ accessory.context.DryingFilterSwitch = this.DryingFilterSwitch as object
150
+
151
+ this.DryingFilterSwitch.Service.setCharacteristic(this.hap.Characteristic.Name, this.DryingFilterSwitch.Name)
152
+ .getCharacteristic(this.hap.Characteristic.On)
153
+ .onGet(() => {
154
+ return this.DryingFilterSwitch!.On
155
+ })
156
+ .onSet(this.DryingFilterOnSet.bind(this))
157
+ this.debugLog('Initialized Drying Filter Switch Service')
158
+ } else {
159
+ const svc = this.accessory.getServiceById(this.hap.Service.Switch, 'DryingFilterSwitch')
160
+ if (svc) {
161
+ this.debugLog('Removing Drying Filter Switch Service')
162
+ this.accessory.removeService(svc)
163
+ }
164
+ }
165
+
135
166
  // Retrieve initial values and updateHomekit
136
167
  try {
137
168
  this.debugLog('Retrieve initial values and update Homekit')
@@ -326,7 +357,7 @@ export class Humidifier extends deviceBase {
326
357
  // Update HomeKit
327
358
  if (serviceData.model === SwitchBotBLEModel.Humidifier && serviceData.modelName === SwitchBotBLEModelName.Humidifier) {
328
359
  this.serviceData = serviceData
329
- if (serviceData !== undefined || serviceData !== null) {
360
+ if (serviceData !== undefined && serviceData !== null) {
330
361
  await this.BLEparseStatus()
331
362
  await this.updateHomeKitCharacteristics()
332
363
  } else {
@@ -352,7 +383,7 @@ export class Humidifier extends deviceBase {
352
383
  this.platform.bleEventHandler[this.device.bleMac] = async (context: humidifierServiceData | humidifier2ServiceData) => {
353
384
  try {
354
385
  this.serviceData = context
355
- if (context !== undefined || context !== null) {
386
+ if (context !== undefined && context !== null) {
356
387
  this.debugLog(`received BLE: ${JSON.stringify(context)}`)
357
388
  await this.BLEparseStatus()
358
389
  await this.updateHomeKitCharacteristics()
@@ -397,7 +428,7 @@ export class Humidifier extends deviceBase {
397
428
  this.platform.webhookEventHandler[this.device.deviceId] = async (context: humidifierWebhookContext | humidifier2WebhookContext) => {
398
429
  try {
399
430
  this.webhookContext = context
400
- if (context !== undefined || context !== null) {
431
+ if (context !== undefined && context !== null) {
401
432
  this.debugLog(`received Webhook: ${JSON.stringify(context)}`)
402
433
  await this.parseStatusWebhook()
403
434
  await this.updateHomeKitCharacteristics()
@@ -446,7 +477,17 @@ export class Humidifier extends deviceBase {
446
477
  switchBotBLE
447
478
  .discover({ model: this.device.bleModel, quick: true, id: this.device.bleMac })
448
479
  .then(async (device_list: SwitchbotDevice[]) => {
449
- return await (device_list[0] as WoHumi).percentage(Number(this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold))
480
+ this.debugLog(`device_list: ${JSON.stringify(device_list)}`)
481
+ return await this.retryBLE({
482
+ max: this.maxRetryBLE(),
483
+ fn: async () => {
484
+ if (device_list.length > 0) {
485
+ return await (device_list[0] as WoHumi).percentage(Number(this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold))
486
+ } else {
487
+ throw new Error('No devices found during discovery.')
488
+ }
489
+ },
490
+ })
450
491
  })
451
492
  .then(async () => {
452
493
  this.successLog(`RelativeHumidityHumidifierThreshold: ${this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold} sent over BLE, sent successfully`)
@@ -630,6 +671,58 @@ export class Humidifier extends deviceBase {
630
671
  if (!(this.device as humidifierConfig).hide_temperature && this.TemperatureSensor?.Service) {
631
672
  await this.updateCharacteristic(this.TemperatureSensor.Service, this.hap.Characteristic.CurrentTemperature, this.TemperatureSensor.CurrentTemperature, 'CurrentTemperature')
632
673
  }
674
+ // DryingFilter switch is momentary; ensure it reflects current cached state
675
+ if (this.DryingFilterSwitch?.Service) {
676
+ await this.updateCharacteristic(this.DryingFilterSwitch.Service, this.hap.Characteristic.On, this.DryingFilterSwitch.On, 'DryingFilter.On')
677
+ }
678
+ }
679
+
680
+ /**
681
+ * Handle requests to set the Drying Filter switch (Humidifier2 only)
682
+ */
683
+ async DryingFilterOnSet(value: CharacteristicValue): Promise<void> {
684
+ if (!this.DryingFilterSwitch?.Service) {
685
+ this.debugLog('DryingFilterOnSet called but service not initialized')
686
+ return
687
+ }
688
+ this.DryingFilterSwitch.On = value
689
+ if (value !== true) {
690
+ // Switch is momentary; ignore turning off manually
691
+ this.debugLog('Drying Filter switch set to false (ignored)')
692
+ return
693
+ }
694
+
695
+ this.infoLog('Trigger Drying Filter mode')
696
+ if (this.OpenAPI && this.platform.config.credentials?.token) {
697
+ const bodyChange: bodyChange = {
698
+ command: 'setMode',
699
+ parameter: '8',
700
+ commandType: 'command',
701
+ }
702
+ this.debugLog(`DryingFilterOnSet, SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`)
703
+ try {
704
+ const deviceStatus = await this.pushChangeRequest(bodyChange)
705
+ this.debugLog(`statusCode: ${deviceStatus.statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`)
706
+ if (await this.successfulStatusCodes(deviceStatus)) {
707
+ this.debugSuccessLog(`statusCode: ${deviceStatus.statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`)
708
+ } else {
709
+ await this.statusCode(deviceStatus.statusCode)
710
+ }
711
+ } catch (e: any) {
712
+ await this.apiError(e)
713
+ this.errorLog(`failed DryingFilterOnSet with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`)
714
+ }
715
+ } else {
716
+ this.debugWarnLog('Drying Filter requires OpenAPI; BLE not supported for this action')
717
+ }
718
+
719
+ // Auto-reset the momentary switch back to off after a short delay
720
+ setTimeout(async () => {
721
+ if (this.DryingFilterSwitch) {
722
+ this.DryingFilterSwitch.On = false
723
+ await this.updateHomeKitCharacteristics()
724
+ }
725
+ }, 1500)
633
726
  }
634
727
 
635
728
  async BLEPushConnection() {
@@ -178,27 +178,41 @@ export class IOSensor extends deviceBase {
178
178
  async BLEparseStatus(): Promise<void> {
179
179
  this.debugLog('BLEparseStatus')
180
180
  this.debugLog(`(battery, temperature, humidity) = BLE:(${this.serviceData.battery}, ${this.serviceData.celsius}, ${this.serviceData.humidity}), current:(${this.Battery.BatteryLevel}, ${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity})`)
181
+
181
182
  // Battery Info
182
183
  if ('battery' in this.serviceData) {
183
- // BatteryLevel
184
- this.Battery.BatteryLevel = this.serviceData.battery
185
- this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`)
186
- // StatusLowBattery
187
- this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10
188
- ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW
189
- : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL
190
- this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`)
184
+ if (typeof this.serviceData.battery === 'number') {
185
+ this.Battery.BatteryLevel = this.serviceData.battery
186
+ this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`)
187
+ this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10
188
+ ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW
189
+ : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL
190
+ this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`)
191
+ } else {
192
+ this.debugWarnLog(`BLE battery is undefined; skipping battery update.`)
193
+ }
191
194
  }
195
+
192
196
  // CurrentRelativeHumidity
193
197
  if (!(this.device as indoorOutdoorSensorConfig).hide_humidity && this.HumiditySensor?.Service) {
194
- this.HumiditySensor.CurrentRelativeHumidity = validHumidity(this.serviceData.humidity, 0, 100)
195
- this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`)
198
+ if (typeof this.serviceData.humidity === 'number') {
199
+ this.HumiditySensor.CurrentRelativeHumidity = validHumidity(this.serviceData.humidity, 0, 100)
200
+ this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`)
201
+ } else {
202
+ this.debugWarnLog(`BLE humidity is undefined; skipping humidity update.`)
203
+ }
196
204
  }
205
+
197
206
  // Current Temperature
198
207
  if (!(this.device as indoorOutdoorSensorConfig).hide_temperature && this.TemperatureSensor?.Service) {
199
- const CELSIUS = this.serviceData.celsius < 0 ? 0 : this.serviceData.celsius > 100 ? 100 : this.serviceData.celsius
200
- this.TemperatureSensor.CurrentTemperature = CELSIUS
201
- this.debugLog(`Temperature: ${this.TemperatureSensor.CurrentTemperature}°c`)
208
+ if (typeof this.serviceData.celsius === 'number') {
209
+ const c = this.serviceData.celsius
210
+ const CELSIUS = c < 0 ? 0 : c > 100 ? 100 : c
211
+ this.TemperatureSensor.CurrentTemperature = CELSIUS
212
+ this.debugLog(`Temperature: ${this.TemperatureSensor.CurrentTemperature}°c`)
213
+ } else {
214
+ this.debugWarnLog(`BLE temperature is undefined; skipping temperature update.`)
215
+ }
202
216
  }
203
217
  }
204
218
 
@@ -224,8 +238,9 @@ export class IOSensor extends deviceBase {
224
238
 
225
239
  // Current Temperature
226
240
  if (!(this.device as indoorOutdoorSensorConfig).hide_temperature && this.TemperatureSensor?.Service) {
227
- this.TemperatureSensor.CurrentTemperature = this.deviceStatus.temperature
228
- this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`)
241
+ // OpenAPI returns Celsius; convert if user configured a different unit
242
+ this.TemperatureSensor.CurrentTemperature = convertUnits(this.deviceStatus.temperature as number, 'CELSIUS', (this.device as indoorOutdoorSensorConfig).convertUnitTo)
243
+ this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}`)
229
244
  }
230
245
 
231
246
  // Firmware Version
@@ -252,12 +267,12 @@ export class IOSensor extends deviceBase {
252
267
  this.warnLog(`received a non-CELSIUS Webhook scale: ${this.webhookContext.scale}, Use the *convertUnitsTo* config under Hub settings, if displaying incorrectly in HomeKit.`)
253
268
  }
254
269
  // CurrentRelativeHumidity
255
- if ((this.device as indoorOutdoorSensorConfig).hide_humidity && this.HumiditySensor?.Service) {
270
+ if (!(this.device as indoorOutdoorSensorConfig).hide_humidity && this.HumiditySensor?.Service) {
256
271
  this.HumiditySensor.CurrentRelativeHumidity = this.webhookContext.humidity
257
272
  this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`)
258
273
  }
259
274
  // CurrentTemperature
260
- if ((this.device as indoorOutdoorSensorConfig).hide_temperature && this.TemperatureSensor?.Service) {
275
+ if (!(this.device as indoorOutdoorSensorConfig).hide_temperature && this.TemperatureSensor?.Service) {
261
276
  this.TemperatureSensor.CurrentTemperature = convertUnits(this.webhookContext.temperature, this.webhookContext.scale, (this.device as indoorOutdoorSensorConfig).convertUnitTo)
262
277
  this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`)
263
278
  }
@@ -290,7 +305,7 @@ export class IOSensor extends deviceBase {
290
305
  // Update HomeKit
291
306
  if (serviceData.model === SwitchBotBLEModel.OutdoorMeter && serviceData.modelName === SwitchBotBLEModelName.OutdoorMeter) {
292
307
  this.serviceData = serviceData
293
- if (serviceData !== undefined || serviceData !== null) {
308
+ if (serviceData !== undefined && serviceData !== null) {
294
309
  await this.BLEparseStatus()
295
310
  await this.updateHomeKitCharacteristics()
296
311
  } else {
@@ -316,7 +331,7 @@ export class IOSensor extends deviceBase {
316
331
  this.platform.bleEventHandler[this.device.bleMac] = async (context: outdoorMeterServiceData) => {
317
332
  try {
318
333
  this.serviceData = context
319
- if (context !== undefined || context !== null) {
334
+ if (context !== undefined && context !== null) {
320
335
  this.debugLog(`received BLE: ${JSON.stringify(context)}`)
321
336
  await this.BLEparseStatus()
322
337
  await this.updateHomeKitCharacteristics()
@@ -351,7 +366,7 @@ export class IOSensor extends deviceBase {
351
366
  }
352
367
  } catch (e: any) {
353
368
  await this.apiError(e)
354
- this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`)
369
+ this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error: ${e.message ?? e}`)
355
370
  }
356
371
  }
357
372
 
@@ -361,7 +376,7 @@ export class IOSensor extends deviceBase {
361
376
  this.platform.webhookEventHandler[this.device.deviceId] = async (context: outdoorMeterWebhookContext) => {
362
377
  try {
363
378
  this.webhookContext = context
364
- if (context !== undefined || context !== null) {
379
+ if (context !== undefined && context !== null) {
365
380
  this.debugLog(`received Webhook: ${JSON.stringify(context)}`)
366
381
  await this.parseStatusWebhook()
367
382
  await this.updateHomeKitCharacteristics()
@@ -328,7 +328,7 @@ export class StripLight extends deviceBase {
328
328
  // Update HomeKit
329
329
  if (serviceData.model === SwitchBotBLEModel.StripLight && serviceData.modelName === SwitchBotBLEModelName.StripLight) {
330
330
  this.serviceData = serviceData
331
- if (serviceData !== undefined || serviceData !== null) {
331
+ if (serviceData !== undefined && serviceData !== null) {
332
332
  await this.BLEparseStatus()
333
333
  await this.updateHomeKitCharacteristics()
334
334
  } else {
@@ -354,7 +354,7 @@ export class StripLight extends deviceBase {
354
354
  this.platform.bleEventHandler[this.device.bleMac] = async (context: stripLightServiceData) => {
355
355
  try {
356
356
  this.serviceData = context
357
- if (context !== undefined || context !== null) {
357
+ if (context !== undefined && context !== null) {
358
358
  this.debugLog(`received BLE: ${JSON.stringify(context)}`)
359
359
  await this.BLEparseStatus()
360
360
  await this.updateHomeKitCharacteristics()
@@ -399,7 +399,7 @@ export class StripLight extends deviceBase {
399
399
  this.platform.webhookEventHandler[this.device.deviceId] = async (context: stripLightWebhookContext) => {
400
400
  try {
401
401
  this.webhookContext = context
402
- if (context !== undefined || context !== null) {
402
+ if (context !== undefined && context !== null) {
403
403
  this.debugLog(`received Webhook: ${JSON.stringify(context)}`)
404
404
  await this.parseStatusWebhook()
405
405
  await this.updateHomeKitCharacteristics()
@@ -485,13 +485,18 @@ export class StripLight extends deviceBase {
485
485
  .then(async (device_list: SwitchbotDevice[]) => {
486
486
  const deviceList = device_list as WoStrip[]
487
487
  this.infoLog(`On: ${this.LightBulb.On}`)
488
+ this.debugLog(`device_list: ${JSON.stringify(device_list)}`)
488
489
  return await this.retryBLE({
489
490
  max: this.maxRetryBLE(),
490
491
  fn: async () => {
491
- if (this.LightBulb.On) {
492
- return await deviceList[0].turnOn()
492
+ if (deviceList.length > 0) {
493
+ if (this.LightBulb.On) {
494
+ return await deviceList[0].turnOn()
495
+ } else {
496
+ return await deviceList[0].turnOff()
497
+ }
493
498
  } else {
494
- return await deviceList[0].turnOff()
499
+ throw new Error('No devices found during discovery.')
495
500
  }
496
501
  },
497
502
  })
@@ -529,8 +534,19 @@ export class StripLight extends deviceBase {
529
534
  switchBotBLE
530
535
  .discover({ model: this.device.bleModel, id: this.device.bleMac })
531
536
  .then(async (device_list: SwitchbotDevice[]) => {
537
+ const deviceList = device_list as WoStrip[]
532
538
  this.infoLog(`Brightness: ${this.LightBulb.Brightness}`)
533
- return await (device_list[0] as WoStrip).setBrightness(Number(this.LightBulb.Brightness))
539
+ this.debugLog(`device_list: ${JSON.stringify(device_list)}`)
540
+ return await this.retryBLE({
541
+ max: this.maxRetryBLE(),
542
+ fn: async () => {
543
+ if (deviceList.length > 0) {
544
+ return await deviceList[0].setBrightness(Number(this.LightBulb.Brightness))
545
+ } else {
546
+ throw new Error('No devices found during discovery.')
547
+ }
548
+ },
549
+ })
534
550
  })
535
551
  .then(async () => {
536
552
  this.successLog(`Brightness: ${this.LightBulb.Brightness} sent over SwitchBot BLE, sent successfully`)
@@ -568,8 +584,19 @@ export class StripLight extends deviceBase {
568
584
  switchBotBLE
569
585
  .discover({ model: this.device.bleModel, id: this.device.bleMac })
570
586
  .then(async (device_list: SwitchbotDevice[]) => {
587
+ const deviceList = device_list as WoStrip[]
571
588
  this.infoLog(`RGB: ${(this.LightBulb.Brightness, red, green, blue)}`)
572
- return await (device_list[0] as WoStrip).setRGB(Number(this.LightBulb.Brightness), red, green, blue)
589
+ this.debugLog(`device_list: ${JSON.stringify(device_list)}`)
590
+ return await this.retryBLE({
591
+ max: this.maxRetryBLE(),
592
+ fn: async () => {
593
+ if (deviceList.length > 0) {
594
+ return await deviceList[0].setRGB(Number(this.LightBulb.Brightness), red, green, blue)
595
+ } else {
596
+ throw new Error('No devices found during discovery.')
597
+ }
598
+ },
599
+ })
573
600
  })
574
601
  .then(async () => {
575
602
  this.successLog(`RGB: ${(this.LightBulb.Brightness, red, green, blue)} sent over SwitchBot BLE, sent successfully`)