@switchbot/homebridge-switchbot 2.0.0-beta.2 → 2.0.0-beta.20

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 (113) hide show
  1. package/CHANGELOG.md +8 -1
  2. package/README.md +1 -2
  3. package/config.schema.json +75 -20
  4. package/dist/device/bot.d.ts +32 -28
  5. package/dist/device/bot.d.ts.map +1 -1
  6. package/dist/device/bot.js +646 -648
  7. package/dist/device/bot.js.map +1 -1
  8. package/dist/device/colorbulb.d.ts +34 -19
  9. package/dist/device/colorbulb.d.ts.map +1 -1
  10. package/dist/device/colorbulb.js +429 -304
  11. package/dist/device/colorbulb.js.map +1 -1
  12. package/dist/device/contact.d.ts +16 -7
  13. package/dist/device/contact.d.ts.map +1 -1
  14. package/dist/device/contact.js +288 -230
  15. package/dist/device/contact.js.map +1 -1
  16. package/dist/device/curtain.d.ts +21 -16
  17. package/dist/device/curtain.d.ts.map +1 -1
  18. package/dist/device/curtain.js +519 -489
  19. package/dist/device/curtain.js.map +1 -1
  20. package/dist/device/humidifier.d.ts +22 -18
  21. package/dist/device/humidifier.d.ts.map +1 -1
  22. package/dist/device/humidifier.js +415 -360
  23. package/dist/device/humidifier.js.map +1 -1
  24. package/dist/device/lock.d.ts +22 -8
  25. package/dist/device/lock.d.ts.map +1 -1
  26. package/dist/device/lock.js +253 -147
  27. package/dist/device/lock.js.map +1 -1
  28. package/dist/device/meter.d.ts +21 -16
  29. package/dist/device/meter.d.ts.map +1 -1
  30. package/dist/device/meter.js +325 -255
  31. package/dist/device/meter.js.map +1 -1
  32. package/dist/device/meterplus.d.ts +23 -12
  33. package/dist/device/meterplus.d.ts.map +1 -1
  34. package/dist/device/meterplus.js +346 -208
  35. package/dist/device/meterplus.js.map +1 -1
  36. package/dist/device/motion.d.ts +17 -8
  37. package/dist/device/motion.d.ts.map +1 -1
  38. package/dist/device/motion.js +261 -209
  39. package/dist/device/motion.js.map +1 -1
  40. package/dist/device/plug.d.ts +24 -16
  41. package/dist/device/plug.d.ts.map +1 -1
  42. package/dist/device/plug.js +267 -242
  43. package/dist/device/plug.js.map +1 -1
  44. package/dist/device/striplight.d.ts +51 -16
  45. package/dist/device/striplight.d.ts.map +1 -1
  46. package/dist/device/striplight.js +527 -185
  47. package/dist/device/striplight.js.map +1 -1
  48. package/dist/irdevice/airconditioner.d.ts +7 -12
  49. package/dist/irdevice/airconditioner.d.ts.map +1 -1
  50. package/dist/irdevice/airconditioner.js +261 -230
  51. package/dist/irdevice/airconditioner.js.map +1 -1
  52. package/dist/irdevice/airpurifier.d.ts +3 -3
  53. package/dist/irdevice/airpurifier.d.ts.map +1 -1
  54. package/dist/irdevice/airpurifier.js +137 -104
  55. package/dist/irdevice/airpurifier.js.map +1 -1
  56. package/dist/irdevice/camera.d.ts +9 -8
  57. package/dist/irdevice/camera.d.ts.map +1 -1
  58. package/dist/irdevice/camera.js +116 -86
  59. package/dist/irdevice/camera.js.map +1 -1
  60. package/dist/irdevice/fan.d.ts +8 -7
  61. package/dist/irdevice/fan.d.ts.map +1 -1
  62. package/dist/irdevice/fan.js +143 -109
  63. package/dist/irdevice/fan.js.map +1 -1
  64. package/dist/irdevice/light.d.ts +9 -8
  65. package/dist/irdevice/light.d.ts.map +1 -1
  66. package/dist/irdevice/light.js +120 -103
  67. package/dist/irdevice/light.js.map +1 -1
  68. package/dist/irdevice/other.d.ts +9 -8
  69. package/dist/irdevice/other.d.ts.map +1 -1
  70. package/dist/irdevice/other.js +128 -98
  71. package/dist/irdevice/other.js.map +1 -1
  72. package/dist/irdevice/tv.d.ts +10 -9
  73. package/dist/irdevice/tv.d.ts.map +1 -1
  74. package/dist/irdevice/tv.js +126 -93
  75. package/dist/irdevice/tv.js.map +1 -1
  76. package/dist/irdevice/vacuumcleaner.d.ts +7 -10
  77. package/dist/irdevice/vacuumcleaner.d.ts.map +1 -1
  78. package/dist/irdevice/vacuumcleaner.js +115 -89
  79. package/dist/irdevice/vacuumcleaner.js.map +1 -1
  80. package/dist/irdevice/waterheater.d.ts +6 -9
  81. package/dist/irdevice/waterheater.d.ts.map +1 -1
  82. package/dist/irdevice/waterheater.js +115 -89
  83. package/dist/irdevice/waterheater.js.map +1 -1
  84. package/dist/platform.d.ts +3 -5
  85. package/dist/platform.d.ts.map +1 -1
  86. package/dist/platform.js +203 -200
  87. package/dist/platform.js.map +1 -1
  88. package/dist/settings.d.ts +15 -11
  89. package/dist/settings.d.ts.map +1 -1
  90. package/dist/settings.js.map +1 -1
  91. package/package.json +12 -10
  92. package/src/device/bot.ts +641 -657
  93. package/src/device/colorbulb.ts +440 -339
  94. package/src/device/contact.ts +289 -233
  95. package/src/device/curtain.ts +523 -503
  96. package/src/device/humidifier.ts +421 -396
  97. package/src/device/lock.ts +257 -147
  98. package/src/device/meter.ts +335 -268
  99. package/src/device/meterplus.ts +354 -213
  100. package/src/device/motion.ts +264 -213
  101. package/src/device/plug.ts +270 -251
  102. package/src/device/striplight.ts +552 -207
  103. package/src/irdevice/airconditioner.ts +263 -241
  104. package/src/irdevice/airpurifier.ts +132 -103
  105. package/src/irdevice/camera.ts +112 -85
  106. package/src/irdevice/fan.ts +138 -106
  107. package/src/irdevice/light.ts +116 -103
  108. package/src/irdevice/other.ts +124 -97
  109. package/src/irdevice/tv.ts +123 -93
  110. package/src/irdevice/vacuumcleaner.ts +112 -89
  111. package/src/irdevice/waterheater.ts +111 -89
  112. package/src/platform.ts +195 -216
  113. package/src/settings.ts +15 -11
