@switchbot/homebridge-switchbot 2.0.0-beta.2 → 2.0.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -1
- package/README.md +1 -2
- package/config.schema.json +75 -20
- package/dist/device/bot.d.ts +32 -28
- package/dist/device/bot.d.ts.map +1 -1
- package/dist/device/bot.js +646 -648
- package/dist/device/bot.js.map +1 -1
- package/dist/device/colorbulb.d.ts +34 -19
- package/dist/device/colorbulb.d.ts.map +1 -1
- package/dist/device/colorbulb.js +429 -304
- package/dist/device/colorbulb.js.map +1 -1
- package/dist/device/contact.d.ts +16 -7
- package/dist/device/contact.d.ts.map +1 -1
- package/dist/device/contact.js +288 -230
- package/dist/device/contact.js.map +1 -1
- package/dist/device/curtain.d.ts +21 -16
- package/dist/device/curtain.d.ts.map +1 -1
- package/dist/device/curtain.js +519 -489
- package/dist/device/curtain.js.map +1 -1
- package/dist/device/humidifier.d.ts +22 -18
- package/dist/device/humidifier.d.ts.map +1 -1
- package/dist/device/humidifier.js +415 -360
- package/dist/device/humidifier.js.map +1 -1
- package/dist/device/lock.d.ts +22 -8
- package/dist/device/lock.d.ts.map +1 -1
- package/dist/device/lock.js +253 -147
- package/dist/device/lock.js.map +1 -1
- package/dist/device/meter.d.ts +21 -16
- package/dist/device/meter.d.ts.map +1 -1
- package/dist/device/meter.js +325 -255
- package/dist/device/meter.js.map +1 -1
- package/dist/device/meterplus.d.ts +23 -12
- package/dist/device/meterplus.d.ts.map +1 -1
- package/dist/device/meterplus.js +346 -208
- package/dist/device/meterplus.js.map +1 -1
- package/dist/device/motion.d.ts +17 -8
- package/dist/device/motion.d.ts.map +1 -1
- package/dist/device/motion.js +261 -209
- package/dist/device/motion.js.map +1 -1
- package/dist/device/plug.d.ts +24 -16
- package/dist/device/plug.d.ts.map +1 -1
- package/dist/device/plug.js +267 -242
- package/dist/device/plug.js.map +1 -1
- package/dist/device/striplight.d.ts +51 -16
- package/dist/device/striplight.d.ts.map +1 -1
- package/dist/device/striplight.js +527 -185
- package/dist/device/striplight.js.map +1 -1
- package/dist/irdevice/airconditioner.d.ts +7 -12
- package/dist/irdevice/airconditioner.d.ts.map +1 -1
- package/dist/irdevice/airconditioner.js +261 -230
- package/dist/irdevice/airconditioner.js.map +1 -1
- package/dist/irdevice/airpurifier.d.ts +3 -3
- package/dist/irdevice/airpurifier.d.ts.map +1 -1
- package/dist/irdevice/airpurifier.js +137 -104
- package/dist/irdevice/airpurifier.js.map +1 -1
- package/dist/irdevice/camera.d.ts +9 -8
- package/dist/irdevice/camera.d.ts.map +1 -1
- package/dist/irdevice/camera.js +116 -86
- 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 +143 -109
- package/dist/irdevice/fan.js.map +1 -1
- package/dist/irdevice/light.d.ts +9 -8
- package/dist/irdevice/light.d.ts.map +1 -1
- package/dist/irdevice/light.js +120 -103
- package/dist/irdevice/light.js.map +1 -1
- package/dist/irdevice/other.d.ts +9 -8
- package/dist/irdevice/other.d.ts.map +1 -1
- package/dist/irdevice/other.js +128 -98
- package/dist/irdevice/other.js.map +1 -1
- package/dist/irdevice/tv.d.ts +10 -9
- package/dist/irdevice/tv.d.ts.map +1 -1
- package/dist/irdevice/tv.js +126 -93
- package/dist/irdevice/tv.js.map +1 -1
- package/dist/irdevice/vacuumcleaner.d.ts +7 -10
- package/dist/irdevice/vacuumcleaner.d.ts.map +1 -1
- package/dist/irdevice/vacuumcleaner.js +115 -89
- package/dist/irdevice/vacuumcleaner.js.map +1 -1
- package/dist/irdevice/waterheater.d.ts +6 -9
- package/dist/irdevice/waterheater.d.ts.map +1 -1
- package/dist/irdevice/waterheater.js +115 -89
- package/dist/irdevice/waterheater.js.map +1 -1
- package/dist/platform.d.ts +5 -5
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +227 -199
- package/dist/platform.js.map +1 -1
- package/dist/settings.d.ts +15 -11
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js.map +1 -1
- package/package.json +12 -10
- package/src/device/bot.ts +641 -657
- package/src/device/colorbulb.ts +440 -339
- package/src/device/contact.ts +289 -233
- package/src/device/curtain.ts +523 -503
- package/src/device/humidifier.ts +421 -396
- package/src/device/lock.ts +257 -147
- package/src/device/meter.ts +335 -268
- package/src/device/meterplus.ts +354 -213
- package/src/device/motion.ts +264 -213
- package/src/device/plug.ts +270 -251
- package/src/device/striplight.ts +552 -207
- package/src/irdevice/airconditioner.ts +263 -241
- package/src/irdevice/airpurifier.ts +132 -103
- package/src/irdevice/camera.ts +112 -85
- package/src/irdevice/fan.ts +138 -106
- package/src/irdevice/light.ts +116 -103
- package/src/irdevice/other.ts +124 -97
- package/src/irdevice/tv.ts +123 -93
- package/src/irdevice/vacuumcleaner.ts +112 -89
- package/src/irdevice/waterheater.ts +111 -89
- package/src/platform.ts +216 -214
- package/src/settings.ts +15 -11
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import https from 'https';
|
|
2
2
|
import crypto from 'crypto';
|
|
3
3
|
import { IncomingMessage } from 'http';
|
|
4
|
+
import superStringify from 'super-stringify';
|
|
4
5
|
import { SwitchBotPlatform } from '../platform';
|
|
5
6
|
import { irDevicesConfig, irdevice, HostDomain, DevicePath } from '../settings';
|
|
6
7
|
import { CharacteristicValue, PlatformAccessory, Service } from 'homebridge';
|
|
@@ -16,7 +17,6 @@ export class VacuumCleaner {
|
|
|
16
17
|
|
|
17
18
|
// Characteristic Values
|
|
18
19
|
On!: CharacteristicValue;
|
|
19
|
-
OnCached!: CharacteristicValue;
|
|
20
20
|
|
|
21
21
|
// Config
|
|
22
22
|
deviceLogging!: string;
|
|
@@ -25,23 +25,22 @@ export class VacuumCleaner {
|
|
|
25
25
|
// default placeholders
|
|
26
26
|
this.logs(device);
|
|
27
27
|
this.config(device);
|
|
28
|
-
|
|
29
|
-
this.On = false;
|
|
30
|
-
} else {
|
|
31
|
-
this.On = this.accessory.context.On;
|
|
32
|
-
}
|
|
28
|
+
this.context();
|
|
33
29
|
|
|
34
30
|
// set accessory information
|
|
35
31
|
accessory
|
|
36
32
|
.getService(this.platform.Service.AccessoryInformation)!
|
|
37
33
|
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'SwitchBot')
|
|
38
34
|
.setCharacteristic(this.platform.Characteristic.Model, device.remoteType)
|
|
39
|
-
.setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!)
|
|
35
|
+
.setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!)
|
|
36
|
+
.setCharacteristic(this.platform.Characteristic.FirmwareRevision, this.FirmwareRevision(accessory, device))
|
|
37
|
+
.getCharacteristic(this.platform.Characteristic.FirmwareRevision)
|
|
38
|
+
.updateValue(this.FirmwareRevision(accessory, device));
|
|
40
39
|
|
|
41
40
|
// get the Television service if it exists, otherwise create a new Television service
|
|
42
41
|
// you can create multiple services for each accessory
|
|
43
42
|
(this.switchService = accessory.getService(this.platform.Service.Switch) || accessory.addService(this.platform.Service.Switch)),
|
|
44
|
-
`${accessory.displayName}
|
|
43
|
+
`${accessory.displayName} ${device.remoteType}`;
|
|
45
44
|
|
|
46
45
|
// To avoid "Cannot add a Service with the same UUID another Service without also defining a unique 'subtype' property." error,
|
|
47
46
|
// when creating multiple services of the same type, you need to use the following syntax to specify a name and subtype id:
|
|
@@ -56,8 +55,9 @@ export class VacuumCleaner {
|
|
|
56
55
|
}
|
|
57
56
|
|
|
58
57
|
async OnSet(value: CharacteristicValue): Promise<void> {
|
|
59
|
-
this.debugLog(
|
|
58
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} On: ${value}`);
|
|
60
59
|
this.On = value;
|
|
60
|
+
this.accessory.context.On = this.On;
|
|
61
61
|
if (this.On) {
|
|
62
62
|
await this.pushOnChanges();
|
|
63
63
|
} else {
|
|
@@ -65,28 +65,15 @@ export class VacuumCleaner {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
async updateHomeKitCharacteristics(): Promise<void> {
|
|
69
|
-
if (this.On === undefined) {
|
|
70
|
-
this.debugLog(`Vacuum Cleaner: ${this.accessory.displayName} On: ${this.On}`);
|
|
71
|
-
} else {
|
|
72
|
-
this.switchService?.updateCharacteristic(this.platform.Characteristic.On, this.On);
|
|
73
|
-
this.debugLog(`Vacuum Cleaner: ${this.accessory.displayName} updateCharacteristic On: ${this.On}`);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
68
|
/**
|
|
78
69
|
* Pushes the requested changes to the SwitchBot API
|
|
79
|
-
* deviceType
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* Light: "command" "volumeAdd" "default" = volume up
|
|
83
|
-
* Light: "command" "volumeSub" "default" = volume down
|
|
84
|
-
* Light: "command" "channelAdd" "default" = next channel
|
|
85
|
-
* Light: "command" "channelSub" "default" = previous channel
|
|
70
|
+
* deviceType CommandType Command Parameter Description
|
|
71
|
+
* Vacuum Cleaner "command" "turnOff" "default" set to OFF state
|
|
72
|
+
* Vacuum Cleaner "command" "turnOn" "default" set to ON state
|
|
86
73
|
*/
|
|
87
74
|
async pushOnChanges(): Promise<void> {
|
|
88
75
|
if (this.On) {
|
|
89
|
-
const body =
|
|
76
|
+
const body = superStringify({
|
|
90
77
|
'command': 'turnOn',
|
|
91
78
|
'parameter': 'default',
|
|
92
79
|
'commandType': 'command',
|
|
@@ -97,7 +84,7 @@ export class VacuumCleaner {
|
|
|
97
84
|
|
|
98
85
|
async pushOffChanges(): Promise<void> {
|
|
99
86
|
if (!this.On) {
|
|
100
|
-
const body =
|
|
87
|
+
const body = superStringify({
|
|
101
88
|
'command': 'turnOff',
|
|
102
89
|
'parameter': 'default',
|
|
103
90
|
'commandType': 'command',
|
|
@@ -106,94 +93,127 @@ export class VacuumCleaner {
|
|
|
106
93
|
}
|
|
107
94
|
}
|
|
108
95
|
|
|
109
|
-
async pushChanges(body): Promise<void> {
|
|
110
|
-
|
|
96
|
+
async pushChanges(body: any[]): Promise<void> {
|
|
97
|
+
if (this.device.connectionType === 'OpenAPI') {
|
|
98
|
+
try {
|
|
111
99
|
// Make Push On request to the API
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
100
|
+
const t = Date.now();
|
|
101
|
+
const nonce = 'requestID';
|
|
102
|
+
const data = this.platform.config.credentials?.token + t + nonce;
|
|
103
|
+
const signTerm = crypto.createHmac('sha256', this.platform.config.credentials?.secret)
|
|
104
|
+
.update(Buffer.from(data, 'utf-8'))
|
|
105
|
+
.digest();
|
|
106
|
+
const sign = signTerm.toString('base64');
|
|
107
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} sign: ${sign}`);
|
|
108
|
+
this.infoLog(`${this.device.remoteType}: ${this.accessory.displayName} Sending request to SwitchBot API. body: ${body},`);
|
|
109
|
+
const options = {
|
|
110
|
+
hostname: HostDomain,
|
|
111
|
+
port: 443,
|
|
112
|
+
path: `${DevicePath}/${this.device.deviceId}/commands`,
|
|
113
|
+
method: 'POST',
|
|
114
|
+
headers: {
|
|
115
|
+
'Authorization': this.platform.config.credentials?.token,
|
|
116
|
+
'sign': sign,
|
|
117
|
+
'nonce': nonce,
|
|
118
|
+
't': t,
|
|
119
|
+
'Content-Type': 'application/json',
|
|
120
|
+
'Content-Length': body.length,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
const req = https.request(options, res => {
|
|
124
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} pushchanges statusCode: ${res.statusCode}`);
|
|
125
|
+
this.statusCode({ res });
|
|
126
|
+
res.on('data', d => {
|
|
127
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} d: ${d}`);
|
|
128
|
+
});
|
|
140
129
|
});
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (this.deviceLogging.includes('debug')) {
|
|
154
|
-
this.errorLog(
|
|
155
|
-
`Vacuum Cleaner: ${this.accessory.displayName} failed pushChanges with OpenAPI Connection,` +
|
|
156
|
-
` Error Message: ${JSON.stringify(e.message)}`,
|
|
130
|
+
req.on('error', (e: any) => {
|
|
131
|
+
this.errorLog(`${this.device.remoteType}: ${this.accessory.displayName} error message: ${e.message}`);
|
|
132
|
+
});
|
|
133
|
+
req.write(body);
|
|
134
|
+
req.end();
|
|
135
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} pushchanges: ${superStringify(req)}`);
|
|
136
|
+
this.accessory.context.On = this.On;
|
|
137
|
+
this.updateHomeKitCharacteristics();
|
|
138
|
+
} catch (e: any) {
|
|
139
|
+
this.apiError(e);
|
|
140
|
+
this.errorLog(`${this.device.remoteType}: ${this.accessory.displayName} failed pushChanges with ${this.device.connectionType} Connection,`
|
|
141
|
+
+ ` Error Message: ${superStringify(e.message)}`,
|
|
157
142
|
);
|
|
158
143
|
}
|
|
159
|
-
|
|
144
|
+
} else {
|
|
145
|
+
this.warnLog(`${this.device.remoteType}: ${this.accessory.displayName}`
|
|
146
|
+
+ ` Connection Type: ${this.device.connectionType}, commands will not be sent to OpenAPI`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async updateHomeKitCharacteristics(): Promise<void> {
|
|
151
|
+
if (this.On === undefined) {
|
|
152
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} On: ${this.On}`);
|
|
153
|
+
} else {
|
|
154
|
+
this.accessory.context.On = this.On;
|
|
155
|
+
this.switchService?.updateCharacteristic(this.platform.Characteristic.On, this.On);
|
|
156
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} updateCharacteristic On: ${this.On}`);
|
|
160
157
|
}
|
|
161
158
|
}
|
|
162
159
|
|
|
163
160
|
async statusCode({ res }: { res: IncomingMessage }): Promise<void> {
|
|
164
161
|
switch (res.statusCode) {
|
|
165
162
|
case 151:
|
|
166
|
-
this.errorLog(
|
|
163
|
+
this.errorLog(`${this.device.remoteType}: ${this.accessory.displayName} Command not supported by this device type.`);
|
|
167
164
|
break;
|
|
168
165
|
case 152:
|
|
169
|
-
this.errorLog(
|
|
166
|
+
this.errorLog(`${this.device.remoteType}: ${this.accessory.displayName} Device not found.`);
|
|
170
167
|
break;
|
|
171
168
|
case 160:
|
|
172
|
-
this.errorLog(
|
|
169
|
+
this.errorLog(`${this.device.remoteType}: ${this.accessory.displayName} Command is not supported.`);
|
|
173
170
|
break;
|
|
174
171
|
case 161:
|
|
175
|
-
this.errorLog(
|
|
172
|
+
this.errorLog(`${this.device.remoteType}: ${this.accessory.displayName} Device is offline.`);
|
|
176
173
|
break;
|
|
177
174
|
case 171:
|
|
178
|
-
this.errorLog(
|
|
175
|
+
this.errorLog(`${this.device.remoteType}: ${this.accessory.displayName} Hub Device is offline. Hub: ${this.device.hubDeviceId}`);
|
|
179
176
|
break;
|
|
180
177
|
case 190:
|
|
181
178
|
this.errorLog(
|
|
182
|
-
|
|
183
|
-
` with server, Or command: ${
|
|
179
|
+
`${this.device.remoteType}: ${this.accessory.displayName} Device internal error due to device states not synchronized` +
|
|
180
|
+
` with server, Or command: ${superStringify(res)} format is invalid`,
|
|
184
181
|
);
|
|
185
182
|
break;
|
|
186
183
|
case 100:
|
|
187
|
-
this.debugLog(
|
|
184
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} Command successfully sent.`);
|
|
188
185
|
break;
|
|
189
186
|
default:
|
|
190
|
-
this.debugLog(
|
|
187
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} Unknown statusCode.`);
|
|
191
188
|
}
|
|
192
189
|
}
|
|
193
190
|
|
|
194
191
|
async apiError(e: any): Promise<void> {
|
|
195
192
|
this.switchService.updateCharacteristic(this.platform.Characteristic.On, e);
|
|
196
|
-
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
FirmwareRevision(accessory: PlatformAccessory, device: irdevice & irDevicesConfig): string {
|
|
196
|
+
let FirmwareRevision: string;
|
|
197
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName}`
|
|
198
|
+
+ ` accessory.context.FirmwareRevision: ${accessory.context.FirmwareRevision}`);
|
|
199
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} device.firmware: ${device.firmware}`);
|
|
200
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} this.platform.version: ${this.platform.version}`);
|
|
201
|
+
if (accessory.context.FirmwareRevision) {
|
|
202
|
+
FirmwareRevision = accessory.context.FirmwareRevision;
|
|
203
|
+
} else if (device.firmware) {
|
|
204
|
+
FirmwareRevision = device.firmware;
|
|
205
|
+
} else {
|
|
206
|
+
FirmwareRevision = this.platform.version;
|
|
207
|
+
}
|
|
208
|
+
return FirmwareRevision;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async context() {
|
|
212
|
+
if (this.On === undefined) {
|
|
213
|
+
this.On = false;
|
|
214
|
+
} else {
|
|
215
|
+
this.On = this.accessory.context.On;
|
|
216
|
+
}
|
|
197
217
|
}
|
|
198
218
|
|
|
199
219
|
async config(device: irdevice & irDevicesConfig): Promise<void> {
|
|
@@ -204,24 +224,27 @@ export class VacuumCleaner {
|
|
|
204
224
|
if (device.logging !== undefined) {
|
|
205
225
|
config['logging'] = device.logging;
|
|
206
226
|
}
|
|
227
|
+
if (device.connectionType !== undefined) {
|
|
228
|
+
config['connectionType'] = device.connectionType;
|
|
229
|
+
}
|
|
207
230
|
if (Object.entries(config).length !== 0) {
|
|
208
|
-
this.infoLog(
|
|
231
|
+
this.infoLog(`${this.device.remoteType}: ${this.accessory.displayName} Config: ${superStringify(config)}`);
|
|
209
232
|
}
|
|
210
233
|
}
|
|
211
234
|
|
|
212
235
|
async logs(device: irdevice & irDevicesConfig): Promise<void> {
|
|
213
236
|
if (this.platform.debugMode) {
|
|
214
237
|
this.deviceLogging = this.accessory.context.logging = 'debugMode';
|
|
215
|
-
this.debugLog(
|
|
238
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} Using Debug Mode Logging: ${this.deviceLogging}`);
|
|
216
239
|
} else if (device.logging) {
|
|
217
240
|
this.deviceLogging = this.accessory.context.logging = device.logging;
|
|
218
|
-
this.debugLog(
|
|
241
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} Using Device Config Logging: ${this.deviceLogging}`);
|
|
219
242
|
} else if (this.platform.config.options?.logging) {
|
|
220
243
|
this.deviceLogging = this.accessory.context.logging = this.platform.config.options?.logging;
|
|
221
|
-
this.debugLog(
|
|
244
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} Using Platform Config Logging: ${this.deviceLogging}`);
|
|
222
245
|
} else {
|
|
223
246
|
this.deviceLogging = this.accessory.context.logging = 'standard';
|
|
224
|
-
this.debugLog(
|
|
247
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} Logging Not Set, Using: ${this.deviceLogging}`);
|
|
225
248
|
}
|
|
226
249
|
}
|
|
227
250
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import https from 'https';
|
|
2
2
|
import crypto from 'crypto';
|
|
3
3
|
import { IncomingMessage } from 'http';
|
|
4
|
+
import superStringify from 'super-stringify';
|
|
4
5
|
import { SwitchBotPlatform } from '../platform';
|
|
5
6
|
import { irDevicesConfig, irdevice, HostDomain, DevicePath } from '../settings';
|
|
6
7
|
import { CharacteristicValue, PlatformAccessory, Service } from 'homebridge';
|
|
@@ -16,7 +17,6 @@ export class WaterHeater {
|
|
|
16
17
|
|
|
17
18
|
// Characteristic Values
|
|
18
19
|
Active!: CharacteristicValue;
|
|
19
|
-
ActiveCached!: CharacteristicValue;
|
|
20
20
|
|
|
21
21
|
// Config
|
|
22
22
|
deviceLogging!: string;
|
|
@@ -25,23 +25,22 @@ export class WaterHeater {
|
|
|
25
25
|
// default placeholders
|
|
26
26
|
this.logs(device);
|
|
27
27
|
this.config(device);
|
|
28
|
-
|
|
29
|
-
this.Active = this.platform.Characteristic.Active.INACTIVE;
|
|
30
|
-
} else {
|
|
31
|
-
this.Active = this.accessory.context.Active;
|
|
32
|
-
}
|
|
28
|
+
this.context();
|
|
33
29
|
|
|
34
30
|
// set accessory information
|
|
35
31
|
accessory
|
|
36
32
|
.getService(this.platform.Service.AccessoryInformation)!
|
|
37
33
|
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'SwitchBot')
|
|
38
34
|
.setCharacteristic(this.platform.Characteristic.Model, device.remoteType)
|
|
39
|
-
.setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!)
|
|
35
|
+
.setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!)
|
|
36
|
+
.setCharacteristic(this.platform.Characteristic.FirmwareRevision, this.FirmwareRevision(accessory, device))
|
|
37
|
+
.getCharacteristic(this.platform.Characteristic.FirmwareRevision)
|
|
38
|
+
.updateValue(this.FirmwareRevision(accessory, device));
|
|
40
39
|
|
|
41
40
|
// get the Television service if it exists, otherwise create a new Television service
|
|
42
41
|
// you can create multiple services for each accessory
|
|
43
42
|
(this.valveService = accessory.getService(this.platform.Service.Valve) || accessory.addService(this.platform.Service.Valve)),
|
|
44
|
-
`${accessory.displayName}
|
|
43
|
+
`${accessory.displayName} ${device.remoteType}`;
|
|
45
44
|
|
|
46
45
|
// To avoid "Cannot add a Service with the same UUID another Service without also defining a unique 'subtype' property." error,
|
|
47
46
|
// when creating multiple services of the same type, you need to use the following syntax to specify a name and subtype id:
|
|
@@ -59,7 +58,9 @@ export class WaterHeater {
|
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
async ActiveSet(value: CharacteristicValue): Promise<void> {
|
|
62
|
-
this.debugLog(
|
|
61
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} Active: ${value}`);
|
|
62
|
+
this.Active = value;
|
|
63
|
+
this.accessory.context.Active = this.Active;
|
|
63
64
|
if (value === this.platform.Characteristic.Active.INACTIVE) {
|
|
64
65
|
await this.pushWaterHeaterOffChanges();
|
|
65
66
|
this.valveService.setCharacteristic(this.platform.Characteristic.InUse, this.platform.Characteristic.InUse.NOT_IN_USE);
|
|
@@ -67,33 +68,17 @@ export class WaterHeater {
|
|
|
67
68
|
await this.pushWaterHeaterOnChanges();
|
|
68
69
|
this.valveService.setCharacteristic(this.platform.Characteristic.InUse, this.platform.Characteristic.InUse.IN_USE);
|
|
69
70
|
}
|
|
70
|
-
this.Active = value;
|
|
71
|
-
this.ActiveCached = this.Active;
|
|
72
|
-
this.accessory.context.Active = this.ActiveCached;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async updateHomeKitCharacteristics(): Promise<void> {
|
|
76
|
-
if (this.Active === undefined) {
|
|
77
|
-
this.debugLog(`Water Heater: ${this.accessory.displayName} Active: ${this.Active}`);
|
|
78
|
-
} else {
|
|
79
|
-
this.valveService?.updateCharacteristic(this.platform.Characteristic.Active, this.Active);
|
|
80
|
-
this.debugLog(`Water Heater: ${this.accessory.displayName} updateCharacteristic Active: ${this.Active}`);
|
|
81
|
-
}
|
|
82
71
|
}
|
|
83
72
|
|
|
84
73
|
/**
|
|
85
74
|
* Pushes the requested changes to the SwitchBot API
|
|
86
|
-
* deviceType
|
|
87
|
-
* WaterHeater
|
|
88
|
-
* WaterHeater
|
|
89
|
-
* WaterHeater: "command" "volumeAdd" "default" = volume up
|
|
90
|
-
* WaterHeater: "command" "volumeSub" "default" = volume down
|
|
91
|
-
* WaterHeater: "command" "channelAdd" "default" = next channel
|
|
92
|
-
* WaterHeater: "command" "channelSub" "default" = previous channel
|
|
75
|
+
* deviceType Command Type Command Parameter Description
|
|
76
|
+
* WaterHeater "command" "turnOff" "default" set to OFF state
|
|
77
|
+
* WaterHeater "command" "turnOn" "default" set to ON state
|
|
93
78
|
*/
|
|
94
79
|
async pushWaterHeaterOnChanges(): Promise<void> {
|
|
95
80
|
if (this.Active !== 1) {
|
|
96
|
-
const body =
|
|
81
|
+
const body = superStringify({
|
|
97
82
|
'command': 'turnOn',
|
|
98
83
|
'parameter': 'default',
|
|
99
84
|
'commandType': 'command',
|
|
@@ -104,7 +89,7 @@ export class WaterHeater {
|
|
|
104
89
|
|
|
105
90
|
async pushWaterHeaterOffChanges(): Promise<void> {
|
|
106
91
|
if (this.Active !== 0) {
|
|
107
|
-
const body =
|
|
92
|
+
const body = superStringify({
|
|
108
93
|
'command': 'turnOff',
|
|
109
94
|
'parameter': 'default',
|
|
110
95
|
'commandType': 'command',
|
|
@@ -114,90 +99,124 @@ export class WaterHeater {
|
|
|
114
99
|
}
|
|
115
100
|
|
|
116
101
|
async pushChanges(body): Promise<void> {
|
|
117
|
-
|
|
102
|
+
if (this.device.connectionType === 'OpenAPI') {
|
|
103
|
+
try {
|
|
118
104
|
// Make Push On request to the API
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
105
|
+
const t = Date.now();
|
|
106
|
+
const nonce = 'requestID';
|
|
107
|
+
const data = this.platform.config.credentials?.token + t + nonce;
|
|
108
|
+
const signTerm = crypto.createHmac('sha256', this.platform.config.credentials?.secret)
|
|
109
|
+
.update(Buffer.from(data, 'utf-8'))
|
|
110
|
+
.digest();
|
|
111
|
+
const sign = signTerm.toString('base64');
|
|
112
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} sign: ${sign}`);
|
|
113
|
+
this.infoLog(`${this.device.remoteType}: ${this.accessory.displayName} Sending request to SwitchBot API. body: ${body},`);
|
|
114
|
+
const options = {
|
|
115
|
+
hostname: HostDomain,
|
|
116
|
+
port: 443,
|
|
117
|
+
path: `${DevicePath}/${this.device.deviceId}/commands`,
|
|
118
|
+
method: 'POST',
|
|
119
|
+
headers: {
|
|
120
|
+
'Authorization': this.platform.config.credentials?.token,
|
|
121
|
+
'sign': sign,
|
|
122
|
+
'nonce': nonce,
|
|
123
|
+
't': t,
|
|
124
|
+
'Content-Type': 'application/json',
|
|
125
|
+
'Content-Length': body.length,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
const req = https.request(options, res => {
|
|
129
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} pushchanges statusCode: ${res.statusCode}`);
|
|
130
|
+
this.statusCode({ res });
|
|
131
|
+
res.on('data', d => {
|
|
132
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} d: ${d}`);
|
|
133
|
+
});
|
|
147
134
|
});
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
this.errorLog(
|
|
160
|
-
`Water Heater: ${this.accessory.displayName} failed pushChanges with OpenAPI Connection,` + ` Error Message: ${JSON.stringify(e.message)}`,
|
|
161
|
-
);
|
|
135
|
+
req.on('error', (e: any) => {
|
|
136
|
+
this.errorLog(`${this.device.remoteType}: ${this.accessory.displayName} error message: ${e.message}`);
|
|
137
|
+
});
|
|
138
|
+
req.write(body);
|
|
139
|
+
req.end();
|
|
140
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} pushchanges: ${superStringify(req)}`);
|
|
141
|
+
this.updateHomeKitCharacteristics();
|
|
142
|
+
} catch (e: any) {
|
|
143
|
+
this.apiError(e);
|
|
144
|
+
this.errorLog(`${this.device.remoteType}: ${this.accessory.displayName} failed pushChanges with ${this.device.connectionType} Connection,`
|
|
145
|
+
+ ` Error Message: ${superStringify(e.message)}`);
|
|
162
146
|
}
|
|
163
|
-
|
|
147
|
+
} else {
|
|
148
|
+
this.warnLog(`${this.device.remoteType}: ${this.accessory.displayName}`
|
|
149
|
+
+ ` Connection Type: ${this.device.connectionType}, commands will not be sent to OpenAPI`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async updateHomeKitCharacteristics(): Promise<void> {
|
|
154
|
+
if (this.Active === undefined) {
|
|
155
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} Active: ${this.Active}`);
|
|
156
|
+
} else {
|
|
157
|
+
this.accessory.context.Active = this.Active;
|
|
158
|
+
this.valveService?.updateCharacteristic(this.platform.Characteristic.Active, this.Active);
|
|
159
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} updateCharacteristic Active: ${this.Active}`);
|
|
164
160
|
}
|
|
165
161
|
}
|
|
166
162
|
|
|
167
163
|
async statusCode({ res }: { res: IncomingMessage }): Promise<void> {
|
|
168
164
|
switch (res.statusCode) {
|
|
169
165
|
case 151:
|
|
170
|
-
this.errorLog(
|
|
166
|
+
this.errorLog(`${this.device.remoteType}: ${this.accessory.displayName} Command not supported by this device type.`);
|
|
171
167
|
break;
|
|
172
168
|
case 152:
|
|
173
|
-
this.errorLog(
|
|
169
|
+
this.errorLog(`${this.device.remoteType}: ${this.accessory.displayName} Device not found.`);
|
|
174
170
|
break;
|
|
175
171
|
case 160:
|
|
176
|
-
this.errorLog(
|
|
172
|
+
this.errorLog(`${this.device.remoteType}: ${this.accessory.displayName} Command is not supported.`);
|
|
177
173
|
break;
|
|
178
174
|
case 161:
|
|
179
|
-
this.errorLog(
|
|
175
|
+
this.errorLog(`${this.device.remoteType}: ${this.accessory.displayName} Device is offline.`);
|
|
180
176
|
break;
|
|
181
177
|
case 171:
|
|
182
|
-
this.errorLog(
|
|
178
|
+
this.errorLog(`${this.device.remoteType}: ${this.accessory.displayName} Hub Device is offline. Hub: ${this.device.hubDeviceId}`);
|
|
183
179
|
break;
|
|
184
180
|
case 190:
|
|
185
181
|
this.errorLog(
|
|
186
|
-
|
|
187
|
-
` with server, Or command: ${
|
|
182
|
+
`${this.device.remoteType}: ${this.accessory.displayName} Device internal error due to device states not synchronized` +
|
|
183
|
+
` with server, Or command: ${superStringify(res)} format is invalid`,
|
|
188
184
|
);
|
|
189
185
|
break;
|
|
190
186
|
case 100:
|
|
191
|
-
this.debugLog(
|
|
187
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} Command successfully sent.`);
|
|
192
188
|
break;
|
|
193
189
|
default:
|
|
194
|
-
this.debugLog(
|
|
190
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} Unknown statusCode.`);
|
|
195
191
|
}
|
|
196
192
|
}
|
|
197
193
|
|
|
198
194
|
async apiError(e: any): Promise<void> {
|
|
199
195
|
this.valveService.updateCharacteristic(this.platform.Characteristic.Active, e);
|
|
200
|
-
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
FirmwareRevision(accessory: PlatformAccessory, device: irdevice & irDevicesConfig): string {
|
|
199
|
+
let FirmwareRevision: string;
|
|
200
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName}`
|
|
201
|
+
+ ` accessory.context.FirmwareRevision: ${accessory.context.FirmwareRevision}`);
|
|
202
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} device.firmware: ${device.firmware}`);
|
|
203
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} this.platform.version: ${this.platform.version}`);
|
|
204
|
+
if (accessory.context.FirmwareRevision) {
|
|
205
|
+
FirmwareRevision = accessory.context.FirmwareRevision;
|
|
206
|
+
} else if (device.firmware) {
|
|
207
|
+
FirmwareRevision = device.firmware;
|
|
208
|
+
} else {
|
|
209
|
+
FirmwareRevision = this.platform.version;
|
|
210
|
+
}
|
|
211
|
+
return FirmwareRevision;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async context() {
|
|
215
|
+
if (this.Active === undefined) {
|
|
216
|
+
this.Active = this.platform.Characteristic.Active.INACTIVE;
|
|
217
|
+
} else {
|
|
218
|
+
this.Active = this.accessory.context.Active;
|
|
219
|
+
}
|
|
201
220
|
}
|
|
202
221
|
|
|
203
222
|
async config(device: irdevice & irDevicesConfig): Promise<void> {
|
|
@@ -208,24 +227,27 @@ export class WaterHeater {
|
|
|
208
227
|
if (device.logging !== undefined) {
|
|
209
228
|
config['logging'] = device.logging;
|
|
210
229
|
}
|
|
230
|
+
if (device.connectionType !== undefined) {
|
|
231
|
+
config['connectionType'] = device.connectionType;
|
|
232
|
+
}
|
|
211
233
|
if (Object.entries(config).length !== 0) {
|
|
212
|
-
this.infoLog(
|
|
234
|
+
this.infoLog(`${this.device.remoteType}: ${this.accessory.displayName} Config: ${superStringify(config)}`);
|
|
213
235
|
}
|
|
214
236
|
}
|
|
215
237
|
|
|
216
238
|
async logs(device: irdevice & irDevicesConfig): Promise<void> {
|
|
217
239
|
if (this.platform.debugMode) {
|
|
218
240
|
this.deviceLogging = this.accessory.context.logging = 'debugMode';
|
|
219
|
-
this.debugLog(
|
|
241
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} Using Debug Mode Logging: ${this.deviceLogging}`);
|
|
220
242
|
} else if (device.logging) {
|
|
221
243
|
this.deviceLogging = this.accessory.context.logging = device.logging;
|
|
222
|
-
this.debugLog(
|
|
244
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} Using Device Config Logging: ${this.deviceLogging}`);
|
|
223
245
|
} else if (this.platform.config.options?.logging) {
|
|
224
246
|
this.deviceLogging = this.accessory.context.logging = this.platform.config.options?.logging;
|
|
225
|
-
this.debugLog(
|
|
247
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} Using Platform Config Logging: ${this.deviceLogging}`);
|
|
226
248
|
} else {
|
|
227
249
|
this.deviceLogging = this.accessory.context.logging = 'standard';
|
|
228
|
-
this.debugLog(
|
|
250
|
+
this.debugLog(`${this.device.remoteType}: ${this.accessory.displayName} Logging Not Set, Using: ${this.deviceLogging}`);
|
|
229
251
|
}
|
|
230
252
|
}
|
|
231
253
|
|