homebridge-tuya-without-developer-account 1.0.6 → 1.0.7
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 +8 -0
- package/README.md +7 -0
- package/SUPPORTED_DEVICES.md +15 -0
- package/config.schema.json +43 -0
- package/dist/platform.js +30 -0
- package/dist/shared/accessories/DiffuserAccessory.js +7 -0
- package/dist/shared/accessories/PetFeederAccessory.js +59 -88
- package/dist/shared/accessories/SecuritySystemAccessory.js +144 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.7
|
|
4
|
+
|
|
5
|
+
- Added Smart Pet Feeder support for `quick_feed`, `manual_feed`, `slow_feed`, `feed_state`, battery, and charging state.
|
|
6
|
+
- Added optional `deviceOverrides[].petFeeder.manualFeedAmount` and `deviceOverrides[].petFeeder.exposeSlowFeed`.
|
|
7
|
+
- Added Tuya alarm panel support as a HomeKit Security System using `master_mode`, `master_state`, and optional tamper/battery DPs.
|
|
8
|
+
- Added optional `deviceOverrides[].alarm` fields for alarm sound, muffling, and notification switches.
|
|
9
|
+
- Added clearer logging when Tuya returns an empty schema for aroma diffusers. Diffuser scenes remain exposed separately.
|
|
10
|
+
|
|
3
11
|
## 1.0.6
|
|
4
12
|
|
|
5
13
|
- Fixed a Homebridge UI issue where clicking **Save Configuration** could leave the custom settings page spinner running indefinitely even when QR authentication data had already been saved.
|
package/README.md
CHANGED
|
@@ -313,3 +313,10 @@ If this still happens after upgrading, open the plugin settings, clear the saved
|
|
|
313
313
|
|
|
314
314
|
Version **1.0.5** adds support for DP10-style Tuya dimmer plugs that expose `switch_led` and `bright_value_v2`. These are exposed in HomeKit as Lightbulb accessories with On and Brightness. If the accessory was previously shown as **Not Supported**, remove only that cached accessory in Homebridge UI and restart Homebridge after upgrading.
|
|
315
315
|
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
## Version 1.0.7 device support
|
|
319
|
+
|
|
320
|
+
This release adds native support for Tuya Smart Pet Feeders and Tuya alarm panels that expose `master_mode`. Pet feeders expose quick/manual feed controls, optional slow-feed control, feed-state sensor, and battery when available. Alarm panels are exposed as HomeKit Security System accessories, with optional extra switches controlled through `deviceOverrides[].alarm`.
|
|
321
|
+
|
|
322
|
+
Aroma diffuser devices whose Tuya QR cloud schema is empty remain visible as unsupported direct devices, but any diffuser scenes returned by Tuya are still exposed separately.
|
package/SUPPORTED_DEVICES.md
CHANGED
|
@@ -214,3 +214,18 @@ Supported from **v1.0.5**. These devices normally report category `tgq` and expo
|
|
|
214
214
|
|
|
215
215
|
They are exposed to HomeKit as a Lightbulb with On and Brightness. After upgrading from an older version where the device showed as unsupported, remove the affected cached accessory from Homebridge UI and restart Homebridge.
|
|
216
216
|
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
## Added in 1.0.7
|
|
220
|
+
|
|
221
|
+
### Smart Pet Feeder
|
|
222
|
+
|
|
223
|
+
Supported when Tuya exposes one or more of: `quick_feed`, `manual_feed`, `slow_feed`, `feed_state`, `battery_percentage`, `charge_state`.
|
|
224
|
+
|
|
225
|
+
### Alarm / Security System
|
|
226
|
+
|
|
227
|
+
Supported when Tuya exposes `master_mode`. Alarm-triggered state is detected from `master_state=alarm`, `sos_state=true`, or `master_mode=sos` when available. Optional extra switches can be enabled through `deviceOverrides[].alarm`.
|
|
228
|
+
|
|
229
|
+
### Aroma Diffuser with Empty Schema
|
|
230
|
+
|
|
231
|
+
If Tuya QR cloud returns an empty schema for the diffuser device, direct device control cannot be mapped. The plugin keeps diffuser Tuya scenes exposed and logs a clearer explanation.
|
package/config.schema.json
CHANGED
|
@@ -110,6 +110,49 @@
|
|
|
110
110
|
"default": 1
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
|
+
},
|
|
114
|
+
"petFeeder": {
|
|
115
|
+
"type": "object",
|
|
116
|
+
"title": "Smart Pet Feeder Options",
|
|
117
|
+
"description": "Optional settings for Tuya pet feeders. The plugin exposes quick/manual feed switches, feed-state sensor, and battery when supported.",
|
|
118
|
+
"properties": {
|
|
119
|
+
"manualFeedAmount": {
|
|
120
|
+
"type": "integer",
|
|
121
|
+
"title": "Manual feed amount",
|
|
122
|
+
"description": "Portions sent when the Manual Feed switch is turned on.",
|
|
123
|
+
"minimum": 1,
|
|
124
|
+
"maximum": 12,
|
|
125
|
+
"default": 1
|
|
126
|
+
},
|
|
127
|
+
"exposeSlowFeed": {
|
|
128
|
+
"type": "boolean",
|
|
129
|
+
"title": "Expose Slow Feed switch",
|
|
130
|
+
"description": "Expose slow_feed as a HomeKit switch when the device supports it.",
|
|
131
|
+
"default": true
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
"alarm": {
|
|
136
|
+
"type": "object",
|
|
137
|
+
"title": "Alarm Options",
|
|
138
|
+
"description": "Optional extra switches for Tuya alarm panels. The main alarm is exposed as a HomeKit Security System when master_mode is available.",
|
|
139
|
+
"properties": {
|
|
140
|
+
"exposeAlarmSoundSwitch": {
|
|
141
|
+
"type": "boolean",
|
|
142
|
+
"title": "Expose Alarm Sound switch",
|
|
143
|
+
"default": false
|
|
144
|
+
},
|
|
145
|
+
"exposeMufflingSwitch": {
|
|
146
|
+
"type": "boolean",
|
|
147
|
+
"title": "Expose Mute/Muffling switch",
|
|
148
|
+
"default": false
|
|
149
|
+
},
|
|
150
|
+
"exposeNotificationSwitches": {
|
|
151
|
+
"type": "boolean",
|
|
152
|
+
"title": "Expose Call/SMS/App/Low Battery switches",
|
|
153
|
+
"default": false
|
|
154
|
+
}
|
|
155
|
+
}
|
|
113
156
|
}
|
|
114
157
|
},
|
|
115
158
|
"required": [
|
package/dist/platform.js
CHANGED
|
@@ -122,6 +122,34 @@ class TuyaPlatform {
|
|
|
122
122
|
delete item.airConditioner;
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
|
+
if (item.petFeeder && typeof item.petFeeder === 'object') {
|
|
126
|
+
const normalizedPetFeeder = {};
|
|
127
|
+
const manualFeedAmount = Number(item.petFeeder.manualFeedAmount);
|
|
128
|
+
if (Number.isFinite(manualFeedAmount)) {
|
|
129
|
+
normalizedPetFeeder.manualFeedAmount = Math.max(1, Math.min(12, Math.round(manualFeedAmount)));
|
|
130
|
+
}
|
|
131
|
+
if (typeof item.petFeeder.exposeSlowFeed === 'boolean') {
|
|
132
|
+
normalizedPetFeeder.exposeSlowFeed = item.petFeeder.exposeSlowFeed;
|
|
133
|
+
}
|
|
134
|
+
if (Object.keys(normalizedPetFeeder).length > 0) {
|
|
135
|
+
item.petFeeder = normalizedPetFeeder;
|
|
136
|
+
} else {
|
|
137
|
+
delete item.petFeeder;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (item.alarm && typeof item.alarm === 'object') {
|
|
141
|
+
const normalizedAlarm = {};
|
|
142
|
+
for (const key of ['exposeAlarmSoundSwitch', 'exposeMufflingSwitch', 'exposeNotificationSwitches']) {
|
|
143
|
+
if (typeof item.alarm[key] === 'boolean') {
|
|
144
|
+
normalizedAlarm[key] = item.alarm[key];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (Object.keys(normalizedAlarm).length > 0) {
|
|
148
|
+
item.alarm = normalizedAlarm;
|
|
149
|
+
} else {
|
|
150
|
+
delete item.alarm;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
125
153
|
seenIds.add(id);
|
|
126
154
|
validOverrides.push(item);
|
|
127
155
|
}
|
|
@@ -249,6 +277,8 @@ class TuyaPlatform {
|
|
|
249
277
|
unbridged: deviceConfig?.unbridged ?? false,
|
|
250
278
|
schemaOverrides: deviceConfig?.schema ? JSON.stringify(deviceConfig.schema) : undefined,
|
|
251
279
|
airConditioner: deviceConfig?.airConditioner ? JSON.stringify(deviceConfig.airConditioner) : undefined,
|
|
280
|
+
petFeeder: deviceConfig?.petFeeder ? JSON.stringify(deviceConfig.petFeeder) : undefined,
|
|
281
|
+
alarm: deviceConfig?.alarm ? JSON.stringify(deviceConfig.alarm) : undefined,
|
|
252
282
|
adaptiveLighting: deviceConfig?.adaptiveLighting ?? false,
|
|
253
283
|
};
|
|
254
284
|
const { changed: configChanged } = this.configHash.hasConfigChanged(device.id, configToHash);
|
|
@@ -23,6 +23,13 @@ class DiffuserAccessory extends BaseAccessory_1.default {
|
|
|
23
23
|
requiredSchema() {
|
|
24
24
|
return [SCHEMA_CODE.SPRAY_ON];
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
checkRequirements() {
|
|
28
|
+
if (this.device && Array.isArray(this.device.schema) && this.device.schema.length === 0) {
|
|
29
|
+
this.log.warn('Tuya returned an empty schema for this diffuser. Direct diffuser control cannot be mapped until Tuya exposes switch_spray or equivalent DPs. Any Tuya scenes for this diffuser will still be exposed separately.');
|
|
30
|
+
}
|
|
31
|
+
return super.checkRequirements();
|
|
32
|
+
}
|
|
26
33
|
configureServices() {
|
|
27
34
|
// Main Switch
|
|
28
35
|
(0, On_1.configureOn)(this, undefined, this.getSchema(...SCHEMA_CODE.ON));
|
|
@@ -4,136 +4,107 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const BaseAccessory_1 = __importDefault(require("./BaseAccessory"));
|
|
7
|
-
const
|
|
7
|
+
const Name_1 = require("./characteristic/Name");
|
|
8
8
|
const SCHEMA_CODE = {
|
|
9
|
-
ACTIVE: ['switch'],
|
|
10
|
-
LIGHT: ['light'],
|
|
11
9
|
QUICK_FEED: ['quick_feed'],
|
|
12
10
|
SLOW_FEED: ['slow_feed'],
|
|
13
11
|
MANUAL_FEED: ['manual_feed'],
|
|
14
|
-
MEAL_PLAN: ['meal_plan'],
|
|
15
|
-
BATTERY_PERCENTAGE: ['battery_percentage'],
|
|
16
|
-
FEED_REPORT: ['feed_report'],
|
|
17
12
|
FEED_STATE: ['feed_state'],
|
|
18
13
|
};
|
|
19
14
|
class PetFeederAccessory extends BaseAccessory_1.default {
|
|
20
15
|
requiredSchema() {
|
|
21
|
-
|
|
16
|
+
// Tuya pet feeders often do not expose a generic "switch" DP. A feeder is
|
|
17
|
+
// considered supported when it has at least one command DP we can expose.
|
|
18
|
+
return [[...SCHEMA_CODE.QUICK_FEED, ...SCHEMA_CODE.MANUAL_FEED, ...SCHEMA_CODE.SLOW_FEED]];
|
|
19
|
+
}
|
|
20
|
+
getPetFeederConfig() {
|
|
21
|
+
const config = this.device ? this.platform.getDeviceConfig(this.device) : undefined;
|
|
22
|
+
const feeder = (config && typeof config.petFeeder === 'object') ? config.petFeeder : {};
|
|
23
|
+
const manualFeedAmount = Number(feeder.manualFeedAmount);
|
|
24
|
+
return {
|
|
25
|
+
manualFeedAmount: Number.isFinite(manualFeedAmount) ? Math.max(1, Math.min(12, Math.round(manualFeedAmount))) : 1,
|
|
26
|
+
exposeSlowFeed: feeder.exposeSlowFeed !== false,
|
|
27
|
+
};
|
|
22
28
|
}
|
|
23
29
|
configureServices() {
|
|
24
|
-
(0, Active_1.configureActive)(this, this.mainService(), this.getSchema(...SCHEMA_CODE.ACTIVE));
|
|
25
|
-
this.configureLight();
|
|
26
30
|
this.configureQuickFeed();
|
|
27
|
-
this.configureSlowFeed();
|
|
28
31
|
this.configureManualFeed();
|
|
29
|
-
this.
|
|
30
|
-
this.configureBatteryPercentage();
|
|
31
|
-
this.configureFeedReport();
|
|
32
|
+
this.configureSlowFeed();
|
|
32
33
|
this.configureFeedState();
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
-
return this.accessory.getService(this.Service.Switch)
|
|
36
|
-
|| this.accessory.addService(this.Service.Switch);
|
|
37
|
-
}
|
|
38
|
-
configureLight() {
|
|
39
|
-
const schema = this.getSchema(...SCHEMA_CODE.LIGHT);
|
|
40
|
-
if (!schema) {
|
|
41
|
-
this.log.warn('Light is not supported.');
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
this.mainService().getCharacteristic(this.Characteristic.On)
|
|
45
|
-
.onSet(async (value) => {
|
|
46
|
-
await this.sendCommands([{ code: schema.code, value: value }]);
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
configureQuickFeed() {
|
|
50
|
-
const schema = this.getSchema(...SCHEMA_CODE.QUICK_FEED);
|
|
35
|
+
configureActionSwitch(schema, name, subtype, onSet) {
|
|
51
36
|
if (!schema) {
|
|
52
|
-
this.log.warn('Quick feed is not supported.');
|
|
53
37
|
return;
|
|
54
38
|
}
|
|
55
|
-
this.
|
|
39
|
+
const service = this.accessory.getServiceById(this.Service.Switch, subtype)
|
|
40
|
+
|| this.accessory.addService(this.Service.Switch, name, subtype);
|
|
41
|
+
(0, Name_1.configureName)(this, service, name);
|
|
42
|
+
service.getCharacteristic(this.Characteristic.On)
|
|
43
|
+
.onGet(() => {
|
|
44
|
+
this.checkOnlineStatus();
|
|
45
|
+
// quick_feed/manual_feed are momentary actions. Always display them as off.
|
|
46
|
+
return false;
|
|
47
|
+
})
|
|
56
48
|
.onSet(async (value) => {
|
|
57
49
|
if (value) {
|
|
58
|
-
await
|
|
50
|
+
await onSet();
|
|
59
51
|
}
|
|
52
|
+
setTimeout(() => service.getCharacteristic(this.Characteristic.On).updateValue(false), 500);
|
|
60
53
|
});
|
|
61
54
|
}
|
|
62
|
-
|
|
63
|
-
const schema = this.getSchema(...SCHEMA_CODE.SLOW_FEED);
|
|
55
|
+
configureBooleanSwitch(schema, name, subtype) {
|
|
64
56
|
if (!schema) {
|
|
65
|
-
this.log.warn('Slow feed is not supported.');
|
|
66
57
|
return;
|
|
67
58
|
}
|
|
68
|
-
this.
|
|
69
|
-
.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const schema = this.getSchema(...SCHEMA_CODE.MANUAL_FEED);
|
|
77
|
-
if (!schema) {
|
|
78
|
-
this.log.warn('Manual feed is not supported.');
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
this.mainService().getCharacteristic(this.Characteristic.On)
|
|
59
|
+
const service = this.accessory.getServiceById(this.Service.Switch, subtype)
|
|
60
|
+
|| this.accessory.addService(this.Service.Switch, name, subtype);
|
|
61
|
+
(0, Name_1.configureName)(this, service, name);
|
|
62
|
+
service.getCharacteristic(this.Characteristic.On)
|
|
63
|
+
.onGet(() => {
|
|
64
|
+
this.checkOnlineStatus();
|
|
65
|
+
return !!(this.getStatus(schema.code)?.value ?? false);
|
|
66
|
+
})
|
|
82
67
|
.onSet(async (value) => {
|
|
83
|
-
|
|
84
|
-
await this.sendCommands([{ code: schema.code, value: 1 }]);
|
|
85
|
-
}
|
|
68
|
+
await this.sendCommands([{ code: schema.code, value: !!value }], true);
|
|
86
69
|
});
|
|
87
70
|
}
|
|
88
|
-
|
|
89
|
-
const schema = this.getSchema(...SCHEMA_CODE.
|
|
90
|
-
|
|
91
|
-
this.
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
this.mainService().getCharacteristic(this.Characteristic.On)
|
|
95
|
-
.onSet(async (value) => {
|
|
96
|
-
if (value) {
|
|
97
|
-
await this.sendCommands([{ code: schema.code, value: value }]);
|
|
98
|
-
}
|
|
71
|
+
configureQuickFeed() {
|
|
72
|
+
const schema = this.getSchema(...SCHEMA_CODE.QUICK_FEED);
|
|
73
|
+
this.configureActionSwitch(schema, `${this.device?.name || 'Pet Feeder'} Quick Feed`, 'quick_feed', async () => {
|
|
74
|
+
await this.sendCommands([{ code: schema.code, value: true }], true);
|
|
99
75
|
});
|
|
100
76
|
}
|
|
101
|
-
|
|
102
|
-
const schema = this.getSchema(...SCHEMA_CODE.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
this.mainService().getCharacteristic(this.Characteristic.BatteryLevel)
|
|
108
|
-
.onGet(() => {
|
|
109
|
-
const status = this.getStatus(schema.code);
|
|
110
|
-
return status.value;
|
|
77
|
+
configureManualFeed() {
|
|
78
|
+
const schema = this.getSchema(...SCHEMA_CODE.MANUAL_FEED);
|
|
79
|
+
const { manualFeedAmount } = this.getPetFeederConfig();
|
|
80
|
+
this.configureActionSwitch(schema, `${this.device?.name || 'Pet Feeder'} Manual Feed`, 'manual_feed', async () => {
|
|
81
|
+
await this.sendCommands([{ code: schema.code, value: manualFeedAmount }], true);
|
|
111
82
|
});
|
|
112
83
|
}
|
|
113
|
-
|
|
114
|
-
const schema = this.getSchema(...SCHEMA_CODE.
|
|
115
|
-
|
|
116
|
-
|
|
84
|
+
configureSlowFeed() {
|
|
85
|
+
const schema = this.getSchema(...SCHEMA_CODE.SLOW_FEED);
|
|
86
|
+
const { exposeSlowFeed } = this.getPetFeederConfig();
|
|
87
|
+
if (!exposeSlowFeed) {
|
|
117
88
|
return;
|
|
118
89
|
}
|
|
119
|
-
this.
|
|
120
|
-
.onGet(() => {
|
|
121
|
-
const status = this.getStatus(schema.code);
|
|
122
|
-
return status.value;
|
|
123
|
-
});
|
|
90
|
+
this.configureBooleanSwitch(schema, `${this.device?.name || 'Pet Feeder'} Slow Feed`, 'slow_feed');
|
|
124
91
|
}
|
|
125
92
|
configureFeedState() {
|
|
126
93
|
const schema = this.getSchema(...SCHEMA_CODE.FEED_STATE);
|
|
127
94
|
if (!schema) {
|
|
128
|
-
this.log.warn('Feed state is not supported.');
|
|
129
95
|
return;
|
|
130
96
|
}
|
|
131
|
-
this.
|
|
97
|
+
const service = this.accessory.getServiceById(this.Service.OccupancySensor, 'feed_state')
|
|
98
|
+
|| this.accessory.addService(this.Service.OccupancySensor, `${this.device?.name || 'Pet Feeder'} Feeding`, 'feed_state');
|
|
99
|
+
(0, Name_1.configureName)(this, service, `${this.device?.name || 'Pet Feeder'} Feeding`);
|
|
100
|
+
const { OCCUPANCY_DETECTED, OCCUPANCY_NOT_DETECTED } = this.Characteristic.OccupancyDetected;
|
|
101
|
+
service.getCharacteristic(this.Characteristic.OccupancyDetected)
|
|
132
102
|
.onGet(() => {
|
|
133
|
-
|
|
134
|
-
|
|
103
|
+
this.checkOnlineStatus();
|
|
104
|
+
const value = this.getStatus(schema.code)?.value;
|
|
105
|
+
return value === 'feeding' ? OCCUPANCY_DETECTED : OCCUPANCY_NOT_DETECTED;
|
|
135
106
|
});
|
|
136
107
|
}
|
|
137
108
|
}
|
|
138
109
|
exports.default = PetFeederAccessory;
|
|
139
|
-
//# sourceMappingURL=PetFeederAccessory.js.map
|
|
110
|
+
//# sourceMappingURL=PetFeederAccessory.js.map
|
|
@@ -4,11 +4,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const BaseAccessory_1 = __importDefault(require("./BaseAccessory"));
|
|
7
|
-
const SecuritySystemState_1 = require("./characteristic/SecuritySystemState");
|
|
8
7
|
const Name_1 = require("./characteristic/Name");
|
|
9
8
|
const SCHEMA_CODE = {
|
|
10
9
|
MASTER_MODE: ['master_mode'],
|
|
10
|
+
MASTER_STATE: ['master_state'],
|
|
11
11
|
SOS_STATE: ['sos_state'],
|
|
12
|
+
TAMPER_ALARM: ['temper_alarm', 'tamper_alarm'],
|
|
13
|
+
ALARM_SOUND: ['switch_alarm_sound'],
|
|
14
|
+
MUFFLING: ['muffling'],
|
|
15
|
+
ALARM_CALL: ['switch_alarm_call'],
|
|
16
|
+
ALARM_SMS: ['switch_alarm_sms'],
|
|
17
|
+
ALARM_PROPEL: ['switch_alarm_propel'],
|
|
18
|
+
LOW_BATTERY_ALERT: ['switch_low_battery'],
|
|
19
|
+
MODE_DELAY_SOUND: ['switch_mode_dl_sound'],
|
|
12
20
|
};
|
|
13
21
|
class SecuritySystemAccessory extends BaseAccessory_1.default {
|
|
14
22
|
constructor() {
|
|
@@ -16,15 +24,146 @@ class SecuritySystemAccessory extends BaseAccessory_1.default {
|
|
|
16
24
|
this.isNightArm = false;
|
|
17
25
|
}
|
|
18
26
|
requiredSchema() {
|
|
19
|
-
|
|
27
|
+
// Some Tuya alarm panels expose master_mode + master_state, but not sos_state.
|
|
28
|
+
return [SCHEMA_CODE.MASTER_MODE];
|
|
29
|
+
}
|
|
30
|
+
getAlarmConfig() {
|
|
31
|
+
const config = this.device ? this.platform.getDeviceConfig(this.device) : undefined;
|
|
32
|
+
const alarm = (config && typeof config.alarm === 'object') ? config.alarm : {};
|
|
33
|
+
return {
|
|
34
|
+
exposeAlarmSoundSwitch: !!alarm.exposeAlarmSoundSwitch,
|
|
35
|
+
exposeMufflingSwitch: !!alarm.exposeMufflingSwitch,
|
|
36
|
+
exposeNotificationSwitches: !!alarm.exposeNotificationSwitches,
|
|
37
|
+
};
|
|
20
38
|
}
|
|
21
39
|
configureServices() {
|
|
22
40
|
const service = this.accessory.getService(this.Service.SecuritySystem)
|
|
23
41
|
|| this.accessory.addService(this.Service.SecuritySystem);
|
|
24
42
|
(0, Name_1.configureName)(this, service, this.device.name);
|
|
25
|
-
|
|
26
|
-
|
|
43
|
+
this.configureCurrentState(service);
|
|
44
|
+
this.configureTargetState(service);
|
|
45
|
+
this.configureTamper(service);
|
|
46
|
+
this.configureExtraSwitches();
|
|
47
|
+
}
|
|
48
|
+
mapTuyaModeToHomeKit(value, current = true) {
|
|
49
|
+
const Current = this.Characteristic.SecuritySystemCurrentState;
|
|
50
|
+
const Target = this.Characteristic.SecuritySystemTargetState;
|
|
51
|
+
const map = current ? {
|
|
52
|
+
disarmed: Current.DISARMED,
|
|
53
|
+
arm: Current.AWAY_ARM,
|
|
54
|
+
home: this.isNightArm ? Current.NIGHT_ARM : Current.STAY_ARM,
|
|
55
|
+
sos: Current.ALARM_TRIGGERED,
|
|
56
|
+
} : {
|
|
57
|
+
disarmed: Target.DISARM,
|
|
58
|
+
arm: Target.AWAY_ARM,
|
|
59
|
+
home: this.isNightArm ? Target.NIGHT_ARM : Target.STAY_ARM,
|
|
60
|
+
sos: Target.AWAY_ARM,
|
|
61
|
+
};
|
|
62
|
+
return map[value] ?? (current ? Current.DISARMED : Target.DISARM);
|
|
63
|
+
}
|
|
64
|
+
mapHomeKitTargetToTuya(value) {
|
|
65
|
+
const Target = this.Characteristic.SecuritySystemTargetState;
|
|
66
|
+
switch (value) {
|
|
67
|
+
case Target.DISARM:
|
|
68
|
+
return 'disarmed';
|
|
69
|
+
case Target.STAY_ARM:
|
|
70
|
+
case Target.NIGHT_ARM:
|
|
71
|
+
return 'home';
|
|
72
|
+
case Target.AWAY_ARM:
|
|
73
|
+
default:
|
|
74
|
+
return 'arm';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
isAlarmTriggered() {
|
|
78
|
+
const masterStateSchema = this.getSchema(...SCHEMA_CODE.MASTER_STATE);
|
|
79
|
+
if (masterStateSchema && this.getStatus(masterStateSchema.code)?.value === 'alarm') {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
const sosStateSchema = this.getSchema(...SCHEMA_CODE.SOS_STATE);
|
|
83
|
+
if (sosStateSchema && this.getStatus(sosStateSchema.code)?.value) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
const masterModeSchema = this.getSchema(...SCHEMA_CODE.MASTER_MODE);
|
|
87
|
+
if (masterModeSchema && this.getStatus(masterModeSchema.code)?.value === 'sos') {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
configureCurrentState(service) {
|
|
93
|
+
const masterModeSchema = this.getSchema(...SCHEMA_CODE.MASTER_MODE);
|
|
94
|
+
service.getCharacteristic(this.Characteristic.SecuritySystemCurrentState)
|
|
95
|
+
.onGet(() => {
|
|
96
|
+
this.checkOnlineStatus();
|
|
97
|
+
if (this.isAlarmTriggered()) {
|
|
98
|
+
return this.Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED;
|
|
99
|
+
}
|
|
100
|
+
return this.mapTuyaModeToHomeKit(this.getStatus(masterModeSchema.code)?.value, true);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
configureTargetState(service) {
|
|
104
|
+
const masterModeSchema = this.getSchema(...SCHEMA_CODE.MASTER_MODE);
|
|
105
|
+
service.getCharacteristic(this.Characteristic.SecuritySystemTargetState)
|
|
106
|
+
.onGet(() => {
|
|
107
|
+
this.checkOnlineStatus();
|
|
108
|
+
return this.mapTuyaModeToHomeKit(this.getStatus(masterModeSchema.code)?.value, false);
|
|
109
|
+
})
|
|
110
|
+
.onSet(async (value) => {
|
|
111
|
+
this.isNightArm = value === this.Characteristic.SecuritySystemTargetState.NIGHT_ARM;
|
|
112
|
+
const commands = [{ code: masterModeSchema.code, value: this.mapHomeKitTargetToTuya(value) }];
|
|
113
|
+
const sosStateSchema = this.getSchema(...SCHEMA_CODE.SOS_STATE);
|
|
114
|
+
if (sosStateSchema && value === this.Characteristic.SecuritySystemTargetState.DISARM) {
|
|
115
|
+
commands.push({ code: sosStateSchema.code, value: false });
|
|
116
|
+
}
|
|
117
|
+
await this.sendCommands(commands, true);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
configureTamper(service) {
|
|
121
|
+
const schema = this.getSchema(...SCHEMA_CODE.TAMPER_ALARM);
|
|
122
|
+
if (!schema) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (!service.testCharacteristic(this.Characteristic.StatusTampered)) {
|
|
126
|
+
service.addOptionalCharacteristic(this.Characteristic.StatusTampered);
|
|
127
|
+
}
|
|
128
|
+
const { TAMPERED, NOT_TAMPERED } = this.Characteristic.StatusTampered;
|
|
129
|
+
service.getCharacteristic(this.Characteristic.StatusTampered)
|
|
130
|
+
.onGet(() => {
|
|
131
|
+
this.checkOnlineStatus();
|
|
132
|
+
return this.getStatus(schema.code)?.value ? TAMPERED : NOT_TAMPERED;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
configureExtraSwitches() {
|
|
136
|
+
const config = this.getAlarmConfig();
|
|
137
|
+
if (config.exposeAlarmSoundSwitch) {
|
|
138
|
+
this.configureBooleanSwitch(this.getSchema(...SCHEMA_CODE.ALARM_SOUND), `${this.device.name} Alarm Sound`, 'switch_alarm_sound');
|
|
139
|
+
}
|
|
140
|
+
if (config.exposeMufflingSwitch) {
|
|
141
|
+
this.configureBooleanSwitch(this.getSchema(...SCHEMA_CODE.MUFFLING), `${this.device.name} Mute`, 'muffling');
|
|
142
|
+
}
|
|
143
|
+
if (config.exposeNotificationSwitches) {
|
|
144
|
+
this.configureBooleanSwitch(this.getSchema(...SCHEMA_CODE.ALARM_CALL), `${this.device.name} Alarm Call`, 'switch_alarm_call');
|
|
145
|
+
this.configureBooleanSwitch(this.getSchema(...SCHEMA_CODE.ALARM_SMS), `${this.device.name} Alarm SMS`, 'switch_alarm_sms');
|
|
146
|
+
this.configureBooleanSwitch(this.getSchema(...SCHEMA_CODE.ALARM_PROPEL), `${this.device.name} App Push`, 'switch_alarm_propel');
|
|
147
|
+
this.configureBooleanSwitch(this.getSchema(...SCHEMA_CODE.LOW_BATTERY_ALERT), `${this.device.name} Low Battery Alert`, 'switch_low_battery');
|
|
148
|
+
this.configureBooleanSwitch(this.getSchema(...SCHEMA_CODE.MODE_DELAY_SOUND), `${this.device.name} Mode Delay Sound`, 'switch_mode_dl_sound');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
configureBooleanSwitch(schema, name, subtype) {
|
|
152
|
+
if (!schema) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const service = this.accessory.getServiceById(this.Service.Switch, subtype)
|
|
156
|
+
|| this.accessory.addService(this.Service.Switch, name, subtype);
|
|
157
|
+
(0, Name_1.configureName)(this, service, name);
|
|
158
|
+
service.getCharacteristic(this.Characteristic.On)
|
|
159
|
+
.onGet(() => {
|
|
160
|
+
this.checkOnlineStatus();
|
|
161
|
+
return !!(this.getStatus(schema.code)?.value ?? false);
|
|
162
|
+
})
|
|
163
|
+
.onSet(async (value) => {
|
|
164
|
+
await this.sendCommands([{ code: schema.code, value: !!value }], true);
|
|
165
|
+
});
|
|
27
166
|
}
|
|
28
167
|
}
|
|
29
168
|
exports.default = SecuritySystemAccessory;
|
|
30
|
-
//# sourceMappingURL=SecuritySystemAccessory.js.map
|
|
169
|
+
//# sourceMappingURL=SecuritySystemAccessory.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-tuya-without-developer-account",
|
|
3
3
|
"displayName": "Tuya without developer account for Homebridge",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.7",
|
|
5
5
|
"description": "Homebridge plugin for Tuya and Smart Life devices using QR cloud authentication without a Tuya IoT developer account.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Kosztyk",
|