@@ -3,6 +3,7 @@ import crypto from 'crypto';
3
3
  import { Context } from 'vm';
4
4
  import { IncomingMessage } from 'http';
5
5
  import { interval, Subject } from 'rxjs';
6
+ import superStringify from 'super-stringify';
6
7
  import { SwitchBotPlatform } from '../platform';
7
8
  import { debounceTime, skipWhile, take, tap } from 'rxjs/operators';
8
9
  import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge';
@@ -14,7 +15,6 @@ export class Plug {
14
15
 
15
16
  // Characteristic Values
16
17
  On!: CharacteristicValue;
17
- OnCached!: CharacteristicValue;
18
18
 
19
19
  // OpenAPI Others
20
20
  power: deviceStatus['power'];
@@ -43,17 +43,17 @@ export class Plug {
43
43
  plugUpdateInProgress!: boolean;
44
44
  doPlugUpdate!: Subject<void>;
45
45
 
46
+ // Connection
47
+ private readonly BLE = (this.device.connectionType === 'BLE' || this.device.connectionType === 'Both');
48
+ private readonly OpenAPI = (this.device.connectionType === 'OpenAPI' || this.device.connectionType === 'Both');
49
+
46
50
  constructor(private readonly platform: SwitchBotPlatform, private accessory: PlatformAccessory, public device: device & devicesConfig) {
47
51
  // default placeholders
48
52
  this.logs(device);
49
53
  this.scan(device);
50
54
  this.refreshRate(device);
51
55
  this.config(device);
52
- if (this.On === undefined) {
53
- this.On = false;
54
- } else {
55
- this.On = this.accessory.context.On;
56
- }
56
+ this.context();
57
57
 
58
58
  // this is subject we use to track when we need to POST changes to the SwitchBot API
59
59
  this.doPlugUpdate = new Subject();
@@ -89,7 +89,7 @@ export class Plug {
89
89
  // see https://developers.homebridge.io/#/service/Outlet
90
90
 
91
91
  // create handlers for required characteristics
92
- this.outletService.getCharacteristic(this.platform.Characteristic.On).onSet(this.handleOnSet.bind(this));
92
+ this.outletService.getCharacteristic(this.platform.Characteristic.On).onSet(this.OnSet.bind(this));
93
93
 
94
94
  // Update Homekit
95
95
  this.updateHomeKitCharacteristics();
@@ -114,26 +114,31 @@ export class Plug {
114
114
  try {
115
115
  await this.pushChanges();
116
116
  } catch (e: any) {
117
- this.errorLog(`Plug: ${this.accessory.displayName} failed pushChanges`);
118
- if (this.deviceLogging.includes('debug')) {
119
- this.errorLog(`Plug: ${this.accessory.displayName} failed pushChanges,` + ` Error Message: ${JSON.stringify(e.message)}`);
120
- }
121
117
  this.apiError(e);
118
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed pushChanges with ${this.device.connectionType} Connection,`
119
+ + ` Error Message: ${superStringify(e.message)}`);
122
120
  }
123
121
  this.plugUpdateInProgress = false;
124
122
  });
125
123
  }
126
124
 
125
+ /**
126
+ * Parse the device status from the SwitchBot api
127
+ */
127
128
  async parseStatus(): Promise<void> {
128
- if (this.SwitchToOpenAPI || !this.device.ble) {
129
+ if (this.BLE && !this.SwitchToOpenAPI) {
130
+ await this.BLEparseStatus();
131
+ } else if (this.OpenAPI && this.platform.config.credentials?.token) {
132
+ this.SwitchToOpenAPI = false;
129
133
  await this.openAPIparseStatus();
130
134
  } else {
131
- await this.BLEparseStatus();
135
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Connection Type:`
136
+ + ` ${this.device.connectionType}, parseStatus will not happen.`);
132
137
  }
133
138
  }
134
139
 
135
140
  async BLEparseStatus(): Promise<void> {
136
- this.debugLog(`Plug: ${this.accessory.displayName} BLE parseStatus`);
141
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLEparseStatus`);
137
142
  // State
138
143
  switch (this.state) {
139
144
  case 'on':
@@ -142,10 +147,11 @@ export class Plug {
142
147
  default:
143
148
  this.On = false;
144
149
  }
145
- this.debugLog(`Plug: ${this.accessory.displayName} On: ${this.On}`);
150
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}`);
146
151
  }
147
152
 
148
153
  async openAPIparseStatus() {
154
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIparseStatus`);
149
155
  switch (this.power) {
150
156
  case 'on':
151
157
  this.On = true;
@@ -153,26 +159,32 @@ export class Plug {
153
159
  default:
154
160
  this.On = false;
155
161
  }
156
- this.debugLog(`Plug: ${this.accessory.displayName} On: ${this.On}`);
162
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}`);
157
163
  }
158
164
 
165
+ /**
166
+ * Asks the SwitchBot API for the latest device information
167
+ */
159
168
  async refreshStatus(): Promise<void> {
160
- if (this.device.ble) {
169
+ if (this.BLE) {
161
170
  await this.BLERefreshStatus();
162
- } else {
171
+ } else if (this.OpenAPI && this.platform.config.credentials?.token) {
163
172
  await this.openAPIRefreshStatus();
173
+ } else {
174
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Connection Type:`
175
+ + ` ${this.device.connectionType}, refreshStatus will not happen.`);
164
176
  }
165
177
  }
166
178
 
167
179
  async BLERefreshStatus(): Promise<void> {
168
- this.debugLog(`Plug: ${this.accessory.displayName} BLE refreshStatus`);
180
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLERefreshStatus`);
169
181
  const switchbot = await this.platform.connectBLE();
170
182
  // Convert to BLE Address
171
183
  this.device.bleMac = this.device
172
184
  .deviceId!.match(/.{1,2}/g)!
173
185
  .join(':')
174
186
  .toLowerCase();
175
- this.debugLog(`Plug: ${this.accessory.displayName} BLE Address: ${this.device.bleMac}`);
187
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLE Address: ${this.device.bleMac}`);
176
188
  this.getCustomBLEAddress(switchbot);
177
189
  // Start to monitor advertisement packets
178
190
  if (switchbot !== false) {
@@ -188,8 +200,8 @@ export class Plug {
188
200
  if (this.deviceLogging.includes('debug')) {
189
201
  this.infoLog(this.address);
190
202
  this.infoLog(this.device.bleMac);
191
- this.infoLog(`Plug: ${this.accessory.displayName} BLE Address Found: ${this.address}`);
192
- this.infoLog(`Plug: ${this.accessory.displayName} Config BLE Address: ${this.device.bleMac}`);
203
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} BLE Address Found: ${this.address}`);
204
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Config BLE Address: ${this.device.bleMac}`);
193
205
  }
194
206
  this.serviceData = ad.serviceData;
195
207
  this.state = ad.serviceData.state;
@@ -199,19 +211,19 @@ export class Plug {
199
211
  this.wifiRssi = ad.serviceData.wifiRssi;
200
212
  this.overload = ad.serviceData.overload;
201
213
  this.currentPower = ad.serviceData.currentPower;
202
- this.debugLog(`Plug: ${this.accessory.displayName} serviceData: ${JSON.stringify(ad.serviceData)}`);
214
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} serviceData: ${superStringify(ad.serviceData)}`);
203
215
  this.debugLog(
204
- `Plug: ${this.accessory.displayName} state: ${ad.serviceData.state}, ` +
216
+ `${this.device.deviceType}: ${this.accessory.displayName} state: ${ad.serviceData.state}, ` +
205
217
  `delay: ${ad.serviceData.delay}, timer: ${ad.serviceData.timer}, syncUtcTime: ${ad.serviceData.syncUtcTime} ` +
206
218
  `wifiRssi: ${ad.serviceData.wifiRssi}, overload: ${ad.serviceData.overload}, currentPower: ${ad.serviceData.currentPower}`,
207
219
  );
208
220
 
209
221
  if (this.serviceData) {
210
222
  this.connected = true;
211
- this.debugLog(`Plug: ${this.accessory.displayName} connected: ${this.connected}`);
223
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} connected: ${this.connected}`);
212
224
  } else {
213
225
  this.connected = false;
214
- this.debugLog(`Plug: ${this.accessory.displayName} connected: ${this.connected}`);
226
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} connected: ${this.connected}`);
215
227
  }
