@switchbot/homebridge-switchbot 3.7.1-beta.2 → 3.7.1-beta.21

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 (199) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +14 -0
  3. package/assets/security-privacy-bluetooth.png +0 -0
  4. package/config.schema.json +74 -2
  5. package/dist/device/blindtilt.d.ts +21 -9
  6. package/dist/device/blindtilt.d.ts.map +1 -1
  7. package/dist/device/blindtilt.js +167 -96
  8. package/dist/device/blindtilt.js.map +1 -1
  9. package/dist/device/bot-old.d.ts +132 -0
  10. package/dist/device/bot-old.d.ts.map +1 -0
  11. package/dist/device/bot-old.js +1482 -0
  12. package/dist/device/bot-old.js.map +1 -0
  13. package/dist/device/bot.d.ts +10 -9
  14. package/dist/device/bot.d.ts.map +1 -1
  15. package/dist/device/bot.js +186 -187
  16. package/dist/device/bot.js.map +1 -1
  17. package/dist/device/ceilinglight.d.ts +14 -13
  18. package/dist/device/ceilinglight.d.ts.map +1 -1
  19. package/dist/device/ceilinglight.js +87 -97
  20. package/dist/device/ceilinglight.js.map +1 -1
  21. package/dist/device/colorbulb.d.ts +13 -12
  22. package/dist/device/colorbulb.d.ts.map +1 -1
  23. package/dist/device/colorbulb.js +92 -105
  24. package/dist/device/colorbulb.js.map +1 -1
  25. package/dist/device/contact.d.ts +6 -5
  26. package/dist/device/contact.d.ts.map +1 -1
  27. package/dist/device/contact.js +71 -68
  28. package/dist/device/contact.js.map +1 -1
  29. package/dist/device/curtain.d.ts +17 -5
  30. package/dist/device/curtain.d.ts.map +1 -1
  31. package/dist/device/curtain.js +184 -118
  32. package/dist/device/curtain.js.map +1 -1
  33. package/dist/device/device.d.ts +18 -18
  34. package/dist/device/device.d.ts.map +1 -1
  35. package/dist/device/device.js +33 -36
  36. package/dist/device/device.js.map +1 -1
  37. package/dist/device/fan.d.ts +9 -8
  38. package/dist/device/fan.d.ts.map +1 -1
  39. package/dist/device/fan.js +94 -101
  40. package/dist/device/fan.js.map +1 -1
  41. package/dist/device/hub.d.ts +6 -5
  42. package/dist/device/hub.d.ts.map +1 -1
  43. package/dist/device/hub.js +69 -65
  44. package/dist/device/hub.js.map +1 -1
  45. package/dist/device/humidifier.d.ts +6 -5
  46. package/dist/device/humidifier.d.ts.map +1 -1
  47. package/dist/device/humidifier.js +89 -104
  48. package/dist/device/humidifier.js.map +1 -1
  49. package/dist/device/iosensor.d.ts +6 -5
  50. package/dist/device/iosensor.d.ts.map +1 -1
  51. package/dist/device/iosensor.js +72 -66
  52. package/dist/device/iosensor.js.map +1 -1
  53. package/dist/device/lightstrip.d.ts +7 -6
  54. package/dist/device/lightstrip.d.ts.map +1 -1
  55. package/dist/device/lightstrip.js +85 -99
  56. package/dist/device/lightstrip.js.map +1 -1
  57. package/dist/device/lock.d.ts +10 -9
  58. package/dist/device/lock.d.ts.map +1 -1
  59. package/dist/device/lock.js +89 -79
  60. package/dist/device/lock.js.map +1 -1
  61. package/dist/device/meter.d.ts +6 -5
  62. package/dist/device/meter.d.ts.map +1 -1
  63. package/dist/device/meter.js +72 -65
  64. package/dist/device/meter.js.map +1 -1
  65. package/dist/device/meterplus.d.ts +6 -5
  66. package/dist/device/meterplus.d.ts.map +1 -1
  67. package/dist/device/meterplus.js +72 -64
  68. package/dist/device/meterplus.js.map +1 -1
  69. package/dist/device/motion.d.ts +6 -5
  70. package/dist/device/motion.d.ts.map +1 -1
  71. package/dist/device/motion.js +65 -58
  72. package/dist/device/motion.js.map +1 -1
  73. package/dist/device/plug.d.ts +13 -12
  74. package/dist/device/plug.d.ts.map +1 -1
  75. package/dist/device/plug.js +68 -52
  76. package/dist/device/plug.js.map +1 -1
  77. package/dist/device/robotvacuumcleaner.d.ts +12 -11
  78. package/dist/device/robotvacuumcleaner.d.ts.map +1 -1
  79. package/dist/device/robotvacuumcleaner.js +93 -82
  80. package/dist/device/robotvacuumcleaner.js.map +1 -1
  81. package/dist/device/waterdetector.d.ts +6 -5
  82. package/dist/device/waterdetector.d.ts.map +1 -1
  83. package/dist/device/waterdetector.js +73 -63
  84. package/dist/device/waterdetector.js.map +1 -1
  85. package/dist/homebridge-ui/server.js +2 -2
  86. package/dist/homebridge-ui/server.js.map +1 -1
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +0 -4
  89. package/dist/index.js.map +1 -1
  90. package/dist/irdevice/airconditioner.d.ts +8 -8
  91. package/dist/irdevice/airconditioner.d.ts.map +1 -1
  92. package/dist/irdevice/airconditioner.js +61 -105
  93. package/dist/irdevice/airconditioner.js.map +1 -1
  94. package/dist/irdevice/airpurifier.d.ts +10 -10
  95. package/dist/irdevice/airpurifier.d.ts.map +1 -1
  96. package/dist/irdevice/airpurifier.js +22 -42
  97. package/dist/irdevice/airpurifier.js.map +1 -1
  98. package/dist/irdevice/camera.d.ts +9 -9
  99. package/dist/irdevice/camera.d.ts.map +1 -1
  100. package/dist/irdevice/camera.js +15 -25
  101. package/dist/irdevice/camera.js.map +1 -1
  102. package/dist/irdevice/fan.d.ts +8 -8
  103. package/dist/irdevice/fan.d.ts.map +1 -1
  104. package/dist/irdevice/fan.js +21 -40
  105. package/dist/irdevice/fan.js.map +1 -1
  106. package/dist/irdevice/irdevice.d.ts +11 -11
  107. package/dist/irdevice/irdevice.d.ts.map +1 -1
  108. package/dist/irdevice/irdevice.js +14 -22
  109. package/dist/irdevice/irdevice.js.map +1 -1
  110. package/dist/irdevice/light.d.ts +9 -9
  111. package/dist/irdevice/light.d.ts.map +1 -1
  112. package/dist/irdevice/light.js +24 -47
  113. package/dist/irdevice/light.js.map +1 -1
  114. package/dist/irdevice/other.d.ts +9 -9
  115. package/dist/irdevice/other.d.ts.map +1 -1
  116. package/dist/irdevice/other.js +40 -83
  117. package/dist/irdevice/other.js.map +1 -1
  118. package/dist/irdevice/tv.d.ts +9 -9
  119. package/dist/irdevice/tv.d.ts.map +1 -1
  120. package/dist/irdevice/tv.js +29 -54
  121. package/dist/irdevice/tv.js.map +1 -1
  122. package/dist/irdevice/vacuumcleaner.d.ts +5 -5
  123. package/dist/irdevice/vacuumcleaner.d.ts.map +1 -1
  124. package/dist/irdevice/vacuumcleaner.js +11 -21
  125. package/dist/irdevice/vacuumcleaner.js.map +1 -1
  126. package/dist/irdevice/waterheater.d.ts +5 -5
  127. package/dist/irdevice/waterheater.d.ts.map +1 -1
  128. package/dist/irdevice/waterheater.js +11 -22
  129. package/dist/irdevice/waterheater.js.map +1 -1
  130. package/dist/platform.d.ts +24 -23
  131. package/dist/platform.d.ts.map +1 -1
  132. package/dist/platform.js +509 -723
  133. package/dist/platform.js.map +1 -1
  134. package/dist/settings.d.ts +5 -2
  135. package/dist/settings.d.ts.map +1 -1
  136. package/dist/settings.js.map +1 -1
  137. package/dist/types/bledevicestatus.d.ts +9 -9
  138. package/dist/types/bledevicestatus.d.ts.map +1 -1
  139. package/dist/types/devicelist.d.ts +6 -6
  140. package/dist/types/devicelist.d.ts.map +1 -1
  141. package/dist/types/deviceresponse.d.ts +4 -4
  142. package/dist/types/deviceresponse.d.ts.map +1 -1
  143. package/dist/types/devicestatus.d.ts +2 -2
  144. package/dist/types/devicestatus.d.ts.map +1 -1
  145. package/dist/types/devicewebhookstatus.d.ts +5 -5
  146. package/dist/types/devicewebhookstatus.d.ts.map +1 -1
  147. package/dist/types/irdevicelist.d.ts +4 -4
  148. package/dist/types/irdevicelist.d.ts.map +1 -1
  149. package/dist/types/pushbody.d.ts +2 -2
  150. package/dist/types/pushbody.d.ts.map +1 -1
  151. package/dist/utils.d.ts +4 -4
  152. package/dist/utils.d.ts.map +1 -1
  153. package/dist/utils.js +9 -13
  154. package/dist/utils.js.map +1 -1
  155. package/eslint.config.js +48 -93
  156. package/package.json +64 -57
  157. package/src/device/blindtilt.ts +537 -456
  158. package/src/device/bot-old.ts +1540 -0
  159. package/src/device/bot.ts +732 -725
  160. package/src/device/ceilinglight.ts +359 -368
  161. package/src/device/colorbulb.ts +441 -453
  162. package/src/device/contact.ts +229 -226
  163. package/src/device/curtain.ts +526 -444
  164. package/src/device/device.ts +206 -208
  165. package/src/device/fan.ts +336 -345
  166. package/src/device/hub.ts +213 -214
  167. package/src/device/humidifier.ts +326 -347
  168. package/src/device/iosensor.ts +211 -209
  169. package/src/device/lightstrip.ts +391 -404
  170. package/src/device/lock.ts +292 -282
  171. package/src/device/meter.ts +212 -210
  172. package/src/device/meterplus.ts +212 -209
  173. package/src/device/motion.ts +198 -190
  174. package/src/device/plug.ts +203 -182
  175. package/src/device/robotvacuumcleaner.ts +280 -269
  176. package/src/device/waterdetector.ts +198 -189
  177. package/src/homebridge-ui/server.ts +16 -15
  178. package/src/index.ts +5 -6
  179. package/src/irdevice/airconditioner.ts +277 -326
  180. package/src/irdevice/airpurifier.ts +124 -143
  181. package/src/irdevice/camera.ts +58 -64
  182. package/src/irdevice/fan.ts +106 -123
  183. package/src/irdevice/irdevice.ts +116 -119
  184. package/src/irdevice/light.ts +119 -143
  185. package/src/irdevice/other.ts +417 -461
  186. package/src/irdevice/tv.ts +155 -177
  187. package/src/irdevice/vacuumcleaner.ts +54 -60
  188. package/src/irdevice/waterheater.ts +56 -63
  189. package/src/platform.ts +1491 -1681
  190. package/src/settings.ts +172 -169
  191. package/src/types/bledevicestatus.ts +252 -256
  192. package/src/types/devicelist.ts +79 -79
  193. package/src/types/deviceresponse.ts +12 -13
  194. package/src/types/devicestatus.ts +124 -124
  195. package/src/types/devicewebhookstatus.ts +126 -131
  196. package/src/types/irdevicelist.ts +10 -10
  197. package/src/types/pushbody.ts +5 -5
  198. package/src/utils.ts +64 -65
  199. package/tsconfig.json +10 -9
