@switchbot/homebridge-switchbot 1.14.2 → 1.14.3-beta.0

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 (41) hide show
  1. package/config.schema.json +1 -1
  2. package/dist/device/bot.js +3 -3
  3. package/dist/device/bot.js.map +1 -1
  4. package/dist/device/contact.js +3 -3
  5. package/dist/device/contact.js.map +1 -1
  6. package/dist/device/curtain.js +3 -3
  7. package/dist/device/curtain.js.map +1 -1
  8. package/dist/device/humidifier.js +3 -3
  9. package/dist/device/lock.js +3 -3
  10. package/dist/device/lock.js.map +1 -1
  11. package/dist/device/meter.js +3 -3
  12. package/dist/device/meter.js.map +1 -1
  13. package/dist/device/meterplus.js +3 -3
  14. package/dist/device/motion.js +3 -3
  15. package/dist/device/motion.js.map +1 -1
  16. package/dist/device/plug.d.ts +31 -1
  17. package/dist/device/plug.d.ts.map +1 -1
  18. package/dist/device/plug.js +231 -5
  19. package/dist/device/plug.js.map +1 -1
  20. package/dist/device/striplight.js +3 -3
  21. package/dist/device/striplight.js.map +1 -1
  22. package/dist/irdevice/airconditioner.d.ts +2 -0
  23. package/dist/irdevice/airconditioner.d.ts.map +1 -1
  24. package/dist/irdevice/airconditioner.js +4 -2
  25. package/dist/irdevice/airconditioner.js.map +1 -1
  26. package/dist/settings.d.ts +6 -0
  27. package/dist/settings.d.ts.map +1 -1
  28. package/dist/settings.js.map +1 -1
  29. package/package.json +78 -78
  30. package/src/device/bot.ts +3 -3
  31. package/src/device/contact.ts +3 -3
  32. package/src/device/curtain.ts +3 -3
  33. package/src/device/humidifier.ts +3 -3
  34. package/src/device/lock.ts +3 -3
  35. package/src/device/meter.ts +3 -3
  36. package/src/device/meterplus.ts +3 -3
  37. package/src/device/motion.ts +3 -3
  38. package/src/device/plug.ts +258 -7
  39. package/src/device/striplight.ts +3 -3
  40. package/src/irdevice/airconditioner.ts +5 -2
  41. package/src/settings.ts +13 -1
@@ -3,7 +3,7 @@ import { interval, Subject } from 'rxjs';
3
3
  import { SwitchBotPlatform } from '../platform';
4
4
  import { debounceTime, skipWhile, take, tap } from 'rxjs/operators';
5
5
  import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge';
6
- import { DeviceURL, device, devicesConfig, deviceStatusResponse, payload, deviceStatus } from '../settings';
6
+ import { DeviceURL, device, devicesConfig, deviceStatusResponse, payload, deviceStatus, ad, serviceData, switchbot } from '../settings';
7
7
  import { Context } from 'vm';
8
8
 
