homebridge-tuya-plus 3.14.0-dev.15 → 3.14.0-dev.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Changelog.md +1 -0
- package/lib/BaseAccessory.js +12 -2
- package/lib/IrrigationSystemAccessory.js +1 -1
- package/lib/SimpleHeaterAccessory.js +1 -1
- package/package.json +1 -1
- package/test/BaseAccessory.test.js +11 -5
- package/test/MultiOutletAccessory.test.js +6 -3
- package/test/support/mocks.js +11 -0
package/Changelog.md
CHANGED
|
@@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file. This projec
|
|
|
15
15
|
* [*] **IrrigationSystem: add the HAP Service Label service for multi-valve controllers** — an accessory that exposes a collection of same-type services (more than one `Valve`) must include a `ServiceLabel` service to anchor each valve's `ServiceLabelIndex`. It was missing, so stricter Home app clients (notably iOS) scattered the zones as separate tiles instead of nesting them under the single irrigation tile. The service is added automatically (with the Arabic-numerals namespace) whenever there is more than one valve; user-set zone names still take precedence.
|
|
16
16
|
* [*] **IrrigationSystem: stop the valve toggle flickering after a press** — tapping a zone briefly snapped back to the old state before settling on the new one. The `Active` getters returned the raw `device.state`, which (for cloud devices) only advances once the realtime stream echoes the write back, so a read in that window reported the pre-press value. The getters now report the value HomeKit already shows (optimistic on press, then confirmed/corrected by device-side change events); they still surface "No Response" while disconnected.
|
|
17
17
|
* [*] **Tuya Cloud: report real device online/offline status.** Cloud devices previously always showed as reachable. They now mirror Tuya's `online` flag (read from the device record on connect and re-checked when the realtime stream reconnects), so HomeKit shows **"No Response"** when the device is genuinely offline. If the lookup isn't permitted (the project lacks the device-management API), the device is assumed reachable so control is never blocked.
|
|
18
|
+
* [*] **Signal unreachable devices with `HapStatusError`.** The shared getters (`getStateAsync` / `getDividedStateAsync`, used by nearly every accessory's read handlers, plus the irrigation `Active` getter) threw a plain `Error` to make HomeKit show "No Response". Newer Homebridge logs a characteristic warning for that before falling back to a generic status; they now throw `HapStatusError(SERVICE_COMMUNICATION_FAILURE)` directly — same "No Response", no warning spam. No behaviour change for users.
|
|
18
19
|
|
|
19
20
|
## 2.0.1 (2021-03-25)
|
|
20
21
|
This update includes the following changes:
|
package/lib/BaseAccessory.js
CHANGED
|
@@ -65,6 +65,16 @@ class BaseAccessory {
|
|
|
65
65
|
return typeof b === 'boolean' ? b : (typeof b === 'string' ? b.toLowerCase().trim() === 'true' : (typeof b === 'number' ? b !== 0 : df));
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
// A HapStatusError to throw from a get/set handler when the value can't be
|
|
69
|
+
// served (device unreachable, or an unusable reading). HomeKit shows the
|
|
70
|
+
// accessory as "No Response". Preferred over a plain `throw new Error(...)`,
|
|
71
|
+
// which newer Homebridge logs a characteristic warning for before falling
|
|
72
|
+
// back to this same status code (SERVICE_COMMUNICATION_FAILURE, -70402).
|
|
73
|
+
_commError() {
|
|
74
|
+
const {HapStatusError, HAPStatus} = this.hap;
|
|
75
|
+
return new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
|
76
|
+
}
|
|
77
|
+
|
|
68
78
|
getState(dp, callback) {
|
|
69
79
|
if (!this.device.connected) return callback(true);
|
|
70
80
|
const _callback = () => {
|
|
@@ -120,7 +130,7 @@ class BaseAccessory {
|
|
|
120
130
|
}
|
|
121
131
|
|
|
122
132
|
getStateAsync(dp) {
|
|
123
|
-
if (!this.device.connected) throw
|
|
133
|
+
if (!this.device.connected) throw this._commError();
|
|
124
134
|
if (Array.isArray(dp)) {
|
|
125
135
|
const ret = {};
|
|
126
136
|
dp.forEach(p => { ret[p] = this.device.state[p]; });
|
|
@@ -155,7 +165,7 @@ class BaseAccessory {
|
|
|
155
165
|
|
|
156
166
|
getDividedStateAsync(dp, divisor) {
|
|
157
167
|
const data = this.getStateAsync(dp);
|
|
158
|
-
if (!isFinite(data)) throw
|
|
168
|
+
if (!isFinite(data)) throw this._commError();
|
|
159
169
|
return this._getDividedState(data, divisor);
|
|
160
170
|
}
|
|
161
171
|
|
|
@@ -373,7 +373,7 @@ class IrrigationSystemAccessory extends BaseAccessory {
|
|
|
373
373
|
// back to the old state before settling. Throwing while disconnected keeps
|
|
374
374
|
// the HomeKit "No Response" behaviour.
|
|
375
375
|
_reportCached(char) {
|
|
376
|
-
if (!this.device.connected) throw
|
|
376
|
+
if (!this.device.connected) throw this._commError();
|
|
377
377
|
return char.value;
|
|
378
378
|
}
|
|
379
379
|
|
|
@@ -147,7 +147,7 @@ class SimpleHeaterAccessory extends BaseAccessory {
|
|
|
147
147
|
|
|
148
148
|
_getCurrentTempAsync() {
|
|
149
149
|
const data = this.getStateAsync(this.dpCurrentTemperature);
|
|
150
|
-
if (!isFinite(data)) throw
|
|
150
|
+
if (!isFinite(data)) throw this._commError();
|
|
151
151
|
return this._getDividedState(data, this.temperatureDivisor, this.currentTemperatureOffset);
|
|
152
152
|
}
|
|
153
153
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-tuya-plus",
|
|
3
|
-
"version": "3.14.0-dev.
|
|
3
|
+
"version": "3.14.0-dev.17",
|
|
4
4
|
"description": "A community-maintained Homebridge plugin for controlling Tuya devices locally over LAN. Includes new features, fixes, and updated device support.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const BaseAccessory = require('../lib/BaseAccessory');
|
|
4
|
-
const { makeInstance } = require('./support/mocks');
|
|
4
|
+
const { makeInstance, HAP } = require('./support/mocks');
|
|
5
5
|
|
|
6
6
|
// Minimal concrete subclass — BaseAccessory itself doesn't define _registerCharacteristics
|
|
7
7
|
class TestAccessory extends BaseAccessory {
|
|
@@ -158,10 +158,13 @@ describe('getStateAsync', () => {
|
|
|
158
158
|
expect(instance.getStateAsync(['1', '2'])).toEqual({ '1': true, '2': 50 });
|
|
159
159
|
});
|
|
160
160
|
|
|
161
|
-
test('throws when device is not connected', () => {
|
|
161
|
+
test('throws a comm-failure HapStatusError when device is not connected', () => {
|
|
162
162
|
const { instance, device } = make();
|
|
163
163
|
device.connected = false;
|
|
164
|
-
|
|
164
|
+
let err;
|
|
165
|
+
try { instance.getStateAsync('1'); } catch (e) { err = e; }
|
|
166
|
+
expect(err).toBeInstanceOf(HAP.HapStatusError);
|
|
167
|
+
expect(err.hapStatus).toBe(HAP.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
|
165
168
|
});
|
|
166
169
|
});
|
|
167
170
|
|
|
@@ -231,9 +234,12 @@ describe('getDividedStateAsync', () => {
|
|
|
231
234
|
expect(instance.getDividedStateAsync('5', 10)).toBeCloseTo(220);
|
|
232
235
|
});
|
|
233
236
|
|
|
234
|
-
test('throws when state value is not finite', () => {
|
|
237
|
+
test('throws a comm-failure HapStatusError when state value is not finite', () => {
|
|
235
238
|
const { instance } = make({ '5': 'bad' });
|
|
236
|
-
|
|
239
|
+
let err;
|
|
240
|
+
try { instance.getDividedStateAsync('5', 10); } catch (e) { err = e; }
|
|
241
|
+
expect(err).toBeInstanceOf(HAP.HapStatusError);
|
|
242
|
+
expect(err.hapStatus).toBe(HAP.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
|
237
243
|
});
|
|
238
244
|
});
|
|
239
245
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const MultiOutletAccessory = require('../lib/MultiOutletAccessory');
|
|
4
|
-
const { makeInstance } = require('./support/mocks');
|
|
4
|
+
const { makeInstance, HAP } = require('./support/mocks');
|
|
5
5
|
|
|
6
6
|
function makeOutlet(state = {}) {
|
|
7
7
|
return makeInstance(MultiOutletAccessory, state);
|
|
@@ -14,10 +14,13 @@ describe('MultiOutletAccessory.getPower', () => {
|
|
|
14
14
|
expect(instance.getPower('2')).toBe(false);
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
test('throws when device is disconnected', () => {
|
|
17
|
+
test('throws a comm-failure HapStatusError when device is disconnected', () => {
|
|
18
18
|
const { instance, device } = makeOutlet();
|
|
19
19
|
device.connected = false;
|
|
20
|
-
|
|
20
|
+
let err;
|
|
21
|
+
try { instance.getPower('1'); } catch (e) { err = e; }
|
|
22
|
+
expect(err).toBeInstanceOf(HAP.HapStatusError);
|
|
23
|
+
expect(err.hapStatus).toBe(HAP.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
|
21
24
|
});
|
|
22
25
|
});
|
|
23
26
|
|
package/test/support/mocks.js
CHANGED
|
@@ -75,6 +75,17 @@ const HAP = {
|
|
|
75
75
|
},
|
|
76
76
|
Formats: { FLOAT: 'float', UINT32: 'uint32', UINT16: 'uint16' },
|
|
77
77
|
Perms: { READ: 'pr', NOTIFY: 'ev', WRITE: 'pw' },
|
|
78
|
+
// Subset of hap-nodejs HAPStatus + the HapStatusError class, so accessories
|
|
79
|
+
// can signal "No Response" the proper way (throw a HapStatusError from a
|
|
80
|
+
// get/set handler) and tests can assert on it.
|
|
81
|
+
HAPStatus: { SUCCESS: 0, SERVICE_COMMUNICATION_FAILURE: -70402 },
|
|
82
|
+
HapStatusError: class HapStatusError extends Error {
|
|
83
|
+
constructor(hapStatus) {
|
|
84
|
+
super('HapStatusError: ' + hapStatus);
|
|
85
|
+
this.name = 'HapStatusError';
|
|
86
|
+
this.hapStatus = hapStatus;
|
|
87
|
+
}
|
|
88
|
+
},
|
|
78
89
|
// Mirrors hap-nodejs Categories. Intentionally kept faithful to the real
|
|
79
90
|
// enum (no FANLIGHT, no bare DEHUMIDIFIER) so getCategory() regressions are
|
|
80
91
|
// caught here instead of in production (see issue #45).
|