216
228
  };
217
229
  // Wait 2 seconds
@@ -228,67 +240,25 @@ export class Plug {
228
240
  }
229
241
  })
230
242
  .catch(async (e: any) => {
231
- this.errorLog(`Plug: ${this.accessory.displayName} failed refreshStatus with BLE Connection`);
232
- if (this.deviceLogging.includes('debug')) {
233
- this.errorLog(
234
- `Plug: ${this.accessory.displayName} failed refreshStatus with BLE Connection,` + ` Error Message: ${JSON.stringify(e.message)}`,
235
- );
236
- }
237
- if (this.platform.config.credentials?.token) {
238
- this.warnLog(`Plug: ${this.accessory.displayName} Using OpenAPI Connection`);
239
- this.SwitchToOpenAPI = true;
240
- await this.openAPIRefreshStatus();
241
- }
242
243
  this.apiError(e);
244
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed BLERefreshStatus with ${this.device.connectionType}`
245
+ + ` Connection, Error Message: ${superStringify(e.message)}`);
246
+ await this.BLEconnection(switchbot);
243
247
  });
244
248
  } else {
245
249
  await this.BLEconnection(switchbot);
246
250
  }
247
251
  }
248
252
 
249
- BLEmodel(): 'g' | 'j' {
250
- if (this.device.deviceType === 'Plug Mini (US)') {
251
- return 'g';
252
- } else {
253
- return 'j';
254
- }
255
- }
256
-
257
- async getCustomBLEAddress(switchbot: any) {
258
- if (this.device.customBLEaddress && this.deviceLogging.includes('debug')) {
259
- (async () => {
260
- // Start to monitor advertisement packets
261
- await switchbot.startScan({
262
- model: this.BLEmodel(),
263
- });
264
- // Set an event handler
265
- switchbot.onadvertisement = (ad: any) => {
266
- this.warnLog(`Plug: ${this.accessory.displayName} ad: ${JSON.stringify(ad, null, ' ')}`);
267
- };
268
- await switchbot.wait(10000);
269
- // Stop to monitor
270
- switchbot.stopScan();
271
- })();
272
- }
273
- }
274
-
275
- async BLEconnection(switchbot: any): Promise<void> {
276
- this.errorLog(`Plug: ${this.accessory.displayName} wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`);
277
- if (this.platform.config.credentials?.token) {
278
- this.warnLog(`Plug: ${this.accessory.displayName} Using OpenAPI Connection`);
279
- this.SwitchToOpenAPI = true;
280
- await this.openAPIRefreshStatus();
281
- }
282
- }
283
-
284
253
  async openAPIRefreshStatus() {
254
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIRefreshStatus`);
285
255
  try {
286
256
  const t = Date.now();
287
257
  const nonce = 'requestID';
288
258
  const data = this.platform.config.credentials?.token + t + nonce;
289
259
  const signTerm = crypto.createHmac('sha256', this.platform.config.credentials?.secret).update(Buffer.from(data, 'utf-8')).digest();
290
260
  const sign = signTerm.toString('base64');
291
- this.debugLog(`Plug: ${this.accessory.displayName} sign: ${sign}`);
261
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} sign: ${sign}`);
292
262
  const options = {
293
263
  hostname: HostDomain,
294
264
  port: 443,
@@ -303,36 +273,33 @@ export class Plug {
303
273
  },
304
274
  };
305
275
  const req = https.request(options, (res) => {
306
- this.debugLog(`Plug: ${this.accessory.displayName} statusCode: ${res.statusCode}`);
276
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIRefreshStatus statusCode: ${res.statusCode}`);
277
+ this.statusCode({ res });
307
278
  let rawData = '';
