@switchbot/homebridge-switchbot 1.10.2-beta.3 → 1.10.2-beta.4
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/dist/{devices/bots.d.ts → device/bot.d.ts} +1 -1
- package/dist/device/bot.d.ts.map +1 -0
- package/dist/{devices/bots.js → device/bot.js} +1 -1
- package/dist/device/bot.js.map +1 -0
- package/dist/{devices → device}/colorbulb.d.ts +6 -0
- package/dist/device/colorbulb.d.ts.map +1 -0
- package/dist/{devices → device}/colorbulb.js +6 -0
- package/dist/device/colorbulb.js.map +1 -0
- package/dist/{devices → device}/contact.d.ts +0 -0
- package/dist/device/contact.d.ts.map +1 -0
- package/dist/{devices → device}/contact.js +0 -0
- package/dist/device/contact.js.map +1 -0
- package/dist/{devices/curtains.d.ts → device/curtain.d.ts} +1 -1
- package/dist/device/curtain.d.ts.map +1 -0
- package/dist/{devices/curtains.js → device/curtain.js} +2 -2
- package/dist/device/curtain.js.map +1 -0
- package/dist/{devices/humidifiers.d.ts → device/humidifier.d.ts} +1 -1
- package/dist/device/humidifier.d.ts.map +1 -0
- package/dist/{devices/humidifiers.js → device/humidifier.js} +1 -1
- package/dist/device/humidifier.js.map +1 -0
- package/dist/{devices/plugs.d.ts → device/lock.d.ts} +12 -11
- package/dist/device/lock.d.ts.map +1 -0
- package/dist/device/lock.js +299 -0
- package/dist/device/lock.js.map +1 -0
- package/dist/{devices/meters.d.ts → device/meter.d.ts} +2 -1
- package/dist/device/meter.d.ts.map +1 -0
- package/dist/{devices/meters.js → device/meter.js} +15 -2
- package/dist/device/meter.js.map +1 -0
- package/dist/{devices → device}/motion.d.ts +0 -0
- package/dist/device/motion.d.ts.map +1 -0
- package/dist/{devices → device}/motion.js +0 -0
- package/dist/device/motion.js.map +1 -0
- package/dist/device/plug.d.ts +54 -0
- package/dist/device/plug.d.ts.map +1 -0
- package/dist/{devices/plugs.js → device/plug.js} +26 -10
- package/dist/device/plug.js.map +1 -0
- package/dist/{irdevices/airconditioners.d.ts → irdevice/airconditioner.d.ts} +1 -1
- package/dist/irdevice/airconditioner.d.ts.map +1 -0
- package/dist/{irdevices/airconditioners.js → irdevice/airconditioner.js} +1 -1
- package/dist/irdevice/airconditioner.js.map +1 -0
- package/dist/{irdevices/airpurifiers.d.ts → irdevice/airpurifier.d.ts} +1 -1
- package/dist/irdevice/airpurifier.d.ts.map +1 -0
- package/dist/{irdevices/airpurifiers.js → irdevice/airpurifier.js} +1 -1
- package/dist/irdevice/airpurifier.js.map +1 -0
- package/dist/{irdevices/cameras.d.ts → irdevice/camera.d.ts} +1 -1
- package/dist/irdevice/camera.d.ts.map +1 -0
- package/dist/{irdevices/cameras.js → irdevice/camera.js} +1 -1
- package/dist/irdevice/camera.js.map +1 -0
- package/dist/{irdevices/fans.d.ts → irdevice/fan.d.ts} +1 -1
- package/dist/irdevice/fan.d.ts.map +1 -0
- package/dist/{irdevices/fans.js → irdevice/fan.js} +1 -1
- package/dist/irdevice/fan.js.map +1 -0
- package/dist/{irdevices/lights.d.ts → irdevice/light.d.ts} +1 -1
- package/dist/irdevice/light.d.ts.map +1 -0
- package/dist/{irdevices/lights.js → irdevice/light.js} +1 -1
- package/dist/irdevice/light.js.map +1 -0
- package/dist/{irdevices/others.d.ts → irdevice/other.d.ts} +1 -1
- package/dist/irdevice/other.d.ts.map +1 -0
- package/dist/{irdevices/others.js → irdevice/other.js} +1 -1
- package/dist/irdevice/other.js.map +1 -0
- package/dist/{irdevices/tvs.d.ts → irdevice/tv.d.ts} +1 -1
- package/dist/irdevice/tv.d.ts.map +1 -0
- package/dist/{irdevices/tvs.js → irdevice/tv.js} +1 -1
- package/dist/irdevice/tv.js.map +1 -0
- package/dist/{irdevices/vacuumcleaners.d.ts → irdevice/vacuumcleaner.d.ts} +1 -1
- package/dist/irdevice/vacuumcleaner.d.ts.map +1 -0
- package/dist/{irdevices/vacuumcleaners.js → irdevice/vacuumcleaner.js} +1 -1
- package/dist/irdevice/vacuumcleaner.js.map +1 -0
- package/dist/{irdevices/waterheaters.d.ts → irdevice/waterheater.d.ts} +1 -1
- package/dist/irdevice/waterheater.d.ts.map +1 -0
- package/dist/{irdevices/waterheaters.js → irdevice/waterheater.js} +1 -1
- package/dist/irdevice/waterheater.js.map +1 -0
- package/dist/platform.d.ts +1 -0
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +106 -45
- package/dist/platform.js.map +1 -1
- package/dist/settings.d.ts +7 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js.map +1 -1
- package/package.json +1 -1
- package/src/{devices/bots.ts → device/bot.ts} +0 -0
- package/src/{devices → device}/colorbulb.ts +6 -0
- package/src/{devices → device}/contact.ts +0 -0
- package/src/{devices/curtains.ts → device/curtain.ts} +1 -1
- package/src/{devices/humidifiers.ts → device/humidifier.ts} +0 -0
- package/src/device/lock.ts +346 -0
- package/src/{devices/meters.ts → device/meter.ts} +13 -1
- package/src/{devices → device}/motion.ts +0 -0
- package/src/{devices/plugs.ts → device/plug.ts} +28 -13
- package/src/{irdevices/airconditioners.ts → irdevice/airconditioner.ts} +0 -0
- package/src/{irdevices/airpurifiers.ts → irdevice/airpurifier.ts} +0 -0
- package/src/{irdevices/cameras.ts → irdevice/camera.ts} +0 -0
- package/src/{irdevices/fans.ts → irdevice/fan.ts} +0 -0
- package/src/{irdevices/lights.ts → irdevice/light.ts} +0 -0
- package/src/{irdevices/others.ts → irdevice/other.ts} +0 -0
- package/src/{irdevices/tvs.ts → irdevice/tv.ts} +0 -0
- package/src/{irdevices/vacuumcleaners.ts → irdevice/vacuumcleaner.ts} +0 -0
- package/src/{irdevices/waterheaters.ts → irdevice/waterheater.ts} +0 -0
- package/src/platform.ts +82 -17
- package/src/settings.ts +24 -11
- package/dist/devices/bots.d.ts.map +0 -1
- package/dist/devices/bots.js.map +0 -1
- package/dist/devices/colorbulb.d.ts.map +0 -1
- package/dist/devices/colorbulb.js.map +0 -1
- package/dist/devices/contact.d.ts.map +0 -1
- package/dist/devices/contact.js.map +0 -1
- package/dist/devices/curtains.d.ts.map +0 -1
- package/dist/devices/curtains.js.map +0 -1
- package/dist/devices/humidifiers.d.ts.map +0 -1
- package/dist/devices/humidifiers.js.map +0 -1
- package/dist/devices/meters.d.ts.map +0 -1
- package/dist/devices/meters.js.map +0 -1
- package/dist/devices/motion.d.ts.map +0 -1
- package/dist/devices/motion.js.map +0 -1
- package/dist/devices/plugs.d.ts.map +0 -1
- package/dist/devices/plugs.js.map +0 -1
- package/dist/irdevices/airconditioners.d.ts.map +0 -1
- package/dist/irdevices/airconditioners.js.map +0 -1
- package/dist/irdevices/airpurifiers.d.ts.map +0 -1
- package/dist/irdevices/airpurifiers.js.map +0 -1
- package/dist/irdevices/cameras.d.ts.map +0 -1
- package/dist/irdevices/cameras.js.map +0 -1
- package/dist/irdevices/fans.d.ts.map +0 -1
- package/dist/irdevices/fans.js.map +0 -1
- package/dist/irdevices/lights.d.ts.map +0 -1
- package/dist/irdevices/lights.js.map +0 -1
- package/dist/irdevices/others.d.ts.map +0 -1
- package/dist/irdevices/others.js.map +0 -1
- package/dist/irdevices/tvs.d.ts.map +0 -1
- package/dist/irdevices/tvs.js.map +0 -1
- package/dist/irdevices/vacuumcleaners.d.ts.map +0 -1
- package/dist/irdevices/vacuumcleaners.js.map +0 -1
- package/dist/irdevices/waterheaters.d.ts.map +0 -1
- package/dist/irdevices/waterheaters.js.map +0 -1
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { AxiosResponse } from 'axios';
|
|
2
|
+
import { interval, Subject } from 'rxjs';
|
|
3
|
+
import { SwitchBotPlatform } from '../platform';
|
|
4
|
+
import { debounceTime, skipWhile, take, tap } from 'rxjs/operators';
|
|
5
|
+
import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge';
|
|
6
|
+
import { DeviceURL, device, devicesConfig, deviceStatusResponse, payload, deviceStatus } from '../settings';
|
|
7
|
+
|
|
8
|
+
export class Lock {
|
|
9
|
+
// Services
|
|
10
|
+
private service: Service;
|
|
11
|
+
|
|
12
|
+
// Characteristic Values
|
|
13
|
+
LockCurrentState!: CharacteristicValue;
|
|
14
|
+
LockTargetState!: CharacteristicValue;
|
|
15
|
+
LockTargetStateCached!: CharacteristicValue;
|
|
16
|
+
|
|
17
|
+
// OpenAPI Others
|
|
18
|
+
doorState!: deviceStatus['doorState'];
|
|
19
|
+
lockState!: deviceStatus['lockState'];
|
|
20
|
+
deviceStatus!: deviceStatusResponse;
|
|
21
|
+
|
|
22
|
+
// Config
|
|
23
|
+
scanDuration!: number;
|
|
24
|
+
deviceLogging!: string;
|
|
25
|
+
deviceRefreshRate!: number;
|
|
26
|
+
|
|
27
|
+
// Updates
|
|
28
|
+
lockUpdateInProgress!: boolean;
|
|
29
|
+
doLockUpdate!: Subject<void>;
|
|
30
|
+
|
|
31
|
+
constructor(private readonly platform: SwitchBotPlatform, private accessory: PlatformAccessory, public device: device & devicesConfig) {
|
|
32
|
+
// default placeholders
|
|
33
|
+
this.logs(device);
|
|
34
|
+
this.scan(device);
|
|
35
|
+
this.refreshRate(device);
|
|
36
|
+
this.config(device);
|
|
37
|
+
if (this.LockTargetState === undefined) {
|
|
38
|
+
this.LockTargetState = false;
|
|
39
|
+
} else {
|
|
40
|
+
this.LockTargetState = this.accessory.context.On;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Lock Config
|
|
44
|
+
this.debugLog(`Lock: ${this.accessory.displayName} Config: (offline: ${device.offline})`);
|
|
45
|
+
|
|
46
|
+
// this is subject we use to track when we need to POST changes to the SwitchBot API
|
|
47
|
+
this.doLockUpdate = new Subject();
|
|
48
|
+
this.lockUpdateInProgress = false;
|
|
49
|
+
|
|
50
|
+
// Retrieve initial values and updateHomekit
|
|
51
|
+
this.refreshStatus();
|
|
52
|
+
|
|
53
|
+
// set accessory information
|
|
54
|
+
accessory
|
|
55
|
+
.getService(this.platform.Service.AccessoryInformation)!
|
|
56
|
+
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'SwitchBot')
|
|
57
|
+
.setCharacteristic(this.platform.Characteristic.Model, 'SWITCHBOT-LOCK-W1601700')
|
|
58
|
+
.setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!);
|
|
59
|
+
|
|
60
|
+
// get the WindowCovering service if it exists, otherwise create a new WindowCovering service
|
|
61
|
+
// you can create multiple services for each accessory
|
|
62
|
+
(this.service = accessory.getService(this.platform.Service.Outlet) || accessory.addService(this.platform.Service.Outlet)),
|
|
63
|
+
`${device.deviceName} ${device.deviceType}`;
|
|
64
|
+
|
|
65
|
+
// To avoid "Cannot add a Service with the same UUID another Service without also defining a unique 'subtype' property." error,
|
|
66
|
+
// when creating multiple services of the same type, you need to use the following syntax to specify a name and subtype id:
|
|
67
|
+
// accessory.getService('NAME') ?? accessory.addService(this.platform.Service.Lightbulb, 'NAME', 'USER_DEFINED_SUBTYPE');
|
|
68
|
+
|
|
69
|
+
// set the service name, this is what is displayed as the default name on the Home app
|
|
70
|
+
// in this example we are using the name we stored in the `accessory.context` in the `discoverDevices` method.
|
|
71
|
+
this.service.setCharacteristic(this.platform.Characteristic.Name, accessory.displayName);
|
|
72
|
+
|
|
73
|
+
// each service must implement at-minimum the "required characteristics" for the given service type
|
|
74
|
+
// see https://developers.homebridge.io/#/service/WindowCovering
|
|
75
|
+
|
|
76
|
+
// create handlers for required characteristics
|
|
77
|
+
this.service.getCharacteristic(this.platform.Characteristic.LockCurrentState).onSet(this.LockTargetStateSet.bind(this));
|
|
78
|
+
|
|
79
|
+
// Update Homekit
|
|
80
|
+
this.updateHomeKitCharacteristics();
|
|
81
|
+
|
|
82
|
+
// Start an update interval
|
|
83
|
+
interval(this.deviceRefreshRate * 1000)
|
|
84
|
+
.pipe(skipWhile(() => this.lockUpdateInProgress))
|
|
85
|
+
.subscribe(() => {
|
|
86
|
+
this.refreshStatus();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Watch for Lock change events
|
|
90
|
+
// We put in a debounce of 100ms so we don't make duplicate calls
|
|
91
|
+
this.doLockUpdate
|
|
92
|
+
.pipe(
|
|
93
|
+
tap(() => {
|
|
94
|
+
this.lockUpdateInProgress = true;
|
|
95
|
+
}),
|
|
96
|
+
debounceTime(this.platform.config.options!.pushRate! * 1000),
|
|
97
|
+
)
|
|
98
|
+
.subscribe(async () => {
|
|
99
|
+
try {
|
|
100
|
+
await this.pushChanges();
|
|
101
|
+
} catch (e: any) {
|
|
102
|
+
this.errorLog(`Lock: ${this.accessory.displayName} failed pushChanges`);
|
|
103
|
+
if (this.deviceLogging === 'debug') {
|
|
104
|
+
this.errorLog(`Lock: ${this.accessory.displayName} failed pushChanges,` + ` Error Message: ${JSON.stringify(e.message)}`);
|
|
105
|
+
}
|
|
106
|
+
if (this.platform.debugMode) {
|
|
107
|
+
this.errorLog(`Lock: ${this.accessory.displayName} failed pushChanges,` + ` Error: ${JSON.stringify(e)}`);
|
|
108
|
+
}
|
|
109
|
+
this.apiError(e);
|
|
110
|
+
}
|
|
111
|
+
this.lockUpdateInProgress = false;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
parseStatus() {
|
|
116
|
+
switch (this.lockState) {
|
|
117
|
+
case 'on':
|
|
118
|
+
this.LockTargetState = this.platform.Characteristic.LockTargetState.UNSECURED;
|
|
119
|
+
break;
|
|
120
|
+
default:
|
|
121
|
+
this.LockTargetState = this.platform.Characteristic.LockTargetState.SECURED;
|
|
122
|
+
}
|
|
123
|
+
this.debugLog(`Lock ${this.accessory.displayName} On: ${this.LockTargetState}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private async refreshStatus() {
|
|
127
|
+
try {
|
|
128
|
+
this.deviceStatus = (await this.platform.axios.get(`${DeviceURL}/${this.device.deviceId}/status`)).data;
|
|
129
|
+
this.debugLog(`Lock: ${this.accessory.displayName} refreshStatus: ${JSON.stringify(this.deviceStatus)}`);
|
|
130
|
+
this.lockState = this.deviceStatus.body.lockState;
|
|
131
|
+
this.warnLog(this.lockState);
|
|
132
|
+
this.doorState = this.deviceStatus.body.doorState;
|
|
133
|
+
this.warnLog(this.doorState);
|
|
134
|
+
this.parseStatus();
|
|
135
|
+
this.updateHomeKitCharacteristics();
|
|
136
|
+
} catch (e: any) {
|
|
137
|
+
this.errorLog(`Lock: ${this.accessory.displayName} failed refreshStatus with OpenAPI Connection`);
|
|
138
|
+
if (this.deviceLogging === 'debug') {
|
|
139
|
+
this.errorLog(
|
|
140
|
+
`Lock: ${this.accessory.displayName} failed refreshStatus with OpenAPI Connection,` + ` Error Message: ${JSON.stringify(e.message)}`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
if (this.platform.debugMode) {
|
|
144
|
+
this.errorLog(`Lock: ${this.accessory.displayName} failed refreshStatus with OpenAPI Connection,` + ` Error: ${JSON.stringify(e)}`);
|
|
145
|
+
}
|
|
146
|
+
this.apiError(e);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Pushes the requested changes to the SwitchBot API
|
|
152
|
+
* deviceType commandType Command command parameter Description
|
|
153
|
+
* Lock - "command" "????" "????" = set to ???? state
|
|
154
|
+
* Lock - "command" "????" "????" = set to ???? state - LockCurrentState
|
|
155
|
+
*/
|
|
156
|
+
async pushChanges() {
|
|
157
|
+
if (this.LockTargetState !== this.LockTargetStateCached) {
|
|
158
|
+
const payload = {
|
|
159
|
+
commandType: 'command',
|
|
160
|
+
parameter: 'default',
|
|
161
|
+
} as payload;
|
|
162
|
+
|
|
163
|
+
if (this.LockTargetState) {
|
|
164
|
+
payload.command = 'turnOn';
|
|
165
|
+
} else {
|
|
166
|
+
payload.command = 'turnOff';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
this.infoLog(
|
|
170
|
+
`Lock: ${this.accessory.displayName} Sending request to SwitchBot API. command: ${payload.command},` +
|
|
171
|
+
` parameter: ${payload.parameter}, commandType: ${payload.commandType}`,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// Make the API request
|
|
175
|
+
const push: any = await this.platform.axios.post(`${DeviceURL}/${this.device.deviceId}/commands`, payload);
|
|
176
|
+
this.debugLog(`Lock: ${this.accessory.displayName} pushchanges: ${JSON.stringify(push.data)}`);
|
|
177
|
+
this.statusCode(push);
|
|
178
|
+
this.LockTargetStateCached = this.LockTargetState;
|
|
179
|
+
this.accessory.context.On = this.LockTargetStateCached;
|
|
180
|
+
}
|
|
181
|
+
interval(5000)
|
|
182
|
+
.pipe(skipWhile(() => this.lockUpdateInProgress))
|
|
183
|
+
.pipe(take(1))
|
|
184
|
+
.subscribe(() => {
|
|
185
|
+
this.refreshStatus();
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
updateHomeKitCharacteristics() {
|
|
190
|
+
if (this.LockTargetState === undefined) {
|
|
191
|
+
this.debugLog(`Lock: ${this.accessory.displayName} LockTargetState: ${this.LockTargetState}`);
|
|
192
|
+
} else {
|
|
193
|
+
this.service.updateCharacteristic(this.platform.Characteristic.LockTargetState, this.LockTargetState);
|
|
194
|
+
this.debugLog(`Lock: ${this.accessory.displayName} updateCharacteristic LockTargetState: ${this.LockTargetState}`);
|
|
195
|
+
}
|
|
196
|
+
if (this.LockCurrentState === undefined) {
|
|
197
|
+
this.debugLog(`Lock: ${this.accessory.displayName} LockCurrentState: ${this.LockCurrentState}`);
|
|
198
|
+
} else {
|
|
199
|
+
this.service.updateCharacteristic(this.platform.Characteristic.LockCurrentState, this.LockCurrentState);
|
|
200
|
+
this.debugLog(`Lock: ${this.accessory.displayName} updateCharacteristic LockTargetState: ${this.LockCurrentState}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
public apiError(e: any) {
|
|
205
|
+
this.service.updateCharacteristic(this.platform.Characteristic.On, e);
|
|
206
|
+
//throw new this.platform.api.hap.HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private statusCode(push: AxiosResponse<{ statusCode: number }>) {
|
|
210
|
+
switch (push.data.statusCode) {
|
|
211
|
+
case 151:
|
|
212
|
+
this.errorLog(`Lock: ${this.accessory.displayName} Command not supported by this device type.`);
|
|
213
|
+
break;
|
|
214
|
+
case 152:
|
|
215
|
+
this.errorLog(`Lock: ${this.accessory.displayName} Device not found.`);
|
|
216
|
+
break;
|
|
217
|
+
case 160:
|
|
218
|
+
this.errorLog(`Lock: ${this.accessory.displayName} Command is not supported.`);
|
|
219
|
+
break;
|
|
220
|
+
case 161:
|
|
221
|
+
this.errorLog(`Lock: ${this.accessory.displayName} Device is offline.`);
|
|
222
|
+
break;
|
|
223
|
+
case 171:
|
|
224
|
+
this.errorLog(`Lock: ${this.accessory.displayName} Hub Device is offline. Hub: ${this.device.hubDeviceId}`);
|
|
225
|
+
break;
|
|
226
|
+
case 190:
|
|
227
|
+
this.errorLog(
|
|
228
|
+
`Lock: ${this.accessory.displayName} Device internal error due to device states not synchronized with server,` +
|
|
229
|
+
` Or command: ${JSON.stringify(push.data)} format is invalid`,
|
|
230
|
+
);
|
|
231
|
+
break;
|
|
232
|
+
case 100:
|
|
233
|
+
this.debugLog(`Lock: ${this.accessory.displayName} Command successfully sent.`);
|
|
234
|
+
break;
|
|
235
|
+
default:
|
|
236
|
+
this.debugLog(`Lock: ${this.accessory.displayName} Unknown statusCode.`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Handle requests to set the value of the "On" characteristic
|
|
242
|
+
*/
|
|
243
|
+
LockTargetStateSet(value: CharacteristicValue) {
|
|
244
|
+
this.debugLog(`Lock: ${this.accessory.displayName} Set LockTargetState: ${value}`);
|
|
245
|
+
|
|
246
|
+
this.LockTargetState = value;
|
|
247
|
+
this.doLockUpdate.next();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
config(device: device & devicesConfig) {
|
|
251
|
+
let config = {};
|
|
252
|
+
if (device.lock) {
|
|
253
|
+
config = device.lock;
|
|
254
|
+
}
|
|
255
|
+
if (device.ble !== undefined) {
|
|
256
|
+
config['ble'] = device.ble;
|
|
257
|
+
}
|
|
258
|
+
if (device.logging !== undefined) {
|
|
259
|
+
config['logging'] = device.logging;
|
|
260
|
+
}
|
|
261
|
+
if (device.refreshRate !== undefined) {
|
|
262
|
+
config['refreshRate'] = device.refreshRate;
|
|
263
|
+
}
|
|
264
|
+
if (device.scanDuration !== undefined) {
|
|
265
|
+
config['scanDuration'] = device.scanDuration;
|
|
266
|
+
}
|
|
267
|
+
if (Object.entries(config).length !== 0) {
|
|
268
|
+
this.warnLog(`Lock: ${this.accessory.displayName} Config: ${JSON.stringify(config)}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
refreshRate(device: device & devicesConfig) {
|
|
273
|
+
if (device.refreshRate) {
|
|
274
|
+
this.deviceRefreshRate = this.accessory.context.refreshRate = device.refreshRate;
|
|
275
|
+
this.debugLog(`Lock: ${this.accessory.displayName} Using Device Config refreshRate: ${this.deviceRefreshRate}`);
|
|
276
|
+
} else if (this.platform.config.options!.refreshRate) {
|
|
277
|
+
this.deviceRefreshRate = this.accessory.context.refreshRate = this.platform.config.options!.refreshRate;
|
|
278
|
+
this.debugLog(`Lock: ${this.accessory.displayName} Using Platform Config refreshRate: ${this.deviceRefreshRate}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
scan(device: device & devicesConfig) {
|
|
283
|
+
if (device.scanDuration) {
|
|
284
|
+
this.scanDuration = this.accessory.context.scanDuration = device.scanDuration;
|
|
285
|
+
if (device.ble) {
|
|
286
|
+
this.debugLog(`Lock: ${this.accessory.displayName} Using Device Config scanDuration: ${this.scanDuration}`);
|
|
287
|
+
}
|
|
288
|
+
} else {
|
|
289
|
+
this.scanDuration = this.accessory.context.scanDuration = 1;
|
|
290
|
+
if (this.device.ble) {
|
|
291
|
+
this.debugLog(`Lock: ${this.accessory.displayName} Using Default scanDuration: ${this.scanDuration}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
logs(device: device & devicesConfig) {
|
|
297
|
+
if (this.platform.debugMode) {
|
|
298
|
+
this.deviceLogging = this.accessory.context.logging = 'debugMode';
|
|
299
|
+
this.debugLog(`Lock: ${this.accessory.displayName} Using Debug Mode Logging: ${this.deviceLogging}`);
|
|
300
|
+
} else if (device.logging) {
|
|
301
|
+
this.deviceLogging = this.accessory.context.logging = device.logging;
|
|
302
|
+
this.debugLog(`Lock: ${this.accessory.displayName} Using Device Config Logging: ${this.deviceLogging}`);
|
|
303
|
+
} else if (this.platform.config.options?.logging) {
|
|
304
|
+
this.deviceLogging = this.accessory.context.logging = this.platform.config.options?.logging;
|
|
305
|
+
this.debugLog(`Lock: ${this.accessory.displayName} Using Platform Config Logging: ${this.deviceLogging}`);
|
|
306
|
+
} else {
|
|
307
|
+
this.deviceLogging = this.accessory.context.logging = 'standard';
|
|
308
|
+
this.debugLog(`Lock: ${this.accessory.displayName} Logging Not Set, Using: ${this.deviceLogging}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Logging for Device
|
|
314
|
+
*/
|
|
315
|
+
infoLog(...log: any[]) {
|
|
316
|
+
if (this.enablingDeviceLogging()) {
|
|
317
|
+
this.platform.log.info(String(...log));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
warnLog(...log: any[]) {
|
|
322
|
+
if (this.enablingDeviceLogging()) {
|
|
323
|
+
this.platform.log.warn(String(...log));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
errorLog(...log: any[]) {
|
|
328
|
+
if (this.enablingDeviceLogging()) {
|
|
329
|
+
this.platform.log.error(String(...log));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
debugLog(...log: any[]) {
|
|
334
|
+
if (this.enablingDeviceLogging()) {
|
|
335
|
+
if (this.deviceLogging === 'debug') {
|
|
336
|
+
this.platform.log.info('[DEBUG]', String(...log));
|
|
337
|
+
} else {
|
|
338
|
+
this.platform.log.debug(String(...log));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
enablingDeviceLogging(): boolean {
|
|
344
|
+
return this.deviceLogging === 'debug' || this.deviceLogging === 'standard';
|
|
345
|
+
}
|
|
346
|
+
}
|
|
@@ -83,7 +83,7 @@ export class Meter {
|
|
|
83
83
|
accessory
|
|
84
84
|
.getService(this.platform.Service.AccessoryInformation)!
|
|
85
85
|
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'SwitchBot')
|
|
86
|
-
.setCharacteristic(this.platform.Characteristic.Model,
|
|
86
|
+
.setCharacteristic(this.platform.Characteristic.Model, this.model(device))
|
|
87
87
|
.setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!);
|
|
88
88
|
|
|
89
89
|
// Temperature Sensor Service
|
|
@@ -393,6 +393,18 @@ export class Meter {
|
|
|
393
393
|
//throw new this.platform.api.hap.HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
|
394
394
|
}
|
|
395
395
|
|
|
396
|
+
model(device: device & devicesConfig): CharacteristicValue {
|
|
397
|
+
let model: string;
|
|
398
|
+
if (device.deviceType === 'Meter Plus (US)') {
|
|
399
|
+
model = 'SWITCHBOT-METERTH-W2301500';
|
|
400
|
+
} else if (device.deviceType === 'Meter Plus (JP)') {
|
|
401
|
+
model = 'SWITCHBOT-METERTH-W2201500';
|
|
402
|
+
} else {
|
|
403
|
+
model = 'SWITCHBOT-METERTH-S1';
|
|
404
|
+
}
|
|
405
|
+
return model;
|
|
406
|
+
}
|
|
407
|
+
|
|
396
408
|
config(device: device & devicesConfig) {
|
|
397
409
|
let config = {};
|
|
398
410
|
if (device.meter) {
|
|
File without changes
|
|
@@ -52,7 +52,7 @@ export class Plug {
|
|
|
52
52
|
accessory
|
|
53
53
|
.getService(this.platform.Service.AccessoryInformation)!
|
|
54
54
|
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'SwitchBot')
|
|
55
|
-
.setCharacteristic(this.platform.Characteristic.Model,
|
|
55
|
+
.setCharacteristic(this.platform.Characteristic.Model, this.model(device))
|
|
56
56
|
.setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!);
|
|
57
57
|
|
|
58
58
|
// get the WindowCovering service if it exists, otherwise create a new WindowCovering service
|
|
@@ -145,8 +145,11 @@ export class Plug {
|
|
|
145
145
|
/**
|
|
146
146
|
* Pushes the requested changes to the SwitchBot API
|
|
147
147
|
* deviceType commandType Command command parameter Description
|
|
148
|
-
* Plug
|
|
149
|
-
* Plug
|
|
148
|
+
* Plug - "command" "turnOff" "default" = set to OFF state
|
|
149
|
+
* Plug - "command" "turnOn" "default" = set to ON state
|
|
150
|
+
* Plug Mini (US/JP) - "command" turnOn default = set to ON state
|
|
151
|
+
* Plug Mini (US/JP) - "command" turnOff default = set to OFF state
|
|
152
|
+
* Plug Mini (US/JP) - "command" toggle default = toggle state
|
|
150
153
|
*/
|
|
151
154
|
async pushChanges() {
|
|
152
155
|
if (this.On !== this.OnCached) {
|
|
@@ -228,7 +231,7 @@ export class Plug {
|
|
|
228
231
|
}
|
|
229
232
|
}
|
|
230
233
|
|
|
231
|
-
|
|
234
|
+
async offlineOff(): Promise<void> {
|
|
232
235
|
if (this.device.offline) {
|
|
233
236
|
this.On = false;
|
|
234
237
|
this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(this.On);
|
|
@@ -238,14 +241,26 @@ export class Plug {
|
|
|
238
241
|
/**
|
|
239
242
|
* Handle requests to set the value of the "On" characteristic
|
|
240
243
|
*/
|
|
241
|
-
OnSet(value: CharacteristicValue) {
|
|
244
|
+
async OnSet(value: CharacteristicValue): Promise<void> {
|
|
242
245
|
this.debugLog(`Plug: ${this.accessory.displayName} - Set On: ${value}`);
|
|
243
246
|
|
|
244
247
|
this.On = value;
|
|
245
248
|
this.doPlugUpdate.next();
|
|
246
249
|
}
|
|
247
250
|
|
|
248
|
-
|
|
251
|
+
model(device: device & devicesConfig): string {
|
|
252
|
+
let model: string;
|
|
253
|
+
if (device.deviceType === 'Plug Mini (US)') {
|
|
254
|
+
model = 'SWITCHBOT-PLUG-W1901400';
|
|
255
|
+
} else if (device.deviceType === 'Plug Mini (JP)') {
|
|
256
|
+
model = 'SWITCHBOT-PLUG-W2001400';
|
|
257
|
+
} else {
|
|
258
|
+
model = 'SWITCHBOT-PLUG-SP11';
|
|
259
|
+
}
|
|
260
|
+
return model;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async config(device: device & devicesConfig): Promise<void> {
|
|
249
264
|
let config = {};
|
|
250
265
|
if (device.plug) {
|
|
251
266
|
config = device.plug;
|
|
@@ -267,7 +282,7 @@ export class Plug {
|
|
|
267
282
|
}
|
|
268
283
|
}
|
|
269
284
|
|
|
270
|
-
refreshRate(device: device & devicesConfig) {
|
|
285
|
+
async refreshRate(device: device & devicesConfig): Promise<void> {
|
|
271
286
|
if (device.refreshRate) {
|
|
272
287
|
this.deviceRefreshRate = this.accessory.context.refreshRate = device.refreshRate;
|
|
273
288
|
this.debugLog(`Plug: ${this.accessory.displayName} Using Device Config refreshRate: ${this.deviceRefreshRate}`);
|
|
@@ -277,7 +292,7 @@ export class Plug {
|
|
|
277
292
|
}
|
|
278
293
|
}
|
|
279
294
|
|
|
280
|
-
scan(device: device & devicesConfig) {
|
|
295
|
+
async scan(device: device & devicesConfig): Promise<void> {
|
|
281
296
|
if (device.scanDuration) {
|
|
282
297
|
this.scanDuration = this.accessory.context.scanDuration = device.scanDuration;
|
|
283
298
|
if (device.ble) {
|
|
@@ -291,7 +306,7 @@ export class Plug {
|
|
|
291
306
|
}
|
|
292
307
|
}
|
|
293
308
|
|
|
294
|
-
logs(device: device & devicesConfig) {
|
|
309
|
+
async logs(device: device & devicesConfig): Promise<void> {
|
|
295
310
|
if (this.platform.debugMode) {
|
|
296
311
|
this.deviceLogging = this.accessory.context.logging = 'debugMode';
|
|
297
312
|
this.debugLog(`Plug: ${this.accessory.displayName} Using Debug Mode Logging: ${this.deviceLogging}`);
|
|
@@ -310,25 +325,25 @@ export class Plug {
|
|
|
310
325
|
/**
|
|
311
326
|
* Logging for Device
|
|
312
327
|
*/
|
|
313
|
-
infoLog(...log: any[]) {
|
|
328
|
+
infoLog(...log: any[]): void {
|
|
314
329
|
if (this.enablingDeviceLogging()) {
|
|
315
330
|
this.platform.log.info(String(...log));
|
|
316
331
|
}
|
|
317
332
|
}
|
|
318
333
|
|
|
319
|
-
warnLog(...log: any[]) {
|
|
334
|
+
warnLog(...log: any[]): void {
|
|
320
335
|
if (this.enablingDeviceLogging()) {
|
|
321
336
|
this.platform.log.warn(String(...log));
|
|
322
337
|
}
|
|
323
338
|
}
|
|
324
339
|
|
|
325
|
-
errorLog(...log: any[]) {
|
|
340
|
+
errorLog(...log: any[]): void {
|
|
326
341
|
if (this.enablingDeviceLogging()) {
|
|
327
342
|
this.platform.log.error(String(...log));
|
|
328
343
|
}
|
|
329
344
|
}
|
|
330
345
|
|
|
331
|
-
debugLog(...log: any[]) {
|
|
346
|
+
debugLog(...log: any[]): void {
|
|
332
347
|
if (this.enablingDeviceLogging()) {
|
|
333
348
|
if (this.deviceLogging === 'debug') {
|
|
334
349
|
this.platform.log.info('[DEBUG]', String(...log));
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/platform.ts
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import { Bot } from './
|
|
2
|
-
import { Plug } from './
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
1
|
+
import { Bot } from './device/bot';
|
|
2
|
+
import { Plug } from './device/plug';
|
|
3
|
+
import { Lock } from './device/lock';
|
|
4
|
+
import { Meter } from './device/meter';
|
|
5
|
+
import { Motion } from './device/motion';
|
|
6
|
+
import { Contact } from './device/contact';
|
|
7
|
+
import { Curtain } from './device/curtain';
|
|
8
|
+
import { ColorBulb } from './device/colorbulb';
|
|
9
|
+
import { Humidifier } from './device/humidifier';
|
|
10
|
+
import { TV } from './irdevice/tv';
|
|
11
|
+
import { Fan } from './irdevice/fan';
|
|
12
|
+
import { Light } from './irdevice/light';
|
|
13
|
+
import { Others } from './irdevice/other';
|
|
14
|
+
import { Camera } from './irdevice/camera';
|
|
15
|
+
import { AirPurifier } from './irdevice/airpurifier';
|
|
16
|
+
import { WaterHeater } from './irdevice/waterheater';
|
|
17
|
+
import { VacuumCleaner } from './irdevice/vacuumcleaner';
|
|
18
|
+
import { AirConditioner } from './irdevice/airconditioner';
|
|
18
19
|
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
|
|
19
20
|
import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, Service, Characteristic } from 'homebridge';
|
|
20
21
|
import { PLATFORM_NAME, PLUGIN_NAME, DeviceURL, irdevice, device, SwitchBotPlatformConfig, deviceResponses, devicesConfig } from './settings';
|
|
@@ -365,6 +366,8 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
|
|
|
365
366
|
this.createBot(device);
|
|
366
367
|
break;
|
|
367
368
|
case 'Meter':
|
|
369
|
+
case 'Meter Plus (US)':
|
|
370
|
+
case 'Meter Plus (JP)':
|
|
368
371
|
this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
|
|
369
372
|
this.createMeter(device);
|
|
370
373
|
break;
|
|
@@ -381,10 +384,17 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
|
|
|
381
384
|
this.createCurtain(device);
|
|
382
385
|
break;
|
|
383
386
|
case 'Plug':
|
|
387
|
+
case 'Plug Mini (US)':
|
|
388
|
+
case 'Plug Mini (JP)':
|
|
384
389
|
this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
|
|
385
390
|
this.createPlug(device);
|
|
386
391
|
break;
|
|
392
|
+
case 'Lock':
|
|
393
|
+
this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
|
|
394
|
+
this.createLock(device);
|
|
395
|
+
break;
|
|
387
396
|
case 'Color Bulb':
|
|
397
|
+
case 'Strip Light':
|
|
388
398
|
this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`);
|
|
389
399
|
this.createColorBulb(device);
|
|
390
400
|
break;
|
|
@@ -900,6 +910,61 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
|
|
|
900
910
|
}
|
|
901
911
|
}
|
|
902
912
|
|
|
913
|
+
private async createLock(device: device & devicesConfig) {
|
|
914
|
+
const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`);
|
|
915
|
+
|
|
916
|
+
// see if an accessory with the same uuid has already been registered and restored from
|
|
917
|
+
// the cached devices we stored in the `configureAccessory` method above
|
|
918
|
+
const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid);
|
|
919
|
+
|
|
920
|
+
if (existingAccessory) {
|
|
921
|
+
// the accessory already exists
|
|
922
|
+
if (!device.hide_device && device.enableCloudService) {
|
|
923
|
+
this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} DeviceID: ${device.deviceId}`);
|
|
924
|
+
|
|
925
|
+
// if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.:
|
|
926
|
+
existingAccessory.context.model = device.deviceType;
|
|
927
|
+
existingAccessory.context.deviceID = device.deviceId;
|
|
928
|
+
existingAccessory.displayName = device.deviceName;
|
|
929
|
+
existingAccessory.context.firmwareRevision = this.version;
|
|
930
|
+
existingAccessory.context.deviceType = `SwitchBot: ${device.deviceType}`;
|
|
931
|
+
await this.connectionTypeExistingAccessory(device, existingAccessory);
|
|
932
|
+
this.api.updatePlatformAccessories([existingAccessory]);
|
|
933
|
+
// create the accessory handler for the restored accessory
|
|
934
|
+
// this is imported from `platformAccessory.ts`
|
|
935
|
+
new Lock(this, existingAccessory, device);
|
|
936
|
+
this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`);
|
|
937
|
+
} else {
|
|
938
|
+
this.unregisterPlatformAccessories(existingAccessory);
|
|
939
|
+
}
|
|
940
|
+
} else if (!device.hide_device && device.enableCloudService) {
|
|
941
|
+
// the accessory does not yet exist, so we need to create it
|
|
942
|
+
this.infoLog(`Adding new accessory: ${device.deviceName} ${device.deviceType} DeviceID: ${device.deviceId}`);
|
|
943
|
+
|
|
944
|
+
// create a new accessory
|
|
945
|
+
const accessory = new this.api.platformAccessory(device.deviceName, uuid);
|
|
946
|
+
|
|
947
|
+
// store a copy of the device object in the `accessory.context`
|
|
948
|
+
// the `context` property can be used to store any data about the accessory you may need
|
|
949
|
+
accessory.context.device = device;
|
|
950
|
+
accessory.context.model = device.deviceType;
|
|
951
|
+
accessory.context.deviceID = device.deviceId;
|
|
952
|
+
accessory.context.firmwareRevision = this.version;
|
|
953
|
+
accessory.context.deviceType = `SwitchBot: ${device.deviceType}`;
|
|
954
|
+
await this.connectionTypeNewAccessory(device, accessory);
|
|
955
|
+
// create the accessory handler for the newly create accessory
|
|
956
|
+
// this is imported from `platformAccessory.ts`
|
|
957
|
+
new Lock(this, accessory, device);
|
|
958
|
+
this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`);
|
|
959
|
+
|
|
960
|
+
// link the accessory to your platform
|
|
961
|
+
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
962
|
+
this.accessories.push(accessory);
|
|
963
|
+
} else {
|
|
964
|
+
this.debugLog(`Unable to Register new device: ${device.deviceName} ${device.deviceType} - ${device.deviceId}`);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
903
968
|
private async createColorBulb(device: device & devicesConfig) {
|
|
904
969
|
const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`);
|
|
905
970
|
|