@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
@@ -332,7 +332,7 @@ export class Lock extends deviceBase {
332
332
  if ((serviceData.model === SwitchBotBLEModel.Lock || SwitchBotBLEModel.LockPro)
333
333
  && (serviceData.modelName === SwitchBotBLEModelName.Lock || SwitchBotBLEModelName.LockPro)) {
334
334
  this.serviceData = serviceData
335
- if (serviceData !== undefined || serviceData !== null) {
335
+ if (serviceData !== undefined && serviceData !== null) {
336
336
  await this.BLEparseStatus()
337
337
  await this.updateHomeKitCharacteristics()
338
338
  } else {
@@ -358,7 +358,7 @@ export class Lock extends deviceBase {
358
358
  this.platform.bleEventHandler[this.device.bleMac] = async (context: lockServiceData | lockProServiceData) => {
359
359
  try {
360
360
  this.serviceData = context
361
- if (context !== undefined || context !== null) {
361
+ if (context !== undefined && context !== null) {
362
362
  this.debugLog(`received BLE: ${JSON.stringify(context)}`)
363
363
  await this.BLEparseStatus()
364
364
  await this.updateHomeKitCharacteristics()
@@ -403,7 +403,7 @@ export class Lock extends deviceBase {
403
403
  this.platform.webhookEventHandler[this.device.deviceId] = async (context: lockWebhookContext | lockProWebhookContext) => {
404
404
  try {
405
405
  this.webhookContext = context
406
- if (context !== undefined || context !== null) {
406
+ if (context !== undefined && context !== null) {
407
407
  this.debugLog(`received Webhook: ${JSON.stringify(context)}`)
408
408
  await this.parseStatusWebhook()
409
409
  await this.updateHomeKitCharacteristics()
@@ -455,13 +455,20 @@ export class Lock extends deviceBase {
455
455
  switchBotBLE
456
456
  .discover({ model: this.device.bleModel, id: this.device.bleMac })
457
457
  .then(async (device_list: SwitchbotDevice[]) => {
458
+ const deviceList = device_list as WoSmartLock[]
459
+ this.infoLog(`LockTargetState: ${this.LockMechanism.LockTargetState}`)
460
+ this.warnLog(`device_list: ${JSON.stringify(device_list)}`)
458
461
  return await this.retryBLE({
459
462
  max: this.maxRetryBLE(),
460
463
  fn: async () => {
461
- if (this.LockMechanism.LockTargetState === this.hap.Characteristic.LockTargetState.SECURED) {
462
- return await (device_list[0] as WoSmartLock).lock()
464
+ if (deviceList && Array.isArray(deviceList) && deviceList.length > 0) {
465
+ if (this.LockMechanism.LockTargetState === this.hap.Characteristic.LockTargetState.SECURED) {
466
+ return await deviceList[0].lock()
467
+ } else {
468
+ return await deviceList[0].unlock()
469
+ }
463
470
  } else {
464
- return await (device_list[0] as WoSmartLock).unlock()
471
+ throw new Error('No devices found during discovery.')
465
472
  }
466
473
  },
467
474
  })
@@ -209,8 +209,9 @@ export class Meter extends deviceBase {
209
209
 
210
210
  // CurrentTemperature
211
211
  if (!(this.device as meterConfig).hide_temperature && this.TemperatureSensor?.Service) {
212
- this.TemperatureSensor.CurrentTemperature = this.deviceStatus.temperature
213
- this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`)
212
+ // OpenAPI returns Celsius; convert if user configured a different unit
213
+ this.TemperatureSensor.CurrentTemperature = convertUnits(this.deviceStatus.temperature, 'CELSIUS', (this.device as meterConfig).convertUnitTo)
214
+ this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}`)
214
215
  }
215
216
 
216
217
  // BatteryLevel
@@ -287,7 +288,7 @@ export class Meter extends deviceBase {
287
288
  // Update HomeKit
288
289
  if (serviceData.model === SwitchBotBLEModel.Meter && serviceData.modelName === SwitchBotBLEModelName.Meter) {
289
290
  this.serviceData = serviceData
290
- if (serviceData !== undefined || serviceData !== null) {
291
+ if (serviceData !== undefined && serviceData !== null) {
291
292
  await this.BLEparseStatus()
292
293
  await this.updateHomeKitCharacteristics()
293
294
  } else {
@@ -313,7 +314,7 @@ export class Meter extends deviceBase {
313
314
  this.platform.bleEventHandler[this.device.bleMac] = async (context: meterServiceData) => {
314
315
  try {
315
316
  this.serviceData = context
316
- if (context !== undefined || context !== null) {
317
+ if (context !== undefined && context !== null) {
317
318
  this.debugLog(`received BLE: ${JSON.stringify(context)}`)
318
319
  await this.BLEparseStatus()
319
320
  await this.updateHomeKitCharacteristics()
@@ -358,7 +359,7 @@ export class Meter extends deviceBase {
358
359
  this.platform.webhookEventHandler[this.device.deviceId] = async (context: meterWebhookContext) => {
359
360
  try {
360
361
  this.webhookContext = context
361
- if (context !== undefined || context !== null) {
362
+ if (context !== undefined && context !== null) {
362
363
  this.debugLog(`received Webhook: ${JSON.stringify(context)}`)
363
364
  await this.parseStatusWebhook()
364
365
  await this.updateHomeKitCharacteristics()
@@ -213,8 +213,9 @@ export class MeterPlus extends deviceBase {
213
213
 
214
214
  // CurrentTemperature
215
215
  if (!(this.device as meterConfig).hide_temperature && this.TemperatureSensor?.Service) {
216
- this.TemperatureSensor.CurrentTemperature = this.deviceStatus.temperature
217
- this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`)
216
+ // OpenAPI returns Celsius; convert if user configured a different unit
217
+ this.TemperatureSensor.CurrentTemperature = convertUnits(this.deviceStatus.temperature, 'CELSIUS', (this.device as meterConfig).convertUnitTo)
218
+ this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}`)
218
219
  }
219
220
 
220
221
  // BatteryLevel
@@ -291,7 +292,7 @@ export class MeterPlus extends deviceBase {
291
292
  // Update HomeKit
292
293
  if (serviceData.model === SwitchBotBLEModel.MeterPlus && serviceData.modelName === SwitchBotBLEModelName.MeterPlus) {
293
294
  this.serviceData = serviceData
294
- if (serviceData !== undefined || serviceData !== null) {
295
+ if (serviceData !== undefined && serviceData !== null) {
295
296
  await this.BLEparseStatus()
296
297
  await this.updateHomeKitCharacteristics()
297
298
  } else {
@@ -317,7 +318,7 @@ export class MeterPlus extends deviceBase {
317
318
  this.platform.bleEventHandler[this.device.bleMac] = async (context: meterPlusServiceData) => {
318
319
  try {
319
320
  this.serviceData = context
320
- if (context !== undefined || context !== null) {
321
+ if (context !== undefined && context !== null) {
321
322
  this.debugLog(`received BLE: ${JSON.stringify(context)}`)
322
323
  await this.BLEparseStatus()
323
324
  await this.updateHomeKitCharacteristics()
@@ -362,7 +363,7 @@ export class MeterPlus extends deviceBase {
362
363
  this.platform.webhookEventHandler[this.device.deviceId] = async (context: meterPlusWebhookContext) => {
363
364
  try {
364
365
  this.webhookContext = context
365
- if (context !== undefined || context !== null) {
366
+ if (context !== undefined && context !== null) {
366
367
  this.debugLog(`received Webhook: ${JSON.stringify(context)}`)
367
368
  await this.parseStatusWebhook()
368
369
  await this.updateHomeKitCharacteristics()
@@ -263,8 +263,9 @@ export class MeterPro extends deviceBase {
263
263
 
264
264
  // CurrentTemperature
265
265
  if (!(this.device as meterProConfig).hide_temperature && this.TemperatureSensor?.Service) {
266
- this.TemperatureSensor.CurrentTemperature = this.deviceStatus.temperature
267
- this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`)
266
+ // OpenAPI returns Celsius; convert if user configured a different unit
267
+ this.TemperatureSensor.CurrentTemperature = convertUnits(this.deviceStatus.temperature as number, 'CELSIUS', (this.device as meterProConfig).convertUnitTo)
268
+ this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}`)
268
269
  }
269
270
 
270
271
  // Carbon Dioxide Sensor
@@ -367,7 +368,7 @@ export class MeterPro extends deviceBase {
367
368
  if ((serviceData.model === SwitchBotBLEModel.MeterPro && serviceData.modelName === SwitchBotBLEModelName.MeterPro)
368
369
  || (serviceData.model === SwitchBotBLEModel.MeterProCO2 && serviceData.modelName === SwitchBotBLEModelName.MeterProCO2)) {
369
370
  this.serviceData = serviceData
370
- if (serviceData !== undefined || serviceData !== null) {
371
+ if (serviceData !== undefined && serviceData !== null) {
371
372
  await this.BLEparseStatus()
372
373
  await this.updateHomeKitCharacteristics()
373
374
  } else {
@@ -393,7 +394,7 @@ export class MeterPro extends deviceBase {
393
394
  this.platform.bleEventHandler[this.device.bleMac] = async (context: meterProServiceData) => {
394
395
  try {
395
396
  this.serviceData = context
396
- if (context !== undefined || context !== null) {
397
+ if (context !== undefined && context !== null) {
397
398
  this.debugLog(`received BLE: ${JSON.stringify(context)}`)
398
399
  await this.BLEparseStatus()
399
400
  await this.updateHomeKitCharacteristics()
@@ -428,7 +429,7 @@ export class MeterPro extends deviceBase {
428
429
  }
429
430
  } catch (e: any) {
430
431
  await this.apiError(e)
431
- this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`)
432
+ this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error: ${e.message ?? e}`)
432
433
  }
433
434
  }
434
435
 
@@ -438,7 +439,7 @@ export class MeterPro extends deviceBase {
438
439
  this.platform.webhookEventHandler[this.device.deviceId] = async (context: meterProWebhookContext) => {
439
440
  try {
440
441
  this.webhookContext = context
441
- if (context !== undefined || context !== null) {
442
+ if (context !== undefined && context !== null) {
442
443
  this.debugLog(`received Webhook: ${JSON.stringify(context)}`)
443
444
  await this.parseStatusWebhook()
444
445
  await this.updateHomeKitCharacteristics()
@@ -262,7 +262,7 @@ export class Motion extends deviceBase {
262
262
  // Update HomeKit
263
263
  if (serviceData.model === SwitchBotBLEModel.MotionSensor && serviceData.modelName === SwitchBotBLEModelName.MotionSensor) {
264
264
  this.serviceData = serviceData
265
- if (serviceData !== undefined || serviceData !== null) {
265
+ if (serviceData !== undefined && serviceData !== null) {
266
266
  await this.BLEparseStatus()
267
267
  await this.updateHomeKitCharacteristics()
268
268
  } else {
@@ -288,7 +288,7 @@ export class Motion extends deviceBase {
288
288
  this.platform.bleEventHandler[this.device.bleMac] = async (context: motionSensorServiceData) => {
289
289
  try {
290
290
  this.serviceData = context
291
- if (context !== undefined || context !== null) {
291
+ if (context !== undefined && context !== null) {
292
292
  this.debugLog(`received BLE: ${JSON.stringify(context)}`)
293
293
  await this.BLEparseStatus()
294
294
  await this.updateHomeKitCharacteristics()
@@ -333,7 +333,7 @@ export class Motion extends deviceBase {
333
333
  this.platform.webhookEventHandler[this.device.deviceId] = async (context: motionSensorWebhookContext) => {
334
334
  try {
335
335
  this.webhookContext = context
336
- if (context !== undefined || context !== null) {
336
+ if (context !== undefined && context !== null) {
337
337
  this.debugLog(`received Webhook: ${JSON.stringify(context)}`)
338
338
  await this.parseStatusWebhook()
339
339
  await this.updateHomeKitCharacteristics()
@@ -187,7 +187,7 @@ export class Plug extends deviceBase {
187
187
  if ((serviceData.model === SwitchBotBLEModel.PlugMiniUS || SwitchBotBLEModel.PlugMiniJP)
188
188
  && serviceData.modelName === (SwitchBotBLEModelName.PlugMini || SwitchBotBLEModelName.PlugMini)) {
189
189
  this.serviceData = serviceData
190
- if (serviceData !== undefined || serviceData !== null) {
190
+ if (serviceData !== undefined && serviceData !== null) {
191
191
  await this.BLEparseStatus()
192
192
  await this.updateHomeKitCharacteristics()
193
193
  } else {
@@ -213,7 +213,7 @@ export class Plug extends deviceBase {
213
213
  this.platform.bleEventHandler[this.device.bleMac] = async (context: plugMiniUSServiceData | plugMiniJPServiceData) => {
214
214
  try {
215
215
  this.serviceData = context
216
- if (context !== undefined || context !== null) {
216
+ if (context !== undefined && context !== null) {
217
217
  this.debugLog(`received BLE: ${JSON.stringify(context)}`)
218
218
  await this.BLEparseStatus()
219
219
  await this.updateHomeKitCharacteristics()
@@ -258,7 +258,7 @@ export class Plug extends deviceBase {
258
258
  this.platform.webhookEventHandler[this.device.deviceId] = async (context: plugWebhookContext | plugMiniUSWebhookContext | plugMiniJPWebhookContext) => {
259
259
  try {
260
260
  this.webhookContext = context
261
- if (context !== undefined || context !== null) {
261
+ if (context !== undefined && context !== null) {
262
262
  this.debugLog(`received Webhook: ${JSON.stringify(context)}`)
263
263
  await this.parseStatusWebhook()
264
264
  await this.updateHomeKitCharacteristics()
@@ -320,10 +320,14 @@ export class Plug extends deviceBase {
320
320
  return await this.retryBLE({
321
321
  max: this.maxRetryBLE(),
322
322
  fn: async () => {
323
- if (this.Outlet.On) {
324
- return await deviceList[0].turnOn()
323
+ if (deviceList.length > 0) {
324
+ if (this.Outlet.On) {
325
+ return await deviceList[0].turnOn()
326
+ } else {
327
+ return await deviceList[0].turnOff()
328
+ }
325
329
  } else {
326
- return await deviceList[0].turnOff()
330
+ throw new Error('No devices found during discovery.')
327
331
  }
328
332
  },
329
333
  })
@@ -315,7 +315,7 @@ export class RelaySwitch extends deviceBase {
315
315
  // Update HomeKit
316
316
  if ((serviceData.model === SwitchBotBLEModel.RelaySwitch1PM && serviceData.modelName === SwitchBotBLEModelName.RelaySwitch1PM) ?? (serviceData.model === SwitchBotBLEModel.RelaySwitch1 && serviceData.modelName === SwitchBotBLEModelName.RelaySwitch1)) {
317
317
  this.serviceData = serviceData
318
- if (serviceData !== undefined || serviceData !== null) {
318
+ if (serviceData !== undefined && serviceData !== null) {
319
319
  await this.BLEparseStatus()
320
320
  await this.updateHomeKitCharacteristics()
321
321
  } else {
@@ -341,7 +341,7 @@ export class RelaySwitch extends deviceBase {
341
341
  this.platform.bleEventHandler[this.device.bleMac] = async (context: relaySwitch1ServiceData | relaySwitch1PMServiceData) => {
342
342
  try {
343
343
  this.serviceData = context
344
- if (context !== undefined || context !== null) {
344
+ if (context !== undefined && context !== null) {
345
345
  this.debugLog(`received BLE: ${JSON.stringify(context)}`)
346
346
  await this.BLEparseStatus()
347
347
  await this.updateHomeKitCharacteristics()
@@ -386,7 +386,7 @@ export class RelaySwitch extends deviceBase {
386
386
  this.platform.webhookEventHandler[this.device.deviceId] = async (context: relaySwitch1Context | relaySwitch1PMContext) => {
387
387
  try {
388
388
  this.webhookContext = context
389
- if (context !== undefined || context !== null) {
389
+ if (context !== undefined && context !== null) {
390
390
  this.debugLog(`received Webhook: ${JSON.stringify(context)}`)
391
391
  await this.parseStatusWebhook()
392
392
  await this.updateHomeKitCharacteristics()
@@ -275,7 +275,7 @@ export class RobotVacuumCleaner extends deviceBase {
275
275
  // Update HomeKit
276
276
  if (serviceData.model === SwitchBotBLEModel.Unknown && serviceData.modelName === SwitchBotBLEModelName.Unknown) {
277
277
  this.serviceData = serviceData
278
- if (serviceData !== undefined || serviceData !== null) {
278
+ if (serviceData !== undefined && serviceData !== null) {
279
279
  await this.BLEparseStatus()
280
280
  await this.updateHomeKitCharacteristics()
281
281
  } else {
@@ -301,7 +301,7 @@ export class RobotVacuumCleaner extends deviceBase {
301
301
  this.platform.bleEventHandler[this.device.bleMac] = async (context: robotVacuumCleanerServiceData) => {
302
302
  try {
303
303
  this.serviceData = context
304
- if (context !== undefined || context !== null) {
304
+ if (context !== undefined && context !== null) {
305
305
  this.debugLog(`received BLE: ${JSON.stringify(context)}`)
306
306
  await this.BLEparseStatus()
307
307
  await this.updateHomeKitCharacteristics()
@@ -346,7 +346,7 @@ export class RobotVacuumCleaner extends deviceBase {
346
346
  this.platform.webhookEventHandler[this.device.deviceId] = async (context: robotVacuumCleanerS1WebhookContext | robotVacuumCleanerS1PlusWebhookContext | floorCleaningRobotS10WebhookContext) => {
347
347
  try {
348
348
  this.webhookContext = context
349
- if (context !== undefined || context !== null) {
349
+ if (context !== undefined && context !== null) {
350
350
  this.debugLog(`received Webhook: ${JSON.stringify(context)}`)
351
351
  await this.parseStatusWebhook()
352
352
  await this.updateHomeKitCharacteristics()
@@ -400,14 +400,20 @@ export class RobotVacuumCleaner extends deviceBase {
400
400
  switchBotBLE
401
401
  .discover({ model: this.device.bleModel, id: this.device.bleMac })
402
402
  .then(async (device_list: SwitchbotDevice[]) => {
403
+ const deviceList = device_list as any[]
403
404
  this.infoLog(`On: ${this.LightBulb.On}`)
405
+ this.warnLog(`device_list: ${JSON.stringify(device_list)}`)
404
406
  return await this.retryBLE({
405
407
  max: this.maxRetryBLE(),
406
408
  fn: async () => {
407
- if (this.LightBulb.On) {
408
- return await (device_list[0] as any).turnOn()
409
+ if (deviceList && Array.isArray(deviceList) && deviceList.length > 0) {
410
+ if (this.LightBulb.On) {
411
+ return await (deviceList[0] as any).turnOn()
412
+ } else {
413
+ return await (deviceList[0] as any).turnOff()
414
+ }
409
415
  } else {
410
- return await (device_list[0] as any).turnOff()
416
+ throw new Error('No devices found during discovery.')
411
417
  }
412
418
  },
413
419
  })
@@ -269,7 +269,7 @@ export class WaterDetector extends deviceBase {
269
269
  // Update HomeKit
270
270
  if (serviceData.model === SwitchBotBLEModel.Leak && serviceData.modelName === SwitchBotBLEModelName.Leak) {
271
271
  this.serviceData = serviceData
272
- if (serviceData !== undefined || serviceData !== null) {
272
+ if (serviceData !== undefined && serviceData !== null) {
273
273
  await this.BLEparseStatus()
274
274
  await this.updateHomeKitCharacteristics()
275
275
  } else {
@@ -295,7 +295,7 @@ export class WaterDetector extends deviceBase {
295
295
  this.platform.bleEventHandler[this.device.bleMac] = async (context: waterLeakDetectorServiceData) => {
296
296
  try {
297
297
  this.serviceData = context
298
- if (context !== undefined || context !== null) {
298
+ if (context !== undefined && context !== null) {
299
299
  this.debugLog(`received BLE: ${JSON.stringify(context)}`)
300
300
  await this.BLEparseStatus()
301
301
  await this.updateHomeKitCharacteristics()
@@ -340,7 +340,7 @@ export class WaterDetector extends deviceBase {
340
340
  this.platform.webhookEventHandler[this.device.deviceId] = async (context: waterLeakDetectorWebhookContext) => {
341
341
  try {
342
342
  this.webhookContext = context
343
- if (context !== undefined || context !== null) {
343
+ if (context !== undefined && context !== null) {
344
344
  this.debugLog(`received Webhook: ${JSON.stringify(context)}`)
345
345
  await this.parseStatusWebhook()
346
346
  await this.updateHomeKitCharacteristics()
@@ -43,7 +43,7 @@
43
43
  </form>
44
44
  <table class="table w-100" id="deviceTable" style="display: none">
45
45
  <thead>
46
- <tr class="table-active">
46
+ <tr>
47
47
  <th scope="col" style="width: 40%">Device Name</th>
48
48
  <th scope="col" style="width: 60%" id="displayName"></th>
49
49
  </tr>
@@ -69,6 +69,18 @@
69
69
  <th scope="row">Connection Type</th>
70
70
  <td id="connectionType"></td>
71
71
  </tr>
72
+ <tr>
73
+ <th scope="row">Refresh Rate</th>
74
+ <td id="refreshRate"></td>
75
+ </tr>
76
+ <tr>
77
+ <th scope="row">Update Rate</th>
78
+ <td id="updateRate"></td>
79
+ </tr>
80
+ <tr>
81
+ <th scope="row">Push Rate</th>
82
+ <td id="pushRate"></td>
83
+ </tr>
72
84
  </tbody>
73
85
  </table>
74
86
  <p class="text-center">External accessories will not be displayed here.</p>
@@ -243,6 +243,7 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
243
243
  dailyLimit: dailyApiLimit,
244
244
  reserveForCommands: dailyApiReserveForCommands,
245
245
  pausePollingAtReserve: webhookOnlyOnReserve,
246
+ resetAtLocalMidnight: this.config.options?.dailyApiResetAtLocalMidnight ?? false,
246
247
  })
247
248
  this.apiTracker.startHourlyLogging()
248
249
  } catch (e: any) {
@@ -506,8 +507,12 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
506
507
  const { response, statusCode } = await this.switchBotAPI.getDevices()
507
508
  this.debugLog(`response: ${JSON.stringify(response)}`)
508
509
  if (this.isSuccessfulResponse(statusCode)) {
509
- await this.handleDevices(Array.isArray(response.body.deviceList) ? response.body.deviceList : [])
510
- await this.handleIRDevices(Array.isArray(response.body.infraredRemoteList) ? response.body.infraredRemoteList : [])
510
+ const deviceList = Array.isArray(response.body.deviceList) ? response.body.deviceList : []
511
+ const irDeviceList = Array.isArray(response.body.infraredRemoteList) ? response.body.infraredRemoteList : []
512
+ await this.handleDevices(deviceList)
513
+ await this.handleIRDevices(irDeviceList)
514
+ // Diagnostic: warn users if their device count + refresh rate may exceed daily limits
515
+ this.validateApiUsageConfig(deviceList.length, irDeviceList.length)
511
516
  break
512
517
  } else {
513
518
  // Check if rate limit exceeded (429)
@@ -601,6 +606,7 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
601
606
  'Plug Mini (JP)': Plug,
602
607
  'Smart Lock': Lock,
603
608
  'Smart Lock Pro': Lock,
609
+ 'Smart Lock Ultra': Lock,
604
610
  'Color Bulb': ColorBulb,
605
611
  'K10+': RobotVacuumCleaner,
606
612
  'K10+ Pro': RobotVacuumCleaner,
@@ -723,12 +729,29 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
723
729
  const allowConfigOnly = Boolean(this.config.options?.allowConfigOnlyDevices)
724
730
  const devices = mergeByDeviceId(this.config.options.devices ?? [], devicesWithTemplates ?? [], allowConfigOnly)
725
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
+
726
742
  this.debugLog(`SwitchBot Devices: ${JSON.stringify(devices)}`)
727
743
 
728
744
  for (const device of devices) {
729
745
  if (device.configDeviceName) {
730
746
  device.deviceName = device.configDeviceName
731
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
+ }
732
755
  await this.createDevice(device)
733
756
  }
734
757
  }
@@ -768,11 +791,28 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
768
791
  const allowConfigOnly = Boolean(this.config.options?.allowConfigOnlyDevices)
769
792
  const devices = mergeByDeviceId(this.config.options.irdevices ?? [], devicesWithTemplates ?? [], allowConfigOnly)
770
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
+
771
804
  this.debugLog(`IR Devices: ${JSON.stringify(devices)}`)
772
805
  for (const device of devices) {
773
806
  if (device.configDeviceName) {
774
807
  device.deviceName = device.configDeviceName
775
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
+ }
776
816
  await this.createIRDevice(device)
777
817
  }
778
818
  }
@@ -814,6 +854,7 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
814
854
  'Plug Mini (JP)': this.createPlug.bind(this),
815
855
  'Smart Lock': this.createLock.bind(this),
816
856
  'Smart Lock Pro': this.createLock.bind(this),
857
+ 'Smart Lock Ultra': this.createLock.bind(this),
817
858
  'Color Bulb': this.createColorBulb.bind(this),
818
859
  'K10+': this.createRobotVacuumCleaner.bind(this),
819
860
  'K10+ Pro': this.createRobotVacuumCleaner.bind(this),
@@ -1809,7 +1850,7 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
1809
1850
  existingAccessory.context.device = device
1810
1851
  existingAccessory.context.deviceId = device.deviceId
1811
1852
  existingAccessory.context.deviceType = device.deviceType
1812
- 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
1813
1854
  existingAccessory.displayName = device.configDeviceName
1814
1855
  ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
1815
1856
  : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName)
@@ -1835,7 +1876,7 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
1835
1876
  accessory.context.device = device
1836
1877
  accessory.context.deviceId = device.deviceId
1837
1878
  accessory.context.deviceType = device.deviceType
1838
- 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
1839
1880
  accessory.displayName = device.configDeviceName
1840
1881
  ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
1841
1882
  : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName)
@@ -2912,23 +2953,26 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
2912
2953
  }
2913
2954
 
2914
2955
  async retryRequest(device: (device & devicesConfig) | (irdevice & irDevicesConfig), deviceMaxRetries: number, deviceDelayBetweenRetries: number): Promise<{ response: any, statusCode: deviceStatusRequest['statusCode'] }> {
2956
+ // Check API budget BEFORE attempting any retries - don't waste cycles on blocked requests
2957
+ if (!this.apiTracker?.trySpend('poll')) {
2958
+ // Don't log on every blocked request - the ApiRequestTracker handles periodic warnings
2959
+ return {
2960
+ response: {
2961
+ deviceId: '',
2962
+ deviceType: '',
2963
+ hubDeviceId: '',
2964
+ version: 0,
2965
+ deviceName: '',
2966
+ },
2967
+ statusCode: 429,
2968
+ }
2969
+ }
2970
+
2915
2971
  let retryCount = 0
2916
2972
  const maxRetries = deviceMaxRetries
2917
2973
  const delayBetweenRetries = deviceDelayBetweenRetries
2918
2974
  while (retryCount < maxRetries) {
2919
2975
  try {
2920
- if (!this.apiTracker?.trySpend('poll')) {
2921
- return {
2922
- response: {
2923
- deviceId: '',
2924
- deviceType: '',
2925
- hubDeviceId: '',
2926
- version: 0,
2927
- deviceName: '',
2928
- },
2929
- statusCode: 429,
2930
- }
2931
- }
2932
2976
  const { response, statusCode } = await this.switchBotAPI.getDeviceStatus(device.deviceId, this.config.credentials?.token, this.config.credentials?.secret)
2933
2977
  this.debugLog(`response: ${JSON.stringify(response)}`)
2934
2978
  return { response, statusCode }
@@ -3055,6 +3099,56 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
3055
3099
  this.version = version
3056
3100
  }
3057
3101
 
3102
+ /**
3103
+ * Validate that the user's configuration won't exceed API limits
3104
+ * Warn if device count × polling frequency will hit daily limits
3105
+ */
3106
+ private validateApiUsageConfig(deviceCount: number, irDeviceCount: number): void {
3107
+ try {
3108
+ const totalDevices = deviceCount + irDeviceCount
3109
+ if (totalDevices === 0) {
3110
+ return
3111
+ }
3112
+
3113
+ const refreshRate = this.platformRefreshRate ?? 300 // seconds
3114
+ const dailyLimit = this.config.options?.dailyApiLimit ?? 10000
3115
+ const reserveForCommands = this.config.options?.dailyApiReserveForCommands ?? 1000
3116
+
3117
+ // Calculate polls per day (86400 seconds in a day)
3118
+ const pollsPerDevicePerDay = Math.floor(86400 / refreshRate)
3119
+ const totalPollsPerDay = pollsPerDevicePerDay * totalDevices
3120
+
3121
+ // Add discovery calls (typically 1-2 per day)
3122
+ const estimatedDiscoveryCalls = 2
3123
+ const totalEstimatedCalls = totalPollsPerDay + estimatedDiscoveryCalls
3124
+
3125
+ const usableLimit = dailyLimit - reserveForCommands
3126
+ const percentOfLimit = Math.round((totalEstimatedCalls / usableLimit) * 100)
3127
+
3128
+ this.debugLog(`[API Usage Diagnostic] ${totalDevices} devices × ${pollsPerDevicePerDay} polls/day = ${totalPollsPerDay} estimated daily polls`)
3129
+ this.debugLog(`[API Usage Diagnostic] With ${reserveForCommands} reserved for commands, usable limit is ${usableLimit}`)
3130
+
3131
+ if (totalEstimatedCalls > dailyLimit) {
3132
+ this.errorLog(`⚠️ API LIMIT WARNING: Your configuration will exceed the daily API limit!`)
3133
+ this.errorLog(` Devices: ${totalDevices} | Refresh rate: ${refreshRate}s | Estimated daily polls: ${totalEstimatedCalls}`)
3134
+ this.errorLog(` Daily limit: ${dailyLimit} | You will use ${percentOfLimit}% of available budget`)
3135
+ this.errorLog(` SOLUTION: Increase refreshRate to ${Math.ceil((totalDevices * 86400) / usableLimit)} seconds or higher`)
3136
+ this.errorLog(` OR: Enable webhooks and set 'webhookOnlyOnReserve: true' to reduce polling`)
3137
+ } else if (totalEstimatedCalls > usableLimit) {
3138
+ this.warnLog(`⚠️ API USAGE WARNING: Configuration may exceed usable daily API budget`)
3139
+ this.warnLog(` Devices: ${totalDevices} | Refresh rate: ${refreshRate}s | Estimated daily polls: ${totalEstimatedCalls}`)
3140
+ this.warnLog(` Usable limit (after reserve): ${usableLimit} | You will use ${percentOfLimit}% of budget`)
3141
+ this.warnLog(` Polling may pause when approaching limit. Consider increasing refreshRate to ${Math.ceil((totalDevices * 86400) / usableLimit)}s`)
3142
+ } else if (percentOfLimit > 75) {
3143
+ this.infoLog(`[API Usage] Using ${percentOfLimit}% of daily budget (${totalEstimatedCalls}/${usableLimit} calls). Monitor usage if adding more devices.`)
3144
+ } else {
3145
+ this.debugLog(`[API Usage] Configuration looks good: ${percentOfLimit}% of daily budget (${totalEstimatedCalls}/${usableLimit} calls)`)
3146
+ }
3147
+ } catch (e: any) {
3148
+ this.debugErrorLog(`Failed to validate API usage config: ${e.message ?? e}`)
3149
+ }
3150
+ }
3151
+
3058
3152
  /**
3059
3153
  * Validate and clean a string value for a Name Characteristic.
3060
3154
  * @param displayName - The display name of the accessory.