308
279
  res.on('data', (d) => {
309
280
  rawData += d;
310
- this.debugLog(`Plug: ${this.accessory.displayName} d: ${d}`);
281
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} d: ${d}`);
311
282
  });
312
283
  res.on('end', () => {
313
284
  try {
314
285
  this.deviceStatus = JSON.parse(rawData);
315
- this.debugLog(`Plug: ${this.accessory.displayName} refreshStatus: ${JSON.stringify(this.deviceStatus)}`);
286
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIRefreshStatus: ${superStringify(this.deviceStatus)}`);
316
287
  this.power = this.deviceStatus.body.power;
317
288
  this.parseStatus();
318
289
  this.updateHomeKitCharacteristics();
319
290
  } catch (e: any) {
320
- this.errorLog(`Plug: ${this.accessory.displayName} error message: ${e.message}`);
291
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} error message: ${e.message}`);
321
292
  }
322
293
  });
323
294
  });
324
295
  req.on('error', (e: any) => {
325
- this.errorLog(`Plug: ${this.accessory.displayName} error message: ${e.message}`);
296
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} error message: ${e.message}`);
326
297
  });
327
298
  req.end();
328
299
  } catch (e: any) {
329
- this.errorLog(`Plug: ${this.accessory.displayName} failed refreshStatus with OpenAPI Connection`);
330
- if (this.deviceLogging.includes('debug')) {
331
- this.errorLog(
332
- `Plug: ${this.accessory.displayName} failed refreshStatus with OpenAPI Connection,` + ` Error Message: ${JSON.stringify(e.message)}`,
333
- );
334
- }
335
300
  this.apiError(e);
301
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed openAPIRefreshStatus with ${this.device.connectionType}`
302
+ + ` Connection, Error Message: ${superStringify(e.message)}`);
336
303
  }
337
304
  }
338
305
 
@@ -347,76 +314,67 @@ export class Plug {
347
314
  */
348
315
 
349
316
  async pushChanges(): Promise<void> {
350
- if (this.device.ble) {
317
+ if (this.BLE) {
351
318
  await this.BLEpushChanges();
352
- } else {
319
+ } else if (this.OpenAPI && this.platform.config.credentials?.token) {
353
320
  await this.openAPIpushChanges();
321
+ } else {
322
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Connection Type:`
323
+ + ` ${this.device.connectionType}, pushChanges will not happen.`);
354
324
  }