@@ -0,0 +1,1540 @@
1
+ import type { API, CharacteristicValue, HAP, Logging, PlatformAccessory, Service } from 'homebridge'
2
+
3
+ import type { SwitchBotPlatform } from '../platform.js'
4
+ import type { devicesConfig, SwitchBotPlatformConfig } from '../settings.js'
5
+ import type { botServiceData as serviceData } from '../types/bledevicestatus.js'
6
+ import type { device } from '../types/devicelist.js'
7
+ import type { botStatus as deviceStatus } from '../types/devicestatus.js'
8
+
9
+ import { interval, Subject } from 'rxjs'
10
+ import { debounceTime, skipWhile, take, tap } from 'rxjs/operators'
11
+ import { request } from 'undici'
12
+
13
+ import { Devices } from '../settings.js'
14
+ import { sleep } from '../utils.js'
15
+
16
+ /**
17
+ * Platform Accessory
18
+ * An instance of this class is created for each accessory your platform registers
19
+ * Each accessory may expose multiple services of different service types.
20
+ */
21
+ export class Bot {
22
+ public readonly api: API
23
+ public readonly log: Logging
24
+ public readonly config!: SwitchBotPlatformConfig
25
+ protected readonly hap: HAP
26
+ // Services
27
+ fanService?: Service
28
+ doorService?: Service
29
+ lockService?: Service
30
+ faucetService?: Service
31
+ windowService?: Service
32
+ switchService?: Service
33
+ outletService?: Service
34
+ batteryService: Service
35
+ garageDoorService?: Service
36
+ windowCoveringService?: Service
37
+ statefulProgrammableSwitchService?: Service
38
+
39
+ // Characteristic Values
40
+ On!: CharacteristicValue
41
+ BatteryLevel!: CharacteristicValue
42
+ FirmwareRevision!: CharacteristicValue
43
+ StatusLowBattery!: CharacteristicValue
44
+
45
+ // OpenAPI Status
46
+ OpenAPI_On!: deviceStatus['power']
47
+ OpenAPI_BatteryLevel!: deviceStatus['battery']
48
+ OpenAPI_FirmwareRevision!: deviceStatus['version']
49
+
50
+ // BLE Status
51
+ BLE_On!: serviceData['state']
52
+ BLE_Mode!: serviceData['mode']
53
+ BLE_BatteryLevel!: serviceData['battery']
54
+
55
+ // BLE Others
56
+ BLE_IsConnected?: boolean
57
+
58
+ // Config
59
+ botMode!: string
60
+ allowPush?: boolean
61
+ doublePress!: number
62
+ scanDuration!: number
63
+ botDeviceType!: string
64
+ pushRatePress!: number
65
+ deviceLogging!: string
66
+ multiPressCount!: number
67
+ deviceRefreshRate!: number
68
+
69
+ // Updates
70
+ botUpdateInProgress!: boolean
71
+ doBotUpdate!: Subject<void>
72
+
73
+ // Connection
74
+ private readonly OpenAPI: boolean
75
+ private readonly BLE: boolean
76
+
77
+ constructor(
78
+ private readonly platform: SwitchBotPlatform,
79
+ private accessory: PlatformAccessory,
80
+ public device: device & devicesConfig,
81
+ ) {
82
+ this.api = this.platform.api
83
+ this.log = this.platform.log
84
+ this.config = this.platform.config
85
+ this.hap = this.api.hap
86
+ // Connection
87
+ this.BLE = this.device.connectionType === 'BLE' || this.device.connectionType === 'BLE/OpenAPI'
88
+ this.OpenAPI = this.device.connectionType === 'OpenAPI' || this.device.connectionType === 'BLE/OpenAPI'
89
+ // default placeholders
90
+ this.deviceLogs(device)
91
+ this.deviceType(device)
92
+ this.scan(device)
93
+ this.refreshRate(device)
94
+ this.PressOrSwitch(device)
95
+ this.allowPushChanges(device)
96
+ this.deviceContext()
97
+ this.DoublePress(device)
98
+ this.deviceConfig(device)
99
+
100
+ this.multiPressCount = 0
101
+
102
+ // this is subject we use to track when we need to POST changes to the SwitchBot API
103
+ this.doBotUpdate = new Subject()
104
+ this.botUpdateInProgress = false
105
+
106
+ // Retrieve initial values and updateHomekit
107
+ this.refreshStatus()
108
+
109
+ // set accessory information
110
+ accessory
111
+ .getService(this.hap.Service.AccessoryInformation)!
112
+ .setCharacteristic(this.hap.Characteristic.Manufacturer, 'SwitchBot')
113
+ .setCharacteristic(this.hap.Characteristic.Model, 'SWITCHBOT-BOT-S1')
114
+ .setCharacteristic(this.hap.Characteristic.SerialNumber, device.deviceId)
115
+ .setCharacteristic(this.hap.Characteristic.FirmwareRevision, accessory.context.FirmwareRevision)
116
+
117
+ // deviceType
118
+ if (this.botDeviceType === 'switch') {
119
+ this.removeFanService(accessory)
120
+ this.removeLockService(accessory)
121
+ this.removeDoorService(accessory)
122
+ this.removeFaucetService(accessory)
123
+ this.removeOutletService(accessory)
124
+ this.removeWindowService(accessory)
125
+ this.removeGarageDoorService(accessory)
126
+ this.removeWindowCoveringService(accessory)
127
+ this.removeStatefulProgrammableSwitchService(accessory)
128
+
129
+ // Add switchService
130
+ this.switchService = (accessory.getService(this.hap.Service.Switch) || accessory.addService(this.hap.Service.Switch))
131
+ this.debugWarnLog(`${this.device.deviceType}: ${accessory.displayName} Displaying as Switch`)
132
+
133
+ this.switchService.setCharacteristic(this.hap.Characteristic.Name, accessory.displayName)
134
+ if (!this.switchService.testCharacteristic(this.hap.Characteristic.ConfiguredName)) {
135
+ this.switchService.addCharacteristic(this.hap.Characteristic.ConfiguredName, accessory.displayName)
136
+ }
137
+ this.switchService.getCharacteristic(this.hap.Characteristic.On).onSet(this.OnSet.bind(this))
138
+ } else if (this.botDeviceType === 'garagedoor') {
139
+ this.removeFanService(accessory)
140
+ this.removeLockService(accessory)
141
+ this.removeDoorService(accessory)
142
+ this.removeFaucetService(accessory)
143
+ this.removeOutletService(accessory)
144
+ this.removeSwitchService(accessory)
145
+ this.removeWindowService(accessory)
146
+ this.removeWindowCoveringService(accessory)
147
+ this.removeStatefulProgrammableSwitchService(accessory)
148
+
149
+ // Add garageDoorService
150
+ this.garageDoorService = (accessory.getService(this.hap.Service.GarageDoorOpener) || accessory.addService(this.hap.Service.GarageDoorOpener))
151
+ this.debugWarnLog(`${this.device.deviceType}: ${accessory.displayName} Displaying as Garage Door Opener`)
152
+
153
+ this.garageDoorService.setCharacteristic(this.hap.Characteristic.Name, accessory.displayName)
154
+ if (!this.garageDoorService.testCharacteristic(this.hap.Characteristic.ConfiguredName)) {
155
+ this.garageDoorService.addCharacteristic(this.hap.Characteristic.ConfiguredName, accessory.displayName)
156
+ }
157
+ this.garageDoorService.getCharacteristic(this.hap.Characteristic.TargetDoorState).onSet(this.OnSet.bind(this))
158
+ this.garageDoorService.setCharacteristic(this.hap.Characteristic.ObstructionDetected, false)
159
+ } else if (this.botDeviceType === 'door') {
160
+ this.removeFanService(accessory)
161
+ this.removeLockService(accessory)
162
+ this.removeOutletService(accessory)
163
+ this.removeFaucetService(accessory)
164
+ this.removeSwitchService(accessory)
165
+ this.removeWindowService(accessory)
166
+ this.removeGarageDoorService(accessory)
167
+ this.removeWindowCoveringService(accessory)
168
+ this.removeStatefulProgrammableSwitchService(accessory)
169
+
170
+ // Add doorService
171
+ this.doorService = accessory.getService(this.hap.Service.Door) || accessory.addService(this.hap.Service.Door)
172
+ this.debugWarnLog(`${this.device.deviceType}: ${accessory.displayName} Displaying as Door`)
173
+
174
+ this.doorService.setCharacteristic(this.hap.Characteristic.Name, accessory.displayName)
175
+ if (!this.doorService.testCharacteristic(this.hap.Characteristic.ConfiguredName)) {
176
+ this.doorService.addCharacteristic(this.hap.Characteristic.ConfiguredName, accessory.displayName)
177
+ }
178
+ this.doorService
179
+ .getCharacteristic(this.hap.Characteristic.TargetPosition)
180
+ .setProps({
181
+ validValues: [0, 100],
182
+ minValue: 0,
183
+ maxValue: 100,
184
+ minStep: 100,
185
+ })
186
+ .onSet(this.OnSet.bind(this))
187
+ this.doorService.setCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED)
188
+ } else if (this.botDeviceType === 'window') {
189
+ this.removeFanService(accessory)
190
+ this.removeLockService(accessory)
191
+ this.removeDoorService(accessory)
192
+ this.removeOutletService(accessory)
193
+ this.removeFaucetService(accessory)
194
+ this.removeSwitchService(accessory)
195
+ this.removeGarageDoorService(accessory)
196
+ this.removeWindowCoveringService(accessory)
197
+ this.removeStatefulProgrammableSwitchService(accessory)
198
+
199
+ // Add windowService
200
+ this.windowService = accessory.getService(this.hap.Service.Window) || accessory.addService(this.hap.Service.Window)
201
+ this.debugWarnLog(`${this.device.deviceType}: ${accessory.displayName} Displaying as Window`)
202
+
203
+ this.windowService.setCharacteristic(this.hap.Characteristic.Name, accessory.displayName)
204
+ if (!this.windowService.testCharacteristic(this.hap.Characteristic.ConfiguredName)) {
205
+ this.windowService.addCharacteristic(this.hap.Characteristic.ConfiguredName, accessory.displayName)
206
+ }
207
+ this.windowService
208
+ .getCharacteristic(this.hap.Characteristic.TargetPosition)
209
+ .setProps({
210
+ validValues: [0, 100],
211
+ minValue: 0,
212
+ maxValue: 100,
213
+ minStep: 100,
214
+ })
215
+ .onSet(this.OnSet.bind(this))
216
+ this.windowService.setCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED)
217
+ } else if (this.botDeviceType === 'windowcovering') {
218
+ this.removeFanService(accessory)
219
+ this.removeLockService(accessory)
220
+ this.removeDoorService(accessory)
221
+ this.removeOutletService(accessory)
222
+ this.removeFaucetService(accessory)
223
+ this.removeSwitchService(accessory)
224
+ this.removeWindowService(accessory)
225
+ this.removeGarageDoorService(accessory)
226
+ this.removeStatefulProgrammableSwitchService(accessory)
227
+
228
+ // Add windowCoveringService
229
+ this.windowCoveringService = accessory.getService(this.hap.Service.WindowCovering) || accessory.addService(this.hap.Service.WindowCovering)
230
+ this.debugWarnLog(`${this.device.deviceType}: ${accessory.displayName} Displaying as Window Covering`)
231
+
232
+ this.windowCoveringService.setCharacteristic(this.hap.Characteristic.Name, accessory.displayName)
233
+ if (!this.windowCoveringService.testCharacteristic(this.hap.Characteristic.ConfiguredName)) {
234
+ this.windowCoveringService.addCharacteristic(this.hap.Characteristic.ConfiguredName, accessory.displayName)
235
+ }
236
+ this.windowCoveringService
237
+ .getCharacteristic(this.hap.Characteristic.TargetPosition)
238
+ .setProps({
239
+ validValues: [0, 100],
240
+ minValue: 0,
241
+ maxValue: 100,
242
+ minStep: 100,
243
+ })
244
+ .onSet(this.OnSet.bind(this))
245
+ this.windowCoveringService.setCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED)
246
+ } else if (this.botDeviceType === 'lock') {
247
+ this.removeFanService(accessory)
248
+ this.removeDoorService(accessory)
249
+ this.removeOutletService(accessory)
250
+ this.removeSwitchService(accessory)
251
+ this.removeFaucetService(accessory)
252
+ this.removeWindowService(accessory)
253
+ this.removeGarageDoorService(accessory)
254
+ this.removeWindowCoveringService(accessory)
255
+ this.removeStatefulProgrammableSwitchService(accessory)
256
+
257
+ // Add lockService
258
+ this.lockService = accessory.getService(this.hap.Service.LockMechanism) || accessory.addService(this.hap.Service.LockMechanism)
259
+ this.debugWarnLog(`${this.device.deviceType}: ${accessory.displayName} Displaying as Lock`)
260
+
261
+ this.lockService.setCharacteristic(this.hap.Characteristic.Name, accessory.displayName)
262
+ if (!this.lockService.testCharacteristic(this.hap.Characteristic.ConfiguredName)) {
263
+ this.lockService.addCharacteristic(this.hap.Characteristic.ConfiguredName, accessory.displayName)
264
+ }
265
+ this.lockService.getCharacteristic(this.hap.Characteristic.LockTargetState).onSet(this.OnSet.bind(this))
266
+ } else if (this.botDeviceType === 'faucet') {
267
+ this.removeFanService(accessory)
268
+ this.removeLockService(accessory)
269
+ this.removeDoorService(accessory)
270
+ this.removeOutletService(accessory)
271
+ this.removeSwitchService(accessory)
272
+ this.removeWindowService(accessory)
273
+ this.removeGarageDoorService(accessory)
274
+ this.removeWindowCoveringService(accessory)
275
+ this.removeStatefulProgrammableSwitchService(accessory)
276
+
277
+ // Add faucetService
278
+ this.faucetService = accessory.getService(this.hap.Service.Faucet) || accessory.addService(this.hap.Service.Faucet)
279
+ this.debugWarnLog(`${this.device.deviceType}: ${accessory.displayName} Displaying as Faucet`)
280
+
281
+ this.faucetService.setCharacteristic(this.hap.Characteristic.Name, accessory.displayName)
282
+ if (!this.faucetService.testCharacteristic(this.hap.Characteristic.ConfiguredName)) {
283
+ this.faucetService.addCharacteristic(this.hap.Characteristic.ConfiguredName, accessory.displayName)
284
+ }
285
+ this.faucetService.getCharacteristic(this.hap.Characteristic.Active).onSet(this.OnSet.bind(this))
286
+ } else if (this.botDeviceType === 'fan') {
287
+ this.removeLockService(accessory)
288
+ this.removeDoorService(accessory)
289
+ this.removeFaucetService(accessory)
290
+ this.removeOutletService(accessory)
291
+ this.removeSwitchService(accessory)
292
+ this.removeWindowService(accessory)
293
+ this.removeGarageDoorService(accessory)
294
+ this.removeWindowCoveringService(accessory)
295
+ this.removeStatefulProgrammableSwitchService(accessory)
296
+
297
+ // Add fanService
298
+ this.fanService = accessory.getService(this.hap.Service.Fan) || accessory.addService(this.hap.Service.Fan)
299
+ this.debugWarnLog(`${this.device.deviceType}: ${accessory.displayName} Displaying as Fan`)
300
+
301
+ this.fanService.setCharacteristic(this.hap.Characteristic.Name, accessory.displayName)
302
+ if (!this.fanService.testCharacteristic(this.hap.Characteristic.ConfiguredName)) {
303
+ this.fanService.addCharacteristic(this.hap.Characteristic.ConfiguredName, accessory.displayName)
304
+ }
305
+ this.fanService.getCharacteristic(this.hap.Characteristic.On).onSet(this.OnSet.bind(this))
306
+ } else if (this.botDeviceType === 'stateful') {
307
+ this.removeFanService(accessory)
308
+ this.removeLockService(accessory)
309
+ this.removeDoorService(accessory)
310
+ this.removeFaucetService(accessory)
311
+ this.removeOutletService(accessory)
312
+ this.removeSwitchService(accessory)
313
+ this.removeWindowService(accessory)
314
+ this.removeGarageDoorService(accessory)
315
+ this.removeWindowCoveringService(accessory)
316
+
317
+ // Add statefulProgrammableSwitchService
318
+ this.statefulProgrammableSwitchService = (accessory.getService(this.hap.Service.StatefulProgrammableSwitch) || accessory.addService(this.hap.Service.StatefulProgrammableSwitch))
319
+ this.debugWarnLog(`${this.device.deviceType}: ${accessory.displayName} Displaying as Stateful Programmable Switch`)
320
+
321
+ this.statefulProgrammableSwitchService.setCharacteristic(this.hap.Characteristic.Name, accessory.displayName)
322
+ if (!this.statefulProgrammableSwitchService.testCharacteristic(this.hap.Characteristic.ConfiguredName)) {
323
+ this.statefulProgrammableSwitchService.addCharacteristic(this.hap.Characteristic.ConfiguredName, accessory.displayName)
324
+ }
325
+ this.statefulProgrammableSwitchService
326
+ .getCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState)
327
+ .onSet(this.OnSet.bind(this))
328
+ } else {
329
+ this.removeFanService(accessory)
330
+ this.removeLockService(accessory)
331
+ this.removeDoorService(accessory)
332
+ this.removeFaucetService(accessory)
333
+ this.removeSwitchService(accessory)
334
+ this.removeWindowService(accessory)
335
+ this.removeGarageDoorService(accessory)
336
+ this.removeWindowCoveringService(accessory)
337
+ this.removeStatefulProgrammableSwitchService(accessory)
338
+
339
+ // Add outletService
340
+ this.outletService = (accessory.getService(this.hap.Service.Outlet) || accessory.addService(this.hap.Service.Outlet))
341
+ this.debugWarnLog(`${this.device.deviceType}: ${accessory.displayName} Displaying as Outlet`)
342
+
343
+ this.outletService.setCharacteristic(this.hap.Characteristic.Name, accessory.displayName)
344
+ if (!this.outletService.testCharacteristic(this.hap.Characteristic.ConfiguredName)) {
345
+ this.outletService.addCharacteristic(this.hap.Characteristic.ConfiguredName, accessory.displayName)
346
+ }
347
+ this.outletService.getCharacteristic(this.hap.Characteristic.On).onSet(this.OnSet.bind(this))
348
+ }
349
+
350
+ // batteryService
351
+ this.batteryService = (this.accessory.getService(this.hap.Service.Battery) || accessory.addService(this.hap.Service.Battery))
352
+
353
+ this.batteryService.setCharacteristic(this.hap.Characteristic.Name, `${accessory.displayName} Battery`)
354
+ if (!this.batteryService.testCharacteristic(this.hap.Characteristic.ConfiguredName)) {
355
+ this.batteryService.addCharacteristic(this.hap.Characteristic.ConfiguredName, `${accessory.displayName} Battery`)
356
+ }
357
+ this.batteryService.setCharacteristic(this.hap.Characteristic.ChargingState, this.hap.Characteristic.ChargingState.NOT_CHARGEABLE)
358
+
359
+ // Retrieve initial values and updateHomekit
360
+ this.updateHomeKitCharacteristics()
361
+
362
+ // Start an update interval
363
+ interval(this.deviceRefreshRate * 1000)
364
+ .pipe(skipWhile(() => this.botUpdateInProgress))
365
+ .subscribe(async () => {
366
+ await this.refreshStatus()
367
+ })
368
+
369
+ // regisiter webhook event handler
370
+ if (this.device.webhook) {
371
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} is listening webhook.`)
372
+ this.platform.webhookEventHandler[this.device.deviceId] = async (context) => {
373
+ try {
374
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} received Webhook: ${JSON.stringify(context)}`)
375
+ const { power, battery, deviceMode } = context
376
+ const { On, BatteryLevel, botMode } = this
377
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} (power, battery, deviceMode) = Webhook:(${power}, ${battery}, ${deviceMode}), current:(${On}, ${BatteryLevel}, ${botMode})`)
378
+ this.On = power
379
+ this.BatteryLevel = battery
380
+ this.botMode = deviceMode
381
+ this.updateHomeKitCharacteristics()
382
+ } catch (e: any) {
383
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`)
384
+ }
385
+ }
386
+ }
387
+
388
+ // Watch for Bot change events
389
+ // We put in a debounce of 1000ms so we don't make duplicate calls
390
+ this.doBotUpdate
391
+ .pipe(
392
+ tap(() => {
393
+ this.botUpdateInProgress = true
394
+ }),
395
+ debounceTime(this.platform.config.options!.pushRate! * 1000),
396
+ )
397
+ .subscribe(async () => {
398
+ try {
399
+ if (this.doublePress > 1) {
400
+ interval(this.pushRatePress * 1000)
401
+ .pipe(take(this.doublePress!))
402
+ .subscribe(async () => {
403
+ await this.pushChanges()
404
+ })
405
+ } else {
406
+ await this.pushChanges()
407
+ }
408
+ } catch (e: any) {
409
+ this.apiError(e)
410
+ this.errorLog(
411
+ `${this.device.deviceType}: ${this.accessory.displayName} failed pushChanges with ${this.device.connectionType} Connection,`
412
+ + ` Error Message: ${JSON.stringify(e.message)}`,
413
+ )
414
+ }
415
+ this.botUpdateInProgress = false
416
+ })
417
+ }
418
+
419
+ /**
420
+ * Parse the device status from the SwitchBot api
421
+ */
422
+ async parseStatus(): Promise<void> {
423
+ if (!this.device.enableCloudService && this.OpenAPI) {
424
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} parseStatus enableCloudService: ${this.device.enableCloudService}`)
425
+ } else if (this.BLE) {
426
+ await this.BLEparseStatus()
427
+ } else if (this.OpenAPI && this.platform.config.credentials?.token) {
428
+ await this.openAPIparseStatus()
429
+ } else {
430
+ await this.offlineOff()
431
+ this.debugWarnLog(
432
+ `${this.device.deviceType}: ${this.accessory.displayName} Connection Type:` + ` ${this.device.connectionType}, parseStatus will not happen.`,
433
+ )
434
+ }
435
+ }
436
+
437
+ async BLEparseStatus(): Promise<void> {
438
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLEparseStatus`)
439
+ // BLEmode (true if Switch Mode) | (false if Press Mode)
440
+ if (this.BLE_Mode) {
441
+ this.accessory.context.On = this.On
442
+ if (this.On === undefined) {
443
+ this.On = Boolean(this.BLE_On)
444
+ }
445
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Switch Mode, mode: ${this.BLE_Mode}, On: ${this.On}`)
446
+ } else {
447
+ this.On = false
448
+ this.accessory.context.On = this.On
449
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Press Mode, mode: ${this.BLE_Mode}, On: ${this.On}`)
450
+ }
451
+
452
+ this.BatteryLevel = Number(this.BLE_BatteryLevel)
453
+ if (this.BatteryLevel < 10) {
454
+ this.StatusLowBattery = this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW
455
+ } else {
456
+ this.StatusLowBattery = this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL
457
+ }
458
+ if (Number.isNaN(this.BatteryLevel)) {
459
+ this.BatteryLevel = 100
460
+ }
461
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BatteryLevel: ${this.BatteryLevel}, StatusLowBattery: ${this.StatusLowBattery}`)
462
+ }
463
+
464
+ async openAPIparseStatus(): Promise<void> {
465
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIparseStatus`)
466
+ if (this.botMode === 'press') {
467
+ this.On = false
468
+ this.accessory.context.On = this.On
469
+ } else {
470
+ this.accessory.context.On = this.On
471
+ if (this.On === undefined) {
472
+ this.On = false
473
+ }
474
+ }
475
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}`)
476
+
477
+ // Battery
478
+ this.BatteryLevel = Number(this.OpenAPI_BatteryLevel)
479
+ if (this.BatteryLevel < 10) {
480
+ this.StatusLowBattery = this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW
481
+ } else {
482
+ this.StatusLowBattery = this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL
483
+ }
484
+ if (Number.isNaN(this.BatteryLevel)) {
485
+ this.BatteryLevel = 100
486
+ }
487
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BatteryLevel: ${this.BatteryLevel}, StatusLowBattery: ${this.StatusLowBattery}`)
488
+
489
+ // FirmwareRevision
490
+ this.FirmwareRevision = this.OpenAPI_FirmwareRevision!
491
+ this.accessory.context.FirmwareRevision = this.FirmwareRevision
492
+ }
493
+
494
+ /**
495
+ * Asks the SwitchBot API for the latest device information
496
+ */
497
+ async refreshStatus(): Promise<void> {
498
+ if (!this.device.enableCloudService && this.OpenAPI) {
499
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} refreshStatus enableCloudService: ${this.device.enableCloudService}`)
500
+ } else if (this.BLE) {
501
+ await this.BLERefreshStatus()
502
+ } else if (this.OpenAPI && this.platform.config.credentials?.token) {
503
+ await this.openAPIRefreshStatus()
504
+ } else {
505
+ await this.offlineOff()
506
+ this.debugWarnLog(
507
+ `${this.device.deviceType}: ${this.accessory.displayName} Connection Type:`
508
+ + ` ${this.device.connectionType}, refreshStatus will not happen.`,
509
+ )
510
+ }
511
+ }
512
+
513
+ async BLERefreshStatus(): Promise<void> {
514
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLERefreshStatus`)
515
+ const switchbot = await this.platform.connectBLE(this.accessory, this.device)
516
+ // Convert to BLE Address
517
+ this.device.bleMac = this.device
518
+ .deviceId!.match(/.{1,2}/g)!
519
+ .join(':')
520
+ .toLowerCase()
521
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLE Address: ${this.device.bleMac}`)
522
+ this.getCustomBLEAddress(switchbot);
523
+ // Start to monitor advertisement packets
524
+ (async () => {
525
+ // Start to monitor advertisement packets
526
+ await switchbot.startScan({
527
+ model: 'H',
528
+ id: this.device.bleMac,
529
+ })
530
+ // Set an event handler
531
+ switchbot.onadvertisement = (ad: any) => {
532
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} ${JSON.stringify(ad, null, ' ')}`)
533
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} address: ${ad.address}, model: ${ad.model}`)
534
+ if (this.device.bleMac === ad.address && ad.model === 'H') {
535
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} serviceData: ${JSON.stringify(ad.serviceData)}`)
536
+ this.BLE_Mode = ad.serviceData.mode
537
+ this.BLE_On = ad.serviceData.state
538
+ this.BLE_BatteryLevel = ad.serviceData.battery
539
+ } else {
540
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} serviceData: ${JSON.stringify(ad.serviceData)}`)
541
+ }
542
+ }
543
+ // Wait 1 seconds
544
+ await switchbot.wait(this.scanDuration * 1000)
545
+ // Stop to monitor
546
+ await switchbot.stopScan()
547
+ // Update HomeKit
548
+ await this.BLEparseStatus()
549
+ await this.updateHomeKitCharacteristics()
550
+ })()
551
+ /* if (switchbot !== false) {
552
+ switchbot
553
+ .startScan({
554
+ model: 'H',
555
+ id: this.device.bleMac,
556
+ })
557
+ .then(async () => {
558
+ // Set an event handler
559
+ switchbot.onadvertisement = async (ad: ad) => {
560
+ this.debugLog(
561
+ `${this.device.deviceType}: ${this.accessory.displayName} Config BLE Address: ${this.device.bleMac},` +
562
+ ` BLE Address Found: ${ad.address}`,
563
+ );
564
+ this.BLE_Mode = ad.serviceData.mode;
565
+ this.BLE_On = ad.serviceData.state;
566
+ this.BLE_BatteryLevel = ad.serviceData.battery;
567
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} serviceData: ${JSON.stringify(ad.serviceData)}`);
568
+ this.debugLog(
569
+ `${this.device.deviceType}: ${this.accessory.displayName}, model: ${ad.serviceData.model}, modelName: ` +
570
+ `${ad.serviceData.modelName}, mode: ${ad.serviceData.mode}, state: ${ad.serviceData.state}, battery: ${ad.serviceData.battery}`,
571
+ );
572
+
573
+ if (ad.serviceData) {
574
+ this.BLE_IsConnected = true;
575
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} connected: ${this.BLE_IsConnected}`);
576
+ await this.stopScanning(switchbot);
577
+ } else {
578
+ this.BLE_IsConnected = false;
579
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} connected: ${this.BLE_IsConnected}`);
580
+ }
581
+ };
582
+ // Wait
583
+ return await sleep(this.scanDuration * 1000);
584
+ })
585
+ .then(async () => {
586
+ // Stop to monitor
587
+ await this.stopScanning(switchbot);
588
+ })
589
+ .catch(async (e: any) => {
590
+ this.apiError(e);
591
+ this.errorLog(
592
+ `${this.device.deviceType}: ${this.accessory.displayName} failed BLERefreshStatus with ${this.device.connectionType}` +
593
+ ` Connection, Error Message: ${JSON.stringify(e.message)}`,
594
+ );
595
+ await this.BLERefreshConnection(switchbot);
596
+ });
597
+ } else {
598
+ await this.BLERefreshConnection(switchbot);
599
+ } */
600
+ }
601
+
602
+ async openAPIRefreshStatus(): Promise<void> {
603
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIRefreshStatus`)
604
+ try {
605
+ const { body, statusCode } = await request(`${Devices}/${this.device.deviceId}/status`, {
606
+ headers: this.platform.generateHeaders(),
607
+ })
608
+ this.debugWarnLog(`${this.device.deviceType}: ${this.accessory.displayName} statusCode: ${statusCode}`)
609
+ const deviceStatus: any = await body.json()
610
+ this.debugWarnLog(`${this.device.deviceType}: ${this.accessory.displayName} deviceStatus: ${JSON.stringify(deviceStatus)}`)
611
+ this.debugWarnLog(`${this.device.deviceType}: ${this.accessory.displayName} deviceStatus statusCode: ${deviceStatus.statusCode}`)
612
+ if ((statusCode === 200 || statusCode === 100) && (deviceStatus.statusCode === 200 || deviceStatus.statusCode === 100)) {
613
+ this.debugErrorLog(`${this.device.deviceType}: ${this.accessory.displayName} statusCode: ${statusCode} & deviceStatus StatusCode: ${deviceStatus.statusCode}`)
614
+ this.OpenAPI_On = deviceStatus.body.power
615
+ this.OpenAPI_BatteryLevel = deviceStatus.body.battery
616
+ this.OpenAPI_FirmwareRevision = deviceStatus.body.version
617
+ this.openAPIparseStatus()
618
+ this.updateHomeKitCharacteristics()
619
+ } else {
620
+ this.statusCode(statusCode)
621
+ this.statusCode(deviceStatus.statusCode)
622
+ }
623
+ } catch (e: any) {
624
+ this.apiError(e)
625
+ this.errorLog(
626
+ `${this.device.deviceType}: ${this.accessory.displayName} failed openAPIRefreshStatus with ${this.device.connectionType}`
627
+ + ` Connection, Error Message: ${JSON.stringify(e.message)}`,
628
+ )
629
+ }
630
+ }
631
+
632
+ /**
633
+ * Pushes the requested changes to the SwitchBot API
634
+ * deviceType commandType Command command parameter Description
635
+ * Bot - "command" "turnOff" "default" = set to OFF state
636
+ * Bot - "command" "turnOn" "default" = set to ON state
637
+ * Bot - "command" "press" "default" = trigger press
638
+ */
639
+ async pushChanges(): Promise<void> {
640
+ if (!this.device.enableCloudService && this.OpenAPI) {
641
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} pushChanges enableCloudService: ${this.device.enableCloudService}`)
642
+ } else if (this.BLE) {
643
+ await this.BLEpushChanges()
644
+ } else if (this.OpenAPI) {
645
+ await this.openAPIpushChanges()
646
+ } else {
647
+ await this.offlineOff()
648
+ this.debugWarnLog(
649
+ `${this.device.deviceType}: ${this.accessory.displayName} Connection Type:` + ` ${this.device.connectionType}, pushChanges will not happen.`,
650
+ )
651
+ }
652
+ // Refresh the status from the API
653
+ interval(15000)
654
+ .pipe(skipWhile(() => this.botUpdateInProgress))
655
+ .pipe(take(1))
656
+ .subscribe(async () => {
657
+ await this.refreshStatus()
658
+ })
659
+ }
660
+
661
+ async BLEpushChanges(): Promise<void> {
662
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLEpushChanges`)
663
+ if (this.On !== this.accessory.context.On || this.allowPush) {
664
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLEpushChanges On: ${this.On} OnCached: ${this.accessory.context.On}`)
665
+ const switchbot = await this.platform.connectBLE(this.accessory, this.device)
666
+ // Convert to BLE Address
667
+ this.device.bleMac = this.device
668
+ .deviceId!.match(/.{1,2}/g)!
669
+ .join(':')
670
+ .toLowerCase()
671
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLE Address: ${this.device.bleMac}`)
672
+ // if (switchbot !== false) {
673
+ if (this.botMode === 'press') {
674
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Bot Mode: ${this.botMode}`)
675
+ switchbot
676
+ .discover({ model: 'H', quick: true, id: this.device.bleMac })
677
+ .then(async (device_list: { press: (arg0: { id: string | undefined }) => any }[]) => {
678
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}`)
679
+ return await device_list[0].press({ id: this.device.bleMac })
680
+ })
681
+ .then(() => {
682
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Done.`)
683
+ this.accessory.context.On = this.On
684
+ setTimeout(() => {
685
+ if (this.botDeviceType === 'switch') {
686
+ this.switchService?.getCharacteristic(this.hap.Characteristic.On).updateValue(this.On)
687
+ } else {
688
+ this.outletService?.getCharacteristic(this.hap.Characteristic.On).updateValue(this.On)
689
+ }
690
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}, Switch Timeout`)
691
+ }, 500)
692
+ })
693
+ .catch(async (e: any) => {
694
+ this.apiError(e)
695
+ this.errorLog(
696
+ `${this.device.deviceType}: ${this.accessory.displayName} failed BLEpushChanges with ${this.device.connectionType}`
697
+ + ` Connection & botMode: ${this.botMode}, Error Message: ${JSON.stringify(e.message)}`,
698
+ )
699
+ await this.BLEPushConnection()
700
+ })
701
+ } else if (this.botMode === 'switch') {
702
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Press Mode: ${this.botMode}`)
703
+ switchbot
704
+ .discover({ model: 'H', quick: true, id: this.device.bleMac })
705
+ .then(async (device_list: any) => {
706
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}`)
707
+ return await this.retry({
708
+ max: this.maxRetry(),
709
+ fn: async () => {
710
+ if (this.On) {
711
+ return await device_list[0].turnOn({ id: this.device.bleMac })
712
+ } else {
713
+ return await device_list[0].turnOff({ id: this.device.bleMac })
714
+ }
715
+ },
716
+ })
717
+ })
718
+ .then(() => {
719
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Done.`)
720
+ this.accessory.context.On = this.On
721
+ })
722
+ .catch(async (e: any) => {
723
+ this.apiError(e)
724
+ this.errorLog(
725
+ `${this.device.deviceType}: ${this.accessory.displayName} failed BLEpushChanges with ${this.device.connectionType}`
726
+ + ` Connection & botMode: ${this.botMode}, Error Message: ${JSON.stringify(e.message)}`,
727
+ )
728
+ await this.BLEPushConnection()
729
+ })
730
+ } else {
731
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Bot Mode: ${this.botMode}`)
732
+ }
733
+ } else {
734
+ this.debugLog(
735
+ `${this.device.deviceType}: ${this.accessory.displayName} No BLEpushChanges.` + `On: ${this.On}, ` + `OnCached: ${this.accessory.context.On}`,
736
+ )
737
+ }
738
+ }
739
+
740
+ async openAPIpushChanges(): Promise<void> {
741
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIpushChanges`)
742
+ if (this.multiPressCount > 0) {
743
+ this.debugLog(`${this.device.deviceType}: ${this.multiPressCount} request(s) queued.`)
744
+ this.On = true
745
+ }
746
+ if (this.On !== this.accessory.context.On || this.allowPush || this.multiPressCount > 0) {
747
+ let command = ''
748
+ if (this.botMode === 'switch' && this.On) {
749
+ command = 'turnOn'
750
+ this.On = true
751
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Switch Mode, Turning ${this.On}`)
752
+ } else if (this.botMode === 'switch' && !this.On) {
753
+ command = 'turnOff'
754
+ this.On = false
755
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Switch Mode, Turning ${this.On}`)
756
+ } else if (this.botMode === 'press' || this.botMode === 'multipress') {
757
+ command = 'press'
758
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Press Mode`)
759
+ this.On = false
760
+ } else {
761
+ throw new Error(`${this.device.deviceType}: ${this.accessory.displayName} Device Parameters not set for this Bot.`)
762
+ }
763
+ const bodyChange = JSON.stringify({
764
+ command: `${command}`,
765
+ parameter: 'default',
766
+ commandType: 'command',
767
+ })
768
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Sending request to SwitchBot API, body: ${bodyChange},`)
769
+ try {
770
+ const { body, statusCode } = await request(`${Devices}/${this.device.deviceId}/commands`, {
771
+ body: bodyChange,
772
+ method: 'POST',
773
+ headers: this.platform.generateHeaders(),
774
+ })
775
+ this.debugWarnLog(`${this.device.deviceType}: ${this.accessory.displayName} statusCode: ${statusCode}`)
776
+ const deviceStatus: any = await body.json()
777
+ this.debugWarnLog(`${this.device.deviceType}: ${this.accessory.displayName} deviceStatus: ${JSON.stringify(deviceStatus)}`)
778
+ this.debugWarnLog(`${this.device.deviceType}: ${this.accessory.displayName} deviceStatus body: ${JSON.stringify(deviceStatus.body)}`)
779
+ this.debugWarnLog(`${this.device.deviceType}: ${this.accessory.displayName} deviceStatus statusCode: ${deviceStatus.statusCode}`)
780
+ if ((statusCode === 200 || statusCode === 100) && (deviceStatus.statusCode === 200 || deviceStatus.statusCode === 100)) {
781
+ this.debugErrorLog(`${this.device.deviceType}: ${this.accessory.displayName} statusCode: ${statusCode} & deviceStatus StatusCode: ${deviceStatus.statusCode}`)
782
+ } else {
783
+ this.statusCode(statusCode)
784
+ this.statusCode(deviceStatus.statusCode)
785
+ }
786
+ if (this.device.bot?.mode === 'multipress') {
787
+ this.multiPressCount--
788
+ if (this.multiPressCount > 0) {
789
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Multi Press Count: ${this.multiPressCount}`)
790
+ this.On = true
791
+ this.openAPIpushChanges()
792
+ }
793
+ }
794
+ } catch (e: any) {
795
+ this.apiError(e)
796
+ this.errorLog(
797
+ `${this.device.deviceType}: ${this.accessory.displayName} failed openAPIpushChanges with ${this.device.connectionType}`
798
+ + ` Connection, Error Message: ${JSON.stringify(e.message)}`,
799
+ )
800
+ }
801
+ } else {
802
+ this.debugLog(
803
+ `${this.device.deviceType}: ${this.accessory.displayName} No openAPIpushChanges.`
804
+ + `On: ${this.On}, `
805
+ + `OnCached: ${this.accessory.context.On}`,
806
+ )
807
+ }
808
+ }
809
+
810
+ /**
811
+ * Handle requests to set the "On" characteristic
812
+ */
813
+ async OnSet(value: CharacteristicValue): Promise<void> {
814
+ if (this.botDeviceType === 'garagedoor') {
815
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Set TargetDoorState: ${value}`)
816
+ if (value === this.hap.Characteristic.TargetDoorState.CLOSED) {
817
+ this.On = false
818
+ } else {
819
+ this.On = true
820
+ }
821
+ } else if (
822
+ this.botDeviceType === 'door'
823
+ || this.botDeviceType === 'window'
824
+ || this.botDeviceType === 'windowcovering'
825
+ ) {
826
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Set TargetPosition: ${value}`)
827
+ if (value === 0) {
828
+ this.On = false
829
+ } else {
830
+ this.On = true
831
+ }
832
+ } else if (this.botDeviceType === 'lock') {
833
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Set LockTargetState: ${value}`)
834
+ if (value === this.hap.Characteristic.LockTargetState.SECURED) {
835
+ this.On = false
836
+ } else {
837
+ this.On = true
838
+ }
839
+ } else if (this.botDeviceType === 'faucet') {
840
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Set Active: ${value}`)
841
+ if (value === this.hap.Characteristic.Active.INACTIVE) {
842
+ this.On = false
843
+ } else {
844
+ this.On = true
845
+ }
846
+ } else if (this.botDeviceType === 'stateful') {
847
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Set ProgrammableSwitchOutputState: ${value}`)
848
+ if (value === 0) {
849
+ this.On = false
850
+ } else {
851
+ this.On = true
852
+ }
853
+ } else {
854
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Set On: ${value}`)
855
+ if (this.device.bot?.mode === 'multipress') {
856
+ if (value === true) {
857
+ this.multiPressCount++
858
+ this.debugLog(`${this.device.deviceType} set to Multi-Press. Multi-Press count: ${this.multiPressCount}`)
859
+ }
860
+ }
861
+ this.On = value
862
+ }
863
+ this.doBotUpdate.next()
864
+ }
865
+
866
+ /**
867
+ * Updates the status for each of the HomeKit Characteristics
868
+ */
869
+ async updateHomeKitCharacteristics(): Promise<void> {
870
+ // State
871
+ if (this.botDeviceType === 'garagedoor') {
872
+ if (this.On === undefined) {
873
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}`)
874
+ } else {
875
+ if (this.On) {
876
+ this.garageDoorService?.updateCharacteristic(
877
+ this.hap.Characteristic.TargetDoorState,
878
+ this.hap.Characteristic.TargetDoorState.OPEN,
879
+ )
880
+ this.garageDoorService?.updateCharacteristic(
881
+ this.hap.Characteristic.CurrentDoorState,
882
+ this.hap.Characteristic.CurrentDoorState.OPEN,
883
+ )
884
+ this.debugLog(
885
+ `${this.device.deviceType}: ` + `${this.accessory.displayName} updateCharacteristic TargetDoorState: Open, CurrentDoorState: Open`,
886
+ )
887
+ } else {
888
+ this.garageDoorService?.updateCharacteristic(
889
+ this.hap.Characteristic.TargetDoorState,
890
+ this.hap.Characteristic.TargetDoorState.CLOSED,
891
+ )
892
+ this.garageDoorService?.updateCharacteristic(
893
+ this.hap.Characteristic.CurrentDoorState,
894
+ this.hap.Characteristic.CurrentDoorState.CLOSED,
895
+ )
896
+ this.debugLog(
897
+ `${this.device.deviceType}: ` + `${this.accessory.displayName} updateCharacteristic TargetDoorState: Open, CurrentDoorState: Open`,
898
+ )
899
+ }
900
+ }
901
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Garage Door On: ${this.On}`)
902
+ } else if (this.botDeviceType === 'door') {
903
+ if (this.On === undefined) {
904
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}`)
905
+ } else {
906
+ if (this.On) {
907
+ this.doorService?.updateCharacteristic(this.hap.Characteristic.TargetPosition, 100)
908
+ this.doorService?.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 100)
909
+ this.doorService?.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED)
910
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic TargetPosition: 100, CurrentPosition: 100`)
911
+ } else {
912
+ this.doorService?.updateCharacteristic(this.hap.Characteristic.TargetPosition, 0)
913
+ this.doorService?.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 0)
914
+ this.doorService?.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED)
915
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic TargetPosition: 0, CurrentPosition: 0`)
916
+ }
917
+ }
918
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Door On: ${this.On}`)
919
+ } else if (this.botDeviceType === 'window') {
920
+ if (this.On === undefined) {
921
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}`)
922
+ } else {
923
+ if (this.On) {
924
+ this.windowService?.updateCharacteristic(this.hap.Characteristic.TargetPosition, 100)
925
+ this.windowService?.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 100)
926
+ this.windowService?.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED)
927
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic TargetPosition: 100, CurrentPosition: 100`)
928
+ } else {
929
+ this.windowService?.updateCharacteristic(this.hap.Characteristic.TargetPosition, 0)
930
+ this.windowService?.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 0)
931
+ this.windowService?.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED)
932
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic TargetPosition: 0, CurrentPosition: 0`)
933
+ }
934
+ }
935
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Window On: ${this.On}`)
936
+ } else if (this.botDeviceType === 'windowcovering') {
937
+ if (this.On === undefined) {
938
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}`)
939
+ } else {
940
+ if (this.On) {
941
+ this.windowCoveringService?.updateCharacteristic(this.hap.Characteristic.TargetPosition, 100)
942
+ this.windowCoveringService?.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 100)
943
+ this.windowCoveringService?.updateCharacteristic(
944
+ this.hap.Characteristic.PositionState,
945
+ this.hap.Characteristic.PositionState.STOPPED,
946
+ )
947
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic TargetPosition: 100, CurrentPosition: 100`)
948
+ } else {
949
+ this.windowCoveringService?.updateCharacteristic(this.hap.Characteristic.TargetPosition, 0)
950
+ this.windowCoveringService?.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 0)
951
+ this.windowCoveringService?.updateCharacteristic(
952
+ this.hap.Characteristic.PositionState,
953
+ this.hap.Characteristic.PositionState.STOPPED,
954
+ )
955
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic TargetPosition: 0, CurrentPosition: 0`)
956
+ }
957
+ }
958
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Window Covering On: ${this.On}`)
959
+ } else if (this.botDeviceType === 'lock') {
960
+ if (this.On === undefined) {
961
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}`)
962
+ } else {
963
+ if (this.On) {
964
+ this.lockService?.updateCharacteristic(
965
+ this.hap.Characteristic.LockTargetState,
966
+ this.hap.Characteristic.LockTargetState.UNSECURED,
967
+ )
968
+ this.lockService?.updateCharacteristic(
969
+ this.hap.Characteristic.LockCurrentState,
970
+ this.hap.Characteristic.LockCurrentState.UNSECURED,
971
+ )
972
+ this.debugLog(
973
+ `${this.device.deviceType}: `
974
+ + `${this.accessory.displayName} updateCharacteristic LockTargetState: UNSECURED, LockCurrentState: UNSECURED`,
975
+ )
976
+ } else {
977
+ this.lockService?.updateCharacteristic(this.hap.Characteristic.LockTargetState, this.hap.Characteristic.LockTargetState.SECURED)
978
+ this.lockService?.updateCharacteristic(
979
+ this.hap.Characteristic.LockCurrentState,
980
+ this.hap.Characteristic.LockCurrentState.SECURED,
981
+ )
982
+ this.debugLog(
983
+ `${this.device.deviceType}: ` + `${this.accessory.displayName} updateCharacteristic LockTargetState: SECURED, LockCurrentState: SECURED`,
984
+ )
985
+ }
986
+ }
987
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Lock On: ${this.On}`)
988
+ } else if (this.botDeviceType === 'faucet') {
989
+ if (this.On === undefined) {
990
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}`)
991
+ } else {
992
+ if (this.On) {
993
+ this.faucetService?.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.ACTIVE)
994
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic Active: ${this.On}`)
995
+ } else {
996
+ this.faucetService?.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.INACTIVE)
997
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic Active: ${this.On}`)
998
+ }
999
+ }
1000
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Faucet On: ${this.On}`)
1001
+ } else if (this.botDeviceType === 'fan') {
1002
+ if (this.On === undefined) {
1003
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}`)
1004
+ } else {
1005
+ if (this.On) {
1006
+ this.fanService?.updateCharacteristic(this.hap.Characteristic.On, this.On)
1007
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic On: ${this.On}`)
1008
+ } else {
1009
+ this.fanService?.updateCharacteristic(this.hap.Characteristic.On, this.On)
1010
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic On: ${this.On}`)
1011
+ }
1012
+ }
1013
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Fan On: ${this.On}`)
1014
+ } else if (this.botDeviceType === 'stateful') {
1015
+ if (this.On === undefined) {
1016
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}`)
1017
+ } else {
1018
+ if (this.On) {
1019
+ this.statefulProgrammableSwitchService?.updateCharacteristic(
1020
+ this.hap.Characteristic.ProgrammableSwitchEvent,
1021
+ this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS,
1022
+ )
1023
+ this.statefulProgrammableSwitchService?.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState, 1)
1024
+ this.debugLog(
1025
+ `${this.device.deviceType}: `
1026
+ + `${this.accessory.displayName} updateCharacteristic ProgrammableSwitchEvent: SINGLE, ProgrammableSwitchOutputState: 1`,
1027
+ )
1028
+ } else {
1029
+ this.statefulProgrammableSwitchService?.updateCharacteristic(
1030
+ this.hap.Characteristic.ProgrammableSwitchEvent,
1031
+ this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS,
1032
+ )
1033
+ this.statefulProgrammableSwitchService?.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState, 0)
1034
+ this.debugLog(
1035
+ `${this.device.deviceType}: `
1036
+ + `${this.accessory.displayName} updateCharacteristic ProgrammableSwitchEvent: SINGLE, ProgrammableSwitchOutputState: 0`,
1037
+ )
1038
+ }
1039
+ }
1040
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} StatefulProgrammableSwitch On: ${this.On}`)
1041
+ } else if (this.botDeviceType === 'switch') {
1042
+ if (this.On === undefined) {
1043
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}`)
1044
+ } else {
1045
+ this.switchService?.updateCharacteristic(this.hap.Characteristic.On, this.On)
1046
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic On: ${this.On}`)
1047
+ }
1048
+ } else {
1049
+ if (this.On === undefined) {
1050
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}`)
1051
+ } else {
1052
+ this.outletService?.updateCharacteristic(this.hap.Characteristic.On, this.On)
1053
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic On: ${this.On}`)
1054
+ }
1055
+ }
1056
+ this.accessory.context.On = this.On
1057
+ // BatteryLevel
1058
+ if (this.BatteryLevel === undefined) {
1059
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BatteryLevel: ${this.BatteryLevel}`)
1060
+ } else {
1061
+ this.accessory.context.BatteryLevel = this.BatteryLevel
1062
+ this.batteryService?.updateCharacteristic(this.hap.Characteristic.BatteryLevel, this.BatteryLevel)
1063
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic BatteryLevel: ${this.BatteryLevel}`)
1064
+ }
1065
+ // StatusLowBattery
1066
+ if (this.StatusLowBattery === undefined) {
1067
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} StatusLowBattery: ${this.StatusLowBattery}`)
1068
+ } else {
1069
+ this.accessory.context.StatusLowBattery = this.StatusLowBattery
1070
+ this.batteryService?.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, this.StatusLowBattery)
1071
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic StatusLowBattery: ${this.StatusLowBattery}`)
1072
+ }
1073
+ }
1074
+
1075
+ async removeOutletService(accessory: PlatformAccessory): Promise<void> {
1076
+ // If outletService still present, then remove first
1077
+ this.outletService = this.accessory.getService(this.hap.Service.Outlet)
1078
+ if (this.outletService) {
1079
+ this.warnLog(`${this.device.deviceType}: ${accessory.displayName} Removing Leftover Outlet Service`)
1080
+ }
1081
+ accessory.removeService(this.outletService!)
1082
+ }
1083
+
1084
+ async removeGarageDoorService(accessory: PlatformAccessory): Promise<void> {
1085
+ // If garageDoorService still present, then remove first
1086
+ this.garageDoorService = this.accessory.getService(this.hap.Service.GarageDoorOpener)
1087
+ if (this.garageDoorService) {
1088
+ this.warnLog(`${this.device.deviceType}: ${accessory.displayName} Removing Leftover Garage Door Service`)
1089
+ }
1090
+ accessory.removeService(this.garageDoorService!)
1091
+ }
1092
+
1093
+ async removeDoorService(accessory: PlatformAccessory): Promise<void> {
1094
+ // If doorService still present, then remove first
1095
+ this.doorService = this.accessory.getService(this.hap.Service.Door)
1096
+ if (this.doorService) {
1097
+ this.warnLog(`${this.device.deviceType}: ${accessory.displayName} Removing Leftover Door Service`)
1098
+ }
1099
+ accessory.removeService(this.doorService!)
1100
+ }
1101
+
1102
+ async removeLockService(accessory: PlatformAccessory): Promise<void> {
1103
+ // If lockService still present, then remove first
1104
+ this.lockService = this.accessory.getService(this.hap.Service.LockMechanism)
1105
+ if (this.lockService) {
1106
+ this.warnLog(`${this.device.deviceType}: ${accessory.displayName} Removing Leftover Lock Service`)
1107
+ }
1108
+ accessory.removeService(this.lockService!)
1109
+ }
1110
+
1111
+ async removeFaucetService(accessory: PlatformAccessory): Promise<void> {
1112
+ // If faucetService still present, then remove first
1113
+ this.faucetService = this.accessory.getService(this.hap.Service.Faucet)
1114
+ if (this.faucetService) {
1115
+ this.warnLog(`${this.device.deviceType}: ${accessory.displayName} Removing Leftover Faucet Service`)
1116
+ }
1117
+ accessory.removeService(this.faucetService!)
1118
+ }
1119
+
1120
+ async removeFanService(accessory: PlatformAccessory): Promise<void> {
1121
+ // If fanService still present, then remove first
1122
+ this.fanService = this.accessory.getService(this.hap.Service.Fan)
1123
+ if (this.fanService) {
1124
+ this.warnLog(`${this.device.deviceType}: ${accessory.displayName} Removing Leftover Fan Service`)
1125
+ }
1126
+ accessory.removeService(this.fanService!)
1127
+ }
1128
+
1129
+ async removeWindowService(accessory: PlatformAccessory): Promise<void> {
1130
+ // If windowService still present, then remove first
1131
+ this.windowService = this.accessory.getService(this.hap.Service.Window)
1132
+ if (this.windowService) {
1133
+ this.warnLog(`${this.device.deviceType}: ${accessory.displayName} Removing Leftover Window Service`)
1134
+ }
1135
+ accessory.removeService(this.windowService!)
1136
+ }
1137
+
1138
+ async removeWindowCoveringService(accessory: PlatformAccessory): Promise<void> {
1139
+ // If windowCoveringService still present, then remove first
1140
+ this.windowCoveringService = this.accessory.getService(this.hap.Service.WindowCovering)
1141
+ if (this.windowCoveringService) {
1142
+ this.warnLog(`${this.device.deviceType}: ${accessory.displayName} Removing Leftover Window Covering Service`)
1143
+ }
1144
+ accessory.removeService(this.windowCoveringService!)
1145
+ }
1146
+
1147
+ async removeStatefulProgrammableSwitchService(accessory: PlatformAccessory): Promise<void> {
1148
+ // If statefulProgrammableSwitchService still present, then remove first
1149
+ this.statefulProgrammableSwitchService = this.accessory.getService(this.hap.Service.StatefulProgrammableSwitch)
1150
+ if (this.statefulProgrammableSwitchService) {
1151
+ this.warnLog(`${this.device.deviceType}: ${accessory.displayName} Removing Leftover Stateful Programmable Switch Service`)
1152
+ }
1153
+ accessory.removeService(this.statefulProgrammableSwitchService!)
1154
+ }
1155
+
1156
+ async removeSwitchService(accessory: PlatformAccessory): Promise<void> {
1157
+ // If switchService still present, then remove first
1158
+ this.switchService = this.accessory.getService(this.hap.Service.Switch)
1159
+ if (this.switchService) {
1160
+ this.warnLog(`${this.device.deviceType}: ${accessory.displayName} Removing Leftover Switch Service`)
1161
+ }
1162
+ accessory.removeService(this.switchService!)
1163
+ }
1164
+
1165
+ private DoublePress(device: device & devicesConfig) {
1166
+ if (device.bot?.doublePress) {
1167
+ this.doublePress = device.bot?.doublePress
1168
+ this.accessory.context.doublePress = this.doublePress
1169
+ } else {
1170
+ this.doublePress = 1
1171
+ }
1172
+ }
1173
+
1174
+ async stopScanning(switchbot: any) {
1175
+ switchbot.stopScan()
1176
+ if (this.BLE_IsConnected) {
1177
+ await this.BLEparseStatus()
1178
+ await this.updateHomeKitCharacteristics()
1179
+ } else {
1180
+ await this.BLERefreshConnection(switchbot)
1181
+ }
1182
+ }
1183
+
1184
+ async getCustomBLEAddress(switchbot: any) {
1185
+ if (this.device.customBLEaddress && this.deviceLogging.includes('debug')) {
1186
+ (async () => {
1187
+ // Start to monitor advertisement packets
1188
+ await switchbot.startScan({
1189
+ model: 'H',
1190
+ })
1191
+ // Set an event handler
1192
+ switchbot.onadvertisement = (ad: any) => {
1193
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} ad: ${JSON.stringify(ad, null, ' ')}`)
1194
+ }
1195
+ await sleep(10000)
1196
+ // Stop to monitor
1197
+ switchbot.stopScan()
1198
+ })()
1199
+ }
1200
+ }
1201
+
1202
+ async BLEPushConnection() {
1203
+ if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') {
1204
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Using OpenAPI Connection to Push Changes`)
1205
+ await this.openAPIpushChanges()
1206
+ }
1207
+ }
1208
+
1209
+ async BLERefreshConnection(switchbot: any): Promise<void> {
1210
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} wasn't able to establish BLE Connection, node-switchbot: ${JSON.stringify(switchbot)}`)
1211
+ if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') {
1212
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Using OpenAPI Connection to Refresh Status`)
1213
+ await this.openAPIRefreshStatus()
1214
+ }
1215
+ }
1216
+
1217
+ async retry({ max, fn }: { max: number, fn: { (): any, (): Promise<any> } }): Promise<null> {
1218
+ return fn().catch(async (e: any) => {
1219
+ if (max === 0) {
1220
+ throw e
1221
+ }
1222
+ this.infoLog(e)
1223
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Retrying`)
1224
+ await sleep(1000)
1225
+ return this.retry({ max: max - 1, fn })
1226
+ })
1227
+ }
1228
+
1229
+ maxRetry(): number {
1230
+ if (this.device.maxRetry) {
1231
+ return this.device.maxRetry
1232
+ } else {
1233
+ return 5
1234
+ }
1235
+ }
1236
+
1237
+ async PressOrSwitch(device: device & devicesConfig): Promise<void> {
1238
+ if (!device.bot?.mode) {
1239
+ this.botMode = 'switch'
1240
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} does not have bot mode set in the Plugin's SwitchBot Device Settings,`)
1241
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} is defaulting to "${this.botMode}" mode, you may experience issues.`)
1242
+ } else if (device.bot?.mode === 'switch') {
1243
+ this.botMode = 'switch'
1244
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Bot Mode: ${this.botMode}`)
1245
+ } else if (device.bot?.mode === 'press') {
1246
+ this.botMode = 'press'
1247
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Bot Mode: ${this.botMode}`)
1248
+ } else if (device.bot?.mode === 'multipress') {
1249
+ this.botMode = 'multipress'
1250
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Bot Mode: ${this.botMode}`)
1251
+ } else {
1252
+ throw new Error(`${this.device.deviceType}: ${this.accessory.displayName} Bot Mode: ${this.botMode}`)
1253
+ }
1254
+ }
1255
+
1256
+ async allowPushChanges(device: device & devicesConfig): Promise<void> {
1257
+ if (device.bot?.allowPush) {
1258
+ this.allowPush = true
1259
+ } else {
1260
+ this.allowPush = false
1261
+ }
1262
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Allowing Push Changes: ${this.allowPush}`)
1263
+ }
1264
+
1265
+ async scan(device: device & devicesConfig): Promise<void> {
1266
+ if (device.scanDuration) {
1267
+ this.scanDuration = this.accessory.context.scanDuration = device.scanDuration
1268
+ if (this.BLE) {
1269
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Config scanDuration: ${this.scanDuration}`)
1270
+ }
1271
+ } else {
1272
+ this.scanDuration = this.accessory.context.scanDuration = 1
1273
+ if (this.BLE) {
1274
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Default scanDuration: ${this.scanDuration}`)
1275
+ }
1276
+ }
1277
+ }
1278
+
1279
+ async statusCode(statusCode: number): Promise<void> {
1280
+ switch (statusCode) {
1281
+ case 151:
1282
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Command not supported by this deviceType, statusCode: ${statusCode}`)
1283
+ break
1284
+ case 152:
1285
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Device not found, statusCode: ${statusCode}`)
1286
+ break
1287
+ case 160:
1288
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Command is not supported, statusCode: ${statusCode}`)
1289
+ break
1290
+ case 161:
1291
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Device is offline, statusCode: ${statusCode}`)
1292
+ this.offlineOff()
1293
+ break
1294
+ case 171:
1295
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Hub Device is offline, statusCode: ${statusCode}. Hub: ${this.device.hubDeviceId}`)
1296
+ this.offlineOff()
1297
+ break
1298
+ case 190:
1299
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Device internal error due to device states not synchronized with server, Or command format is invalid, statusCode: ${statusCode}`)
1300
+ break
1301
+ case 100:
1302
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Command successfully sent, statusCode: ${statusCode}`)
1303
+ break
1304
+ case 200:
1305
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Request successful, statusCode: ${statusCode}`)
1306
+ break
1307
+ case 400:
1308
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Bad Request, The client has issued an invalid request. This is commonly used to specify validation errors in a request payload, statusCode: ${statusCode}`)
1309
+ break
1310
+ case 401:
1311
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Unauthorized, Authorization for the API is required, but the request has not been authenticated, statusCode: ${statusCode}`)
1312
+ break
1313
+ case 403:
1314
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Forbidden, The request has been authenticated but does not have appropriate permissions, or a requested resource is not found, statusCode: ${statusCode}`)
1315
+ break
1316
+ case 404:
1317
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Not Found, Specifies the requested path does not exist, statusCode: ${statusCode}`)
1318
+ break
1319
+ case 406:
1320
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Not Acceptable, The client has requested a MIME type via the Accept header for a value not supported by the server, statusCode: ${statusCode}`)
1321
+ break
1322
+ case 415:
1323
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Unsupported Media Type, The client has defined a contentType header that is not supported by the server, statusCode: ${statusCode}`)
1324
+ break
1325
+ case 422:
1326
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Unprocessable Entity, The client has made a valid request, but the server cannot process it. This is often used for APIs for which certain limits have been exceeded, statusCode: ${statusCode}`)
1327
+ break
1328
+ case 429:
1329
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Too Many Requests, The client has exceeded the number of requests allowed for a given time window, statusCode: ${statusCode}`)
1330
+ break
1331
+ case 500:
1332
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Internal Server Error, An unexpected error on the SmartThings servers has occurred. These errors should be rare, statusCode: ${statusCode}`)
1333
+ break
1334
+ default:
1335
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Unknown statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`)
1336
+ }
1337
+ }
1338
+
1339
+ async offlineOff(): Promise<void> {
1340
+ if (this.device.offline) {
1341
+ await this.deviceContext()
1342
+ await this.updateHomeKitCharacteristics()
1343
+ }
1344
+ }
1345
+
1346
+ async apiError(e: any): Promise<void> {
1347
+ if (this.botDeviceType === 'garagedoor') {
1348
+ this.garageDoorService?.updateCharacteristic(this.hap.Characteristic.TargetDoorState, e)
1349
+ this.garageDoorService?.updateCharacteristic(this.hap.Characteristic.CurrentDoorState, e)
1350
+ this.garageDoorService?.updateCharacteristic(this.hap.Characteristic.ObstructionDetected, e)
1351
+ } else if (this.botDeviceType === 'door') {
1352
+ this.doorService?.updateCharacteristic(this.hap.Characteristic.TargetPosition, e)
1353
+ this.doorService?.updateCharacteristic(this.hap.Characteristic.CurrentPosition, e)
1354
+ this.doorService?.updateCharacteristic(this.hap.Characteristic.PositionState, e)
1355
+ } else if (this.botDeviceType === 'window') {
1356
+ this.windowService?.updateCharacteristic(this.hap.Characteristic.TargetPosition, e)
1357
+ this.windowService?.updateCharacteristic(this.hap.Characteristic.CurrentPosition, e)
1358
+ this.windowService?.updateCharacteristic(this.hap.Characteristic.PositionState, e)
1359
+ } else if (this.botDeviceType === 'windowcovering') {
1360
+ this.windowCoveringService?.updateCharacteristic(this.hap.Characteristic.TargetPosition, e)
1361
+ this.windowCoveringService?.updateCharacteristic(this.hap.Characteristic.CurrentPosition, e)
1362
+ this.windowCoveringService?.updateCharacteristic(this.hap.Characteristic.PositionState, e)
1363
+ } else if (this.botDeviceType === 'lock') {
1364
+ this.doorService?.updateCharacteristic(this.hap.Characteristic.LockTargetState, e)
1365
+ this.doorService?.updateCharacteristic(this.hap.Characteristic.LockCurrentState, e)
1366
+ } else if (this.botDeviceType === 'faucet') {
1367
+ this.faucetService?.updateCharacteristic(this.hap.Characteristic.Active, e)
1368
+ } else if (this.botDeviceType === 'fan') {
1369
+ this.fanService?.updateCharacteristic(this.hap.Characteristic.On, e)
1370
+ } else if (this.botDeviceType === 'stateful') {
1371
+ this.statefulProgrammableSwitchService?.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent, e)
1372
+ this.statefulProgrammableSwitchService?.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState, e)
1373
+ } else if (this.botDeviceType === 'switch') {
1374
+ this.switchService?.updateCharacteristic(this.hap.Characteristic.On, e)
1375
+ } else {
1376
+ this.outletService?.updateCharacteristic(this.hap.Characteristic.On, e)
1377
+ }
1378
+ this.batteryService?.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e)
1379
+ this.batteryService?.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e)
1380
+ }
1381
+
1382
+ async deviceType(device: device & devicesConfig): Promise<void> {
1383
+ if (!device.bot?.deviceType && this.accessory.context.deviceType) {
1384
+ this.botDeviceType = this.accessory.context.deviceType
1385
+ this.debugWarnLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Type: ${this.botDeviceType}, from Accessory Cache.`)
1386
+ } else if (device.bot?.deviceType) {
1387
+ this.accessory.context.deviceType = device.bot.deviceType
1388
+ this.debugWarnLog(`${this.device.deviceType}: ${this.accessory.displayName} Accessory Cache: ${this.accessory.context.deviceType}`)
1389
+ this.botDeviceType = this.accessory.context.deviceType
1390
+ this.debugWarnLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Type: ${this.botDeviceType}`)
1391
+ } else {
1392
+ this.botDeviceType = 'outlet'
1393
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} No Device Type Set, deviceType: ${this.device.bot?.deviceType}`)
1394
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Using default deviceType: ${this.botDeviceType}`)
1395
+ }
1396
+ }
1397
+
1398
+ async deviceContext() {
1399
+ if (this.On === undefined) {
1400
+ this.On = false
1401
+ this.accessory.context.On = this.On
1402
+ } else {
1403
+ this.On = this.accessory.context.On
1404
+ }
1405
+ if (this.BatteryLevel === undefined) {
1406
+ this.BatteryLevel = 100
1407
+ } else {
1408
+ this.BatteryLevel = this.accessory.context.BatteryLevel
1409
+ }
1410
+ if (this.StatusLowBattery === undefined) {
1411
+ this.StatusLowBattery = this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL
1412
+ this.accessory.context.StatusLowBattery = this.StatusLowBattery
1413
+ } else {
1414
+ this.StatusLowBattery = this.accessory.context.StatusLowBattery
1415
+ }
1416
+ if (this.FirmwareRevision === undefined) {
1417
+ this.FirmwareRevision = this.platform.version
1418
+ this.accessory.context.FirmwareRevision = this.FirmwareRevision
1419
+ }
1420
+ }
1421
+
1422
+ async refreshRate(device: device & devicesConfig): Promise<void> {
1423
+ if (device.refreshRate) {
1424
+ this.deviceRefreshRate = this.accessory.context.refreshRate = device.refreshRate
1425
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Config refreshRate: ${this.deviceRefreshRate}`)
1426
+ } else if (this.platform.config.options!.refreshRate) {
1427
+ this.deviceRefreshRate = this.accessory.context.refreshRate = this.platform.config.options!.refreshRate
1428
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Platform Config refreshRate: ${this.deviceRefreshRate}`)
1429
+ }
1430
+ // pushRatePress
1431
+ if (device?.bot?.pushRatePress) {
1432
+ this.pushRatePress = device?.bot?.pushRatePress
1433
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Config Bot pushRatePress: ${this.pushRatePress}`)
1434
+ } else {
1435
+ this.pushRatePress = 15
1436
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Default Bot pushRatePress: ${this.pushRatePress}`)
1437
+ }
1438
+ }
1439
+
1440
+ async deviceConfig(device: device & devicesConfig): Promise<void> {
1441
+ let config: Partial<devicesConfig> = {}
1442
+ if (device.bot) {
1443
+ config = device.bot
1444
+ }
1445
+ if (device.connectionType !== undefined) {
1446
+ config.connectionType = device.connectionType
1447
+ }
1448
+ if (device.external !== undefined) {
1449
+ config.external = device.external
1450
+ }
1451
+ if (device.logging !== undefined) {
1452
+ config.logging = device.logging
1453
+ }
1454
+ if (device.refreshRate !== undefined) {
1455
+ config.refreshRate = device.refreshRate
1456
+ }
1457
+ if (device.scanDuration !== undefined) {
1458
+ config.scanDuration = device.scanDuration
1459
+ }
1460
+ if (device.offline !== undefined) {
1461
+ config.offline = device.offline
1462
+ }
1463
+ if (device.maxRetry !== undefined) {
1464
+ config.maxRetry = device.maxRetry
1465
+ }
1466
+ if (device.webhook !== undefined) {
1467
+ config.webhook = device.webhook
1468
+ }
1469
+ if (Object.entries(config).length !== 0) {
1470
+ this.debugWarnLog(`${this.device.deviceType}: ${this.accessory.displayName} Config: ${JSON.stringify(config)}`)
1471
+ }
1472
+ }
1473
+
1474
+ async deviceLogs(device: device & devicesConfig): Promise<void> {
1475
+ if (this.platform.debugMode) {
1476
+ this.deviceLogging = this.accessory.context.logging = 'debugMode'
1477
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Debug Mode Logging: ${this.deviceLogging}`)
1478
+ } else if (device.logging) {
1479
+ this.deviceLogging = this.accessory.context.logging = device.logging
1480
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Config Logging: ${this.deviceLogging}`)
1481
+ } else if (this.platform.config.options?.logging) {
1482
+ this.deviceLogging = this.accessory.context.logging = this.platform.config.options?.logging
1483
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Platform Config Logging: ${this.deviceLogging}`)
1484
+ } else {
1485
+ this.deviceLogging = this.accessory.context.logging = 'standard'
1486
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Logging Not Set, Using: ${this.deviceLogging}`)
1487
+ }
1488
+ }
1489
+
1490
+ /**
1491
+ * Logging for Device
1492
+ */
1493
+ infoLog(...log: any[]): void {
1494
+ if (this.enablingDeviceLogging()) {
1495
+ this.platform.log.info(String(...log))
1496
+ }
1497
+ }
1498
+
1499
+ warnLog(...log: any[]): void {
1500
+ if (this.enablingDeviceLogging()) {
1501
+ this.platform.log.warn(String(...log))
1502
+ }
1503
+ }
1504
+
1505
+ debugWarnLog(...log: any[]): void {
1506
+ if (this.enablingDeviceLogging()) {
1507
+ if (this.deviceLogging?.includes('debug')) {
1508
+ this.platform.log.warn('[DEBUG]', String(...log))
1509
+ }
1510
+ }
1511
+ }
1512
+
1513
+ errorLog(...log: any[]): void {
1514
+ if (this.enablingDeviceLogging()) {
1515
+ this.platform.log.error(String(...log))
1516
+ }
1517
+ }
1518
+
1519
+ debugErrorLog(...log: any[]): void {
1520
+ if (this.enablingDeviceLogging()) {
1521
+ if (this.deviceLogging?.includes('debug')) {
1522
+ this.platform.log.error('[DEBUG]', String(...log))
1523
+ }
1524
+ }
1525
+ }
1526
+
1527
+ debugLog(...log: any[]): void {
1528
+ if (this.enablingDeviceLogging()) {
1529
+ if (this.deviceLogging === 'debug') {
1530
+ this.platform.log.info('[DEBUG]', String(...log))
1531
+ } else {
1532
+ this.platform.log.debug(String(...log))
1533
+ }
1534
+ }
1535
+ }
1536
+
1537
+ enablingDeviceLogging(): boolean {
1538
+ return this.deviceLogging.includes('debug') || this.deviceLogging === 'standard'
1539
+ }
1540
+ }