@switchbot/homebridge-switchbot 0.1.2-beta.0 → 0.1.2-beta.12
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.
- package/.github/ISSUE_TEMPLATE/bug-report.yml +1 -1
- package/.github/ISSUE_TEMPLATE/config.yml +6 -6
- package/.github/ISSUE_TEMPLATE/support-request.yml +1 -1
- package/.github/PULL_REQUEST_TEMPLATE/pull_requests.md +26 -6
- package/.github/dependabot.yml +2 -0
- package/.github/workflows/codeql-analysis.yml +71 -0
- package/.github/workflows/issue-validate.yml +3 -3
- package/.github/workflows/nodejs-beta.yml +4 -4
- package/.github/workflows/nodejs.yml +4 -4
- package/CHANGELOG.md +5 -1
- package/config.schema.json +18 -15
- package/dist/devices/bots.d.ts +1 -0
- package/dist/devices/bots.d.ts.map +1 -1
- package/dist/devices/bots.js +72 -24
- package/dist/devices/bots.js.map +1 -1
- package/dist/devices/bulb.d.ts +10 -1
- package/dist/devices/bulb.d.ts.map +1 -1
- package/dist/devices/bulb.js +13 -2
- package/dist/devices/bulb.js.map +1 -1
- package/dist/devices/contact.d.ts +13 -3
- package/dist/devices/contact.d.ts.map +1 -1
- package/dist/devices/contact.js +78 -3
- package/dist/devices/contact.js.map +1 -1
- package/dist/devices/curtains.d.ts +19 -8
- package/dist/devices/curtains.d.ts.map +1 -1
- package/dist/devices/curtains.js +98 -21
- package/dist/devices/curtains.js.map +1 -1
- package/dist/devices/humidifiers.d.ts.map +1 -1
- package/dist/devices/humidifiers.js +4 -4
- package/dist/devices/humidifiers.js.map +1 -1
- package/dist/devices/indoorcam.d.ts +42 -0
- package/dist/devices/indoorcam.d.ts.map +1 -0
- package/dist/devices/indoorcam.js +235 -0
- package/dist/devices/indoorcam.js.map +1 -0
- package/dist/devices/meters.d.ts +13 -4
- package/dist/devices/meters.d.ts.map +1 -1
- package/dist/devices/meters.js +36 -17
- package/dist/devices/meters.js.map +1 -1
- package/dist/devices/motion.d.ts +11 -3
- package/dist/devices/motion.d.ts.map +1 -1
- package/dist/devices/motion.js +70 -8
- package/dist/devices/motion.js.map +1 -1
- package/dist/devices/plugs.d.ts +11 -1
- package/dist/devices/plugs.d.ts.map +1 -1
- package/dist/devices/plugs.js +67 -1
- package/dist/devices/plugs.js.map +1 -1
- package/dist/irdevices/airconditioners.js +2 -2
- package/dist/irdevices/airconditioners.js.map +1 -1
- package/dist/irdevices/airpurifiers.js +2 -2
- package/dist/irdevices/airpurifiers.js.map +1 -1
- package/dist/irdevices/cameras.js +1 -1
- package/dist/irdevices/cameras.js.map +1 -1
- package/dist/irdevices/fans.js +1 -1
- package/dist/irdevices/fans.js.map +1 -1
- package/dist/irdevices/lights.js +3 -3
- package/dist/irdevices/lights.js.map +1 -1
- package/dist/irdevices/others.js +1 -1
- package/dist/irdevices/others.js.map +1 -1
- package/dist/irdevices/tvs.d.ts.map +1 -1
- package/dist/irdevices/tvs.js +2 -3
- package/dist/irdevices/tvs.js.map +1 -1
- package/dist/irdevices/vacuumcleaners.js +1 -1
- package/dist/irdevices/vacuumcleaners.js.map +1 -1
- package/dist/platform.d.ts +1 -0
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +104 -93
- package/dist/platform.js.map +1 -1
- package/dist/settings.d.ts +1 -2
- package/dist/settings.d.ts.map +1 -1
- package/package.json +9 -9
- package/src/devices/bots.ts +87 -37
- package/src/devices/bulb.ts +33 -3
- package/src/devices/contact.ts +102 -10
- package/src/devices/curtains.ts +137 -37
- package/src/devices/humidifiers.ts +9 -4
- package/src/devices/indoorcam.ts +304 -0
- package/src/devices/meters.ts +58 -22
- package/src/devices/motion.ts +93 -13
- package/src/devices/plugs.ts +89 -4
- package/src/irdevices/airconditioners.ts +2 -2
- package/src/irdevices/airpurifiers.ts +2 -2
- package/src/irdevices/cameras.ts +1 -1
- package/src/irdevices/fans.ts +1 -1
- package/src/irdevices/lights.ts +3 -3
- package/src/irdevices/others.ts +1 -1
- package/src/irdevices/tvs.ts +2 -4
- package/src/irdevices/vacuumcleaners.ts +1 -1
- package/src/platform.ts +120 -95
- package/src/settings.ts +1 -2
- package/.github/ISSUE_TEMPLATE.md +0 -48
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { Service, PlatformAccessory, CharacteristicValue, MacAddress } from 'homebridge';
|
|
2
|
+
import { SwitchBotPlatform } from '../platform';
|
|
3
|
+
import { interval, Subject } from 'rxjs';
|
|
4
|
+
import { debounceTime, skipWhile, tap } from 'rxjs/operators';
|
|
5
|
+
import { DeviceURL, device, deviceStatusResponse } from '../settings';
|
|
6
|
+
import { AxiosResponse } from 'axios';
|
|
7
|
+
|
|
8
|
+
export class IndoorCam {
|
|
9
|
+
// Services
|
|
10
|
+
private service: Service;
|
|
11
|
+
|
|
12
|
+
// Characteristic Values
|
|
13
|
+
On!: CharacteristicValue;
|
|
14
|
+
OutletInUse!: CharacteristicValue;
|
|
15
|
+
|
|
16
|
+
// Others
|
|
17
|
+
deviceStatus!: deviceStatusResponse;
|
|
18
|
+
switchbot!: {
|
|
19
|
+
discover: (
|
|
20
|
+
arg0:
|
|
21
|
+
{
|
|
22
|
+
duration: any;
|
|
23
|
+
model: string;
|
|
24
|
+
quick: boolean;
|
|
25
|
+
id: MacAddress;
|
|
26
|
+
}
|
|
27
|
+
) => Promise<any>;
|
|
28
|
+
wait: (
|
|
29
|
+
arg0: number
|
|
30
|
+
) => any;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Updates
|
|
34
|
+
cameraUpdateInProgress!: boolean;
|
|
35
|
+
doCameraUpdate;
|
|
36
|
+
|
|
37
|
+
constructor(
|
|
38
|
+
private readonly platform: SwitchBotPlatform,
|
|
39
|
+
private accessory: PlatformAccessory,
|
|
40
|
+
public device: device,
|
|
41
|
+
) {
|
|
42
|
+
// default placeholders
|
|
43
|
+
this.On = false;
|
|
44
|
+
this.OutletInUse = true;
|
|
45
|
+
|
|
46
|
+
// BLE Connections
|
|
47
|
+
if (this.platform.config.options?.ble?.includes(this.device.deviceId!)) {
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
49
|
+
const SwitchBot = require('node-switchbot');
|
|
50
|
+
this.switchbot = new SwitchBot();
|
|
51
|
+
const colon = device.deviceId!.match(/.{1,2}/g);
|
|
52
|
+
const bleMac = colon!.join(':'); //returns 1A:23:B4:56:78:9A;
|
|
53
|
+
this.device.bleMac = bleMac.toLowerCase();
|
|
54
|
+
if (this.platform.config.options.debug === 'device') {
|
|
55
|
+
this.platform.debug(this.device.bleMac.toLowerCase());
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// this is subject we use to track when we need to POST changes to the SwitchBot API
|
|
60
|
+
this.doCameraUpdate = new Subject();
|
|
61
|
+
this.cameraUpdateInProgress = false;
|
|
62
|
+
|
|
63
|
+
// Retrieve initial values and updateHomekit
|
|
64
|
+
this.refreshStatus();
|
|
65
|
+
|
|
66
|
+
// set accessory information
|
|
67
|
+
accessory
|
|
68
|
+
.getService(this.platform.Service.AccessoryInformation)!
|
|
69
|
+
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'SwitchBot')
|
|
70
|
+
.setCharacteristic(this.platform.Characteristic.Model, 'SWITCHBOT-CAMERA-')
|
|
71
|
+
.setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!);
|
|
72
|
+
|
|
73
|
+
// get the WindowCovering service if it exists, otherwise create a new WindowCovering service
|
|
74
|
+
// you can create multiple services for each accessory
|
|
75
|
+
(this.service =
|
|
76
|
+
accessory.getService(this.platform.Service.Outlet) ||
|
|
77
|
+
accessory.addService(this.platform.Service.Outlet)), `${device.deviceName} ${device.deviceType}`;
|
|
78
|
+
|
|
79
|
+
// To avoid "Cannot add a Service with the same UUID another Service without also defining a unique 'subtype' property." error,
|
|
80
|
+
// when creating multiple services of the same type, you need to use the following syntax to specify a name and subtype id:
|
|
81
|
+
// accessory.getService('NAME') ?? accessory.addService(this.platform.Service.Lightbulb, 'NAME', 'USER_DEFINED_SUBTYPE');
|
|
82
|
+
|
|
83
|
+
// set the service name, this is what is displayed as the default name on the Home app
|
|
84
|
+
// in this example we are using the name we stored in the `accessory.context` in the `discoverDevices` method.
|
|
85
|
+
this.service.setCharacteristic(this.platform.Characteristic.Name, accessory.displayName);
|
|
86
|
+
|
|
87
|
+
// each service must implement at-minimum the "required characteristics" for the given service type
|
|
88
|
+
// see https://developers.homebridge.io/#/service/WindowCovering
|
|
89
|
+
|
|
90
|
+
// create handlers for required characteristics
|
|
91
|
+
this.service.getCharacteristic(this.platform.Characteristic.On).onSet(this.OnSet.bind(this));
|
|
92
|
+
|
|
93
|
+
this.service.setCharacteristic(this.platform.Characteristic.OutletInUse, this.OutletInUse || true);
|
|
94
|
+
|
|
95
|
+
// Update Homekit
|
|
96
|
+
this.updateHomeKitCharacteristics();
|
|
97
|
+
|
|
98
|
+
// Start an update interval
|
|
99
|
+
interval(this.platform.config.options!.refreshRate! * 1000)
|
|
100
|
+
.pipe(skipWhile(() => this.cameraUpdateInProgress))
|
|
101
|
+
.subscribe(() => {
|
|
102
|
+
this.refreshStatus();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Watch for Camera change events
|
|
106
|
+
// We put in a debounce of 100ms so we don't make duplicate calls
|
|
107
|
+
this.doCameraUpdate
|
|
108
|
+
.pipe(
|
|
109
|
+
tap(() => {
|
|
110
|
+
this.cameraUpdateInProgress = true;
|
|
111
|
+
}),
|
|
112
|
+
debounceTime(this.platform.config.options!.pushRate! * 1000),
|
|
113
|
+
)
|
|
114
|
+
.subscribe(async () => {
|
|
115
|
+
try {
|
|
116
|
+
await this.pushChanges();
|
|
117
|
+
} catch (e: any) {
|
|
118
|
+
this.platform.log.error(JSON.stringify(e.message));
|
|
119
|
+
this.platform.debug(`Camera ${accessory.displayName} - ${JSON.stringify(e)}`);
|
|
120
|
+
this.apiError(e);
|
|
121
|
+
}
|
|
122
|
+
this.cameraUpdateInProgress = false;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
parseStatus() {
|
|
127
|
+
switch (this.deviceStatus.body.power) {
|
|
128
|
+
case 'on':
|
|
129
|
+
this.On = true;
|
|
130
|
+
break;
|
|
131
|
+
default:
|
|
132
|
+
this.On = false;
|
|
133
|
+
}
|
|
134
|
+
this.platform.debug(`Camera ${this.accessory.displayName} On: ${this.On}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async refreshStatus() {
|
|
138
|
+
if (this.platform.config.options?.ble?.includes(this.device.deviceId!)) {
|
|
139
|
+
this.platform.log.warn('BLE DEVICE-REFRESH');
|
|
140
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
141
|
+
const Switchbot = require('node-switchbot');
|
|
142
|
+
const switchbot = new Switchbot();
|
|
143
|
+
const colon = this.device.deviceId!.match(/.{1,2}/g);
|
|
144
|
+
const bleMac = colon!.join(':'); //returns 1A:23:B4:56:78:9A;
|
|
145
|
+
this.device.bleMac = bleMac.toLowerCase();
|
|
146
|
+
if (this.platform.config.options.debug === 'device') {
|
|
147
|
+
this.platform.debug(this.device.bleMac!);
|
|
148
|
+
}
|
|
149
|
+
switchbot.onadvertisement = (ad: any) => {
|
|
150
|
+
this.platform.log.info(JSON.stringify(ad, null, ' '));
|
|
151
|
+
this.platform.log.warn('ad:', JSON.stringify(ad));
|
|
152
|
+
};
|
|
153
|
+
switchbot
|
|
154
|
+
.startScan({
|
|
155
|
+
id: this.device.bleMac,
|
|
156
|
+
})
|
|
157
|
+
.then(() => {
|
|
158
|
+
return switchbot.wait(this.platform.config.options!.refreshRate! * 1000);
|
|
159
|
+
})
|
|
160
|
+
.then(() => {
|
|
161
|
+
switchbot.stopScan();
|
|
162
|
+
})
|
|
163
|
+
.catch(async (error: any) => {
|
|
164
|
+
this.platform.log.error(error);
|
|
165
|
+
await this.openAPIRefreshStatus();
|
|
166
|
+
});
|
|
167
|
+
setInterval(() => {
|
|
168
|
+
this.platform.log.info('Start scan ' + this.device.deviceName + '(' + this.device.bleMac + ')');
|
|
169
|
+
switchbot
|
|
170
|
+
.startScan({
|
|
171
|
+
mode: 'T',
|
|
172
|
+
id: bleMac,
|
|
173
|
+
})
|
|
174
|
+
.then(() => {
|
|
175
|
+
return switchbot.wait(this.platform.config.options!.refreshRate! * 1000);
|
|
176
|
+
})
|
|
177
|
+
.then(() => {
|
|
178
|
+
switchbot.stopScan();
|
|
179
|
+
this.platform.log.info('Stop scan ' + this.device.deviceName + '(' + this.device.bleMac + ')');
|
|
180
|
+
})
|
|
181
|
+
.catch(async (error: any) => {
|
|
182
|
+
this.platform.log.error(error);
|
|
183
|
+
await this.openAPIRefreshStatus();
|
|
184
|
+
});
|
|
185
|
+
}, this.platform.config.options!.refreshRate! * 60000);
|
|
186
|
+
} else {
|
|
187
|
+
await this.openAPIRefreshStatus();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private async openAPIRefreshStatus() {
|
|
192
|
+
try {
|
|
193
|
+
this.platform.debug('Camera - Reading', `${DeviceURL}/${this.device.deviceId}/status`);
|
|
194
|
+
const deviceStatus: deviceStatusResponse = (
|
|
195
|
+
await this.platform.axios.get(`${DeviceURL}/${this.device.deviceId}/status`)
|
|
196
|
+
).data;
|
|
197
|
+
if (deviceStatus.message === 'success') {
|
|
198
|
+
this.deviceStatus = deviceStatus;
|
|
199
|
+
this.platform.log.warn(`Camera ${this.accessory.displayName} refreshStatus - ${JSON.stringify(this.deviceStatus)}`);
|
|
200
|
+
this.parseStatus();
|
|
201
|
+
this.updateHomeKitCharacteristics();
|
|
202
|
+
}
|
|
203
|
+
} catch (e: any) {
|
|
204
|
+
this.platform.log.error(
|
|
205
|
+
`Camera - Failed to refresh status of ${this.device.deviceName}`,
|
|
206
|
+
JSON.stringify(e.message),
|
|
207
|
+
this.platform.debug(`Camera ${this.accessory.displayName} - ${JSON.stringify(e)}`));
|
|
208
|
+
this.apiError(e);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Pushes the requested changes to the SwitchBot API
|
|
214
|
+
* deviceType commandType Command command parameter Description
|
|
215
|
+
* Camera - "command" "turnOff" "default" = set to OFF state
|
|
216
|
+
* Camera - "command" "turnOn" "default" = set to ON state
|
|
217
|
+
*/
|
|
218
|
+
async pushChanges() {
|
|
219
|
+
const payload = {
|
|
220
|
+
commandType: 'command',
|
|
221
|
+
parameter: 'default',
|
|
222
|
+
} as any;
|
|
223
|
+
|
|
224
|
+
if (this.On) {
|
|
225
|
+
payload.command = 'turnOn';
|
|
226
|
+
} else {
|
|
227
|
+
payload.command = 'turnOff';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
this.platform.log.info(
|
|
231
|
+
'Sending request for',
|
|
232
|
+
this.accessory.displayName,
|
|
233
|
+
'to SwitchBot API. command:',
|
|
234
|
+
payload.command,
|
|
235
|
+
'parameter:',
|
|
236
|
+
payload.parameter,
|
|
237
|
+
'commandType:',
|
|
238
|
+
payload.commandType,
|
|
239
|
+
);
|
|
240
|
+
this.platform.debug(`Camera ${this.accessory.displayName} pushChanges - ${JSON.stringify(payload)}`);
|
|
241
|
+
|
|
242
|
+
// Make the API request
|
|
243
|
+
const push = await this.platform.axios.post(`${DeviceURL}/${this.device.deviceId}/commands`, payload);
|
|
244
|
+
this.platform.debug(`Camera ${this.accessory.displayName} Changes pushed - ${push.data}`);
|
|
245
|
+
this.statusCode(push);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
updateHomeKitCharacteristics() {
|
|
249
|
+
if (this.On !== undefined) {
|
|
250
|
+
this.service.updateCharacteristic(this.platform.Characteristic.On, this.On);
|
|
251
|
+
}
|
|
252
|
+
if (this.OutletInUse !== undefined) {
|
|
253
|
+
this.service.updateCharacteristic(this.platform.Characteristic.OutletInUse, this.OutletInUse);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
public apiError(e: any) {
|
|
258
|
+
this.service.updateCharacteristic(this.platform.Characteristic.On, e);
|
|
259
|
+
this.service.updateCharacteristic(this.platform.Characteristic.OutletInUse, e);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
private statusCode(push: AxiosResponse<any>) {
|
|
264
|
+
switch (push.data.statusCode) {
|
|
265
|
+
case 151:
|
|
266
|
+
this.platform.log.error('Command not supported by this device type.');
|
|
267
|
+
break;
|
|
268
|
+
case 152:
|
|
269
|
+
this.platform.log.error('Device not found.');
|
|
270
|
+
break;
|
|
271
|
+
case 160:
|
|
272
|
+
this.platform.log.error('Command is not supported.');
|
|
273
|
+
break;
|
|
274
|
+
case 161:
|
|
275
|
+
this.platform.log.error('Device is offline.');
|
|
276
|
+
break;
|
|
277
|
+
case 171:
|
|
278
|
+
this.platform.log.error('Hub Device is offline.');
|
|
279
|
+
break;
|
|
280
|
+
case 190:
|
|
281
|
+
this.platform.log.error('Device internal error due to device states not synchronized with server. Or command fomrat is invalid.');
|
|
282
|
+
break;
|
|
283
|
+
case 100:
|
|
284
|
+
if (this.platform.config.options?.debug) {
|
|
285
|
+
this.platform.log.info('Command successfully sent.');
|
|
286
|
+
}
|
|
287
|
+
break;
|
|
288
|
+
default:
|
|
289
|
+
this.platform.debug('Unknown statusCode.');
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Handle requests to set the value of the "On" characteristic
|
|
295
|
+
*/
|
|
296
|
+
OnSet(value: CharacteristicValue) {
|
|
297
|
+
this.platform.debug(`Camera ${this.accessory.displayName} - Set On: ${value}`);
|
|
298
|
+
|
|
299
|
+
this.On = value;
|
|
300
|
+
this.doCameraUpdate.next();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
}
|
package/src/devices/meters.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Service, PlatformAccessory, Units, CharacteristicValue } from 'homebridge';
|
|
1
|
+
import { Service, PlatformAccessory, Units, CharacteristicValue, MacAddress } from 'homebridge';
|
|
2
2
|
import { SwitchBotPlatform } from '../platform';
|
|
3
3
|
import { interval, Subject } from 'rxjs';
|
|
4
4
|
import { skipWhile } from 'rxjs/operators';
|
|
@@ -10,10 +10,12 @@ import { DeviceURL, device, deviceStatusResponse } from '../settings';
|
|
|
10
10
|
* Each accessory may expose multiple services of different service types.
|
|
11
11
|
*/
|
|
12
12
|
export class Meter {
|
|
13
|
+
// Services
|
|
13
14
|
private service: Service;
|
|
14
15
|
temperatureservice?: Service;
|
|
15
16
|
humidityservice?: Service;
|
|
16
17
|
|
|
18
|
+
// Characteristic Values
|
|
17
19
|
CurrentRelativeHumidity!: CharacteristicValue;
|
|
18
20
|
CurrentTemperature!: CharacteristicValue;
|
|
19
21
|
BatteryLevel!: CharacteristicValue;
|
|
@@ -21,13 +23,29 @@ export class Meter {
|
|
|
21
23
|
StatusLowBattery!: CharacteristicValue;
|
|
22
24
|
Active!: CharacteristicValue;
|
|
23
25
|
WaterLevel!: CharacteristicValue;
|
|
26
|
+
|
|
27
|
+
// Others
|
|
24
28
|
deviceStatus!: deviceStatusResponse;
|
|
25
|
-
|
|
29
|
+
BLEtemperature!: number;
|
|
30
|
+
BLEHumidity!: number;
|
|
31
|
+
switchbot!: {
|
|
32
|
+
discover: (
|
|
33
|
+
arg0:
|
|
34
|
+
{
|
|
35
|
+
duration: any;
|
|
36
|
+
model: string;
|
|
37
|
+
quick: boolean;
|
|
38
|
+
id: MacAddress;
|
|
39
|
+
}
|
|
40
|
+
) => Promise<any>;
|
|
41
|
+
wait: (
|
|
42
|
+
arg0: number
|
|
43
|
+
) => any;
|
|
44
|
+
};
|
|
26
45
|
|
|
46
|
+
// Updates
|
|
27
47
|
meterUpdateInProgress!: boolean;
|
|
28
48
|
doMeterUpdate;
|
|
29
|
-
BLEtemperature: any;
|
|
30
|
-
BLEHumidity: any;
|
|
31
49
|
|
|
32
50
|
constructor(
|
|
33
51
|
private readonly platform: SwitchBotPlatform,
|
|
@@ -40,6 +58,17 @@ export class Meter {
|
|
|
40
58
|
this.StatusLowBattery = this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW;
|
|
41
59
|
this.CurrentRelativeHumidity = 0;
|
|
42
60
|
this.CurrentTemperature = 0;
|
|
61
|
+
if (this.platform.config.options?.ble?.includes(this.device.deviceId!)) {
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
63
|
+
const SwitchBot = require('node-switchbot');
|
|
64
|
+
this.switchbot = new SwitchBot();
|
|
65
|
+
const colon = device.deviceId!.match(/.{1,2}/g);
|
|
66
|
+
const bleMac = colon!.join(':'); //returns 1A:23:B4:56:78:9A;
|
|
67
|
+
this.device.bleMac = bleMac.toLowerCase();
|
|
68
|
+
if (this.platform.config.options.debug === 'device') {
|
|
69
|
+
this.platform.debug(this.device.bleMac.toLowerCase());
|
|
70
|
+
}
|
|
71
|
+
}
|
|
43
72
|
|
|
44
73
|
// this is subject we use to track when we need to POST changes to the SwitchBot API
|
|
45
74
|
this.doMeterUpdate = new Subject();
|
|
@@ -59,7 +88,7 @@ export class Meter {
|
|
|
59
88
|
// you can create multiple services for each accessory
|
|
60
89
|
(this.service =
|
|
61
90
|
accessory.getService(this.platform.Service.Battery) ||
|
|
62
|
-
accessory.addService(this.platform.Service.Battery)),
|
|
91
|
+
accessory.addService(this.platform.Service.Battery)), `${device.deviceName} ${device.deviceType}`;
|
|
63
92
|
|
|
64
93
|
// To avoid "Cannot add a Service with the same UUID another Service without also defining a unique 'subtype' property." error,
|
|
65
94
|
// when creating multiple services of the same type, you need to use the following syntax to specify a name and subtype id:
|
|
@@ -77,18 +106,18 @@ export class Meter {
|
|
|
77
106
|
|
|
78
107
|
// Temperature Sensor Service
|
|
79
108
|
if (this.platform.config.options?.meter?.hide_temperature) {
|
|
80
|
-
if (this.platform.config.options.debug) {
|
|
109
|
+
if (this.platform.config.options.debug === 'device') {
|
|
81
110
|
this.platform.log.error('Removing service');
|
|
82
111
|
}
|
|
83
112
|
this.temperatureservice = this.accessory.getService(this.platform.Service.TemperatureSensor);
|
|
84
113
|
accessory.removeService(this.temperatureservice!);
|
|
85
114
|
} else if (!this.temperatureservice) {
|
|
86
|
-
if (this.platform.config.options?.debug) {
|
|
115
|
+
if (this.platform.config.options?.debug === 'device') {
|
|
87
116
|
this.platform.log.warn('Adding service');
|
|
88
117
|
}
|
|
89
118
|
(this.temperatureservice =
|
|
90
119
|
this.accessory.getService(this.platform.Service.TemperatureSensor) ||
|
|
91
|
-
this.accessory.addService(this.platform.Service.TemperatureSensor)),
|
|
120
|
+
this.accessory.addService(this.platform.Service.TemperatureSensor)), `${device.deviceName} ${device.deviceType} TemperatureSensor`;
|
|
92
121
|
|
|
93
122
|
this.temperatureservice
|
|
94
123
|
.getCharacteristic(this.platform.Characteristic.CurrentTemperature)
|
|
@@ -102,16 +131,16 @@ export class Meter {
|
|
|
102
131
|
.onGet(() => {
|
|
103
132
|
return this.CurrentTemperature;
|
|
104
133
|
});
|
|
105
|
-
this.platform.log.info(this.device.deviceName + ' current temperature: ' + this.CurrentTemperature + '\u2103');
|
|
134
|
+
//this.platform.log.info(this.device.deviceName + ' current temperature: ' + this.CurrentTemperature + '\u2103');
|
|
106
135
|
} else {
|
|
107
|
-
if (this.platform.config.options?.debug) {
|
|
136
|
+
if (this.platform.config.options?.debug === 'device') {
|
|
108
137
|
this.platform.log.warn('TemperatureSensor not added.');
|
|
109
138
|
}
|
|
110
139
|
}
|
|
111
140
|
|
|
112
141
|
// Humidity Sensor Service
|
|
113
142
|
if (this.platform.config.options?.meter?.hide_humidity) {
|
|
114
|
-
if (this.platform.config.options.debug) {
|
|
143
|
+
if (this.platform.config.options.debug === 'device') {
|
|
115
144
|
this.platform.log.error('Removing service');
|
|
116
145
|
}
|
|
117
146
|
this.humidityservice = this.accessory.getService(this.platform.Service.HumiditySensor);
|
|
@@ -119,7 +148,7 @@ export class Meter {
|
|
|
119
148
|
} else if (!this.humidityservice) {
|
|
120
149
|
(this.humidityservice =
|
|
121
150
|
this.accessory.getService(this.platform.Service.HumiditySensor) ||
|
|
122
|
-
this.accessory.addService(this.platform.Service.HumiditySensor)),
|
|
151
|
+
this.accessory.addService(this.platform.Service.HumiditySensor)), `${device.deviceName} ${device.deviceType} HumiditySensor`;
|
|
123
152
|
|
|
124
153
|
this.humidityservice
|
|
125
154
|
.getCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity)
|
|
@@ -129,7 +158,7 @@ export class Meter {
|
|
|
129
158
|
.onGet(() => {
|
|
130
159
|
return this.CurrentRelativeHumidity;
|
|
131
160
|
});
|
|
132
|
-
this.platform.log.info(this.device.deviceName + ' current humidity: ' + this.CurrentRelativeHumidity + '%');
|
|
161
|
+
//this.platform.log.info(this.device.deviceName + ' current humidity: ' + this.CurrentRelativeHumidity + '%');
|
|
133
162
|
} else {
|
|
134
163
|
if (this.platform.config.options?.debug) {
|
|
135
164
|
this.platform.log.warn('HumiditySensor not added.');
|
|
@@ -194,14 +223,15 @@ export class Meter {
|
|
|
194
223
|
*/
|
|
195
224
|
async refreshStatus() {
|
|
196
225
|
if (this.platform.config.options?.ble?.includes(this.device.deviceId!)) {
|
|
226
|
+
this.platform.debug('Meter BLE Device RefreshStatus');
|
|
197
227
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
198
228
|
const Switchbot = require('node-switchbot');
|
|
199
229
|
const switchbot = new Switchbot();
|
|
200
230
|
const colon = this.device.deviceId!.match(/.{1,2}/g);
|
|
201
231
|
const bleMac = colon!.join(':'); //returns 1A:23:B4:56:78:9A;
|
|
202
232
|
this.device.bleMac = bleMac.toLowerCase();
|
|
203
|
-
if (this.platform.config.options.debug) {
|
|
204
|
-
this.platform.
|
|
233
|
+
if (this.platform.config.options.debug === 'device') {
|
|
234
|
+
this.platform.debug(this.device.bleMac!);
|
|
205
235
|
}
|
|
206
236
|
switchbot.onadvertisement = (ad: any) => {
|
|
207
237
|
this.platform.log.info(JSON.stringify(ad, null, ' '));
|
|
@@ -221,14 +251,15 @@ export class Meter {
|
|
|
221
251
|
.then(() => {
|
|
222
252
|
switchbot.stopScan();
|
|
223
253
|
})
|
|
224
|
-
.catch((error: any) => {
|
|
254
|
+
.catch(async (error: any) => {
|
|
225
255
|
this.platform.log.error(error);
|
|
256
|
+
await this.openAPIRefreshStatus();
|
|
226
257
|
});
|
|
227
258
|
setInterval(() => {
|
|
228
|
-
|
|
259
|
+
this.platform.log.info('Start scan ' + this.device.deviceName + '(' + this.device.bleMac + ')');
|
|
229
260
|
switchbot
|
|
230
261
|
.startScan({
|
|
231
|
-
|
|
262
|
+
mode: 'T',
|
|
232
263
|
id: bleMac,
|
|
233
264
|
})
|
|
234
265
|
.then(() => {
|
|
@@ -236,20 +267,26 @@ export class Meter {
|
|
|
236
267
|
})
|
|
237
268
|
.then(() => {
|
|
238
269
|
switchbot.stopScan();
|
|
239
|
-
this.platform.log.info('Stop scan ' + this.device.deviceName + '(' + bleMac + ')');
|
|
270
|
+
this.platform.log.info('Stop scan ' + this.device.deviceName + '(' + this.device.bleMac + ')');
|
|
240
271
|
})
|
|
241
|
-
.catch((error: any) => {
|
|
272
|
+
.catch(async (error: any) => {
|
|
242
273
|
this.platform.log.error(error);
|
|
274
|
+
await this.openAPIRefreshStatus();
|
|
243
275
|
});
|
|
244
276
|
}, this.platform.config.options!.refreshRate! * 60000);
|
|
277
|
+
} else {
|
|
278
|
+
await this.openAPIRefreshStatus();
|
|
245
279
|
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private async openAPIRefreshStatus() {
|
|
246
283
|
try {
|
|
247
284
|
const deviceStatus: deviceStatusResponse = (
|
|
248
285
|
await this.platform.axios.get(`${DeviceURL}/${this.device.deviceId}/status`)
|
|
249
286
|
).data;
|
|
250
287
|
if (deviceStatus.message === 'success') {
|
|
251
288
|
this.deviceStatus = deviceStatus;
|
|
252
|
-
this.platform.debug(`Meter ${this.accessory.displayName}
|
|
289
|
+
this.platform.debug(`Meter ${this.accessory.displayName} openAPIRefreshStatus - ${JSON.stringify(this.deviceStatus)}`);
|
|
253
290
|
this.parseStatus();
|
|
254
291
|
this.updateHomeKitCharacteristics();
|
|
255
292
|
}
|
|
@@ -262,7 +299,6 @@ export class Meter {
|
|
|
262
299
|
);
|
|
263
300
|
this.apiError(e);
|
|
264
301
|
}
|
|
265
|
-
|
|
266
302
|
}
|
|
267
303
|
|
|
268
304
|
/**
|
package/src/devices/motion.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge';
|
|
1
|
+
import { Service, PlatformAccessory, CharacteristicValue, MacAddress } from 'homebridge';
|
|
2
2
|
import { SwitchBotPlatform } from '../platform';
|
|
3
3
|
import { interval, Subject } from 'rxjs';
|
|
4
4
|
import { skipWhile } from 'rxjs/operators';
|
|
@@ -10,13 +10,30 @@ import { DeviceURL, device, deviceStatusResponse } from '../settings';
|
|
|
10
10
|
* Each accessory may expose multiple services of different service types.
|
|
11
11
|
*/
|
|
12
12
|
export class Motion {
|
|
13
|
+
// Services
|
|
13
14
|
private service: Service;
|
|
14
|
-
temperatureservice?: Service;
|
|
15
|
-
humidityservice?: Service;
|
|
16
15
|
|
|
16
|
+
// Characteristic Values
|
|
17
17
|
MotionDetected!: CharacteristicValue;
|
|
18
|
-
deviceStatus!: deviceStatusResponse;
|
|
19
18
|
|
|
19
|
+
// Others
|
|
20
|
+
deviceStatus!: deviceStatusResponse;
|
|
21
|
+
switchbot!: {
|
|
22
|
+
discover: (
|
|
23
|
+
arg0:
|
|
24
|
+
{
|
|
25
|
+
duration: any;
|
|
26
|
+
model: string;
|
|
27
|
+
quick: boolean;
|
|
28
|
+
id: MacAddress;
|
|
29
|
+
}
|
|
30
|
+
) => Promise<any>;
|
|
31
|
+
wait: (
|
|
32
|
+
arg0: number
|
|
33
|
+
) => any;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Updates
|
|
20
37
|
motionUbpdateInProgress!: boolean;
|
|
21
38
|
doMotionUpdate;
|
|
22
39
|
|
|
@@ -28,6 +45,19 @@ export class Motion {
|
|
|
28
45
|
// default placeholders
|
|
29
46
|
this.MotionDetected = false;
|
|
30
47
|
|
|
48
|
+
// BLE Connection
|
|
49
|
+
if (this.platform.config.options?.ble?.includes(this.device.deviceId!)) {
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
51
|
+
const SwitchBot = require('node-switchbot');
|
|
52
|
+
this.switchbot = new SwitchBot();
|
|
53
|
+
const colon = device.deviceId!.match(/.{1,2}/g);
|
|
54
|
+
const bleMac = colon!.join(':'); //returns 1A:23:B4:56:78:9A;
|
|
55
|
+
this.device.bleMac = bleMac.toLowerCase();
|
|
56
|
+
if (this.platform.config.options.debug === 'device') {
|
|
57
|
+
this.platform.debug(this.device.bleMac.toLowerCase());
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
31
61
|
// this is subject we use to track when we need to POST changes to the SwitchBot API
|
|
32
62
|
this.doMotionUpdate = new Subject();
|
|
33
63
|
this.motionUbpdateInProgress = false;
|
|
@@ -39,14 +69,14 @@ export class Motion {
|
|
|
39
69
|
accessory
|
|
40
70
|
.getService(this.platform.Service.AccessoryInformation)!
|
|
41
71
|
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'SwitchBot')
|
|
42
|
-
.setCharacteristic(this.platform.Characteristic.Model, 'SWITCHBOT-MOTION-')
|
|
72
|
+
.setCharacteristic(this.platform.Characteristic.Model, 'SWITCHBOT-MOTION-W1101500')
|
|
43
73
|
.setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId);
|
|
44
74
|
|
|
45
75
|
// get the Battery service if it exists, otherwise create a new Motion service
|
|
46
76
|
// you can create multiple services for each accessory
|
|
47
77
|
(this.service =
|
|
48
78
|
accessory.getService(this.platform.Service.MotionSensor) ||
|
|
49
|
-
accessory.addService(this.platform.Service.MotionSensor)),
|
|
79
|
+
accessory.addService(this.platform.Service.MotionSensor)), `${device.deviceName} ${device.deviceType}`;
|
|
50
80
|
|
|
51
81
|
// To avoid "Cannot add a Service with the same UUID another Service without also defining a unique 'subtype' property." error,
|
|
52
82
|
// when creating multiple services of the same type, you need to use the following syntax to specify a name and subtype id:
|
|
@@ -78,17 +108,68 @@ export class Motion {
|
|
|
78
108
|
*/
|
|
79
109
|
parseStatus() {
|
|
80
110
|
// Set Room Sensor State
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
} else {
|
|
84
|
-
this.MotionDetected = false;
|
|
85
|
-
}
|
|
111
|
+
this.MotionDetected = Boolean(this.deviceStatus.body.moveDetected);
|
|
112
|
+
this.platform.debug(`${this.accessory.displayName}, MotionDetected: ${this.MotionDetected}`);
|
|
86
113
|
}
|
|
87
114
|
|
|
88
115
|
/**
|
|
89
116
|
* Asks the SwitchBot API for the latest device information
|
|
90
117
|
*/
|
|
91
118
|
async refreshStatus() {
|
|
119
|
+
if (this.platform.config.options?.ble?.includes(this.device.deviceId!)) {
|
|
120
|
+
this.platform.debug('Motion BLE Device RefreshStatus');
|
|
121
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
122
|
+
const Switchbot = require('node-switchbot');
|
|
123
|
+
const switchbot = new Switchbot();
|
|
124
|
+
const colon = this.device.deviceId!.match(/.{1,2}/g);
|
|
125
|
+
const bleMac = colon!.join(':'); //returns 1A:23:B4:56:78:9A;
|
|
126
|
+
this.device.bleMac = bleMac.toLowerCase();
|
|
127
|
+
if (this.platform.config.options.debug === 'device') {
|
|
128
|
+
this.platform.debug(this.device.bleMac!);
|
|
129
|
+
}
|
|
130
|
+
switchbot.onadvertisement = (ad: any) => {
|
|
131
|
+
this.platform.log.info(JSON.stringify(ad, null, ' '));
|
|
132
|
+
this.platform.log.warn('ad:', JSON.stringify(ad));
|
|
133
|
+
};
|
|
134
|
+
switchbot
|
|
135
|
+
.startScan({
|
|
136
|
+
id: this.device.bleMac,
|
|
137
|
+
})
|
|
138
|
+
.then(() => {
|
|
139
|
+
return switchbot.wait(this.platform.config.options!.refreshRate! * 1000);
|
|
140
|
+
})
|
|
141
|
+
.then(() => {
|
|
142
|
+
switchbot.stopScan();
|
|
143
|
+
})
|
|
144
|
+
.catch(async (error: any) => {
|
|
145
|
+
this.platform.log.error(error);
|
|
146
|
+
await this.openAPIRefreshStatus();
|
|
147
|
+
});
|
|
148
|
+
setInterval(() => {
|
|
149
|
+
this.platform.log.info('Start scan ' + this.device.deviceName + '(' + this.device.bleMac + ')');
|
|
150
|
+
switchbot
|
|
151
|
+
.startScan({
|
|
152
|
+
mode: 'S',
|
|
153
|
+
id: bleMac,
|
|
154
|
+
})
|
|
155
|
+
.then(() => {
|
|
156
|
+
return switchbot.wait(this.platform.config.options!.refreshRate! * 1000);
|
|
157
|
+
})
|
|
158
|
+
.then(() => {
|
|
159
|
+
switchbot.stopScan();
|
|
160
|
+
this.platform.log.info('Stop scan ' + this.device.deviceName + '(' + this.device.bleMac + ')');
|
|
161
|
+
})
|
|
162
|
+
.catch(async (error: any) => {
|
|
163
|
+
this.platform.log.error(error);
|
|
164
|
+
await this.openAPIRefreshStatus();
|
|
165
|
+
});
|
|
166
|
+
}, this.platform.config.options!.refreshRate! * 60000);
|
|
167
|
+
} else {
|
|
168
|
+
await this.openAPIRefreshStatus();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private async openAPIRefreshStatus() {
|
|
92
173
|
try {
|
|
93
174
|
const deviceStatus: deviceStatusResponse = (
|
|
94
175
|
await this.platform.axios.get(`${DeviceURL}/${this.device.deviceId}/status`)
|
|
@@ -105,8 +186,7 @@ export class Motion {
|
|
|
105
186
|
'Motion - Failed to update status of',
|
|
106
187
|
this.device.deviceName,
|
|
107
188
|
JSON.stringify(e.message),
|
|
108
|
-
this.platform.debug(`Motion ${this.accessory.displayName} - ${JSON.stringify(e)}`)
|
|
109
|
-
);
|
|
189
|
+
this.platform.debug(`Motion ${this.accessory.displayName} - ${JSON.stringify(e)}`));
|
|
110
190
|
this.apiError(e);
|
|
111
191
|
}
|
|
112
192
|
}
|