355
- }
356
-
357
- async BLEpushChanges(): Promise<void> {
358
- this.debugLog(`Plug: ${this.accessory.displayName} BLE pushChanges On: ${this.On} OnCached: ${this.OnCached}`);
359
- this.debugLog(`Plug: ${this.accessory.displayName} BLE pushChanges`);
360
- const switchbot = await this.platform.connectBLE();
361
- // Convert to BLE Address
362
- this.device.bleMac = this.device
363
- .deviceId!.match(/.{1,2}/g)!
364
- .join(':')
365
- .toLowerCase();
366
- this.debugLog(`Plug: ${this.accessory.displayName} BLE Address: ${this.device.bleMac}`);
367
- switchbot
368
- .discover({
369
- model: this.BLEmodel(),
370
- id: this.device.bleMac,
371
- })
372
- .then((device_list: any) => {
373
- this.infoLog(`Plug: ${this.accessory.displayName} On: ${this.On}`);
374
- return this.turnOnOff(device_list);
375
- })
376
- .then(() => {
377
- this.debugLog(`Plug: ${this.accessory.displayName} Done.`);
378
- this.On = false;
379
- this.OnCached = this.On;
380
- this.accessory.context.On = this.OnCached;
381
- })
382
- .catch(async (e: any) => {
383
- this.errorLog(`Plug: ${this.accessory.displayName} failed pushChanges with BLE Connection`);
384
- if (this.deviceLogging.includes('debug')) {
385
- this.errorLog(
386
- `Plug: ${this.accessory.displayName} failed pushChanges with BLE Connection,` + ` Error Message: ${JSON.stringify(e.message)}`,
387
- );
388
- }
389
- if (this.platform.debugMode) {
390
- this.errorLog(`Plug: ${this.accessory.displayName} failed pushChanges with BLE Connection,` + ` Error: ${JSON.stringify(e)}`);
391
- }
392
- if (this.platform.config.credentials?.token) {
393
- this.warnLog(`Plug: ${this.accessory.displayName} Using OpenAPI Connection`);
394
- await this.openAPIpushChanges();
395
- }
396
- this.apiError(e);
325
+ interval(5000)
326
+ .pipe(take(1))
327
+ .subscribe(async () => {
328
+ await this.refreshStatus();
397
329
  });
398
- this.OnCached = this.On;
399
- this.accessory.context.On = this.OnCached;
400
330
  }
401
331
 
402
- async turnOnOff(device_list: any): Promise<any> {
403
- return await this.retry({
404
- max: await this.maxRetry(),
405
- fn: () => {
406
- if (this.On) {
407
- return device_list[0].turnOn({ id: this.device.bleMac });
408
- } else {
409
- return device_list[0].turnOff({ id: this.device.bleMac });
410
- }
411
- },
412
- });
332
+ async BLEpushChanges(): Promise<void> {
333
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLEpushChanges`);
334
+ if (this.On !== this.accessory.context.On) {
335
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLEpushChanges On: ${this.On} OnCached: ${this.accessory.context.On}`);
336
+ const switchbot = await this.platform.connectBLE();
337
+ // Convert to BLE Address
338
+ this.device.bleMac = this.device
339
+ .deviceId!.match(/.{1,2}/g)!
340
+ .join(':')
341
+ .toLowerCase();
342
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLE Address: ${this.device.bleMac}`);
343
+ switchbot
344
+ .discover({
345
+ model: this.BLEmodel(),
346
+ id: this.device.bleMac,
347
+ })
348
+ .then((device_list: any) => {
349
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}`);
350
+ return this.turnOnOff(device_list);
351
+ })
352
+ .then(() => {
353
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Done.`);
354
+ this.On = false;
355
+ })
356
+ .catch(async (e: any) => {
357
+ this.apiError(e);
358
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed BLEpushChanges with ${this.device.connectionType}`
359
+ + ` Connection, Error Message: ${superStringify(e.message)}`);
360
+ if (this.platform.config.credentials?.token && this.device.connectionType !== 'Both') {
361
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Using OpenAPI Connection`);
362
+ await this.openAPIpushChanges();
363
+ }
364
+ });
365
+ } else {
366
+ this.debugLog(
367
+ `${this.device.deviceType}: ${this.accessory.displayName} No BLEpushChanges.` + `On: ${this.On}, `
368
+ +`OnCached: ${this.accessory.context.On}`,
369
+ );
370
+ }
413
371
  }
414
372
 
415
373
  async openAPIpushChanges() {
416
- if (this.platform.config.credentials?.token) {
417
- if (this.On !== this.OnCached) {
374
+ try {
375
+ if (this.On !== this.accessory.context.On) {
418
376
  // Make Push On request to the API
419
- this.debugLog(`Plug: ${this.accessory.displayName} OpenAPI pushChanges`);
377
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIpushChanges`);
420
378
  const t = Date.now();
421
379
  const nonce = 'requestID';
422
380
  const data = this.platform.config.credentials?.token + t + nonce;
@@ -424,7 +382,7 @@ export class Plug {
424
382
  .update(Buffer.from(data, 'utf-8'))
425
383
  .digest();
426
384
  const sign = signTerm.toString('base64');
427
- this.debugLog(`Plug: ${this.accessory.displayName} sign: ${sign}`);
385
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} sign: ${sign}`);
428
386
 
429
387
  let command = '';
430
388
  if (this.On) {
@@ -432,12 +390,12 @@ export class Plug {
432
390
  } else {
433
391
  command = 'turnOff';
434
392
  }
435
- const body = JSON.stringify({
393
+ const body = superStringify({
436
394
  'command': `${command}`,
437
395
  'parameter': 'default',
438
396
  'commandType': 'command',
439
397
  });
440
- this.infoLog(`Plug: ${this.accessory.displayName} Sending request to SwitchBot API. body: ${body},`);
398
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Sending request to SwitchBot API. body: ${body},`);
441
399
  const options = {
442
400
  hostname: HostDomain,
443
401
  port: 443,
@@ -453,73 +411,178 @@ export class Plug {
453
411
  },
454
412
  };
