homebridge-tuya-community 3.3.0 → 3.3.1
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/workflows/npm-publish.yml +54 -0
- package/Readme.MD +3 -3
- package/bin/cli-decode.js +1 -1
- package/bin/cli-find.js +10 -10
- package/index.js +3 -3
- package/lib/AirPurifierAccessory.js +140 -139
- package/lib/CustomMultiOutletAccessory.js +3 -3
- package/lib/DehumidifierAccessory.js +24 -27
- package/lib/GarageDoorAccessory.js +11 -11
- package/lib/MultiOutletAccessory.js +2 -2
- package/lib/OilDiffuserAccessory.js +5 -6
- package/lib/RGBTWLightAccessory.js +11 -11
- package/lib/RGBTWOutletAccessory.js +1 -1
- package/lib/SimpleFanAccessory.js +35 -40
- package/lib/SimpleFanLightAccessory.js +35 -46
- package/lib/SwitchAccessory.js +2 -2
- package/lib/TWLightAccessory.js +3 -3
- package/lib/TuyaAccessory.js +34 -34
- package/package.json +2 -2
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
id-token: write # Required for provenance
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Setup Node.js
|
|
17
|
+
uses: actions/setup-node@v4
|
|
18
|
+
with:
|
|
19
|
+
node-version: '20'
|
|
20
|
+
registry-url: 'https://registry.npmjs.org'
|
|
21
|
+
|
|
22
|
+
- name: Cache npm dependencies
|
|
23
|
+
uses: actions/cache@v4
|
|
24
|
+
with:
|
|
25
|
+
path: ~/.npm
|
|
26
|
+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
27
|
+
restore-keys: |
|
|
28
|
+
${{ runner.os }}-node-
|
|
29
|
+
|
|
30
|
+
- name: Get version from tag
|
|
31
|
+
id: get_version
|
|
32
|
+
run: |
|
|
33
|
+
# Remove 'v' prefix if present (e.g., v3.3.1 -> 3.3.1)
|
|
34
|
+
VERSION=${GITHUB_REF_NAME#v}
|
|
35
|
+
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
|
|
36
|
+
echo "Publishing version: $VERSION"
|
|
37
|
+
|
|
38
|
+
- name: Install dependencies
|
|
39
|
+
run: npm ci
|
|
40
|
+
|
|
41
|
+
- name: Update package.json and package-lock.json version
|
|
42
|
+
run: npm version ${{ steps.get_version.outputs.VERSION }} --no-git-tag-version --allow-same-version
|
|
43
|
+
|
|
44
|
+
- name: Publish to npm
|
|
45
|
+
run: |
|
|
46
|
+
if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then
|
|
47
|
+
echo "Publishing with 'next' tag (pre-release)"
|
|
48
|
+
npm publish --access public --tag next
|
|
49
|
+
else
|
|
50
|
+
echo "Publishing with 'latest' tag (stable)"
|
|
51
|
+
npm publish --access public
|
|
52
|
+
fi
|
|
53
|
+
env:
|
|
54
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/Readme.MD
CHANGED
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
|
|
6
6
|
<span align="center">
|
|
7
7
|
|
|
8
|
-
# Homebridge-Tuya
|
|
8
|
+
# Homebridge-Tuya-Community
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
</span>
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
Control your supported Tuya accessories locally in HomeKit
|
|
15
|
+
Control your supported Tuya accessories locally in HomeKit!
|
|
16
16
|
|
|
17
17
|
* [Supported Device Types](#supported-device-types)
|
|
18
18
|
* [Installation Instructions](#installation-instructions)
|
|
@@ -46,7 +46,7 @@ Control your supported Tuya accessories locally in HomeKit
|
|
|
46
46
|
* Outlets<sup>[14](https://github.com/iRayanKhan/homebridge-tuya/wiki/Supported-Device-Types#outlets)</sup>
|
|
47
47
|
* Switches<sup>[15](https://github.com/iRayanKhan/homebridge-tuya/wiki/Supported-Device-Types)</sup>
|
|
48
48
|
|
|
49
|
-
Note: Motion
|
|
49
|
+
Note: Motion sensors and others don't behave well with response requests, so they will not be supported.
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
## Installation Instructions
|
package/bin/cli-decode.js
CHANGED
|
@@ -43,7 +43,7 @@ const decodeLine = (key, input, log = true) => {
|
|
|
43
43
|
const len = buffer.length;
|
|
44
44
|
if (buffer.readUInt32BE(0) !== 0x000055aa || buffer.readUInt32BE(len - 4) !== 0x0000aa55) {
|
|
45
45
|
console.log("*** Input doesn't match the expected signature:", buffer.readUInt32BE(0).toString(16).padStart(8, '0'), buffer.readUInt32BE(len - 4).toString(16).padStart(8, '0'));
|
|
46
|
-
return
|
|
46
|
+
return;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// Try 3.3
|
package/bin/cli-find.js
CHANGED
|
@@ -12,7 +12,7 @@ const fs = require('fs-extra');
|
|
|
12
12
|
// Disable debug messages from the proxy
|
|
13
13
|
try {
|
|
14
14
|
require('debug').disable();
|
|
15
|
-
} catch(ex) {}
|
|
15
|
+
} catch (ex) { /* Intentionally empty */ }
|
|
16
16
|
|
|
17
17
|
const ROOT = path.resolve(__dirname);
|
|
18
18
|
|
|
@@ -52,7 +52,7 @@ const localIPPorts = localIPs.map(ip => `${ip}:${program.port}`);
|
|
|
52
52
|
|
|
53
53
|
const escapeUnicode = str => str.replace(/[\u00A0-\uffff]/gu, c => '\\u' + ('000' + c.charCodeAt().toString(16)).slice(-4));
|
|
54
54
|
|
|
55
|
-
proxy.onError(function(ctx, err) {
|
|
55
|
+
proxy.onError(function (ctx, err) {
|
|
56
56
|
switch (err.code) {
|
|
57
57
|
case 'ERR_STREAM_DESTROYED':
|
|
58
58
|
case 'ECONNRESET':
|
|
@@ -71,7 +71,7 @@ proxy.onError(function(ctx, err) {
|
|
|
71
71
|
}
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
proxy.onRequest(function(ctx, callback) {
|
|
74
|
+
proxy.onRequest(function (ctx, callback) {
|
|
75
75
|
if (ctx.clientToProxyRequest.method === 'GET' && ctx.clientToProxyRequest.url === '/cert' && localIPPorts.includes(ctx.clientToProxyRequest.headers.host)) {
|
|
76
76
|
ctx.use(Proxy.gunzip);
|
|
77
77
|
console.log('Intercepted certificate request');
|
|
@@ -94,19 +94,19 @@ proxy.onRequest(function(ctx, callback) {
|
|
|
94
94
|
} else if (ctx.clientToProxyRequest.method === 'POST' && /tuya/.test(ctx.clientToProxyRequest.headers.host)) {
|
|
95
95
|
ctx.use(Proxy.gunzip);
|
|
96
96
|
|
|
97
|
-
ctx.onRequestData(function(ctx, chunk, callback) {
|
|
97
|
+
ctx.onRequestData(function (ctx, chunk, callback) {
|
|
98
98
|
return callback(null, chunk);
|
|
99
99
|
});
|
|
100
|
-
ctx.onRequestEnd(function(ctx, callback) {
|
|
100
|
+
ctx.onRequestEnd(function (ctx, callback) {
|
|
101
101
|
callback();
|
|
102
102
|
});
|
|
103
103
|
|
|
104
104
|
let chunks = [];
|
|
105
|
-
ctx.onResponseData(function(ctx, chunk, callback) {
|
|
105
|
+
ctx.onResponseData(function (ctx, chunk, callback) {
|
|
106
106
|
chunks.push(chunk);
|
|
107
107
|
return callback(null, chunk);
|
|
108
108
|
});
|
|
109
|
-
ctx.onResponseEnd(function(ctx, callback) {
|
|
109
|
+
ctx.onResponseEnd(function (ctx, callback) {
|
|
110
110
|
emitter.emit('tuya-config', Buffer.concat(chunks).toString());
|
|
111
111
|
callback();
|
|
112
112
|
});
|
|
@@ -188,17 +188,17 @@ emitter.on('tuya-config', body => {
|
|
|
188
188
|
}, 5000);
|
|
189
189
|
});
|
|
190
190
|
|
|
191
|
-
proxy.listen({port: program.port, sslCaDir: ROOT}, err => {
|
|
191
|
+
proxy.listen({ port: program.port, sslCaDir: ROOT }, err => {
|
|
192
192
|
if (err) {
|
|
193
193
|
console.error('Error starting proxy: ' + err);
|
|
194
194
|
return setTimeout(() => {
|
|
195
195
|
process.exit(0);
|
|
196
196
|
}, 5000);
|
|
197
197
|
}
|
|
198
|
-
let {address, port} = proxy.httpServer.address();
|
|
198
|
+
let { address, port } = proxy.httpServer.address();
|
|
199
199
|
if (address === '::' || address === '0.0.0.0') address = localIPs[0];
|
|
200
200
|
|
|
201
|
-
QRCode.toString(`http://${address}:${port}/cert`, {type: 'terminal'}, function(err, url) {
|
|
201
|
+
QRCode.toString(`http://${address}:${port}/cert`, { type: 'terminal' }, function (err, url) {
|
|
202
202
|
console.log(url);
|
|
203
203
|
console.log('\nFollow the instructions on https://github.com/AMoo-Miki/homebridge-tuya-lan/wiki/Setup-Instructions');
|
|
204
204
|
console.log(`Proxy IP: ${address}`);
|
package/index.js
CHANGED
|
@@ -54,12 +54,12 @@ const CLASS_DEF = {
|
|
|
54
54
|
oildiffuser: OilDiffuserAccessory
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
-
let Characteristic, PlatformAccessory, Service, Categories,
|
|
57
|
+
let Characteristic, PlatformAccessory, Service, Categories, _AdaptiveLightingController, UUID, Perms;
|
|
58
58
|
|
|
59
59
|
module.exports = function (homebridge) {
|
|
60
60
|
({
|
|
61
61
|
platformAccessory: PlatformAccessory,
|
|
62
|
-
hap: { Characteristic, Service, AdaptiveLightingController, Accessory: { Categories }, uuid: UUID, Perms }
|
|
62
|
+
hap: { Characteristic, Service, AdaptiveLightingController: _AdaptiveLightingController, Accessory: { Categories }, uuid: UUID, Perms }
|
|
63
63
|
} = homebridge);
|
|
64
64
|
|
|
65
65
|
homebridge.registerPlatform(PLUGIN_NAME, PLATFORM_NAME, TuyaLan, true);
|
|
@@ -117,7 +117,7 @@ class TuyaLan {
|
|
|
117
117
|
device.type = ('' + device.type).trim();
|
|
118
118
|
|
|
119
119
|
device.ip = ('' + (device.ip || '')).trim();
|
|
120
|
-
} catch (ex) { }
|
|
120
|
+
} catch (ex) { /* Intentionally empty */ }
|
|
121
121
|
|
|
122
122
|
if (!device.type) return this.log.error('%s (%s) doesn\'t have a type defined.', device.name || 'Unnamed device', device.id);
|
|
123
123
|
if (!CLASS_DEF[device.type.toLowerCase()]) return this.log.error('%s (%s) doesn\'t have a valid type defined.', device.name || 'Unnamed device', device.id);
|
|
@@ -89,43 +89,43 @@ class AirPurifierAccessory extends BaseAccessory {
|
|
|
89
89
|
}
|
|
90
90
|
constructor(...props) {
|
|
91
91
|
super(...props);
|
|
92
|
-
const {Characteristic} = this.hap;
|
|
92
|
+
const { Characteristic } = this.hap;
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
if (this.device.context.noRotationSpeed) {
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
let fanSpeedSteps = (
|
|
97
|
+
this.device.context.fanSpeedSteps &&
|
|
98
|
+
isFinite(this.device.context.fanSpeedSteps) &&
|
|
99
|
+
this.device.context.fanSpeedSteps > 0 &&
|
|
100
100
|
this.device.context.fanSpeedSteps < 100) ? this.device.context.fanSpeedSteps : 100;
|
|
101
101
|
let _fanSpeedLabels = {};
|
|
102
102
|
// Special handling for particular devices //
|
|
103
103
|
switch (this.device.context.manufacturer) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
case 'Breville':
|
|
105
|
+
_fanSpeedLabels = { 0: 'off', 1: 'low', 2: 'mid', 3: 'high', 4: 'turbo' };
|
|
106
|
+
this._rotationSteps = [...Array(5).keys()];
|
|
107
|
+
fanSpeedSteps = 5;
|
|
108
|
+
break;
|
|
109
109
|
case 'Proscenic':
|
|
110
|
-
_fanSpeedLabels = {0: 'sleep', 1: 'mid', 2: 'high', 3: 'auto'};
|
|
110
|
+
_fanSpeedLabels = { 0: 'sleep', 1: 'mid', 2: 'high', 3: 'auto' };
|
|
111
111
|
fanSpeedSteps = 3;
|
|
112
112
|
this._rotationSteps = [...Array(4).keys()];
|
|
113
113
|
break;
|
|
114
114
|
case 'siguro':
|
|
115
|
-
_fanSpeedLabels = {0: 'sleep', 1: 'auto'};
|
|
115
|
+
_fanSpeedLabels = { 0: 'sleep', 1: 'auto' };
|
|
116
116
|
fanSpeedSteps = 2;
|
|
117
117
|
this._rotationSteps = [...Array(2).keys()];
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
break;
|
|
119
|
+
default: // Just use numeric values
|
|
120
|
+
this._rotationSteps = [...Array(fanSpeedSteps).keys()];
|
|
121
121
|
for (let i = 0; i <= fanSpeedSteps; i++) {
|
|
122
|
-
|
|
122
|
+
_fanSpeedLabels[i] = i;
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
|
-
this._rotationStops = {0: _fanSpeedLabels[0]};
|
|
125
|
+
this._rotationStops = { 0: _fanSpeedLabels[0] };
|
|
126
126
|
for (let i = 0; i < 100; i++) {
|
|
127
127
|
const _rotationStep = Math.floor(fanSpeedSteps * i / 100);
|
|
128
|
-
this._rotationStops[i+1] = _fanSpeedLabels[_rotationStep];
|
|
128
|
+
this._rotationStops[i + 1] = _fanSpeedLabels[_rotationStep];
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
this.airQualityLevels = [
|
|
@@ -145,7 +145,7 @@ class AirPurifierAccessory extends BaseAccessory {
|
|
|
145
145
|
* Register the services that this accessory supports.
|
|
146
146
|
*/
|
|
147
147
|
_registerPlatformAccessory() {
|
|
148
|
-
const {Service} = this.hap;
|
|
148
|
+
const { Service } = this.hap;
|
|
149
149
|
/* Add the main air purifier */
|
|
150
150
|
this.accessory.addService(Service.AirPurifier, this.device.context.name);
|
|
151
151
|
/* If configured to include air quality data, include that service too */
|
|
@@ -161,7 +161,7 @@ class AirPurifierAccessory extends BaseAccessory {
|
|
|
161
161
|
* if the configuration is updated after the device is first added.
|
|
162
162
|
*/
|
|
163
163
|
_addAirQualityService() {
|
|
164
|
-
const {Service} = this.hap;
|
|
164
|
+
const { Service } = this.hap;
|
|
165
165
|
const nameAirQuality = this.device.context.nameAirQuality || 'Air Quality';
|
|
166
166
|
this.log.info('Adding air quality sensor: %s', nameAirQuality);
|
|
167
167
|
this.accessory.addService(Service.AirQualitySensor, nameAirQuality);
|
|
@@ -171,7 +171,7 @@ class AirPurifierAccessory extends BaseAccessory {
|
|
|
171
171
|
* @param {*} dps
|
|
172
172
|
*/
|
|
173
173
|
_registerCharacteristics(dps) {
|
|
174
|
-
const {Service, Characteristic} = this.hap;
|
|
174
|
+
const { Service, Characteristic } = this.hap;
|
|
175
175
|
/* Air purifier service characteristics */
|
|
176
176
|
const airPurifierService = this.accessory.getService(Service.AirPurifier);
|
|
177
177
|
this._checkServiceName(airPurifierService, this.device.context.name);
|
|
@@ -185,10 +185,10 @@ class AirPurifierAccessory extends BaseAccessory {
|
|
|
185
185
|
.on('get', this.getCurrentAirPurifierState.bind(this));
|
|
186
186
|
|
|
187
187
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
const characteristicTargetAirPurifierState = airPurifierService.getCharacteristic(Characteristic.TargetAirPurifierState)
|
|
189
|
+
.updateValue(this._getTargetAirPurifierState(this._getMode(dps)))
|
|
190
|
+
.on('get', this.getTargetAirPurifierState.bind(this))
|
|
191
|
+
.on('set', this.setTargetAirPurifierState.bind(this));
|
|
192
192
|
|
|
193
193
|
let characteristicLockPhysicalControls;
|
|
194
194
|
if (!this.device.context.noChildLock) {
|
|
@@ -197,7 +197,7 @@ class AirPurifierAccessory extends BaseAccessory {
|
|
|
197
197
|
.on('get', this.getLockPhysicalControls.bind(this))
|
|
198
198
|
.on('set', this.setLockPhysicalControls.bind(this));
|
|
199
199
|
} else {
|
|
200
|
-
this._removeCharacteristic(
|
|
200
|
+
this._removeCharacteristic(airPurifierService, Characteristic.LockPhysicalControls);
|
|
201
201
|
}
|
|
202
202
|
const characteristicRotationSpeed = airPurifierService.getCharacteristic(Characteristic.RotationSpeed)
|
|
203
203
|
.updateValue(this._getRotationSpeed(dps))
|
|
@@ -230,46 +230,46 @@ class AirPurifierAccessory extends BaseAccessory {
|
|
|
230
230
|
/* Listen for changes */
|
|
231
231
|
this.device.on('change', (changes, state) => {
|
|
232
232
|
this.log.debug('Changes: %o, State: %o', changes, state);
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
233
|
+
if (changes.hasOwnProperty(DP_SWITCH)) {
|
|
234
|
+
/* On/Off state change */
|
|
235
|
+
const newActive = this._getActive(changes[DP_SWITCH]);
|
|
236
236
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
237
|
+
// switch power on before other updates to avoid "Turning on..." state
|
|
238
|
+
if (changes[DP_SWITCH]) {
|
|
239
|
+
this.log.debug('Switching state first');
|
|
240
|
+
characteristicActive.updateValue(newActive);
|
|
241
241
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
242
|
+
characteristicCurrentAirPurifierState.updateValue(
|
|
243
|
+
this._getCurrentAirPurifierState(changes[DP_SWITCH]));
|
|
244
|
+
}
|
|
245
245
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
246
|
+
if (!changes.hasOwnProperty(DP_FAN_SPEED)) {
|
|
247
|
+
characteristicRotationSpeed.updateValue(this._getRotationSpeed(state));
|
|
248
|
+
}
|
|
249
|
+
if (!changes.hasOwnProperty(DP_MODE)) {
|
|
250
|
+
characteristicTargetAirPurifierState.updateValue(
|
|
251
|
+
this._getTargetAirPurifierState(this._getMode(state)));
|
|
252
|
+
}
|
|
253
253
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
254
|
+
// switch power off after other updates to avoid "Turning off..." state
|
|
255
|
+
if (!changes[DP_SWITCH]) {
|
|
256
|
+
this.log.debug('Switching state last');
|
|
257
|
+
characteristicCurrentAirPurifierState.updateValue(
|
|
258
|
+
this._getCurrentAirPurifierState(changes[DP_SWITCH]));
|
|
259
259
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
260
|
+
characteristicActive.updateValue(newActive);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
263
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
264
|
+
if (changes.hasOwnProperty(DP_FAN_SPEED)) {
|
|
265
|
+
/* Fan speed change */
|
|
266
|
+
const newRotationSpeed = this._getRotationSpeed(state);
|
|
267
|
+
// Proscenic "auto" fan speed is not mapped and should not trigger a rotation speed update
|
|
268
|
+
if (newRotationSpeed) {
|
|
269
|
+
if (characteristicRotationSpeed.value !== newRotationSpeed) {
|
|
270
|
+
characteristicRotationSpeed.updateValue(newRotationSpeed);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
273
|
|
|
274
274
|
if (!changes.hasOwnProperty(DP_MODE)) {
|
|
275
275
|
characteristicTargetAirPurifierState.updateValue(
|
|
@@ -301,32 +301,32 @@ class AirPurifierAccessory extends BaseAccessory {
|
|
|
301
301
|
characteristicAirQuality.updateValue(this._getAirQuality(state));
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
|
-
|
|
305
|
-
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
306
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
307
|
+
/* Proscenic air purifier does not support DP_MODE but has fan speed 'auto' */
|
|
308
|
+
_getMode(state) {
|
|
309
|
+
if (state[DP_MODE]) {
|
|
310
|
+
return state[DP_MODE];
|
|
311
|
+
} else {
|
|
312
|
+
return state[DP_FAN_SPEED] == 'auto' ? 'auto' : 'manual';
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
315
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
316
|
+
getActive(callback) {
|
|
317
|
+
this.getState(DP_SWITCH, (err, dp) => {
|
|
318
|
+
if (err) {
|
|
319
319
|
return callback(err);
|
|
320
320
|
}
|
|
321
321
|
callback(null, this._getActive(dp));
|
|
322
322
|
});
|
|
323
323
|
}
|
|
324
324
|
_getActive(dp) {
|
|
325
|
-
const {Characteristic} = this.hap;
|
|
325
|
+
const { Characteristic } = this.hap;
|
|
326
326
|
return dp ? Characteristic.Active.ACTIVE : Characteristic.Active.INACTIVE;
|
|
327
327
|
}
|
|
328
328
|
setActive(value, callback) {
|
|
329
|
-
const {Characteristic} = this.hap;
|
|
329
|
+
const { Characteristic } = this.hap;
|
|
330
330
|
switch (value) {
|
|
331
331
|
case Characteristic.Active.ACTIVE:
|
|
332
332
|
return this.setState(DP_SWITCH, true, callback);
|
|
@@ -344,33 +344,33 @@ class AirPurifierAccessory extends BaseAccessory {
|
|
|
344
344
|
});
|
|
345
345
|
}
|
|
346
346
|
_getAirQuality(dps) {
|
|
347
|
-
const {Characteristic} = this.hap;
|
|
347
|
+
const { Characteristic } = this.hap;
|
|
348
348
|
/* TODO: Other DP values can be used for Air Quality */
|
|
349
349
|
switch (this.device.context.manufacturer) {
|
|
350
350
|
case 'Breville':
|
|
351
351
|
if (dps[DP_AIR_QUALITY]) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
352
|
+
switch (dps[DP_AIR_QUALITY]) {
|
|
353
|
+
case 'poor':
|
|
354
|
+
return Characteristic.AirQuality.POOR;
|
|
355
|
+
case 'good':
|
|
356
|
+
return Characteristic.AirQuality.GOOD;
|
|
357
|
+
case 'great':
|
|
358
|
+
return Characteristic.AirQuality.EXCELLENT;
|
|
359
|
+
default:
|
|
360
|
+
this.log.warn('Unhandled _getAirQuality value: %s', dps[DP_AIR_QUALITY]);
|
|
361
|
+
return Characteristic.AirQuality.UNKNOWN;
|
|
362
|
+
}
|
|
363
363
|
}
|
|
364
364
|
break;
|
|
365
365
|
default:
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
366
|
+
if (dps[DP_PM25]) {
|
|
367
|
+
/* Loop through the air quality levels until a match is found */
|
|
368
|
+
for (var item of this.airQualityLevels) {
|
|
369
|
+
if (dps[DP_PM25] >= item[0]) {
|
|
370
|
+
return item[1];
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
374
|
}
|
|
375
375
|
/* Default return value if nothing has already returned */
|
|
376
376
|
return 0;
|
|
@@ -382,7 +382,7 @@ class AirPurifierAccessory extends BaseAccessory {
|
|
|
382
382
|
});
|
|
383
383
|
}
|
|
384
384
|
_getCurrentAirPurifierState(dp) {
|
|
385
|
-
const {Characteristic} = this.hap;
|
|
385
|
+
const { Characteristic } = this.hap;
|
|
386
386
|
/* There isn't really a direct mapping to this from the purifier,
|
|
387
387
|
* so just using as inactive or purifying.
|
|
388
388
|
*/
|
|
@@ -397,11 +397,11 @@ class AirPurifierAccessory extends BaseAccessory {
|
|
|
397
397
|
});
|
|
398
398
|
}
|
|
399
399
|
_getLockPhysicalControls(dp) {
|
|
400
|
-
const {Characteristic} = this.hap;
|
|
400
|
+
const { Characteristic } = this.hap;
|
|
401
401
|
return dp ? Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED : Characteristic.LockPhysicalControls.CONTROL_LOCK_DISABLED;
|
|
402
402
|
}
|
|
403
403
|
setLockPhysicalControls(value, callback) {
|
|
404
|
-
const {Characteristic} = this.hap;
|
|
404
|
+
const { Characteristic } = this.hap;
|
|
405
405
|
switch (value) {
|
|
406
406
|
case Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED:
|
|
407
407
|
return this.setState(DP_LOCK_PHYSICAL_CONTROLS, true, callback);
|
|
@@ -409,16 +409,16 @@ class AirPurifierAccessory extends BaseAccessory {
|
|
|
409
409
|
return this.setState(DP_LOCK_PHYSICAL_CONTROLS, false, callback);
|
|
410
410
|
}
|
|
411
411
|
callback();
|
|
412
|
-
|
|
412
|
+
}
|
|
413
413
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
414
|
+
getPM25(callback) {
|
|
415
|
+
this.getState(DP_PM25, (err, dp) => {
|
|
416
|
+
if (err) {
|
|
417
|
+
return callback(err);
|
|
418
|
+
}
|
|
419
|
+
callback(null, dp);
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
422
|
|
|
423
423
|
getRotationSpeed(callback) {
|
|
424
424
|
this.getState([DP_SWITCH, DP_FAN_SPEED], (err, dps) => {
|
|
@@ -438,7 +438,7 @@ class AirPurifierAccessory extends BaseAccessory {
|
|
|
438
438
|
return this._hkRotationSpeed = this.convertRotationSpeedFromTuyaToHomeKit(dps[DP_FAN_SPEED]);
|
|
439
439
|
}
|
|
440
440
|
setRotationSpeed(value, callback) {
|
|
441
|
-
const {Characteristic} = this.hap;
|
|
441
|
+
const { Characteristic } = this.hap;
|
|
442
442
|
if (value === 0) {
|
|
443
443
|
this.setActive(Characteristic.Active.INACTIVE, callback);
|
|
444
444
|
} else {
|
|
@@ -449,26 +449,27 @@ class AirPurifierAccessory extends BaseAccessory {
|
|
|
449
449
|
//return this.setMultiState(newState, callback);
|
|
450
450
|
return this.setState(DP_FAN_SPEED, this.convertRotationSpeedFromHomeKitToTuya(value), callback);
|
|
451
451
|
}
|
|
452
|
-
|
|
452
|
+
}
|
|
453
453
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
454
|
+
getTargetAirPurifierState(callback) {
|
|
455
|
+
this.getState([DP_MODE, DP_FAN_SPEED], (err, dps) => {
|
|
456
|
+
if (err) {
|
|
457
|
+
return callback(err);
|
|
458
|
+
}
|
|
459
459
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
460
|
+
callback(null, this._getTargetAirPurifierState(this._getMode(dps)));
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
463
|
|
|
464
464
|
_getTargetAirPurifierState(dp) {
|
|
465
|
-
const {Characteristic} = this.hap;
|
|
465
|
+
const { Characteristic } = this.hap;
|
|
466
466
|
switch (dp) {
|
|
467
467
|
case 'manual':
|
|
468
468
|
case 'Manual':
|
|
469
469
|
return Characteristic.TargetAirPurifierState.MANUAL;
|
|
470
470
|
case 'Sleep':
|
|
471
|
-
|
|
471
|
+
//TODO: Handle differently than passing through?
|
|
472
|
+
// falls through
|
|
472
473
|
case 'auto':
|
|
473
474
|
case 'Auto':
|
|
474
475
|
return Characteristic.TargetAirPurifierState.AUTO;
|
|
@@ -478,31 +479,31 @@ class AirPurifierAccessory extends BaseAccessory {
|
|
|
478
479
|
}
|
|
479
480
|
}
|
|
480
481
|
setTargetAirPurifierState(value, callback) {
|
|
481
|
-
const {Characteristic} = this.hap;
|
|
482
|
+
const { Characteristic } = this.hap;
|
|
482
483
|
switch (value) {
|
|
483
|
-
|
|
484
|
+
case Characteristic.TargetAirPurifierState.MANUAL:
|
|
484
485
|
if (this.device.context.manufacturer == 'Breville') {
|
|
485
|
-
|
|
486
|
+
return this.setState(DP_MODE, 'manual', callback);
|
|
486
487
|
} else if (this.device.context.manufacturer == 'Proscenic') {
|
|
487
488
|
// When going from auto to manual, set to the lowest speed
|
|
488
|
-
return this.setState(DP_FAN_SPEED, 'sleep', callback);
|
|
489
|
+
return this.setState(DP_FAN_SPEED, 'sleep', callback);
|
|
489
490
|
|
|
490
491
|
} else if (this.device.context.manufacturer == 'siguro') {
|
|
491
492
|
// When going from auto to manual, set to the lowest speed
|
|
492
|
-
return this.setState(DP_FAN_SPEED, 'sleep', callback);
|
|
493
|
+
return this.setState(DP_FAN_SPEED, 'sleep', callback);
|
|
493
494
|
} else {
|
|
494
495
|
return this.setState(DP_MODE, 'Manual', callback);
|
|
495
496
|
}
|
|
496
497
|
|
|
497
|
-
|
|
498
|
+
case Characteristic.TargetAirPurifierState.AUTO:
|
|
498
499
|
if (this.device.context.manufacturer == 'Breville') {
|
|
499
|
-
|
|
500
|
+
return this.setState(DP_MODE, 'auto', callback);
|
|
500
501
|
} else if (this.device.context.manufacturer == 'Proscenic') {
|
|
501
502
|
return this.setState(DP_FAN_SPEED, 'auto', callback);
|
|
502
503
|
} else if (this.device.context.manufacturer == 'siguro') {
|
|
503
|
-
|
|
504
|
+
return this.setState(DP_FAN_SPEED, 'auto', callback);
|
|
504
505
|
} else {
|
|
505
|
-
|
|
506
|
+
return this.setState(DP_MODE, 'Auto', callback);
|
|
506
507
|
}
|
|
507
508
|
default:
|
|
508
509
|
//TODO: Can we do anything about Sleep?
|
|
@@ -511,21 +512,21 @@ class AirPurifierAccessory extends BaseAccessory {
|
|
|
511
512
|
callback();
|
|
512
513
|
}
|
|
513
514
|
getKeyByValue(object, value) {
|
|
514
|
-
|
|
515
|
+
return Object.keys(object).find(key => object[key] === value);
|
|
515
516
|
}
|
|
516
517
|
convertRotationSpeedFromHomeKitToTuya(value) {
|
|
517
518
|
this.log.debug('convertRotationSpeedFromHomeKitToTuya: %s: %s', value, this._rotationStops[parseInt(value)]);
|
|
518
519
|
return this._rotationStops[parseInt(value)];
|
|
519
520
|
}
|
|
520
521
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
522
|
+
convertRotationSpeedFromTuyaToHomeKit(value) {
|
|
523
|
+
this.log.debug('convertRotationSpeedFromTuyaToHomeKit: %s: %s', value, this.getKeyByValue(this._rotationStops, value));
|
|
524
|
+
let speed = this.device.context.fanSpeedSteps ? '' + this.getKeyByValue(this._rotationStops, value) : this.getKeyByValue(this._rotationStops, value);
|
|
525
|
+
if (speed === undefined) {
|
|
526
|
+
return 0;
|
|
527
|
+
}
|
|
528
|
+
return speed;
|
|
529
|
+
}
|
|
529
530
|
|
|
530
|
-
|
|
531
|
+
}
|
|
531
532
|
module.exports = AirPurifierAccessory;
|