@homebridge-plugins/homebridge-tuya 2.7.1-beta.8 → 2.7.1-beta.9
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.
|
@@ -3,31 +3,111 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const util_1 = require("../util/util");
|
|
6
7
|
const BaseAccessory_1 = __importDefault(require("./BaseAccessory"));
|
|
7
8
|
const SCHEMA_CODE = {
|
|
8
|
-
|
|
9
|
+
SMOKE_STATUS: ['smoke_sensor_status', 'smoke_sensor_state'],
|
|
10
|
+
CO_STATUS: ['co_status', 'co_state'],
|
|
11
|
+
CO_LEVEL: ['co_value'],
|
|
12
|
+
BATTERY_STATE: ['battery_state'],
|
|
13
|
+
BATTERY_PERCENT: ['battery_percentage', 'residual_electricity', 'wireless_electricity', 'va_battery', 'battery'],
|
|
14
|
+
FAULT: ['fault'],
|
|
9
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* Smoke detector (ywbj).
|
|
18
|
+
*
|
|
19
|
+
* Combo smoke + CO detectors are also categorized as `ywbj` and expose
|
|
20
|
+
* `co_state` / `co_status` alongside the smoke DP, so a CarbonMonoxideSensor
|
|
21
|
+
* service is added when one of those DPs is present.
|
|
22
|
+
*/
|
|
10
23
|
class SmokeSensor extends BaseAccessory_1.default {
|
|
11
24
|
requiredSchema() {
|
|
12
|
-
return [SCHEMA_CODE.
|
|
25
|
+
return [SCHEMA_CODE.SMOKE_STATUS];
|
|
13
26
|
}
|
|
14
27
|
configureServices() {
|
|
15
|
-
const
|
|
28
|
+
const sensorServices = [];
|
|
29
|
+
const smokeService = this.configureSmokeDetected();
|
|
30
|
+
if (smokeService) {
|
|
31
|
+
sensorServices.push(smokeService);
|
|
32
|
+
}
|
|
33
|
+
const coService = this.configureCarbonMonoxideDetected();
|
|
34
|
+
if (coService) {
|
|
35
|
+
sensorServices.push(coService);
|
|
36
|
+
this.configureCarbonMonoxideLevel(coService);
|
|
37
|
+
}
|
|
38
|
+
for (const service of sensorServices) {
|
|
39
|
+
this.configureStatusLowBattery(service);
|
|
40
|
+
this.configureStatusFault(service);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
isAlarm(code) {
|
|
44
|
+
const status = this.getStatus(code);
|
|
45
|
+
return status !== undefined && (status.value === 'alarm' || status.value === '1');
|
|
46
|
+
}
|
|
47
|
+
configureSmokeDetected() {
|
|
48
|
+
const schema = this.getSchema(...SCHEMA_CODE.SMOKE_STATUS);
|
|
16
49
|
if (!schema) {
|
|
17
|
-
return;
|
|
50
|
+
return undefined;
|
|
18
51
|
}
|
|
19
52
|
const { SMOKE_NOT_DETECTED, SMOKE_DETECTED } = this.Characteristic.SmokeDetected;
|
|
20
53
|
const service = this.accessory.getService(this.Service.SmokeSensor)
|
|
21
54
|
|| this.accessory.addService(this.Service.SmokeSensor);
|
|
22
55
|
service.getCharacteristic(this.Characteristic.SmokeDetected)
|
|
56
|
+
.onGet(() => this.isAlarm(schema.code) ? SMOKE_DETECTED : SMOKE_NOT_DETECTED);
|
|
57
|
+
return service;
|
|
58
|
+
}
|
|
59
|
+
configureCarbonMonoxideDetected() {
|
|
60
|
+
const schema = this.getSchema(...SCHEMA_CODE.CO_STATUS);
|
|
61
|
+
if (!schema) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const { CO_LEVELS_ABNORMAL, CO_LEVELS_NORMAL } = this.Characteristic.CarbonMonoxideDetected;
|
|
65
|
+
const service = this.accessory.getService(this.Service.CarbonMonoxideSensor)
|
|
66
|
+
|| this.accessory.addService(this.Service.CarbonMonoxideSensor);
|
|
67
|
+
service.getCharacteristic(this.Characteristic.CarbonMonoxideDetected)
|
|
68
|
+
.onGet(() => this.isAlarm(schema.code) ? CO_LEVELS_ABNORMAL : CO_LEVELS_NORMAL);
|
|
69
|
+
return service;
|
|
70
|
+
}
|
|
71
|
+
configureCarbonMonoxideLevel(service) {
|
|
72
|
+
const schema = this.getSchema(...SCHEMA_CODE.CO_LEVEL);
|
|
73
|
+
if (!schema) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const property = schema.property;
|
|
77
|
+
const multiple = Math.pow(10, property ? property.scale : 0);
|
|
78
|
+
service.getCharacteristic(this.Characteristic.CarbonMonoxideLevel)
|
|
79
|
+
.onGet(() => {
|
|
80
|
+
const status = this.getStatus(schema.code);
|
|
81
|
+
return (0, util_1.limit)(status.value / multiple, 0, 100);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
configureStatusLowBattery(service) {
|
|
85
|
+
// When a percentage DP exists, BaseAccessory already exposes a Battery service.
|
|
86
|
+
if (this.getSchema(...SCHEMA_CODE.BATTERY_PERCENT)) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const schema = this.getSchema(...SCHEMA_CODE.BATTERY_STATE);
|
|
90
|
+
if (!schema) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const { BATTERY_LEVEL_NORMAL, BATTERY_LEVEL_LOW } = this.Characteristic.StatusLowBattery;
|
|
94
|
+
service.getCharacteristic(this.Characteristic.StatusLowBattery)
|
|
95
|
+
.onGet(() => {
|
|
96
|
+
const status = this.getStatus(schema.code);
|
|
97
|
+
return (status?.value === 'low') ? BATTERY_LEVEL_LOW : BATTERY_LEVEL_NORMAL;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
configureStatusFault(service) {
|
|
101
|
+
const schema = this.getSchema(...SCHEMA_CODE.FAULT);
|
|
102
|
+
if (!schema) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const { NO_FAULT, GENERAL_FAULT } = this.Characteristic.StatusFault;
|
|
106
|
+
service.getCharacteristic(this.Characteristic.StatusFault)
|
|
23
107
|
.onGet(() => {
|
|
108
|
+
// `fault` is a bitmap: any bit set means a fault is reported.
|
|
24
109
|
const status = this.getStatus(schema.code);
|
|
25
|
-
|
|
26
|
-
return SMOKE_DETECTED;
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
return SMOKE_NOT_DETECTED;
|
|
30
|
-
}
|
|
110
|
+
return (Number(status?.value ?? 0) !== 0) ? GENERAL_FAULT : NO_FAULT;
|
|
31
111
|
});
|
|
32
112
|
}
|
|
33
113
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SmokeSensorAccessory.js","sourceRoot":"","sources":["../../src/accessory/SmokeSensorAccessory.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"SmokeSensorAccessory.js","sourceRoot":"","sources":["../../src/accessory/SmokeSensorAccessory.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homebridge-plugins/homebridge-tuya",
|
|
3
3
|
"displayName": "Homebridge Tuya",
|
|
4
|
-
"version": "2.7.1-beta.
|
|
4
|
+
"version": "2.7.1-beta.9",
|
|
5
5
|
"description": "Forked from 0x5e/homebridge-tuya-platform, with a focus on fixing bugs and adding new device support.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { describe, expect, test, beforeEach, jest } from '@jest/globals';
|
|
3
|
+
|
|
4
|
+
import SmokeSensorAccessory from '../../src/accessory/SmokeSensorAccessory';
|
|
5
|
+
import TuyaDevice, { TuyaDeviceSchemaMode, TuyaDeviceSchemaType } from '../../src/device/TuyaDevice';
|
|
6
|
+
import { initLogger } from '../../src/util/Logger';
|
|
7
|
+
|
|
8
|
+
// HAP constants, mirroring the real values.
|
|
9
|
+
const SmokeDetected = { SMOKE_NOT_DETECTED: 0, SMOKE_DETECTED: 1 };
|
|
10
|
+
const CarbonMonoxideDetected = { CO_LEVELS_NORMAL: 0, CO_LEVELS_ABNORMAL: 1 };
|
|
11
|
+
const StatusLowBattery = { BATTERY_LEVEL_NORMAL: 0, BATTERY_LEVEL_LOW: 1 };
|
|
12
|
+
const StatusFault = { NO_FAULT: 0, GENERAL_FAULT: 1 };
|
|
13
|
+
|
|
14
|
+
const enumSchema = (code: string, range: string[]) => ({
|
|
15
|
+
code,
|
|
16
|
+
mode: TuyaDeviceSchemaMode.READ_ONLY,
|
|
17
|
+
type: TuyaDeviceSchemaType.Enum,
|
|
18
|
+
property: { range },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Schema of a combo smoke + CO detector reported as category `ywbj`.
|
|
22
|
+
const COMBO_SCHEMA = [
|
|
23
|
+
enumSchema('smoke_sensor_state', ['alarm', 'normal']),
|
|
24
|
+
enumSchema('checking_result', ['checking', 'check_success', 'check_failure']),
|
|
25
|
+
{ code: 'fault', mode: TuyaDeviceSchemaMode.READ_ONLY, type: 'Bitmap' as any, property: { label: ['fault'] } },
|
|
26
|
+
enumSchema('battery_state', ['low', 'middle', 'high']),
|
|
27
|
+
enumSchema('co_state', ['alarm', 'normal']),
|
|
28
|
+
{ code: 'mute', mode: TuyaDeviceSchemaMode.READ_WRITE, type: TuyaDeviceSchemaType.Boolean, property: {} },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const NORMAL_STATUS = [
|
|
32
|
+
{ code: 'smoke_sensor_state', value: 'normal' },
|
|
33
|
+
{ code: 'checking_result', value: 'check_success' },
|
|
34
|
+
{ code: 'fault', value: 0 },
|
|
35
|
+
{ code: 'battery_state', value: 'high' },
|
|
36
|
+
{ code: 'co_state', value: 'normal' },
|
|
37
|
+
{ code: 'mute', value: false },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
describe('SmokeSensorAccessory', () => {
|
|
41
|
+
|
|
42
|
+
let services: any[];
|
|
43
|
+
let mockAccessory: any;
|
|
44
|
+
let mockPlatform: any;
|
|
45
|
+
let device: TuyaDevice;
|
|
46
|
+
|
|
47
|
+
const makeService = (type: any) => {
|
|
48
|
+
const characteristics = new Map<any, any>();
|
|
49
|
+
return {
|
|
50
|
+
UUID: type,
|
|
51
|
+
displayName: `${type}`,
|
|
52
|
+
subtype: undefined,
|
|
53
|
+
characteristics: [] as any[],
|
|
54
|
+
getCharacteristic: jest.fn(function(this: any, characteristicType: any) {
|
|
55
|
+
let characteristic = characteristics.get(characteristicType);
|
|
56
|
+
if (!characteristic) {
|
|
57
|
+
characteristic = {
|
|
58
|
+
type: characteristicType,
|
|
59
|
+
value: null,
|
|
60
|
+
getHandler: undefined,
|
|
61
|
+
onGet: jest.fn((handler: any) => (characteristic.getHandler = handler, characteristic)),
|
|
62
|
+
onSet: jest.fn(() => characteristic),
|
|
63
|
+
setProps: jest.fn(() => characteristic),
|
|
64
|
+
updateValue: jest.fn((value: any) => (characteristic.value = value, characteristic)),
|
|
65
|
+
};
|
|
66
|
+
characteristics.set(characteristicType, characteristic);
|
|
67
|
+
this.characteristics.push(characteristic);
|
|
68
|
+
}
|
|
69
|
+
return characteristic;
|
|
70
|
+
}),
|
|
71
|
+
setCharacteristic: jest.fn(function(this: any) {
|
|
72
|
+
return this;
|
|
73
|
+
}),
|
|
74
|
+
addOptionalCharacteristic: jest.fn(),
|
|
75
|
+
testCharacteristic: jest.fn(() => true),
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const createDevice = (schema: any[], status: any[]) => new TuyaDevice({
|
|
80
|
+
id: 'smoke-device-id',
|
|
81
|
+
uuid: 'smoke-uuid',
|
|
82
|
+
name: 'Smoke and CO alarm',
|
|
83
|
+
online: true,
|
|
84
|
+
owner_id: 'owner-1',
|
|
85
|
+
product_id: 'smoke-co-product',
|
|
86
|
+
product_name: 'Smoke and CO alarm',
|
|
87
|
+
category: 'ywbj',
|
|
88
|
+
sub: false,
|
|
89
|
+
schema,
|
|
90
|
+
status,
|
|
91
|
+
} as any);
|
|
92
|
+
|
|
93
|
+
beforeEach(() => {
|
|
94
|
+
services = [];
|
|
95
|
+
|
|
96
|
+
const hap = {
|
|
97
|
+
Service: {
|
|
98
|
+
SmokeSensor: 'SmokeSensor',
|
|
99
|
+
CarbonMonoxideSensor: 'CarbonMonoxideSensor',
|
|
100
|
+
AccessoryInformation: 'AccessoryInformation',
|
|
101
|
+
Battery: 'Battery',
|
|
102
|
+
},
|
|
103
|
+
Characteristic: {
|
|
104
|
+
SmokeDetected,
|
|
105
|
+
CarbonMonoxideDetected,
|
|
106
|
+
CarbonMonoxideLevel: 'CarbonMonoxideLevel',
|
|
107
|
+
StatusLowBattery,
|
|
108
|
+
StatusFault,
|
|
109
|
+
ChargingState: { NOT_CHARGING: 0, CHARGING: 1 },
|
|
110
|
+
BatteryLevel: 'BatteryLevel',
|
|
111
|
+
StatusActive: 'StatusActive',
|
|
112
|
+
Manufacturer: 'Manufacturer',
|
|
113
|
+
Model: 'Model',
|
|
114
|
+
Name: 'Name',
|
|
115
|
+
ConfiguredName: 'ConfiguredName',
|
|
116
|
+
SerialNumber: 'SerialNumber',
|
|
117
|
+
ProgrammableSwitchEvent: { UUID: 'ProgrammableSwitchEvent' },
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const noop = () => undefined;
|
|
122
|
+
mockPlatform = {
|
|
123
|
+
api: { hap },
|
|
124
|
+
options: { debug: false },
|
|
125
|
+
deviceManager: { getDevice: jest.fn(() => device), sendCommands: jest.fn() },
|
|
126
|
+
getDeviceConfig: jest.fn(() => undefined),
|
|
127
|
+
getDeviceSchemaConfig: jest.fn(() => undefined),
|
|
128
|
+
log: { info: noop, warn: noop, error: noop, debug: noop },
|
|
129
|
+
};
|
|
130
|
+
initLogger(mockPlatform.log);
|
|
131
|
+
|
|
132
|
+
mockAccessory = {
|
|
133
|
+
UUID: 'smoke-uuid',
|
|
134
|
+
displayName: 'Smoke and CO alarm',
|
|
135
|
+
context: { deviceID: 'smoke-device-id' },
|
|
136
|
+
services,
|
|
137
|
+
getService: jest.fn((type: any) => services.find(s => s.UUID === type)),
|
|
138
|
+
addService: jest.fn((type: any) => {
|
|
139
|
+
const service = makeService(type);
|
|
140
|
+
services.push(service);
|
|
141
|
+
return service;
|
|
142
|
+
}),
|
|
143
|
+
removeService: jest.fn(),
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const setup = (schema: any[], status: any[]) => {
|
|
148
|
+
device = createDevice(schema, status);
|
|
149
|
+
const accessory = new SmokeSensorAccessory(mockPlatform as any, mockAccessory as any);
|
|
150
|
+
accessory.configureServices();
|
|
151
|
+
return accessory;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const service = (type: string) => services.find(s => s.UUID === type);
|
|
155
|
+
const read = (serviceType: string, characteristicType: any) =>
|
|
156
|
+
service(serviceType)?.getCharacteristic(characteristicType).getHandler();
|
|
157
|
+
const setStatus = (code: string, value: any) => {
|
|
158
|
+
device.status.find(s => s.code === code)!.value = value;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
test('smoke-only detector keeps a single SmokeSensor service', () => {
|
|
162
|
+
setup([enumSchema('smoke_sensor_state', ['alarm', 'normal'])], [{ code: 'smoke_sensor_state', value: 'normal' }]);
|
|
163
|
+
|
|
164
|
+
expect(service('SmokeSensor')).toBeDefined();
|
|
165
|
+
expect(service('CarbonMonoxideSensor')).toBeUndefined();
|
|
166
|
+
expect(read('SmokeSensor', SmokeDetected)).toBe(SmokeDetected.SMOKE_NOT_DETECTED);
|
|
167
|
+
|
|
168
|
+
setStatus('smoke_sensor_state', 'alarm');
|
|
169
|
+
expect(read('SmokeSensor', SmokeDetected)).toBe(SmokeDetected.SMOKE_DETECTED);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('combo detector exposes both SmokeSensor and CarbonMonoxideSensor', () => {
|
|
173
|
+
setup(COMBO_SCHEMA, NORMAL_STATUS.map(s => ({ ...s })));
|
|
174
|
+
|
|
175
|
+
expect(service('SmokeSensor')).toBeDefined();
|
|
176
|
+
expect(service('CarbonMonoxideSensor')).toBeDefined();
|
|
177
|
+
expect(read('SmokeSensor', SmokeDetected)).toBe(SmokeDetected.SMOKE_NOT_DETECTED);
|
|
178
|
+
expect(read('CarbonMonoxideSensor', CarbonMonoxideDetected)).toBe(CarbonMonoxideDetected.CO_LEVELS_NORMAL);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('smoke and CO alarms are reported independently', () => {
|
|
182
|
+
setup(COMBO_SCHEMA, NORMAL_STATUS.map(s => ({ ...s })));
|
|
183
|
+
|
|
184
|
+
setStatus('co_state', 'alarm');
|
|
185
|
+
expect(read('CarbonMonoxideSensor', CarbonMonoxideDetected)).toBe(CarbonMonoxideDetected.CO_LEVELS_ABNORMAL);
|
|
186
|
+
expect(read('SmokeSensor', SmokeDetected)).toBe(SmokeDetected.SMOKE_NOT_DETECTED);
|
|
187
|
+
|
|
188
|
+
setStatus('co_state', 'normal');
|
|
189
|
+
setStatus('smoke_sensor_state', 'alarm');
|
|
190
|
+
expect(read('CarbonMonoxideSensor', CarbonMonoxideDetected)).toBe(CarbonMonoxideDetected.CO_LEVELS_NORMAL);
|
|
191
|
+
expect(read('SmokeSensor', SmokeDetected)).toBe(SmokeDetected.SMOKE_DETECTED);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('battery_state without a percentage DP maps to StatusLowBattery on the sensors', () => {
|
|
195
|
+
setup(COMBO_SCHEMA, NORMAL_STATUS.map(s => ({ ...s })));
|
|
196
|
+
|
|
197
|
+
expect(service('Battery')).toBeUndefined();
|
|
198
|
+
expect(read('SmokeSensor', StatusLowBattery)).toBe(StatusLowBattery.BATTERY_LEVEL_NORMAL);
|
|
199
|
+
|
|
200
|
+
setStatus('battery_state', 'low');
|
|
201
|
+
expect(read('SmokeSensor', StatusLowBattery)).toBe(StatusLowBattery.BATTERY_LEVEL_LOW);
|
|
202
|
+
expect(read('CarbonMonoxideSensor', StatusLowBattery)).toBe(StatusLowBattery.BATTERY_LEVEL_LOW);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('a non-zero fault bitmap maps to StatusFault', () => {
|
|
206
|
+
setup(COMBO_SCHEMA, NORMAL_STATUS.map(s => ({ ...s })));
|
|
207
|
+
|
|
208
|
+
expect(read('SmokeSensor', StatusFault)).toBe(StatusFault.NO_FAULT);
|
|
209
|
+
|
|
210
|
+
setStatus('fault', 4);
|
|
211
|
+
expect(read('SmokeSensor', StatusFault)).toBe(StatusFault.GENERAL_FAULT);
|
|
212
|
+
expect(read('CarbonMonoxideSensor', StatusFault)).toBe(StatusFault.GENERAL_FAULT);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
});
|