455
413
  const req = https.request(options, res => {
456
- this.debugLog(`Plug: ${this.accessory.displayName} statusCode: ${res.statusCode}`);
414
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIpushChanges statusCode: ${res.statusCode}`);
457
415
  this.statusCode({ res });
458
416
  res.on('data', d => {
459
- this.debugLog(`Plug: ${this.accessory.displayName} d: ${d}`);
417
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} d: ${d}`);
460
418
  });
461
419
  });
462
420
  req.on('error', (e: any) => {
463
- this.errorLog(`Plug: ${this.accessory.displayName} error message: ${e.message}`);
421
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} error message: ${e.message}`);
464
422
  });
465
423
  req.write(body);
466
424
  req.end();
467
- this.debugLog(`Plug: ${this.accessory.displayName} openAPIpushChanges: ${JSON.stringify(req)}`);
468
- this.OnCached = this.On;
469
- this.accessory.context.On = this.OnCached;
425
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIpushChanges: ${superStringify(req)}`);
426
+ } else {
427
+ this.debugLog(
428
+ `${this.device.deviceType}: ${this.accessory.displayName} No openAPIpushChanges.` + `On: ${this.On}, `
429
+ +`OnCached: ${this.accessory.context.On}`,
430
+ );
470
431
  }
471
- interval(5000)
472
- .pipe(take(1))
473
- .subscribe(async () => {
474
- await this.refreshStatus();
475
- });
432
+ } catch (e: any) {
433
+ this.apiError(e);
434
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed openAPIpushChanges with ${this.device.connectionType}`
435
+ + ` Connection, Error Message: ${superStringify(e.message)}`,
436
+ );
476
437
  }
477
438
  }
478
439
 
440
+ /**
441
+ * Handle requests to set the value of the "On" characteristic
442
+ */
443
+ async OnSet(value: CharacteristicValue): Promise<void> {
444
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Set On: ${value}`);
445
+
446
+ this.On = value;
447
+ this.doPlugUpdate.next();
448
+ }
449
+
479
450
  async updateHomeKitCharacteristics(): Promise<void> {
480
451
  if (this.On === undefined) {
481
- this.debugLog(`Plug: ${this.accessory.displayName} On: ${this.On}`);
452
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}`);
482
453
  } else {
454
+ this.accessory.context.On = this.On;
483
455
  this.outletService.updateCharacteristic(this.platform.Characteristic.On, this.On);
484
- this.debugLog(`Plug: ${this.accessory.displayName} updateCharacteristic On: ${this.On}`);
456
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic On: ${this.On}`);
485
457
  }
486
458
  }
487
459
 
