homebridge-tuya-plus 3.1.2
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.md +40 -0
- package/.github/ISSUE_TEMPLATE/new-device.md +24 -0
- package/.github/workflows/codeql-analysis.yml +67 -0
- package/.github/workflows/lint.yml +23 -0
- package/Changelog.md +63 -0
- package/LICENSE +21 -0
- package/Readme.MD +106 -0
- package/assets/Tuya-Plugin-Branding.png +0 -0
- package/bin/cli-decode.js +197 -0
- package/bin/cli-find.js +207 -0
- package/bin/cli.js +13 -0
- package/config-example.MD +43 -0
- package/config.schema.json +538 -0
- package/eslint.config.mjs +15 -0
- package/index.js +242 -0
- package/lib/AirConditionerAccessory.js +445 -0
- package/lib/AirPurifierAccessory.js +532 -0
- package/lib/BaseAccessory.js +290 -0
- package/lib/ConvectorAccessory.js +313 -0
- package/lib/CustomMultiOutletAccessory.js +111 -0
- package/lib/DehumidifierAccessory.js +301 -0
- package/lib/DoorbellAccessory.js +208 -0
- package/lib/EnergyCharacteristics.js +86 -0
- package/lib/GarageDoorAccessory.js +307 -0
- package/lib/MultiOutletAccessory.js +106 -0
- package/lib/OilDiffuserAccessory.js +480 -0
- package/lib/OutletAccessory.js +83 -0
- package/lib/RGBTWLightAccessory.js +234 -0
- package/lib/RGBTWOutletAccessory.js +296 -0
- package/lib/SimpleBlindsAccessory.js +299 -0
- package/lib/SimpleDimmer2Accessory.js +54 -0
- package/lib/SimpleDimmerAccessory.js +54 -0
- package/lib/SimpleFanAccessory.js +137 -0
- package/lib/SimpleFanLightAccessory.js +201 -0
- package/lib/SimpleHeaterAccessory.js +154 -0
- package/lib/SimpleLightAccessory.js +39 -0
- package/lib/SwitchAccessory.js +106 -0
- package/lib/TWLightAccessory.js +91 -0
- package/lib/TuyaAccessory.js +746 -0
- package/lib/TuyaDiscovery.js +165 -0
- package/lib/ValveAccessory.js +150 -0
- package/package.json +49 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
const BaseAccessory = require('./BaseAccessory');
|
|
2
|
+
const async = require('async');
|
|
3
|
+
|
|
4
|
+
class CustomMultiOutletAccessory extends BaseAccessory {
|
|
5
|
+
static getCategory(Categories) {
|
|
6
|
+
return Categories.OUTLET;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
constructor(...props) {
|
|
10
|
+
super(...props);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
_registerPlatformAccessory() {
|
|
14
|
+
this._verifyCachedPlatformAccessory();
|
|
15
|
+
this._justRegistered = true;
|
|
16
|
+
|
|
17
|
+
super._registerPlatformAccessory();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
_verifyCachedPlatformAccessory() {
|
|
21
|
+
if (this._justRegistered) return;
|
|
22
|
+
|
|
23
|
+
const {Service} = this.hap;
|
|
24
|
+
|
|
25
|
+
if (!Array.isArray(this.device.context.outlets)) {
|
|
26
|
+
throw new Error('The outlets definition is missing or is malformed: ' + this.device.context.outlets);
|
|
27
|
+
}
|
|
28
|
+
const _validServices = [];
|
|
29
|
+
this.device.context.outlets.forEach((outlet, i) => {
|
|
30
|
+
if (!outlet || !outlet.hasOwnProperty('name') || !outlet.hasOwnProperty('dp') || !isFinite(outlet.dp))
|
|
31
|
+
throw new Error('The outlet definition #${i} is missing or is malformed: ' + outlet);
|
|
32
|
+
|
|
33
|
+
const name = ((outlet.name || '').trim() || 'Unnamed') + ' - ' + this.device.context.name;
|
|
34
|
+
let service = this.accessory.getServiceByUUIDAndSubType(Service.Outlet, 'outlet ' + outlet.dp);
|
|
35
|
+
if (service) this._checkServiceName(service, name);
|
|
36
|
+
else service = this.accessory.addService(Service.Outlet, name, 'outlet ' + outlet.dp);
|
|
37
|
+
|
|
38
|
+
_validServices.push(service);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
this.accessory.services
|
|
42
|
+
.filter(service => service.UUID === Service.Outlet.UUID && !_validServices.includes(service))
|
|
43
|
+
.forEach(service => {
|
|
44
|
+
this.accessory.removeService(service);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
_registerCharacteristics(dps) {
|
|
49
|
+
this._verifyCachedPlatformAccessory();
|
|
50
|
+
|
|
51
|
+
const {Service, Characteristic} = this.hap;
|
|
52
|
+
|
|
53
|
+
const characteristics = {};
|
|
54
|
+
this.accessory.services.forEach(service => {
|
|
55
|
+
if (service.UUID !== Service.Outlet.UUID || !service.subtype) return false;
|
|
56
|
+
|
|
57
|
+
let match;
|
|
58
|
+
if ((match = service.subtype.match(/^outlet (\d+)$/)) === null) return;
|
|
59
|
+
|
|
60
|
+
characteristics[match[1]] = service.getCharacteristic(Characteristic.On)
|
|
61
|
+
.updateValue(dps[match[1]])
|
|
62
|
+
.on('get', this.getPower.bind(this, match[1]))
|
|
63
|
+
.on('set', this.setPower.bind(this, match[1]));
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
this.device.on('change', (changes, state) => {
|
|
67
|
+
Object.keys(changes).forEach(key => {
|
|
68
|
+
if (characteristics[key] && characteristics[key].value !== changes[key]) characteristics[key].updateValue(changes[key]);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
getPower(dp, callback) {
|
|
74
|
+
callback(null, this.device.state[dp]);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
setPower(dp, value, callback) {
|
|
78
|
+
if (!this._pendingPower) {
|
|
79
|
+
this._pendingPower = {props: {}, callbacks: []};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (dp) {
|
|
83
|
+
if (this._pendingPower.timer) clearTimeout(this._pendingPower.timer);
|
|
84
|
+
|
|
85
|
+
this._pendingPower.props = {...this._pendingPower.props, ...{[dp]: value}};
|
|
86
|
+
this._pendingPower.callbacks.push(callback);
|
|
87
|
+
|
|
88
|
+
this._pendingPower.timer = setTimeout(() => {
|
|
89
|
+
this.setPower();
|
|
90
|
+
}, 500);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const callbacks = this._pendingPower.callbacks;
|
|
95
|
+
const callEachBack = err => {
|
|
96
|
+
async.eachSeries(callbacks, (callback, next) => {
|
|
97
|
+
try {
|
|
98
|
+
callback(err);
|
|
99
|
+
} catch (ex) {}
|
|
100
|
+
next();
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const newValue = this._pendingPower.props;
|
|
105
|
+
this._pendingPower = null;
|
|
106
|
+
|
|
107
|
+
this.setMultiState(newValue, callEachBack);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = CustomMultiOutletAccessory;
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
const BaseAccessory = require('./BaseAccessory');
|
|
2
|
+
|
|
3
|
+
const STATE_OTHER = 9;
|
|
4
|
+
|
|
5
|
+
class DehumidifierAccessory extends BaseAccessory {
|
|
6
|
+
static getCategory(Categories) {
|
|
7
|
+
return Categories.DEHUMIDIFIER;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
constructor(...props) {
|
|
11
|
+
super(...props);
|
|
12
|
+
|
|
13
|
+
this.cmdDehumidify = '0';
|
|
14
|
+
this.cmdContinual = '1';
|
|
15
|
+
this.cmdAuto = '2';
|
|
16
|
+
this.cmdLaundry = '3';
|
|
17
|
+
|
|
18
|
+
this.defaultDps = {
|
|
19
|
+
'Active': 1,
|
|
20
|
+
'Mode': 2, // 0 - normal, 1 - continual, 2 - automatic, 3 - laundry
|
|
21
|
+
'Humidity': 4,
|
|
22
|
+
'Cleaning': 5,
|
|
23
|
+
'FanSpeed': 6, // 1 - slow, 3 - fast
|
|
24
|
+
'ChildLock': 7,
|
|
25
|
+
'TankState': 11, // 0 - not full, 8 - removed, ... - ?
|
|
26
|
+
// 12, 101, 105 - ?
|
|
27
|
+
'Sleep': 102,
|
|
28
|
+
'CurrentTemperature': 103,
|
|
29
|
+
'CurrentHumidity': 104,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
_registerPlatformAccessory() {
|
|
34
|
+
const {Service} = this.hap;
|
|
35
|
+
|
|
36
|
+
this.accessory.addService(Service.TemperatureSensor, this.device.context.name);
|
|
37
|
+
this.accessory.addService(Service.HumiditySensor, this.device.context.name);
|
|
38
|
+
this.accessory.addService(Service.HumidifierDehumidifier, this.device.context.name);
|
|
39
|
+
|
|
40
|
+
if (!this.device.context.noChildLock) {
|
|
41
|
+
this.accessory.addService(Service.LockMechanism, this.device.context.name + ' - Child Lock');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!this.device.context.noSpeed) {
|
|
45
|
+
this.accessory.addService(Service.Fan, this.device.context.name);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
super._registerPlatformAccessory();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
_registerCharacteristics(dps) {
|
|
52
|
+
const {Service, Characteristic} = this.hap;
|
|
53
|
+
|
|
54
|
+
const infoService = this.accessory.getService(Service.AccessoryInformation);
|
|
55
|
+
infoService.getCharacteristic(Characteristic.Manufacturer).updateValue(this.device.context.manufacturer);
|
|
56
|
+
infoService.getCharacteristic(Characteristic.Model).updateValue(this.device.context.model);
|
|
57
|
+
|
|
58
|
+
const characteristicTemperature = this.accessory.getService(Service.TemperatureSensor)
|
|
59
|
+
.getCharacteristic(Characteristic.CurrentTemperature)
|
|
60
|
+
.updateValue(this._getCurrentTemperature(dps[this.getDp('CurrentTemperature')]))
|
|
61
|
+
.on('get', this.getCurrentTemperature.bind(this));
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
const characteristicCurrentHumidity = this.accessory.getService(Service.HumiditySensor)
|
|
65
|
+
.getCharacteristic(Characteristic.CurrentRelativeHumidity)
|
|
66
|
+
.updateValue(this._getCurrentHumidity(dps[this.getDp('CurrentHumidity')]))
|
|
67
|
+
.on('get', this.getCurrentHumidity.bind(this));
|
|
68
|
+
|
|
69
|
+
const service = this.accessory.getService(Service.HumidifierDehumidifier);
|
|
70
|
+
this._checkServiceName(service, this.device.context.name);
|
|
71
|
+
|
|
72
|
+
let characteristicSpeed;
|
|
73
|
+
if (!this.device.context.noSpeed) {
|
|
74
|
+
let fanService = this.accessory.getService(Service.Fan);
|
|
75
|
+
characteristicSpeed = fanService.getCharacteristic(Characteristic.RotationSpeed)
|
|
76
|
+
.setProps({
|
|
77
|
+
minValue: this.device.context.minSpeed || 1,
|
|
78
|
+
maxValue: this.device.context.maxSpeed || 2,
|
|
79
|
+
minStep: this.device.context.speedSteps || 1,
|
|
80
|
+
})
|
|
81
|
+
.updateValue(this._getRotationSpeed(dps))
|
|
82
|
+
.on('get', this.getRotationSpeed.bind(this))
|
|
83
|
+
.on('set', this.setRotationSpeed.bind(this));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this._removeCharacteristic(service, Characteristic.SwingMode);
|
|
87
|
+
service.getCharacteristic(Characteristic.CurrentHumidifierDehumidifierState)
|
|
88
|
+
.updateValue(Characteristic.CurrentHumidifierDehumidifierState.DEHUMIDIFYING);
|
|
89
|
+
service.getCharacteristic(Characteristic.TargetHumidifierDehumidifierState)
|
|
90
|
+
.updateValue(Characteristic.TargetHumidifierDehumidifierState.DEHUMIDIFIER);
|
|
91
|
+
|
|
92
|
+
const characteristicCurrentHumidity2 = service.getCharacteristic(Characteristic.CurrentRelativeHumidity)
|
|
93
|
+
.updateValue(this._getCurrentHumidity(dps[this.getDp('CurrentHumidity')]))
|
|
94
|
+
.on('get', this.getCurrentHumidity.bind(this));
|
|
95
|
+
|
|
96
|
+
const characteristicActive = service.getCharacteristic(Characteristic.Active)
|
|
97
|
+
.updateValue(this._getActive(dps[this.getDp('Active')]))
|
|
98
|
+
.on('get', this.getActive.bind(this))
|
|
99
|
+
.on('set', this.setActive.bind(this));
|
|
100
|
+
|
|
101
|
+
const characteristicWaterTank = service.getCharacteristic(Characteristic.WaterLevel)
|
|
102
|
+
.updateValue(dps[this.getDp('TankState')])
|
|
103
|
+
.on('get', this.getTankState.bind(this))
|
|
104
|
+
|
|
105
|
+
let characteristicChildLock;
|
|
106
|
+
|
|
107
|
+
if (!this.device.context.noChildLock) {
|
|
108
|
+
let lockService = this.accessory.getService(Service.LockMechanism);
|
|
109
|
+
characteristicChildLock = lockService.getCharacteristic(Characteristic.LockCurrentState)
|
|
110
|
+
.updateValue(this._getLockTargetState(dps[this.getDp('ChildLock')]))
|
|
111
|
+
.on('get', this.getLockTargetState.bind(this));
|
|
112
|
+
characteristicChildLock = lockService.getCharacteristic(Characteristic.LockTargetState)
|
|
113
|
+
.updateValue(this._getLockTargetState(dps[this.getDp('ChildLock')]))
|
|
114
|
+
.on('get', this.getLockTargetState.bind(this))
|
|
115
|
+
.on('set', this.setLockTargetState.bind(this));
|
|
116
|
+
} else this._removeCharacteristic(service, Characteristic.LockTargetState);
|
|
117
|
+
|
|
118
|
+
this.characteristicHumidity = service.getCharacteristic(Characteristic.RelativeHumidityDehumidifierThreshold);
|
|
119
|
+
this.characteristicHumidity.setProps({
|
|
120
|
+
minStep: this.device.context.humiditySteps || 5,
|
|
121
|
+
})
|
|
122
|
+
.updateValue(dps[this.getDp('Humidity')])
|
|
123
|
+
.on('get', this.getState.bind(this, this.getDp('Humidity')))
|
|
124
|
+
.on('set', this.setTargetHumidity.bind(this));
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
this.device.on('change', (changes, state) => {
|
|
128
|
+
if (changes.hasOwnProperty(this.getDp('Active'))) {
|
|
129
|
+
const newActive = this._getActive(changes[this.getDp('Active')]);
|
|
130
|
+
if (characteristicActive.value !== newActive) {
|
|
131
|
+
characteristicActive.updateValue(newActive);
|
|
132
|
+
|
|
133
|
+
if (!changes.hasOwnProperty(this.getDp('FanSpeed'))) {
|
|
134
|
+
characteristicSpeed.updateValue(this._getRotationSpeed(state));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (changes.hasOwnProperty('Humidity') && this.characteristicHumidity.value !== changes[this.getDp('Humidity')]) this.characteristicHumidity.updateValue(changes[this.getDp('Humidity')]);
|
|
140
|
+
|
|
141
|
+
if (characteristicChildLock && changes.hasOwnProperty(this.getDp('ChildLock'))) {
|
|
142
|
+
const newChildLock = this._getLockTargetState(changes[this.getDp('ChildLock')]);
|
|
143
|
+
if (characteristicChildLock.value !== newChildLock) characteristicChildLock.updateValue(newChildLock);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (changes.hasOwnProperty(this.getDp('FanSpeed'))) {
|
|
147
|
+
const newSpeed = this._getRotationSpeed(state);
|
|
148
|
+
if (characteristicSpeed.value !== newSpeed) characteristicSpeed.updateValue(newSpeed);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
getActive(callback) {
|
|
154
|
+
this.getState(this.getDp('Active'), (err, dp) => {
|
|
155
|
+
if (err) return callback(err);
|
|
156
|
+
|
|
157
|
+
callback(null, this._getActive(dp));
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
_getActive(dp) {
|
|
162
|
+
const {Characteristic} = this.hap;
|
|
163
|
+
|
|
164
|
+
return dp ? Characteristic.Active.ACTIVE : Characteristic.Active.INACTIVE;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
setActive(value, callback) {
|
|
168
|
+
const {Characteristic} = this.hap;
|
|
169
|
+
|
|
170
|
+
switch (value) {
|
|
171
|
+
case Characteristic.Active.ACTIVE:
|
|
172
|
+
return this.setState(this.getDp('Active'), true, callback);
|
|
173
|
+
|
|
174
|
+
case Characteristic.Active.INACTIVE:
|
|
175
|
+
return this.setState(this.getDp('Active'), false, callback);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
callback();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
getTankState(callback) {
|
|
182
|
+
this.getState(this.getDp('TankState'), (err, dp) => {
|
|
183
|
+
if (err) return callback(err);
|
|
184
|
+
|
|
185
|
+
callback(null, this._getTankState(dp));
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
_getTankState(dp) {
|
|
190
|
+
const {Characteristic} = this.hap;
|
|
191
|
+
|
|
192
|
+
return dp ? 100 : 50;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
getLockTargetState(callback) {
|
|
196
|
+
this.getState(this.getDp('ChildLock'), (err, dp) => {
|
|
197
|
+
if (err) return callback(err);
|
|
198
|
+
|
|
199
|
+
callback(null, this._getLockTargetState(dp));
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
_getLockTargetState(dp) {
|
|
204
|
+
const {Characteristic} = this.hap;
|
|
205
|
+
|
|
206
|
+
return dp ? Characteristic.LockTargetState.SECURED : Characteristic.LockTargetState.UNSECURED;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
setLockTargetState(value, callback) {
|
|
210
|
+
if (this.device.context.noLock) return callback();
|
|
211
|
+
|
|
212
|
+
const {Characteristic} = this.hap;
|
|
213
|
+
|
|
214
|
+
switch (value) {
|
|
215
|
+
case Characteristic.LockTargetState.SECURED:
|
|
216
|
+
return this.setState(this.getDp('ChildLock'), true, callback);
|
|
217
|
+
|
|
218
|
+
case Characteristic.LockTargetState.UNSECURED:
|
|
219
|
+
return this.setState(this.getDp('ChildLock'), false, callback);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
callback();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
getRotationSpeed(callback) {
|
|
226
|
+
this.getState(this.getDp('FanSpeed'), (err, dp) => {
|
|
227
|
+
if (err) return callback(err);
|
|
228
|
+
|
|
229
|
+
callback(null, this._getRotationSpeed(dp));
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
_getRotationSpeed(dp) {
|
|
234
|
+
const {Characteristic} = this.hap;
|
|
235
|
+
|
|
236
|
+
return dp > 1 ? dp-1 : dp;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
setRotationSpeed(value, callback) {
|
|
240
|
+
if (this.device.context.noSpeed) return callback();
|
|
241
|
+
value > 1 ? value++ : null;
|
|
242
|
+
return this.setState(this.getDp('FanSpeed'), value.toString(), callback);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
getCurrentHumidity(callback) {
|
|
246
|
+
this.getState(this.getDp('CurrentHumidity'), (err, dp) => {
|
|
247
|
+
if (err) return callback(err);
|
|
248
|
+
|
|
249
|
+
callback(null, this._getCurrentHumidity(dp));
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
_getCurrentHumidity(dp) {
|
|
254
|
+
return dp;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
getCurrentTemperature(callback) {
|
|
258
|
+
this.getState(this.getDp('CurrentTemperature'), (err, dp) => {
|
|
259
|
+
if (err) return callback(err);
|
|
260
|
+
|
|
261
|
+
callback(null, this._getCurrentTemperature(dp));
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
_getCurrentTemperature(dp) {
|
|
266
|
+
return dp;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
getTargetHumidity(callback) {
|
|
270
|
+
this.getState([this.getDp('Active'), this.getDp('Humidity')], (err, dps) => {
|
|
271
|
+
if (err) return callback(err);
|
|
272
|
+
|
|
273
|
+
callback(null, this._getTargetHumidity(dps));
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
_getTargetHumidity(dps) {
|
|
278
|
+
if (!dps[this.getDp('Active')]) return 0;
|
|
279
|
+
|
|
280
|
+
return dps[this.getDp('Humidity')];
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
setTargetHumidity(value, callback) {
|
|
284
|
+
const {Characteristic} = this.hap;
|
|
285
|
+
|
|
286
|
+
let origValue = value;
|
|
287
|
+
value = Math.max(value, this.device.context.minHumidity || 40);
|
|
288
|
+
value = Math.min(value, this.device.context.maxHumidity || 80);
|
|
289
|
+
if (origValue != value) {
|
|
290
|
+
this.characteristicHumidity.updateValue(value);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
this.setMultiState({[this.getDp('Active')]: true, [this.getDp('Humidity')]: value}, callback);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
getDp(name) {
|
|
297
|
+
return this.device.context['dps' + name] ? this.device.context['dps' + name] : this.defaultDps[name];
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
module.exports = DehumidifierAccessory;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
const BaseAccessory = require('./BaseAccessory');
|
|
2
|
+
const async = require('async');
|
|
3
|
+
const https = require('https');
|
|
4
|
+
const url = require('url');
|
|
5
|
+
|
|
6
|
+
const DP_DOORBELL = '154'; // provides base64-encoded URL of static image
|
|
7
|
+
const DP_DOOR = '148'; // provides boolean of whether the switch is toggled
|
|
8
|
+
const DP_GATE = '232'; // provides boolean of whether the switch is toggled
|
|
9
|
+
|
|
10
|
+
const LOCK_TIMEOUT = 5000;
|
|
11
|
+
|
|
12
|
+
class DoorbellDelegate /*extends CameraStreamingDelegate*/ {
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
this.cameraImage = undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
setCameraImage(image) {
|
|
19
|
+
this.cameraImage = image;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
storeImage(urlString, callback) {
|
|
23
|
+
const urlObject = url.parse(urlString);
|
|
24
|
+
const me = this;
|
|
25
|
+
https.get(urlObject,
|
|
26
|
+
function(res) {
|
|
27
|
+
var body=Buffer.alloc(0);
|
|
28
|
+
|
|
29
|
+
if (res.statusCode!==200) {
|
|
30
|
+
return console.error('HTTP '+res.statusCode);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
res.on('data', function(chunk) {
|
|
34
|
+
body=Buffer.concat([body, chunk]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
res.on('end', function() {
|
|
38
|
+
me.setCameraImage(body);
|
|
39
|
+
console.log("Image stored");
|
|
40
|
+
callback();
|
|
41
|
+
console.log("Callback after image store complete");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
res.on('error', function(err) {
|
|
45
|
+
console.error(err);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* SnapshotRequest, SnapshotRequestCallback (function taking error or HAPStatus and Buffer), returns void */
|
|
52
|
+
handleSnapshotRequest(request, callback) {
|
|
53
|
+
//TODO honour the image size in the request (SnapshotRequest).
|
|
54
|
+
callback(undefined, this.cameraImage);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
/* PrepareStreamRequest, PrepareStreamCallback (function taking optional error and PrepareStreamResponse), returns void */
|
|
59
|
+
prepareStream(request, callback) {
|
|
60
|
+
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* StreamingRequest, StreamRequestCallback (function taking optional Error), returns void */
|
|
64
|
+
handleStreamRequest(request, callback) {
|
|
65
|
+
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
class DoorbellAccessory extends BaseAccessory {
|
|
70
|
+
|
|
71
|
+
static getCategory(Categories) {
|
|
72
|
+
return Categories.CAMERA;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
constructor(...props) {
|
|
76
|
+
super(...props);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
_registerPlatformAccessory() {
|
|
80
|
+
const {Service, Characteristic} = this.hap;
|
|
81
|
+
|
|
82
|
+
const doorbellService = this.accessory.getService(Service.Doorbell) || this.accessory.addService(Service.Doorbell);
|
|
83
|
+
this._checkServiceName(doorbellService, this.device.context.name);
|
|
84
|
+
|
|
85
|
+
doorbellService.setPrimaryService(true);
|
|
86
|
+
super._registerPlatformAccessory();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
_registerCharacteristics(dps) {
|
|
91
|
+
const {Service, Characteristic} = this.hap;
|
|
92
|
+
|
|
93
|
+
this.configureCamera();
|
|
94
|
+
|
|
95
|
+
const service = this.accessory.getService(Service.Doorbell);
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
this.device.on('change', (changes, state) => {
|
|
99
|
+
//console.log(`Changes: ${JSON.stringify(changes)}, State: ${JSON.stringify(state)}`);
|
|
100
|
+
|
|
101
|
+
if (changes.hasOwnProperty(DP_DOORBELL)) {
|
|
102
|
+
const urlBase64 = changes[DP_DOORBELL];
|
|
103
|
+
const strUrl = Buffer.from(urlBase64, 'base64');
|
|
104
|
+
//console.log(`Image URL: ${strUrl}`);
|
|
105
|
+
|
|
106
|
+
this.streamingDelegate.storeImage(strUrl.toString(), () => {
|
|
107
|
+
service.updateCharacteristic(Characteristic.ProgrammableSwitchEvent, Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (changes.hasOwnProperty(DP_DOOR)) {
|
|
112
|
+
console.log("Door button pressed");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (changes.hasOwnProperty(DP_GATE)) {
|
|
116
|
+
console.log("Gate button pressed");
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
configureCamera() {
|
|
122
|
+
this.streamingDelegate = new DoorbellDelegate();
|
|
123
|
+
console.log("created streaming delegate");
|
|
124
|
+
const options = {
|
|
125
|
+
cameraStreamCount: 1,
|
|
126
|
+
delegate: this.streamingDelegate,
|
|
127
|
+
streamingOptions: {
|
|
128
|
+
supportedCryptoSuites: [this.hap.SRTPCryptoSuites.AES_CM_128_HMAC_SHA1_80],
|
|
129
|
+
video: {
|
|
130
|
+
resolutions: [
|
|
131
|
+
[320, 180, 30],
|
|
132
|
+
[320, 240, 15], // Apple Watch requires this configuration
|
|
133
|
+
[320, 240, 30],
|
|
134
|
+
[480, 270, 30],
|
|
135
|
+
[480, 360, 30],
|
|
136
|
+
[640, 360, 30],
|
|
137
|
+
[640, 480, 30],
|
|
138
|
+
[1280, 720, 30],
|
|
139
|
+
[1280, 960, 30],
|
|
140
|
+
[1920, 1080, 30],
|
|
141
|
+
[1600, 1200, 30],
|
|
142
|
+
],
|
|
143
|
+
codec: {
|
|
144
|
+
profiles: [this.hap.H264Profile.BASELINE, this.hap.H264Profile.MAIN, this.hap.H264Profile.HIGH],
|
|
145
|
+
levels: [this.hap.H264Level.LEVEL3_1, this.hap.H264Level.LEVEL3_2, this.hap.H264Level.LEVEL4_0],
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
audio: {
|
|
149
|
+
twoWayAudio: true,
|
|
150
|
+
codecs: [
|
|
151
|
+
{
|
|
152
|
+
type: this.hap.AudioStreamingCodecType.AAC_ELD,
|
|
153
|
+
samplerate: this.hap.AudioStreamingSamplerate.KHZ_16,
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
//console.log("created options");
|
|
161
|
+
const cameraController = new this.hap.CameraController(options);
|
|
162
|
+
//console.log("created controller");
|
|
163
|
+
|
|
164
|
+
this.accessory.configureController(cameraController);
|
|
165
|
+
//console.log("configured controller with accessory");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
pushDoor(callback) {
|
|
169
|
+
this.device.update({[DP_DOOR.toString()] : !this.device.state[DP_DOOR]});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
pushGate(callback) {
|
|
173
|
+
this.setState(DP_GATE, !this.device.state[DP_GATE]);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
handleLockCurrentStateGet() {
|
|
178
|
+
return this.doorState;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
handleLockTargetStateGet() {
|
|
182
|
+
return this.doorState;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async handleLockTargetStateSet(value) {
|
|
186
|
+
const {Service, Characteristic} = this.hap;
|
|
187
|
+
const service = this.accessory.getService(Service.LockMechanism);
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
this.pushDoor();
|
|
191
|
+
|
|
192
|
+
service.getCharacteristic(Characteristic.LockCurrentState).updateValue(value);
|
|
193
|
+
service.getCharacteristic(Characteristic.LockTargetState).updateValue(value);
|
|
194
|
+
this.doorState = value;
|
|
195
|
+
|
|
196
|
+
setTimeout(() => {
|
|
197
|
+
service.getCharacteristic(Characteristic.LockCurrentState).updateValue(Characteristic.LockCurrentState.SECURED);
|
|
198
|
+
service.getCharacteristic(Characteristic.LockTargetState).updateValue(Characteristic.LockTargetState.SECURED);
|
|
199
|
+
this.doorState = Characteristic.LockTargetState.SECURED;
|
|
200
|
+
}, LOCK_TIMEOUT);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
this.log.error('Unlock failed', error);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
module.exports = DoorbellAccessory;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Thanks to homebridge-tplink-smarthome
|
|
2
|
+
|
|
3
|
+
module.exports = function(Characteristic) {
|
|
4
|
+
class EnergyCharacteristic extends Characteristic {
|
|
5
|
+
constructor(displayName, UUID, props) {
|
|
6
|
+
super(displayName, UUID);
|
|
7
|
+
this.setProps(Object.assign({
|
|
8
|
+
format: Characteristic.Formats.FLOAT,
|
|
9
|
+
minValue: 0,
|
|
10
|
+
maxValue: 65535,
|
|
11
|
+
perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY]
|
|
12
|
+
}, props));
|
|
13
|
+
this.value = this.getDefaultValue();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class Amperes extends EnergyCharacteristic {
|
|
18
|
+
constructor() {
|
|
19
|
+
super('Amperes', Amperes.UUID, {
|
|
20
|
+
unit: 'A',
|
|
21
|
+
minStep: 0.001
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Amperes.UUID = 'E863F126-079E-48FF-8F27-9C2605A29F52';
|
|
27
|
+
|
|
28
|
+
class KilowattHours extends EnergyCharacteristic {
|
|
29
|
+
constructor() {
|
|
30
|
+
super('Total Consumption', KilowattHours.UUID, {
|
|
31
|
+
unit: 'kWh',
|
|
32
|
+
minStep: 0.001
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
KilowattHours.UUID = 'E863F10C-079E-48FF-8F27-9C2605A29F52';
|
|
38
|
+
|
|
39
|
+
class KilowattVoltAmpereHour extends EnergyCharacteristic {
|
|
40
|
+
constructor() {
|
|
41
|
+
super('Apparent Energy', KilowattVoltAmpereHour.UUID, {
|
|
42
|
+
format: Characteristic.Formats.UINT32,
|
|
43
|
+
unit: 'kVAh',
|
|
44
|
+
minStep: 1
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
KilowattVoltAmpereHour.UUID = 'E863F127-079E-48FF-8F27-9C2605A29F52';
|
|
50
|
+
|
|
51
|
+
class VoltAmperes extends EnergyCharacteristic {
|
|
52
|
+
constructor() {
|
|
53
|
+
super('Apparent Power', VoltAmperes.UUID, {
|
|
54
|
+
format: Characteristic.Formats.UINT16,
|
|
55
|
+
unit: 'VA',
|
|
56
|
+
minStep: 1
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
VoltAmperes.UUID = 'E863F110-079E-48FF-8F27-9C2605A29F52';
|
|
62
|
+
|
|
63
|
+
class Volts extends EnergyCharacteristic {
|
|
64
|
+
constructor() {
|
|
65
|
+
super('Volts', Volts.UUID, {
|
|
66
|
+
unit: 'V',
|
|
67
|
+
minStep: 0.1
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
Volts.UUID = 'E863F10A-079E-48FF-8F27-9C2605A29F52';
|
|
73
|
+
|
|
74
|
+
class Watts extends EnergyCharacteristic {
|
|
75
|
+
constructor() {
|
|
76
|
+
super('Consumption', Watts.UUID, {
|
|
77
|
+
unit: 'W',
|
|
78
|
+
minStep: 0.1
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
Watts.UUID = 'E863F10D-079E-48FF-8F27-9C2605A29F52';
|
|
84
|
+
|
|
85
|
+
return {Amperes, KilowattHours, KilowattVoltAmpereHour, VoltAmperes, Volts, Watts};
|
|
86
|
+
};
|