@switchbot/homebridge-switchbot 1.15.1-beta.0 → 1.15.1-beta.10
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 -0
- package/.github/ISSUE_TEMPLATE/feature-request.yml +1 -0
- package/.github/ISSUE_TEMPLATE/support-request.yml +1 -0
- package/.prettierrc +1 -1
- package/CHANGELOG.md +9 -0
- package/config.schema.json +11 -4
- package/dist/device/bot.d.ts +8 -7
- package/dist/device/bot.d.ts.map +1 -1
- package/dist/device/bot.js +104 -28
- package/dist/device/bot.js.map +1 -1
- package/dist/device/colorbulb.d.ts +8 -7
- package/dist/device/colorbulb.d.ts.map +1 -1
- package/dist/device/colorbulb.js +223 -58
- package/dist/device/colorbulb.js.map +1 -1
- package/dist/device/contact.d.ts +3 -3
- package/dist/device/contact.d.ts.map +1 -1
- package/dist/device/contact.js +54 -11
- package/dist/device/contact.js.map +1 -1
- package/dist/device/curtain.d.ts +9 -8
- package/dist/device/curtain.d.ts.map +1 -1
- package/dist/device/curtain.js +102 -28
- package/dist/device/curtain.js.map +1 -1
- package/dist/device/humidifier.d.ts +8 -7
- package/dist/device/humidifier.d.ts.map +1 -1
- package/dist/device/humidifier.js +187 -66
- package/dist/device/humidifier.js.map +1 -1
- package/dist/device/lock.d.ts +8 -7
- package/dist/device/lock.d.ts.map +1 -1
- package/dist/device/lock.js +98 -22
- package/dist/device/lock.js.map +1 -1
- package/dist/device/meter.d.ts +4 -4
- package/dist/device/meter.d.ts.map +1 -1
- package/dist/device/meter.js +55 -12
- package/dist/device/meter.js.map +1 -1
- package/dist/device/meterplus.d.ts +3 -3
- package/dist/device/meterplus.d.ts.map +1 -1
- package/dist/device/meterplus.js +53 -10
- package/dist/device/meterplus.js.map +1 -1
- package/dist/device/motion.d.ts +3 -3
- package/dist/device/motion.d.ts.map +1 -1
- package/dist/device/motion.js +53 -13
- package/dist/device/motion.js.map +1 -1
- package/dist/device/plug.d.ts +8 -7
- package/dist/device/plug.d.ts.map +1 -1
- package/dist/device/plug.js +100 -24
- package/dist/device/plug.js.map +1 -1
- package/dist/device/striplight.d.ts +8 -7
- package/dist/device/striplight.d.ts.map +1 -1
- package/dist/device/striplight.js +186 -47
- package/dist/device/striplight.js.map +1 -1
- package/dist/irdevice/airconditioner.d.ts +7 -6
- package/dist/irdevice/airconditioner.d.ts.map +1 -1
- package/dist/irdevice/airconditioner.js +40 -6
- package/dist/irdevice/airconditioner.js.map +1 -1
- package/dist/irdevice/airpurifier.d.ts +7 -6
- package/dist/irdevice/airpurifier.d.ts.map +1 -1
- package/dist/irdevice/airpurifier.js +46 -11
- package/dist/irdevice/airpurifier.js.map +1 -1
- package/dist/irdevice/camera.d.ts +7 -6
- package/dist/irdevice/camera.d.ts.map +1 -1
- package/dist/irdevice/camera.js +40 -6
- package/dist/irdevice/camera.js.map +1 -1
- package/dist/irdevice/fan.d.ts +8 -7
- package/dist/irdevice/fan.d.ts.map +1 -1
- package/dist/irdevice/fan.js +40 -6
- package/dist/irdevice/fan.js.map +1 -1
- package/dist/irdevice/light.d.ts +7 -6
- package/dist/irdevice/light.d.ts.map +1 -1
- package/dist/irdevice/light.js +40 -6
- package/dist/irdevice/light.js.map +1 -1
- package/dist/irdevice/other.d.ts +7 -6
- package/dist/irdevice/other.d.ts.map +1 -1
- package/dist/irdevice/other.js +40 -6
- package/dist/irdevice/other.js.map +1 -1
- package/dist/irdevice/tv.d.ts +8 -7
- package/dist/irdevice/tv.d.ts.map +1 -1
- package/dist/irdevice/tv.js +40 -6
- package/dist/irdevice/tv.js.map +1 -1
- package/dist/irdevice/vacuumcleaner.d.ts +7 -6
- package/dist/irdevice/vacuumcleaner.d.ts.map +1 -1
- package/dist/irdevice/vacuumcleaner.js +40 -6
- package/dist/irdevice/vacuumcleaner.js.map +1 -1
- package/dist/irdevice/waterheater.d.ts +7 -6
- package/dist/irdevice/waterheater.d.ts.map +1 -1
- package/dist/irdevice/waterheater.js +40 -6
- package/dist/irdevice/waterheater.js.map +1 -1
- package/dist/platform.d.ts +10 -6
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +211 -93
- package/dist/platform.js.map +1 -1
- package/dist/settings.d.ts +4 -21
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +3 -3
- package/dist/settings.js.map +1 -1
- package/package.json +12 -12
- package/src/device/bot.ts +104 -37
- package/src/device/colorbulb.ts +227 -95
- package/src/device/contact.ts +53 -14
- package/src/device/curtain.ts +105 -38
- package/src/device/humidifier.ts +189 -84
- package/src/device/lock.ts +97 -30
- package/src/device/meter.ts +55 -16
- package/src/device/meterplus.ts +52 -13
- package/src/device/motion.ts +52 -16
- package/src/device/plug.ts +101 -33
- package/src/device/striplight.ts +189 -68
- package/src/irdevice/airconditioner.ts +44 -9
- package/src/irdevice/airpurifier.ts +52 -17
- package/src/irdevice/camera.ts +46 -11
- package/src/irdevice/fan.ts +50 -15
- package/src/irdevice/light.ts +48 -13
- package/src/irdevice/other.ts +46 -11
- package/src/irdevice/tv.ts +56 -21
- package/src/irdevice/vacuumcleaner.ts +46 -11
- package/src/irdevice/waterheater.ts +46 -11
- package/src/platform.ts +232 -107
- package/src/settings.ts +4 -25
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import https from 'https';
|
|
2
|
+
import crypto from 'crypto';
|
|
3
|
+
import { IncomingMessage } from 'http';
|
|
2
4
|
import { SwitchBotPlatform } from '../platform';
|
|
3
|
-
import { irDevicesConfig,
|
|
5
|
+
import { irDevicesConfig, irdevice, HostDomain, DevicePath } from '../settings';
|
|
4
6
|
import { CharacteristicValue, PlatformAccessory, Service } from 'homebridge';
|
|
5
7
|
|
|
6
8
|
/**
|
|
@@ -95,7 +97,7 @@ export class WaterHeater {
|
|
|
95
97
|
commandType: 'command',
|
|
96
98
|
parameter: 'default',
|
|
97
99
|
command: 'turnOn',
|
|
98
|
-
}
|
|
100
|
+
};
|
|
99
101
|
await this.pushChanges(payload);
|
|
100
102
|
}
|
|
101
103
|
}
|
|
@@ -106,12 +108,12 @@ export class WaterHeater {
|
|
|
106
108
|
commandType: 'command',
|
|
107
109
|
parameter: 'default',
|
|
108
110
|
command: 'turnOff',
|
|
109
|
-
}
|
|
111
|
+
};
|
|
110
112
|
await this.pushChanges(payload);
|
|
111
113
|
}
|
|
112
114
|
}
|
|
113
115
|
|
|
114
|
-
async pushChanges(payload
|
|
116
|
+
async pushChanges(payload): Promise<void> {
|
|
115
117
|
try {
|
|
116
118
|
this.infoLog(
|
|
117
119
|
`Water Heater: ${this.accessory.displayName} Sending request to SwitchBot API. command: ${payload.command},` +
|
|
@@ -119,9 +121,42 @@ export class WaterHeater {
|
|
|
119
121
|
);
|
|
120
122
|
|
|
121
123
|
// Make the API request
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
this.
|
|
124
|
+
const t = Date.now();
|
|
125
|
+
const nonce = 'requestID';
|
|
126
|
+
const data = this.platform.config.credentials?.token + t + nonce;
|
|
127
|
+
const signTerm = crypto.createHmac('sha256', this.platform.config.credentials?.secret).update(Buffer.from(data, 'utf-8')).digest();
|
|
128
|
+
const sign = signTerm.toString('base64');
|
|
129
|
+
this.debugLog(`Water Heater: ${this.accessory.displayName} sign: ${sign}`);
|
|
130
|
+
const options = {
|
|
131
|
+
hostname: HostDomain,
|
|
132
|
+
port: 443,
|
|
133
|
+
path: `${DevicePath}/${this.device.deviceId}/commands`,
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers: {
|
|
136
|
+
Authorization: this.platform.config.credentials?.token,
|
|
137
|
+
sign: sign,
|
|
138
|
+
nonce: nonce,
|
|
139
|
+
t: t,
|
|
140
|
+
'Content-Type': 'application/json',
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const req = https.request(options, (res) => {
|
|
145
|
+
this.debugLog(`Water Heater: ${this.accessory.displayName} statusCode: ${res.statusCode}`);
|
|
146
|
+
this.statusCode({ res });
|
|
147
|
+
res.on('data', (d) => {
|
|
148
|
+
this.debugLog(`Water Heater: ${this.accessory.displayName} d: ${d}`);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
req.on('error', (error) => {
|
|
153
|
+
this.errorLog(`Water Heater: ${this.accessory.displayName} error: ${error}`);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
req.write(payload);
|
|
157
|
+
req.end();
|
|
158
|
+
|
|
159
|
+
this.debugLog(`Water Heater: ${this.accessory.displayName} pushchanges: ${JSON.stringify(req)}`);
|
|
125
160
|
this.updateHomeKitCharacteristics();
|
|
126
161
|
} catch (e: any) {
|
|
127
162
|
this.errorLog(`Water Heater: ${this.accessory.displayName} failed pushChanges with OpenAPI Connection`);
|
|
@@ -134,8 +169,8 @@ export class WaterHeater {
|
|
|
134
169
|
}
|
|
135
170
|
}
|
|
136
171
|
|
|
137
|
-
async statusCode(
|
|
138
|
-
switch (
|
|
172
|
+
async statusCode({ res }: { res: IncomingMessage }): Promise<void> {
|
|
173
|
+
switch (res.statusCode) {
|
|
139
174
|
case 151:
|
|
140
175
|
this.errorLog(`Water Heater: ${this.accessory.displayName} Command not supported by this device type.`);
|
|
141
176
|
break;
|
|
@@ -154,7 +189,7 @@ export class WaterHeater {
|
|
|
154
189
|
case 190:
|
|
155
190
|
this.errorLog(
|
|
156
191
|
`Water Heater: ${this.accessory.displayName} Device internal error due to device states not synchronized` +
|
|
157
|
-
` with server, Or command: ${JSON.stringify(
|
|
192
|
+
` with server, Or command: ${JSON.stringify(res)} format is invalid`,
|
|
158
193
|
);
|
|
159
194
|
break;
|
|
160
195
|
case 100:
|
package/src/platform.ts
CHANGED
|
@@ -18,10 +18,13 @@ import { AirPurifier } from './irdevice/airpurifier';
|
|
|
18
18
|
import { WaterHeater } from './irdevice/waterheater';
|
|
19
19
|
import { VacuumCleaner } from './irdevice/vacuumcleaner';
|
|
20
20
|
import { AirConditioner } from './irdevice/airconditioner';
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
-
import {
|
|
21
|
+
import https from 'https';
|
|
22
|
+
import crypto from 'crypto';
|
|
23
|
+
import { Buffer } from 'buffer';
|
|
24
24
|
import fakegato from 'fakegato-history';
|
|
25
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
26
|
+
import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, Service, Characteristic } from 'homebridge';
|
|
27
|
+
import { PLATFORM_NAME, PLUGIN_NAME, irdevice, device, SwitchBotPlatformConfig, devicesConfig, DevicePath, HostDomain } from './settings';
|
|
25
28
|
|
|
26
29
|
/**
|
|
27
30
|
* HomebridgePlatform
|
|
@@ -35,12 +38,8 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
|
|
|
35
38
|
// this is used to track restored cached accessories
|
|
36
39
|
public readonly accessories: PlatformAccessory[] = [];
|
|
37
40
|
|
|
38
|
-
public axios: AxiosInstance = axios.create({
|
|
39
|
-
responseType: 'json',
|
|
40
|
-
});
|
|
41
|
-
|
|
42
41
|
version = require('../package.json').version || '1.12.8'; // eslint-disable-line @typescript-eslint/no-var-requires
|
|
43
|
-
deviceStatus!:
|
|
42
|
+
deviceStatus!: any;
|
|
44
43
|
registeringDevice!: boolean;
|
|
45
44
|
debugMode!: boolean;
|
|
46
45
|
platformLogging?: string;
|
|
@@ -49,6 +48,7 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
|
|
|
49
48
|
|
|
50
49
|
constructor(public readonly log: Logger, public readonly config: SwitchBotPlatformConfig, public readonly api: API) {
|
|
51
50
|
this.logs();
|
|
51
|
+
|
|
52
52
|
this.debugLog('Finished initializing platform:', this.config.name);
|
|
53
53
|
// only load if configured
|
|
54
54
|
if (!this.config) {
|
|
@@ -72,13 +72,6 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
|
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
// setup axios interceptor to add headers / api key to each request
|
|
76
|
-
this.axios.interceptors.request.use((request: AxiosRequestConfig) => {
|
|
77
|
-
request.headers!.Authorization = this.config.credentials?.openToken;
|
|
78
|
-
request.headers!['Content-Type'] = 'application/json; charset=utf8';
|
|
79
|
-
return request;
|
|
80
|
-
});
|
|
81
|
-
|
|
82
75
|
// import fakegato-history module
|
|
83
76
|
this.fakegatoAPI = fakegato(api);
|
|
84
77
|
|
|
@@ -90,7 +83,11 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
|
|
|
90
83
|
this.debugLog('Executed didFinishLaunching callback');
|
|
91
84
|
// run the method to discover / register your devices as accessories
|
|
92
85
|
try {
|
|
93
|
-
this.
|
|
86
|
+
if (this.config.credentials?.openToken && !this.config.credentials.token) {
|
|
87
|
+
await this.updateToken();
|
|
88
|
+
} else {
|
|
89
|
+
this.discoverDevices();
|
|
90
|
+
}
|
|
94
91
|
} catch (e: any) {
|
|
95
92
|
this.errorLog('Failed to Discover Devices.', JSON.stringify(e.message));
|
|
96
93
|
if (this.platformLogging?.includes('debug')) {
|
|
@@ -134,7 +131,7 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
|
|
|
134
131
|
/**
|
|
135
132
|
* Verify the config passed to the plugin is valid
|
|
136
133
|
*/
|
|
137
|
-
verifyConfig() {
|
|
134
|
+
async verifyConfig() {
|
|
138
135
|
this.config.options = this.config.options || {};
|
|
139
136
|
|
|
140
137
|
const platformConfig = {};
|
|
@@ -181,8 +178,8 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
|
|
|
181
178
|
}
|
|
182
179
|
}
|
|
183
180
|
|
|
184
|
-
if (this.config.options!.refreshRate! <
|
|
185
|
-
throw new Error('Refresh Rate must be above
|
|
181
|
+
if (this.config.options!.refreshRate! < 5) {
|
|
182
|
+
throw new Error('Refresh Rate must be above 5 (5 seconds).');
|
|
186
183
|
}
|
|
187
184
|
|
|
188
185
|
if (!this.config.options.refreshRate) {
|
|
@@ -205,12 +202,71 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
|
|
|
205
202
|
this.debugLog('Missing Credentials');
|
|
206
203
|
}
|
|
207
204
|
}
|
|
208
|
-
if (!this.config.credentials?.
|
|
205
|
+
if (!this.config.credentials?.token) {
|
|
209
206
|
if (this.platformLogging?.includes('debug')) {
|
|
210
|
-
this.errorLog('Missing
|
|
207
|
+
this.errorLog('Missing token');
|
|
211
208
|
this.warnLog('Cloud Enabled SwitchBot Devices & IR Devices will not work');
|
|
212
209
|
}
|
|
213
210
|
}
|
|
211
|
+
if (this.config.credentials?.token) {
|
|
212
|
+
if (!this.config.credentials?.secret) {
|
|
213
|
+
if (this.platformLogging?.includes('debug')) {
|
|
214
|
+
this.errorLog('Missing secret');
|
|
215
|
+
this.warnLog('Cloud Enabled SwitchBot Devices & IR Devices will not work');
|
|
216
|
+
}
|
|
217
|
+
throw Error('Missing secret');
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* The openToken was old config.
|
|
225
|
+
* This method saves the openToken as the token in the config.json file
|
|
226
|
+
* @param this.config.credentials.openToken
|
|
227
|
+
*/
|
|
228
|
+
async updateToken() {
|
|
229
|
+
try {
|
|
230
|
+
// check the new token was provided
|
|
231
|
+
if (!this.config.credentials?.openToken) {
|
|
232
|
+
throw new Error('New token not provided');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// load in the current config
|
|
236
|
+
const currentConfig = JSON.parse(readFileSync(this.api.user.configPath(), 'utf8'));
|
|
237
|
+
|
|
238
|
+
// check the platforms section is an array before we do array things on it
|
|
239
|
+
if (!Array.isArray(currentConfig.platforms)) {
|
|
240
|
+
throw new Error('Cannot find platforms array in config');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// find this plugins current config
|
|
244
|
+
const pluginConfig = currentConfig.platforms.find((x: { platform: string }) => x.platform === PLATFORM_NAME);
|
|
245
|
+
|
|
246
|
+
if (!pluginConfig) {
|
|
247
|
+
throw new Error(`Cannot find config for ${PLATFORM_NAME} in platforms array`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// check the .credentials is an object before doing object things with it
|
|
251
|
+
if (typeof pluginConfig.credentials !== 'object') {
|
|
252
|
+
throw new Error('pluginConfig.credentials is not an object');
|
|
253
|
+
}
|
|
254
|
+
// Move openToken to token
|
|
255
|
+
this.warnLog('This plugin has been updated to use OpenAPI v1.1, config is set with openToken.');
|
|
256
|
+
this.warnLog('`openToken` config has been moved to the `token` config, Please restart Homebridge.');
|
|
257
|
+
|
|
258
|
+
// set the refresh token
|
|
259
|
+
pluginConfig.credentials.token = this.config.credentials?.openToken;
|
|
260
|
+
pluginConfig.credentials.openToken = 'Delete';
|
|
261
|
+
|
|
262
|
+
if (this.platformLogging?.includes('debug')) {
|
|
263
|
+
this.warnLog(`Token: ${pluginConfig.credentials.token}`);
|
|
264
|
+
}
|
|
265
|
+
// save the config, ensuring we maintain pretty json
|
|
266
|
+
writeFileSync(this.api.user.configPath(), JSON.stringify(currentConfig, null, 4));
|
|
267
|
+
this.verifyConfig();
|
|
268
|
+
} catch (e: any) {
|
|
269
|
+
this.errorLog(e);
|
|
214
270
|
}
|
|
215
271
|
}
|
|
216
272
|
|
|
@@ -219,92 +275,130 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
|
|
|
219
275
|
*/
|
|
220
276
|
async discoverDevices() {
|
|
221
277
|
try {
|
|
222
|
-
if (this.config.credentials?.
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
this.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
278
|
+
if (this.config.credentials?.token) {
|
|
279
|
+
const t = Date.now();
|
|
280
|
+
const nonce = 'requestID';
|
|
281
|
+
const data = this.config.credentials?.token + t + nonce;
|
|
282
|
+
const signTerm = crypto.createHmac('sha256', this.config.credentials?.secret).update(Buffer.from(data, 'utf-8')).digest();
|
|
283
|
+
const sign = signTerm.toString('base64');
|
|
284
|
+
this.debugLog(sign);
|
|
285
|
+
const options = {
|
|
286
|
+
hostname: HostDomain,
|
|
287
|
+
port: 443,
|
|
288
|
+
path: DevicePath,
|
|
289
|
+
method: 'GET',
|
|
290
|
+
headers: {
|
|
291
|
+
'Authorization': this.config.credentials?.token,
|
|
292
|
+
'sign': sign,
|
|
293
|
+
'nonce': nonce,
|
|
294
|
+
't': t,
|
|
295
|
+
'Content-Type': 'application/json',
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
const req = https.request(options, (res) => {
|
|
299
|
+
this.debugLog(`statusCode: ${res.statusCode}`);
|
|
300
|
+
this.debugLog(`headers: ${JSON.stringify(res.headers)}`);
|
|
301
|
+
let rawData = '';
|
|
302
|
+
res.on('data', (d) => {
|
|
303
|
+
rawData += d;
|
|
304
|
+
this.debugLog(`d: ${d}`);
|
|
305
|
+
});
|
|
306
|
+
res.on('end', () => {
|
|
307
|
+
try {
|
|
308
|
+
const devicesAPI = JSON.parse(rawData);
|
|
309
|
+
this.debugLog(`devicesAPI: ${JSON.stringify(devicesAPI.body)}`);
|
|
310
|
+
|
|
311
|
+
// SwitchBot Devices
|
|
312
|
+
if (devicesAPI.body.deviceList.length !== 0) {
|
|
313
|
+
this.infoLog(`Total SwitchBot Devices Found: ${devicesAPI.body.deviceList.length}`);
|
|
314
|
+
} else {
|
|
315
|
+
this.debugLog(`Total SwitchBot Devices Found: ${devicesAPI.body.deviceList.length}`);
|
|
241
316
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
317
|
+
const deviceLists = devicesAPI.body.deviceList;
|
|
318
|
+
if (!this.config.options?.devices) {
|
|
319
|
+
this.debugLog(`SwitchBot Device Config Not Set: ${JSON.stringify(this.config.options?.devices)}`);
|
|
320
|
+
const devices = deviceLists.map((v: any) => v);
|
|
321
|
+
for (const device of devices) {
|
|
322
|
+
if (device.deviceType) {
|
|
323
|
+
if (device.configDeviceName) {
|
|
324
|
+
device.deviceName = device.configDeviceName;
|
|
325
|
+
}
|
|
326
|
+
this.createDevice(device);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
} else if (this.config.credentials?.token && this.config.options.devices) {
|
|
330
|
+
this.debugLog(`SwitchBot Device Config Set: ${JSON.stringify(this.config.options?.devices)}`);
|
|
331
|
+
const deviceConfigs = this.config.options?.devices;
|
|
332
|
+
|
|
333
|
+
const mergeBydeviceId = (a1: { deviceId: string }[], a2: any[]) =>
|
|
334
|
+
a1.map((itm: { deviceId: string }) => ({
|
|
335
|
+
...a2.find(
|
|
336
|
+
(item: { deviceId: string }) =>
|
|
337
|
+
item.deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '') === itm.deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '') && item,
|
|
338
|
+
),
|
|
339
|
+
...itm,
|
|
340
|
+
}));
|
|
341
|
+
|
|
342
|
+
const devices = mergeBydeviceId(deviceLists, deviceConfigs);
|
|
343
|
+
this.debugLog(`SwitchBot Devices: ${JSON.stringify(devices)}`);
|
|
344
|
+
for (const device of devices) {
|
|
345
|
+
if (device.deviceType) {
|
|
346
|
+
if (device.configDeviceName) {
|
|
347
|
+
device.deviceName = device.configDeviceName;
|
|
348
|
+
}
|
|
349
|
+
this.createDevice(device);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
} else {
|
|
353
|
+
this.errorLog('SwitchBot Token Supplied, Issue with Auth.');
|
|
264
354
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
this.
|
|
355
|
+
|
|
356
|
+
// IR Devices
|
|
357
|
+
if (devicesAPI.body.infraredRemoteList.length !== 0) {
|
|
358
|
+
this.infoLog(`Total IR Devices Found: ${devicesAPI.body.infraredRemoteList.length}`);
|
|
359
|
+
} else {
|
|
360
|
+
this.debugLog(`Total IR Devices Found: ${devicesAPI.body.infraredRemoteList.length}`);
|
|
361
|
+
}
|
|
362
|
+
const irDeviceLists = devicesAPI.body.infraredRemoteList;
|
|
363
|
+
if (!this.config.options?.irdevices) {
|
|
364
|
+
this.debugLog(`IR Device Config Not Set: ${JSON.stringify(this.config.options?.irdevices)}`);
|
|
365
|
+
const devices = irDeviceLists.map((v: any) => v);
|
|
366
|
+
for (const device of devices) {
|
|
367
|
+
if (device.remoteType) {
|
|
368
|
+
this.createIRDevice(device);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
} else {
|
|
372
|
+
this.debugLog(`IR Device Config Set: ${JSON.stringify(this.config.options?.irdevices)}`);
|
|
373
|
+
const irDeviceConfig = this.config.options?.irdevices;
|
|
374
|
+
|
|
375
|
+
const mergeIRBydeviceId = (a1: { deviceId: string }[], a2: any[]) =>
|
|
376
|
+
a1.map((itm: { deviceId: string }) => ({
|
|
377
|
+
...a2.find(
|
|
378
|
+
(item: { deviceId: string }) =>
|
|
379
|
+
item.deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '') === itm.deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '') && item,
|
|
380
|
+
),
|
|
381
|
+
...itm,
|
|
382
|
+
}));
|
|
383
|
+
|
|
384
|
+
const devices = mergeIRBydeviceId(irDeviceLists, irDeviceConfig);
|
|
385
|
+
this.debugLog(`IR Devices: ${JSON.stringify(devices)}`);
|
|
386
|
+
for (const device of devices) {
|
|
387
|
+
if (device.remoteType) {
|
|
388
|
+
this.createIRDevice(device);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
} catch (e:any) {
|
|
393
|
+
this.errorLog(e.message);
|
|
304
394
|
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
req.on('error', (error) => {
|
|
398
|
+
this.errorLog(error);
|
|
399
|
+
});
|
|
400
|
+
req.end();
|
|
401
|
+
} else if (!this.config.credentials?.token && this.config.options?.devices) {
|
|
308
402
|
this.debugLog(`SwitchBot Device Manual Config Set: ${JSON.stringify(this.config.options?.devices)}`);
|
|
309
403
|
const deviceConfigs = this.config.options?.devices;
|
|
310
404
|
const devices = deviceConfigs.map((v: any) => v);
|
|
@@ -316,7 +410,7 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
|
|
|
316
410
|
}
|
|
317
411
|
}
|
|
318
412
|
} else {
|
|
319
|
-
this.errorLog('Neither SwitchBot
|
|
413
|
+
this.errorLog('Neither SwitchBot Token or Device Config are not set.');
|
|
320
414
|
}
|
|
321
415
|
} catch (e: any) {
|
|
322
416
|
this.deviceError(e);
|
|
@@ -1676,7 +1770,7 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
|
|
|
1676
1770
|
this.warnLog(`Removing existing accessory from cache: ${existingAccessory.displayName}`);
|
|
1677
1771
|
}
|
|
1678
1772
|
|
|
1679
|
-
public deviceListInfo(devices
|
|
1773
|
+
public deviceListInfo(devices) {
|
|
1680
1774
|
if (this.platformLogging?.includes('debug')) {
|
|
1681
1775
|
this.warnLog(`deviceListInfoStatus: ${JSON.stringify(devices)}`);
|
|
1682
1776
|
}
|
|
@@ -1685,7 +1779,38 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
|
|
|
1685
1779
|
public async deviceInfo(device: (irdevice & devicesConfig) | (device & devicesConfig)) {
|
|
1686
1780
|
if (this.platformLogging?.includes('debug')) {
|
|
1687
1781
|
this.warnLog(JSON.stringify(device));
|
|
1688
|
-
|
|
1782
|
+
|
|
1783
|
+
const t = Date.now();
|
|
1784
|
+
const nonce = 'requestID';
|
|
1785
|
+
const data = this.config.credentials?.token + t + nonce;
|
|
1786
|
+
const signTerm = crypto.createHmac('sha256', this.config.credentials?.secret).update(Buffer.from(data, 'utf-8')).digest();
|
|
1787
|
+
const sign = signTerm.toString('base64');
|
|
1788
|
+
this.debugLog(sign);
|
|
1789
|
+
const options = {
|
|
1790
|
+
hostname: HostDomain,
|
|
1791
|
+
port: 443,
|
|
1792
|
+
path: `${DevicePath}/${device.deviceId}/status`,
|
|
1793
|
+
method: 'GET',
|
|
1794
|
+
headers: {
|
|
1795
|
+
Authorization: this.config.credentials?.token,
|
|
1796
|
+
sign: sign,
|
|
1797
|
+
nonce: nonce,
|
|
1798
|
+
t: t,
|
|
1799
|
+
'Content-Type': 'application/json',
|
|
1800
|
+
},
|
|
1801
|
+
};
|
|
1802
|
+
const req = https.request(options, (res) => {
|
|
1803
|
+
this.debugLog(`statusCode: ${res.statusCode}`);
|
|
1804
|
+
res.on('data', (d) => {
|
|
1805
|
+
process.stdout.write(d);
|
|
1806
|
+
});
|
|
1807
|
+
});
|
|
1808
|
+
req.on('error', (error) => {
|
|
1809
|
+
this.errorLog(error);
|
|
1810
|
+
});
|
|
1811
|
+
req.end();
|
|
1812
|
+
|
|
1813
|
+
this.deviceStatus = req;
|
|
1689
1814
|
if (this.deviceStatus.message === 'success') {
|
|
1690
1815
|
this.warnLog(`${device.deviceName} deviceInfoStatus: ${JSON.stringify(this.deviceStatus)}`);
|
|
1691
1816
|
} else {
|
package/src/settings.ts
CHANGED
|
@@ -13,12 +13,12 @@ export const PLUGIN_NAME = '@switchbot/homebridge-switchbot';
|
|
|
13
13
|
/**
|
|
14
14
|
* This is the main url used to access SwitchBot API
|
|
15
15
|
*/
|
|
16
|
-
export const
|
|
16
|
+
export const HostDomain = 'api.switch-bot.com';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* This is the main url used to access SwitchBot API
|
|
20
20
|
*/
|
|
21
|
-
export const
|
|
21
|
+
export const DevicePath = '/v1.1/devices';
|
|
22
22
|
|
|
23
23
|
//Config
|
|
24
24
|
export interface SwitchBotPlatformConfig extends PlatformConfig {
|
|
@@ -27,6 +27,8 @@ export interface SwitchBotPlatformConfig extends PlatformConfig {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export type credentials = {
|
|
30
|
+
token?: any;
|
|
31
|
+
secret?: any;
|
|
30
32
|
openToken?: any;
|
|
31
33
|
};
|
|
32
34
|
|
|
@@ -175,23 +177,6 @@ export type other = {
|
|
|
175
177
|
commandOff?: string;
|
|
176
178
|
};
|
|
177
179
|
|
|
178
|
-
export type payload = {
|
|
179
|
-
commandType: string;
|
|
180
|
-
parameter: string;
|
|
181
|
-
command: string;
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
export interface AxiosRequestConfig {
|
|
185
|
-
params?: Record<string, unknown>;
|
|
186
|
-
headers?: any;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export type deviceResponses = {
|
|
190
|
-
statusCode: number | string;
|
|
191
|
-
message: string;
|
|
192
|
-
body: deviceList | infraredRemoteList;
|
|
193
|
-
};
|
|
194
|
-
|
|
195
180
|
//a list of physical devices.
|
|
196
181
|
export type deviceList = {
|
|
197
182
|
device: Array<device>;
|
|
@@ -234,12 +219,6 @@ export type irdevice = {
|
|
|
234
219
|
hubDeviceId: string; //remote device's parent Hub ID
|
|
235
220
|
};
|
|
236
221
|
|
|
237
|
-
export type deviceStatusResponse = {
|
|
238
|
-
statusCode: number;
|
|
239
|
-
message: string;
|
|
240
|
-
body: deviceStatus;
|
|
241
|
-
};
|
|
242
|
-
|
|
243
222
|
export type deviceStatus = {
|
|
244
223
|
//device ID.
|
|
245
224
|
deviceId: string;
|