488
- async apiError(e: any): Promise<void> {
489
- this.outletService.updateCharacteristic(this.platform.Characteristic.On, e);
490
- //throw new this.platform.api.hap.HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
460
+ BLEmodel(): 'g' | 'j' {
461
+ if (this.device.deviceType === 'Plug Mini (US)') {
462
+ return 'g';
463
+ } else {
464
+ return 'j';
465
+ }
466
+ }
467
+
468
+ async getCustomBLEAddress(switchbot: any) {
469
+ if (this.device.customBLEaddress && this.deviceLogging.includes('debug')) {
470
+ (async () => {
471
+ // Start to monitor advertisement packets
472
+ await switchbot.startScan({
473
+ model: this.BLEmodel(),
474
+ });
475
+ // Set an event handler
476
+ switchbot.onadvertisement = (ad: any) => {
477
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} ad: ${superStringify(ad, null, ' ')}`);
478
+ };
479
+ await switchbot.wait(10000);
480
+ // Stop to monitor
481
+ switchbot.stopScan();
482
+ })();
483
+ }
484
+ }
485
+
486
+ async BLEconnection(switchbot: any): Promise<void> {
487
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`);
488
+ if (this.platform.config.credentials?.token && this.device.connectionType !== 'Both') {
489
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Using OpenAPI Connection`);
490
+ this.SwitchToOpenAPI = true;
491
+ await this.openAPIRefreshStatus();
492
+ }
493
+ }
494
+
495
+ async turnOnOff(device_list: any): Promise<any> {
496
+ return await this.retry({
497
+ max: await this.maxRetry(),
498
+ fn: () => {
499
+ if (this.On) {
500
+ return device_list[0].turnOn({ id: this.device.bleMac });
501
+ } else {
502
+ return device_list[0].turnOff({ id: this.device.bleMac });
503
+ }
504
+ },
505
+ });
506
+ }
507
+
508
+ async retry({ max, fn }: { max: number; fn: { (): any; (): Promise<any> } }): Promise<null> {
509
+ return fn().catch(async (err: any) => {
510
+ if (max === 0) {
511
+ throw err;
512
+ }
513
+ this.infoLog(err);
514
+ this.infoLog('Retrying');
515
+ await this.switchbot.wait(1000);
516
+ return this.retry({ max: max - 1, fn });
517
+ });
518
+ }
519
+
520
+ async maxRetry(): Promise<number> {
521
+ let maxRetry: number;
522
+ if (this.device.bot?.maxRetry) {
523
+ maxRetry = this.device.bot?.maxRetry;
524
+ } else {
525
+ maxRetry = 5;
526
+ }
527
+ return maxRetry;
528
+ }
529
+
530
+ model(device: device & devicesConfig): string {
531
+ let model: string;
532
+ if (device.deviceType === 'Plug Mini (US)') {
533
+ model = 'W1901400';
534
+ } else if (device.deviceType === 'Plug Mini (JP)') {
535
+ model = 'W2001400';
536
+ } else {
537
+ model = 'SP11';
538
+ }
539
+ return model;
540
+ }
541
+
542
+ async scan(device: device & devicesConfig): Promise<void> {
543
+ if (device.scanDuration) {
544
+ this.scanDuration = this.accessory.context.scanDuration = device.scanDuration;
545
+ if (this.BLE) {
546
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Config scanDuration: ${this.scanDuration}`);
547
+ }
548
+ } else {
549
+ this.scanDuration = this.accessory.context.scanDuration = 1;
550
+ if (this.BLE) {
551
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Default scanDuration: ${this.scanDuration}`);
552
+ }
553
+ }
491
554
  }
492
555
 
493
556
  async statusCode({ res }: { res: IncomingMessage }): Promise<void> {
494
557
  switch (res.statusCode) {
495
558
  case 151:
496
- this.errorLog(`Plug: ${this.accessory.displayName} Command not supported by this device type.`);
559
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Command not supported by this device type.`);
497
560
  break;
498
561
  case 152:
499
- this.errorLog(`Plug: ${this.accessory.displayName} Device not found.`);
562
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Device not found.`);
500
563
  break;
501
564
  case 160:
502
- this.errorLog(`Plug: ${this.accessory.displayName} Command is not supported.`);
565
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Command is not supported.`);
503
566
  break;
504
567
  case 161:
505
- this.errorLog(`Plug: ${this.accessory.displayName} Device is offline.`);
568
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Device is offline.`);
506
569
  this.offlineOff();
507
570
  break;
508
571
  case 171:
509
- this.errorLog(`Plug: ${this.accessory.displayName} Hub Device is offline. Hub: ${this.device.hubDeviceId}`);
572
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Hub Device is offline. Hub: ${this.device.hubDeviceId}`);
510
573
  this.offlineOff();
511
574
  break;
512
575
  case 190:
513
576
  this.errorLog(
514
- `Plug: ${this.accessory.displayName} Device internal error due to device states not synchronized with server,` +
515
- ` Or command: ${JSON.stringify(res)} format is invalid`,
577
+ `${this.device.deviceType}: ${this.accessory.displayName} Device internal error due to device states not synchronized with server,` +
578
+ ` Or command: ${superStringify(res)} format is invalid`,
516
579
  );
517
580
  break;
518
581
  case 100:
519
- this.debugLog(`Plug: ${this.accessory.displayName} Command successfully sent.`);
582
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Command successfully sent.`);
520
583
  break;
521
584
  default:
522
- this.debugLog(`Plug: ${this.accessory.displayName} Unknown statusCode.`);
585
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Unknown statusCode.`);
523
586
  }
524
587
  }
525
588
 
@@ -527,51 +590,46 @@ export class Plug {
527
590
  if (this.device.offline) {
528
591
  this.On = false;
529
592
  this.outletService.getCharacteristic(this.platform.Characteristic.On).updateValue(this.On);
593
+ await this.updateHomeKitCharacteristics();
530
594
  }
531
595
  }
532
596
 