9
9
  export class Plug {
@@ -18,6 +18,13 @@ export class Plug {
18
18
  power: deviceStatus['power'];
19
19
  deviceStatus!: deviceStatusResponse;
20
20
 
21
+ // BLE Others
22
+ connected?: boolean;
23
+ switchbot!: switchbot;
24
+ SwitchToOpenAPI?: boolean;
25
+ serviceData!: serviceData;
26
+ address!: ad['address'];
27
+
21
28
  // Config
22
29
  scanDuration!: number;
23
30
  deviceLogging!: string;
@@ -26,6 +33,13 @@ export class Plug {
26
33
  // Updates
27
34
  plugUpdateInProgress!: boolean;
28
35
  doPlugUpdate!: Subject<void>;
36
+ state: serviceData['state'];
37
+ delay: serviceData['delay'];
38
+ timer: serviceData['timer'];
39
+ syncUtcTime: serviceData['syncUtcTime'];
40
+ wifiRssi: serviceData['wifiRssi'];
41
+ overload: serviceData['overload'];
42
+ currentPower: serviceData['currentPower'];
29
43
 
30
44
  constructor(private readonly platform: SwitchBotPlatform, private accessory: PlatformAccessory, public device: device & devicesConfig) {
31
45
  // default placeholders
@@ -53,7 +67,8 @@ export class Plug {
53
67
  .setCharacteristic(this.platform.Characteristic.Model, this.model(device))
54
68
  .setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!)
55
69
  .setCharacteristic(this.platform.Characteristic.FirmwareRevision, this.FirmwareRevision(accessory, device))
56
- .getCharacteristic(this.platform.Characteristic.FirmwareRevision).updateValue(this.FirmwareRevision(accessory, device));
70
+ .getCharacteristic(this.platform.Characteristic.FirmwareRevision)
71
+ .updateValue(this.FirmwareRevision(accessory, device));
57
72
 
58
73
  // get the Outlet service if it exists, otherwise create a new Outlet service
59
74
  // you can create multiple services for each accessory
@@ -108,6 +123,27 @@ export class Plug {
108
123
  }
109
124
 
110
125
  async parseStatus(): Promise<void> {
126
+ if (this.SwitchToOpenAPI || !this.device.ble) {
127
+ await this.openAPIparseStatus();
128
+ } else {
129
+ await this.BLEparseStatus();
130
+ }
131
+ }
132
+
133
+ async BLEparseStatus(): Promise<void> {
134
+ this.debugLog(`Plug: ${this.accessory.displayName} BLE parseStatus`);
135
+ // State
136
+ switch (this.state) {
137
+ case 'on':
138
+ this.On = true;
139
+ break;
140
+ default:
141
+ this.On = false;
142
+ }
143
+ this.debugLog(`Plug: ${this.accessory.displayName} On: ${this.On}`);
144
+ }
145
+
146
+ async openAPIparseStatus() {
111
147
  switch (this.power) {
112
148
  case 'on':
113
149
  this.On = true;
@@ -115,10 +151,135 @@ export class Plug {
115
151
  default:
116
152
  this.On = false;
117
153
  }
118
- this.debugLog(`Plug ${this.accessory.displayName} On: ${this.On}`);
154
+ this.debugLog(`Plug: ${this.accessory.displayName} On: ${this.On}`);
119
155
  }
120
156
 
121
157
  async refreshStatus(): Promise<void> {
158
+ if (this.device.ble) {
159
+ await this.BLERefreshStatus();
160
+ } else {
161
+ await this.openAPIRefreshStatus();
162
+ }
163
+ }
164
+
165
+ async BLERefreshStatus(): Promise<void> {
166
+ this.debugLog(`Plug: ${this.accessory.displayName} BLE refreshStatus`);
167
+ const switchbot = await this.platform.connectBLE();
168
+ // Convert to BLE Address
169
+ this.device.bleMac = this.device
170
+ .deviceId!.match(/.{1,2}/g)!
171
+ .join(':')
172
+ .toLowerCase();
173
+ this.debugLog(`Plug: ${this.accessory.displayName} BLE Address: ${this.device.bleMac}`);
174
+ this.getCustomBLEAddress(switchbot);
175
+ // Start to monitor advertisement packets
176
+ if (switchbot !== false) {
177
+ switchbot
178
+ .startScan({
179
+ model: this.BLEmodel(),
180
+ id: this.device.bleMac,
181
+ })
182
+ .then(() => {
183
+ // Set an event hander
184
+ switchbot.onadvertisement = (ad: any) => {
185
+ this.address = ad.address;
186
+ if (this.deviceLogging.includes('debug')) {
187
+ this.infoLog(this.address);
188
+ this.infoLog(this.device.bleMac);
189
+ this.infoLog(`Plug: ${this.accessory.displayName} BLE Address Found: ${this.address}`);
190
+ this.infoLog(`Plug: ${this.accessory.displayName} Config BLE Address: ${this.device.bleMac}`);
191
+ }
192
+ this.serviceData = ad.serviceData;
193
+ this.state = ad.serviceData.state;
194
+ this.delay = ad.serviceData.delay;
195
+ this.timer = ad.serviceData.timer;
196
+ this.syncUtcTime = ad.serviceData.syncUtcTime;
197
+ this.wifiRssi = ad.serviceData.wifiRssi;
198
+ this.overload = ad.serviceData.overload;
199
+ this.currentPower = ad.serviceData.currentPower;
200
+ this.debugLog(`Plug: ${this.accessory.displayName} serviceData: ${JSON.stringify(ad.serviceData)}`);
201
+ this.debugLog(
202
+ `Plug: ${this.accessory.displayName} state: ${ad.serviceData.state}, ` +
203
+ `delay: ${ad.serviceData.delay}, timer: ${ad.serviceData.timer}, syncUtcTime: ${ad.serviceData.syncUtcTime} ` +
204
+ `wifiRssi: ${ad.serviceData.wifiRssi}, overload: ${ad.serviceData.overload}, currentPower: ${ad.serviceData.currentPower}`,
205
+ );
206
+
207
+ if (this.serviceData) {
208
+ this.connected = true;
209
+ this.debugLog(`Plug: ${this.accessory.displayName} connected: ${this.connected}`);
210
+ } else {
211
+ this.connected = false;
212
+ this.debugLog(`Plug: ${this.accessory.displayName} connected: ${this.connected}`);
213
+ }
214
+ };
215
+ // Wait 2 seconds
216
+ return switchbot.wait(this.scanDuration * 1000);
217
+ })
218
+ .then(async () => {
219
+ // Stop to monitor
220
+ switchbot.stopScan();
221
+ if (this.connected) {
222
+ this.parseStatus();
223
+ this.updateHomeKitCharacteristics();
224
+ } else {
225
+ await this.BLEconnection(switchbot);
226
+ }
227
+ })
228
+ .catch(async (e: any) => {
229
+ this.errorLog(`Plug: ${this.accessory.displayName} failed refreshStatus with BLE Connection`);
230
+ if (this.deviceLogging.includes('debug')) {
231
+ this.errorLog(
232
+ `Plug: ${this.accessory.displayName} failed refreshStatus with BLE Connection,` + ` Error Message: ${JSON.stringify(e.message)}`,
233
+ );
234
+ }
235
+ if (this.platform.config.credentials?.openToken) {
236
+ this.warnLog(`Plug: ${this.accessory.displayName} Using OpenAPI Connection`);
237
+ this.SwitchToOpenAPI = true;
238
+ await this.openAPIRefreshStatus();
239
+ }
240
+ this.apiError(e);
241
+ });
242
+ } else {
243
+ await this.BLEconnection(switchbot);
244
+ }
245
+ }
246
+
247
+ BLEmodel(): 'g' | 'j' {
248
+ if (this.device.deviceType === 'Plug Mini (US)') {
249
+ return 'g';
250
+ } else {
251
+ return 'j';
252
+ }
253
+ }
254
+
255
+ async getCustomBLEAddress(switchbot: any) {
256
+ if (this.device.customBLEaddress && this.deviceLogging.includes('debug')) {
257
+ (async () => {
258
+ // Start to monitor advertisement packets
259
+ await switchbot.startScan({
260
+ model: this.BLEmodel(),
261
+ });
262
+ // Set an event handler
263
+ switchbot.onadvertisement = (ad: any) => {
264
+ this.warnLog(JSON.stringify(ad, null, ' '));
265
+ };
266
+ await switchbot.wait(10000);
267
+ // Stop to monitor
268
+ switchbot.stopScan();
269
+ })();
270
+ }
271
+ }
272
+
273
+ async BLEconnection(switchbot: any): Promise<void> {
274
+ this.errorLog(`Plug: ${this.accessory.displayName} wasn't able to establish BLE Connection, node-switchPlug: ${switchbot}`);
275
+ if (this.platform.config.credentials?.openToken) {
276
+ this.warnLog(`Plug: ${this.accessory.displayName} Using OpenAPI Connection`);
277
+ this.SwitchToOpenAPI = true;
278
+ await this.openAPIRefreshStatus();
279
+ }
280
+ }
281
+
282
+ async openAPIRefreshStatus() {
122
283
  try {
123
284
  this.deviceStatus = (await this.platform.axios.get(`${DeviceURL}/${this.device.deviceId}/status`)).data;
124
285
  this.debugLog(`Plug: ${this.accessory.displayName} refreshStatus: ${JSON.stringify(this.deviceStatus)}`);
@@ -145,7 +306,75 @@ export class Plug {
145
306
  * Plug Mini (US/JP) - "command" turnOff default = set to OFF state
146
307
  * Plug Mini (US/JP) - "command" toggle default = toggle state
147
308
  */
309
+
148
310
  async pushChanges(): Promise<void> {
311
+ if (this.device.ble) {
312
+ await this.BLEpushChanges();
313
+ } else {
314
+ await this.openAPIpushChanges();
315
+ }
316
+ }
317
+
318
+ async BLEpushChanges(): Promise<void> {
319
+ this.debugLog(`Plug: ${this.accessory.displayName} BLE pushChanges On: ${this.On} OnCached: ${this.OnCached}`);
320
+ this.debugLog(`Plug: ${this.accessory.displayName} BLE pushChanges`);
321
+ const switchbot = await this.platform.connectBLE();
322
+ // Convert to BLE Address
323
+ this.device.bleMac = this.device
324
+ .deviceId!.match(/.{1,2}/g)!
325
+ .join(':')
326
+ .toLowerCase();
327
+ this.debugLog(`Plug: ${this.accessory.displayName} BLE Address: ${this.device.bleMac}`);
328
+ switchbot
329
+ .discover({ model: this.BLEmodel, quick: true, id: this.device.bleMac })
330
+ .then((device_list: any) => {
331
+ this.infoLog(`Bot: ${this.accessory.displayName} On: ${this.On}`);
332
+ return this.turnOnOff(device_list);
333
+ })
334
+ .then(() => {
335
+ this.debugLog(`Plug: ${this.accessory.displayName} Done.`);
336
+ this.On = false;
337
+ this.OnCached = this.On;
338
+ this.accessory.context.On = this.OnCached;
339
+ setTimeout(() => {
340
+ this.outletService?.getCharacteristic(this.platform.Characteristic.On).updateValue(this.On);
341
+ this.debugLog(`Plug: ${this.accessory.displayName} On: ${this.On}, Switch Timeout`);
342
+ }, 500);
343
+ })
344
+ .catch(async (e: any) => {
345
+ this.errorLog(`Plug: ${this.accessory.displayName} failed pushChanges with BLE Connection`);
346
+ if (this.deviceLogging.includes('debug')) {
347
+ this.errorLog(
348
+ `Plug: ${this.accessory.displayName} failed pushChanges with BLE Connection,` + ` Error Message: ${JSON.stringify(e.message)}`,
349
+ );
350
+ }
351
+ if (this.platform.debugMode) {
352
+ this.errorLog(`Plug: ${this.accessory.displayName} failed pushChanges with BLE Connection,` + ` Error: ${JSON.stringify(e)}`);
353
+ }
354
+ if (this.platform.config.credentials?.openToken) {
355
+ this.warnLog(`Plug: ${this.accessory.displayName} Using OpenAPI Connection`);
356
+ await this.openAPIpushChanges();
357
+ }
358
+ this.apiError(e);
359
+ });
360
+ this.OnCached = this.On;
361
+ this.accessory.context.On = this.OnCached;
362
+ }
363
+
364
+ async turnOnOff(device_list: any): Promise<any> {
365
+ return await this.retry({
366
+ max: await this.maxRetry(),
367
+ fn: () => {
368
+ if (this.On) {
369
+ return device_list[0].turnOn({ id: this.device.bleMac });
370
+ } else {
371
+ return device_list[0].turnOff({ id: this.device.bleMac });
372
+ }
373
+ },
374
+ });
375
+ }
376
+
377
+ private async openAPIpushChanges() {
149
378
  if (this.On !== this.OnCached) {
150
379
  const payload = {
151
380
  commandType: 'command',
@@ -160,7 +389,7 @@ export class Plug {
160
389
 
161
390
  this.infoLog(
162
391
  `Plug: ${this.accessory.displayName} Sending request to SwitchBot API. command: ${payload.command},` +
163
- ` parameter: ${payload.parameter}, commandType: ${payload.commandType}`,
392
+ ` parameter: ${payload.parameter}, commandType: ${payload.commandType}`,
164
393
  );
165
394
 
166
395
  // Make the API request
@@ -253,6 +482,28 @@ export class Plug {
253
482
  return model;
254
483
  }
255
484
 
485
+ async retry({ max, fn }: { max: number; fn: { (): any; (): Promise<any> } }): Promise<null> {
486
+ return fn().catch(async (err: any) => {
487
+ if (max === 0) {
488
+ throw err;
489
+ }
490
+ this.infoLog(err);
491
+ this.infoLog('Retrying');
492
+ await this.switchbot.wait(1000);
493
+ return this.retry({ max: max - 1, fn });
494
+ });
495
+ }
496
+
497
+ async maxRetry(): Promise<number> {
498
+ let maxRetry: number;
499
+ if (this.device.bot?.maxRetry) {
500
+ maxRetry = this.device.bot?.maxRetry;
501
+ } else {
502
+ maxRetry = 5;
503
+ }
504
+ return maxRetry;
505
+ }
506
+
256
507
  async config(device: device & devicesConfig): Promise<void> {
257
508
  let config = {};
258
509
  if (device.plug) {
@@ -320,9 +571,9 @@ export class Plug {
320
571
 
321
572
  FirmwareRevision(accessory: PlatformAccessory<Context>, device: device & devicesConfig): CharacteristicValue {
322
573
  let FirmwareRevision: string;
323
- this.debugLog(`Color Bulb: ${this.accessory.displayName} accessory.context.FirmwareRevision: ${accessory.context.FirmwareRevision}`);
324
- this.debugLog(`Color Bulb: ${this.accessory.displayName} device.firmware: ${device.firmware}`);
325
- this.debugLog(`Color Bulb: ${this.accessory.displayName} this.platform.version: ${this.platform.version}`);
574
+ this.debugLog(`Plug: ${this.accessory.displayName} accessory.context.FirmwareRevision: ${accessory.context.FirmwareRevision}`);
575
+ this.debugLog(`Plug: ${this.accessory.displayName} device.firmware: ${device.firmware}`);
576
+ this.debugLog(`Plug: ${this.accessory.displayName} this.platform.version: ${this.platform.version}`);
326
577
  if (accessory.context.FirmwareRevision) {
327
578
  FirmwareRevision = accessory.context.FirmwareRevision;
328
579
  } else if (device.firmware) {
@@ -542,9 +542,9 @@ export class StripLight {
542
542
 
543
543
  FirmwareRevision(accessory: PlatformAccessory<Context>, device: device & devicesConfig): CharacteristicValue {
544
544
  let FirmwareRevision: string;
545
- this.debugLog(`Color Bulb: ${this.accessory.displayName} accessory.context.FirmwareRevision: ${accessory.context.FirmwareRevision}`);
546
- this.debugLog(`Color Bulb: ${this.accessory.displayName} device.firmware: ${device.firmware}`);
547
- this.debugLog(`Color Bulb: ${this.accessory.displayName} this.platform.version: ${this.platform.version}`);
545
+ this.debugLog(`Strip Light: ${this.accessory.displayName} accessory.context.FirmwareRevision: ${accessory.context.FirmwareRevision}`);
546
+ this.debugLog(`Strip Light: ${this.accessory.displayName} device.firmware: ${device.firmware}`);
547
+ this.debugLog(`Strip Light: ${this.accessory.displayName} this.platform.version: ${this.platform.version}`);
548
548
  if (accessory.context.FirmwareRevision) {
549
549
  FirmwareRevision = accessory.context.FirmwareRevision;
550
550
  } else if (device.firmware) {
@@ -44,6 +44,9 @@ export class AirConditioner {
44
44
  hide_automode?: boolean;
45
45
  pushOn?: boolean;
46
46
 
47
+ private readonly valid12 = [1, 2];
48
+ private readonly valid012 = [0, 1, 2];
49
+
47
50
  constructor(private readonly platform: SwitchBotPlatform, private accessory: PlatformAccessory, public device: irdevice & irDevicesConfig) {
48
51
  // default placeholders
49
52
  this.logs(device);
@@ -430,13 +433,13 @@ export class AirConditioner {
430
433
  }
431
434
 
432
435
  async TargetHeaterCoolerStateGet(): Promise<CharacteristicValue> {
433
- if (this.ValidValues === [0, 1, 2]) {
436
+ if (this.ValidValues === this.valid012) {
434
437
  this.TargetHeaterCoolerState = this.platform.Characteristic.TargetHeaterCoolerState.AUTO;
435
438
  this.debugLog(
436
439
  `Air Conditioner: ${this.accessory.displayName} Get (AUTO) TargetHeaterCoolerState: ${this.CurrentHeaterCoolerState},` +
437
440
  ` ValidValues: ${this.ValidValues}`,
438
441
  );
439
- } else if (this.ValidValues === [1, 2]) {
442
+ } else if (this.ValidValues === this.valid12) {
440
443
  this.TargetHeaterCoolerState =
441
444
  this.platform.Characteristic.TargetHeaterCoolerState.COOL || this.platform.Characteristic.TargetHeaterCoolerState.HEAT;
442
445
  this.debugLog(
package/src/settings.ts CHANGED
@@ -323,7 +323,7 @@ export type serviceData = {
323
323
  mode?: boolean;
324
324
  //Bot State
325
325
  state?: string;
326
- //Battery percentage left on Bot, Meter, Motion, Contact, and Curtain
326
+ //Battery percentage left on Bot, Meter, Motion, Contact, PlugMini, and Curtain
327
327
  battery?: number;
328
328
  //Humidifier's humidity level percentage
329
329
  percentage?: boolean | string;
@@ -350,6 +350,18 @@ export type serviceData = {
350
350
  calibration?: boolean;
351
351
  //Current Curtain Positon %
352
352
  position?: number;
353
+ //PlugMini - Is there a delay?
354
+ delay?: boolean;
355
+ //PlugMini - Is there a Timer?
356
+ timer?: boolean;
357
+ //PlugMini - Is the UTC time has been synchronized?
358
+ syncUtcTime?: boolean;
359
+ //PlugMini - The Wifi RSSI Signal
360
+ wifiRssi?: number;
361
+ //PlugMini - Whether the Plug Mini is overloaded, more than 15A current overload
362
+ overload?: boolean;
363
+ //PlugMini - Plug Mini current power value of the load
364
+ currentPower?: number;
353
365
  };
354
366
 
355
367
  export type temperature = {