533
- /**
534
- * Handle requests to set the value of the "On" characteristic
535
- */
536
- async handleOnSet(value: CharacteristicValue): Promise<void> {
537
- this.debugLog(`Plug: ${this.accessory.displayName} - Set On: ${value}`);
538
-
539
- this.On = value;
540
- this.doPlugUpdate.next();
597
+ async apiError(e: any): Promise<void> {
598
+ this.outletService.updateCharacteristic(this.platform.Characteristic.On, e);
541
599
  }
542
600
 
543
- model(device: device & devicesConfig): string {
544
- let model: string;
545
- if (device.deviceType === 'Plug Mini (US)') {
546
- model = 'W1901400';
547
- } else if (device.deviceType === 'Plug Mini (JP)') {
548
- model = 'W2001400';
601
+ FirmwareRevision(accessory: PlatformAccessory<Context>, device: device & devicesConfig): CharacteristicValue {
602
+ let FirmwareRevision: string;
603
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName}`
604
+ +` accessory.context.FirmwareRevision: ${accessory.context.FirmwareRevision}`);
605
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} device.firmware: ${device.firmware}`);
606
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} this.platform.version: ${this.platform.version}`);
607
+ if (accessory.context.FirmwareRevision) {
608
+ FirmwareRevision = accessory.context.FirmwareRevision;
609
+ } else if (device.firmware) {
610
+ FirmwareRevision = device.firmware;
549
611
  } else {
550
- model = 'SP11';
612
+ FirmwareRevision = this.platform.version;
551
613
  }
552
- return model;
614
+ return FirmwareRevision;
553
615
  }
554
616
 
555
- async retry({ max, fn }: { max: number; fn: { (): any; (): Promise<any> } }): Promise<null> {
556
- return fn().catch(async (err: any) => {
557
- if (max === 0) {
558
- throw err;
559
- }
560
- this.infoLog(err);
561
- this.infoLog('Retrying');
562
- await this.switchbot.wait(1000);
563
- return this.retry({ max: max - 1, fn });
564
- });
617
+ async context() {
618
+ if (this.On === undefined) {
619
+ this.On = false;
620
+ } else {
621
+ this.On = this.accessory.context.On;
622
+ }
565
623
  }
566
624
 
567
- async maxRetry(): Promise<number> {
568
- let maxRetry: number;
569
- if (this.device.bot?.maxRetry) {
570
- maxRetry = this.device.bot?.maxRetry;
571
- } else {
572
- maxRetry = 5;
625
+ async refreshRate(device: device & devicesConfig): Promise<void> {
626
+ if (device.refreshRate) {
627
+ this.deviceRefreshRate = this.accessory.context.refreshRate = device.refreshRate;
628
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Config refreshRate: ${this.deviceRefreshRate}`);
629
+ } else if (this.platform.config.options!.refreshRate) {
630
+ this.deviceRefreshRate = this.accessory.context.refreshRate = this.platform.config.options!.refreshRate;
631
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Platform Config refreshRate: ${this.deviceRefreshRate}`);
573
632
  }
574
- return maxRetry;
575
633
  }
576
634
 
577
635
  async config(device: device & devicesConfig): Promise<void> {
@@ -579,8 +637,8 @@ export class Plug {
579
637
  if (device.plug) {
580
638
  config = device.plug;
581
639
  }
582
- if (device.ble !== undefined) {
583
- config['ble'] = device.ble;
640
+ if (device.connectionType !== undefined) {
641
+ config['connectionType'] = device.connectionType;
584
642
  }
585
643
  if (device.logging !== undefined) {
586
644
  config['logging'] = device.logging;
@@ -595,65 +653,26 @@ export class Plug {
595
653
  config['offline'] = device.offline;
596
654
  }
597
655
  if (Object.entries(config).length !== 0) {
598
- this.infoLog(`Plug: ${this.accessory.displayName} Config: ${JSON.stringify(config)}`);
599
- }
600
- }
601
-
602
- async refreshRate(device: device & devicesConfig): Promise<void> {
603
- if (device.refreshRate) {
604
- this.deviceRefreshRate = this.accessory.context.refreshRate = device.refreshRate;
605
- this.debugLog(`Plug: ${this.accessory.displayName} Using Device Config refreshRate: ${this.deviceRefreshRate}`);
606
- } else if (this.platform.config.options!.refreshRate) {
607
- this.deviceRefreshRate = this.accessory.context.refreshRate = this.platform.config.options!.refreshRate;
608
- this.debugLog(`Plug: ${this.accessory.displayName} Using Platform Config refreshRate: ${this.deviceRefreshRate}`);
609
- }
610
- }
611
-
612
- async scan(device: device & devicesConfig): Promise<void> {
613
- if (device.scanDuration) {
614
- this.scanDuration = this.accessory.context.scanDuration = device.scanDuration;
615
- if (device.ble) {
616
- this.debugLog(`Plug: ${this.accessory.displayName} Using Device Config scanDuration: ${this.scanDuration}`);
617
- }
618
- } else {
619
- this.scanDuration = this.accessory.context.scanDuration = 1;
620
- if (this.device.ble) {
621
- this.debugLog(`Plug: ${this.accessory.displayName} Using Default scanDuration: ${this.scanDuration}`);
622
- }
656
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Config: ${superStringify(config)}`);
623
657
  }
624
658
  }
625
659
 
626
660
  async logs(device: device & devicesConfig): Promise<void> {
627
661
  if (this.platform.debugMode) {
628
662
  this.deviceLogging = this.accessory.context.logging = 'debugMode';
629
- this.debugLog(`Plug: ${this.accessory.displayName} Using Debug Mode Logging: ${this.deviceLogging}`);
663
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Debug Mode Logging: ${this.deviceLogging}`);
630
664
  } else if (device.logging) {
631
665
  this.deviceLogging = this.accessory.context.logging = device.logging;
632
- this.debugLog(`Plug: ${this.accessory.displayName} Using Device Config Logging: ${this.deviceLogging}`);
666
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Config Logging: ${this.deviceLogging}`);
633
667
  } else if (this.platform.config.options?.logging) {
634
668
  this.deviceLogging = this.accessory.context.logging = this.platform.config.options?.logging;
635
- this.debugLog(`Plug: ${this.accessory.displayName} Using Platform Config Logging: ${this.deviceLogging}`);
669
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Platform Config Logging: ${this.deviceLogging}`);
636
670
  } else {
637
671
  this.deviceLogging = this.accessory.context.logging = 'standard';
638
- this.debugLog(`Plug: ${this.accessory.displayName} Logging Not Set, Using: ${this.deviceLogging}`);
672
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Logging Not Set, Using: ${this.deviceLogging}`);
639
673
  }
640
674
  }
641
675
 
642
- FirmwareRevision(accessory: PlatformAccessory<Context>, device: device & devicesConfig): CharacteristicValue {
643
- let FirmwareRevision: string;
644
- this.debugLog(`Plug: ${this.accessory.displayName} accessory.context.FirmwareRevision: ${accessory.context.FirmwareRevision}`);
645
- this.debugLog(`Plug: ${this.accessory.displayName} device.firmware: ${device.firmware}`);
646
- this.debugLog(`Plug: ${this.accessory.displayName} this.platform.version: ${this.platform.version}`);
647
- if (accessory.context.FirmwareRevision) {
648
- FirmwareRevision = accessory.context.FirmwareRevision;
649
- } else if (device.firmware) {
650
- FirmwareRevision = device.firmware;
651
- } else {
652
- FirmwareRevision = this.platform.version;
653
- }
654
- return FirmwareRevision;
655
- }
656
-
657
676
  /**
658
677
  * Logging for Device
659
678
  */