homebridge-tuya-without-developer-account 1.0.11 → 1.0.13
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
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.13
|
|
4
|
+
|
|
5
|
+
- Changed Smart Pet Feeder presentation to use a refined HomeKit `Valve` service as the main **Feed Now** control.
|
|
6
|
+
- `Active` triggers the configured `manual_feed` amount when available, falling back to `quick_feed` when needed.
|
|
7
|
+
- `InUse` reflects `feed_state` so HomeKit can show when the feeder is currently feeding.
|
|
8
|
+
- Kept the dedicated Quick Feed switch, optional Slow Feed switch, Battery service, and Feeding occupancy/status sensor.
|
|
9
|
+
- Removes the old cached Manual Feed switch from feeder accessories when reconfigured, since the Valve now handles manual feeding.
|
|
10
|
+
|
|
11
|
+
## 1.0.12
|
|
12
|
+
|
|
13
|
+
- Stopped exposing `switch_inching` as a HomeKit switch because it is an internal Tuya inching/timer configuration DP, not a user-facing relay.
|
|
14
|
+
- Automatically removes cached `switch_inching` Switch/Outlet services from multi-gang switch accessories when the accessory is reconfigured.
|
|
15
|
+
- Filters hidden/internal switch configuration DPs from switch auto-discovery so they cannot create invalid HomeKit names.
|
|
16
|
+
- Prevents future HAP-NodeJS invalid-name warnings caused by the raw `switch_inching` service after the affected accessory cache is refreshed.
|
|
17
|
+
|
|
3
18
|
## 1.0.11
|
|
4
19
|
|
|
5
20
|
- Fixed custom Homebridge UI initialization so the Adaptive Lighting checkbox stays checked after saving and reopening plugin settings.
|
package/README.md
CHANGED
|
@@ -351,6 +351,6 @@ Version **1.0.5** adds support for DP10-style Tuya dimmer plugs that expose `swi
|
|
|
351
351
|
|
|
352
352
|
## Version 1.0.7 device support
|
|
353
353
|
|
|
354
|
-
This release adds native support for Tuya Smart Pet Feeders and Tuya alarm panels that expose `master_mode`. Pet feeders expose
|
|
354
|
+
This release adds native support for Tuya Smart Pet Feeders and Tuya alarm panels that expose `master_mode`. Pet feeders expose a refined HomeKit Valve-style **Feed Now** control, Quick Feed switch, optional Slow Feed switch, feed-state sensor, and battery when available. Alarm panels are exposed as HomeKit Security System accessories, with optional extra switches controlled through `deviceOverrides[].alarm`.
|
|
355
355
|
|
|
356
356
|
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
|
@@ -222,6 +222,8 @@ They are exposed to HomeKit as a Lightbulb with On and Brightness. After upgradi
|
|
|
222
222
|
|
|
223
223
|
Supported when Tuya exposes one or more of: `quick_feed`, `manual_feed`, `slow_feed`, `feed_state`, `battery_percentage`, `charge_state`.
|
|
224
224
|
|
|
225
|
+
From v1.0.13 the feeder uses a refined HomeKit `Valve` service as the main **Feed Now** control. Activating it sends the configured `manual_feed` amount when available, or falls back to `quick_feed`. `InUse` reflects `feed_state`. The plugin also keeps the Quick Feed switch, optional Slow Feed switch, Feeding occupancy/status sensor, and Battery service.
|
|
226
|
+
|
|
225
227
|
### Alarm / Security System
|
|
226
228
|
|
|
227
229
|
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`.
|
package/config.schema.json
CHANGED
|
@@ -135,12 +135,12 @@
|
|
|
135
135
|
"petFeeder": {
|
|
136
136
|
"type": "object",
|
|
137
137
|
"title": "Smart Pet Feeder Options",
|
|
138
|
-
"description": "Optional settings for Tuya pet feeders. The plugin exposes
|
|
138
|
+
"description": "Optional settings for Tuya pet feeders. The plugin exposes a refined HomeKit Valve-style Feed Now control, Quick Feed switch, optional Slow Feed switch, feeding-state sensor, and battery when supported.",
|
|
139
139
|
"properties": {
|
|
140
140
|
"manualFeedAmount": {
|
|
141
141
|
"type": "integer",
|
|
142
142
|
"title": "Manual feed amount",
|
|
143
|
-
"description": "Portions sent when the
|
|
143
|
+
"description": "Portions sent when the Valve-style Feed Now control is activated.",
|
|
144
144
|
"minimum": 1,
|
|
145
145
|
"maximum": 12,
|
|
146
146
|
"default": 1
|
|
@@ -27,10 +27,92 @@ class PetFeederAccessory extends BaseAccessory_1.default {
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
configureServices() {
|
|
30
|
+
this.configureFeedNowValve();
|
|
30
31
|
this.configureQuickFeed();
|
|
31
|
-
this.configureManualFeed();
|
|
32
32
|
this.configureSlowFeed();
|
|
33
33
|
this.configureFeedState();
|
|
34
|
+
this.removeLegacyManualFeedSwitch();
|
|
35
|
+
}
|
|
36
|
+
isFeeding() {
|
|
37
|
+
const schema = this.getSchema(...SCHEMA_CODE.FEED_STATE);
|
|
38
|
+
if (!schema) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const status = this.getStatus(schema.code);
|
|
42
|
+
if (!status) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const value = status.value;
|
|
46
|
+
if (typeof value === 'boolean') {
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
if (typeof value === 'number') {
|
|
50
|
+
return value > 0;
|
|
51
|
+
}
|
|
52
|
+
const text = String(value ?? '').trim().toLowerCase();
|
|
53
|
+
if (!text) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
return !['0', 'false', 'idle', 'standby', 'normal', 'done', 'finish', 'finished', 'complete', 'completed', 'none', 'ready'].includes(text);
|
|
57
|
+
}
|
|
58
|
+
getFeedNowCommand() {
|
|
59
|
+
const manualSchema = this.getSchema(...SCHEMA_CODE.MANUAL_FEED);
|
|
60
|
+
if (manualSchema) {
|
|
61
|
+
const { manualFeedAmount } = this.getPetFeederConfig();
|
|
62
|
+
return [{ code: manualSchema.code, value: manualFeedAmount }];
|
|
63
|
+
}
|
|
64
|
+
const quickSchema = this.getSchema(...SCHEMA_CODE.QUICK_FEED);
|
|
65
|
+
if (quickSchema) {
|
|
66
|
+
return [{ code: quickSchema.code, value: true }];
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
configureFeedNowValve() {
|
|
71
|
+
const commands = this.getFeedNowCommand();
|
|
72
|
+
if (!commands) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const name = `${this.device?.name || 'Pet Feeder'} Feed Now`;
|
|
76
|
+
const service = this.accessory.getServiceById(this.Service.Valve, 'feed_now')
|
|
77
|
+
|| this.accessory.addService(this.Service.Valve, name, 'feed_now');
|
|
78
|
+
(0, Name_1.configureName)(this, service, name);
|
|
79
|
+
const valveType = this.Characteristic.ValveType.GENERIC_VALVE ?? this.Characteristic.ValveType.IRRIGATION;
|
|
80
|
+
service.setCharacteristic(this.Characteristic.ValveType, valveType);
|
|
81
|
+
const { ACTIVE, INACTIVE } = this.Characteristic.Active;
|
|
82
|
+
const { IN_USE, NOT_IN_USE } = this.Characteristic.InUse;
|
|
83
|
+
service.getCharacteristic(this.Characteristic.Active)
|
|
84
|
+
.onGet(() => {
|
|
85
|
+
this.checkOnlineStatus();
|
|
86
|
+
return this.isFeeding() ? ACTIVE : INACTIVE;
|
|
87
|
+
})
|
|
88
|
+
.onSet(async (value) => {
|
|
89
|
+
this.checkOnlineStatus();
|
|
90
|
+
if (value === ACTIVE) {
|
|
91
|
+
await this.sendCommands(commands, true);
|
|
92
|
+
service.getCharacteristic(this.Characteristic.Active).updateValue(ACTIVE);
|
|
93
|
+
service.getCharacteristic(this.Characteristic.InUse).updateValue(IN_USE);
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
if (!this.isFeeding()) {
|
|
96
|
+
service.getCharacteristic(this.Characteristic.Active).updateValue(INACTIVE);
|
|
97
|
+
service.getCharacteristic(this.Characteristic.InUse).updateValue(NOT_IN_USE);
|
|
98
|
+
}
|
|
99
|
+
}, 2500);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
// Tuya pet feeders usually expose manual_feed/quick_feed as momentary actions,
|
|
103
|
+
// not cancellable feed sessions. Show HomeKit as inactive but do not send a
|
|
104
|
+
// fake cancel command that the device does not support.
|
|
105
|
+
service.getCharacteristic(this.Characteristic.Active).updateValue(INACTIVE);
|
|
106
|
+
if (!this.isFeeding()) {
|
|
107
|
+
service.getCharacteristic(this.Characteristic.InUse).updateValue(NOT_IN_USE);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
service.getCharacteristic(this.Characteristic.InUse)
|
|
112
|
+
.onGet(() => {
|
|
113
|
+
this.checkOnlineStatus();
|
|
114
|
+
return this.isFeeding() ? IN_USE : NOT_IN_USE;
|
|
115
|
+
});
|
|
34
116
|
}
|
|
35
117
|
configureActionSwitch(schema, name, subtype, onSet) {
|
|
36
118
|
if (!schema) {
|
|
@@ -42,7 +124,7 @@ class PetFeederAccessory extends BaseAccessory_1.default {
|
|
|
42
124
|
service.getCharacteristic(this.Characteristic.On)
|
|
43
125
|
.onGet(() => {
|
|
44
126
|
this.checkOnlineStatus();
|
|
45
|
-
// quick_feed
|
|
127
|
+
// quick_feed is a momentary action. Always display it as off.
|
|
46
128
|
return false;
|
|
47
129
|
})
|
|
48
130
|
.onSet(async (value) => {
|
|
@@ -74,13 +156,6 @@ class PetFeederAccessory extends BaseAccessory_1.default {
|
|
|
74
156
|
await this.sendCommands([{ code: schema.code, value: true }], true);
|
|
75
157
|
});
|
|
76
158
|
}
|
|
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);
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
159
|
configureSlowFeed() {
|
|
85
160
|
const schema = this.getSchema(...SCHEMA_CODE.SLOW_FEED);
|
|
86
161
|
const { exposeSlowFeed } = this.getPetFeederConfig();
|
|
@@ -101,10 +176,16 @@ class PetFeederAccessory extends BaseAccessory_1.default {
|
|
|
101
176
|
service.getCharacteristic(this.Characteristic.OccupancyDetected)
|
|
102
177
|
.onGet(() => {
|
|
103
178
|
this.checkOnlineStatus();
|
|
104
|
-
|
|
105
|
-
return value === 'feeding' ? OCCUPANCY_DETECTED : OCCUPANCY_NOT_DETECTED;
|
|
179
|
+
return this.isFeeding() ? OCCUPANCY_DETECTED : OCCUPANCY_NOT_DETECTED;
|
|
106
180
|
});
|
|
107
181
|
}
|
|
182
|
+
removeLegacyManualFeedSwitch() {
|
|
183
|
+
const service = this.accessory.getServiceById(this.Service.Switch, 'manual_feed');
|
|
184
|
+
if (service) {
|
|
185
|
+
this.log.warn(`Removing old pet feeder Manual Feed switch from cache: ${service.displayName}`);
|
|
186
|
+
this.accessory.removeService(service);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
108
189
|
}
|
|
109
190
|
exports.default = PetFeederAccessory;
|
|
110
191
|
//# sourceMappingURL=PetFeederAccessory.js.map
|
|
@@ -20,6 +20,9 @@ const SCHEMA_CODE = {
|
|
|
20
20
|
CURRENT_HUMIDITY: ['va_humidity', 'humidity_value'],
|
|
21
21
|
INCHING: ['switch_inching'],
|
|
22
22
|
};
|
|
23
|
+
const INTERNAL_SWITCH_SCHEMA_CODES = new Set([
|
|
24
|
+
'switch_inching',
|
|
25
|
+
]);
|
|
23
26
|
class SwitchAccessory extends BaseAccessory_1.default {
|
|
24
27
|
requiredSchema() {
|
|
25
28
|
return [SCHEMA_CODE.ON];
|
|
@@ -30,7 +33,9 @@ class SwitchAccessory extends BaseAccessory_1.default {
|
|
|
30
33
|
this.platform.log.warn('Remove old service:', oldService.UUID);
|
|
31
34
|
this.accessory.removeService(oldService);
|
|
32
35
|
}
|
|
33
|
-
const schemata = this.device.schema.filter((schema) => schema.code.startsWith('switch')
|
|
36
|
+
const schemata = this.device.schema.filter((schema) => schema.code.startsWith('switch')
|
|
37
|
+
&& schema.type === TuyaDevice_1.TuyaDeviceSchemaType.Boolean
|
|
38
|
+
&& !INTERNAL_SWITCH_SCHEMA_CODES.has(schema.code));
|
|
34
39
|
this.log.info(`[SwitchAccessory] Found ${schemata.length} switch schemas: ${schemata.map(s => s.code).join(', ')}`);
|
|
35
40
|
// Track which switch services should exist
|
|
36
41
|
const validSubtypes = new Set(schemata.map(s => s.code));
|
|
@@ -38,20 +43,27 @@ class SwitchAccessory extends BaseAccessory_1.default {
|
|
|
38
43
|
// Match both Switch and Outlet UUIDs since OutletAccessory uses Service.Outlet.
|
|
39
44
|
const switchOrOutletUUIDs = new Set([this.Service.Switch.UUID, this.Service.Outlet.UUID]);
|
|
40
45
|
const allSwitchServices = this.accessory.services.filter(s => switchOrOutletUUIDs.has(s.UUID) && s.subtype);
|
|
46
|
+
for (const oldService of [...allSwitchServices]) {
|
|
47
|
+
if (oldService.subtype && INTERNAL_SWITCH_SCHEMA_CODES.has(oldService.subtype)) {
|
|
48
|
+
this.log.warn(`Removing internal Tuya switch config service from cache: ${oldService.displayName} (subtype: ${oldService.subtype})`);
|
|
49
|
+
this.accessory.removeService(oldService);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const activeSwitchServices = this.accessory.services.filter(s => switchOrOutletUUIDs.has(s.UUID) && s.subtype);
|
|
41
53
|
// Check early if we'll be keeping services due to auto-detect or config unchanged
|
|
42
54
|
const configChanged = this.device?.configChanged ?? true;
|
|
43
55
|
const isAutoDetecting = this.device?.isAutoDetecting ?? false;
|
|
44
56
|
const shouldRemoveExtras = configChanged && !isAutoDetecting;
|
|
45
|
-
if (
|
|
57
|
+
if (activeSwitchServices.length > schemata.length) {
|
|
46
58
|
if (shouldRemoveExtras) {
|
|
47
|
-
this.log.warn(`[SwitchAccessory] Found ${
|
|
59
|
+
this.log.warn(`[SwitchAccessory] Found ${activeSwitchServices.length} cached switch services but only ${schemata.length} in schema. Removing extras...`);
|
|
48
60
|
}
|
|
49
61
|
else {
|
|
50
|
-
this.log.info(`[SwitchAccessory] Found ${
|
|
62
|
+
this.log.info(`[SwitchAccessory] Found ${activeSwitchServices.length} cached switch services but only ${schemata.length} in schema. ${isAutoDetecting ? 'Auto-detect in progress' : 'Config unchanged'} – keeping for now...`);
|
|
51
63
|
}
|
|
52
64
|
}
|
|
53
65
|
const keptCachedServices = new Map();
|
|
54
|
-
for (const oldService of
|
|
66
|
+
for (const oldService of activeSwitchServices) {
|
|
55
67
|
if (!validSubtypes.has(oldService.subtype)) {
|
|
56
68
|
if (shouldRemoveExtras) {
|
|
57
69
|
// Config changed and not in auto-detect, so enforce the new schema
|
|
@@ -95,7 +107,7 @@ class SwitchAccessory extends BaseAccessory_1.default {
|
|
|
95
107
|
// Other
|
|
96
108
|
(0, CurrentTemperature_1.configureCurrentTemperature)(this, undefined, this.getSchema(...SCHEMA_CODE.CURRENT_TEMP));
|
|
97
109
|
(0, CurrentRelativeHumidity_1.configureCurrentRelativeHumidity)(this, undefined, this.getSchema(...SCHEMA_CODE.CURRENT_HUMIDITY));
|
|
98
|
-
this.
|
|
110
|
+
this.removeInternalSwitchServices();
|
|
99
111
|
}
|
|
100
112
|
async onDeviceInfoUpdate(info) {
|
|
101
113
|
// Re-run service configuration so newly auto-detected switches get their handlers registered.
|
|
@@ -115,33 +127,16 @@ class SwitchAccessory extends BaseAccessory_1.default {
|
|
|
115
127
|
(0, EnergyUsage_1.configureEnergyUsage)(this.platform.api, this, service, this.getSchema(...SCHEMA_CODE.CURRENT), this.getSchema(...SCHEMA_CODE.POWER), this.getSchema(...SCHEMA_CODE.VOLTAGE), this.getSchema(...SCHEMA_CODE.TOTAL_POWER));
|
|
116
128
|
}
|
|
117
129
|
}
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
service.getCharacteristic(this.Characteristic.On)
|
|
127
|
-
.onGet(() => {
|
|
128
|
-
this.checkOnlineStatus();
|
|
129
|
-
const status = this.getStatus(schema.code);
|
|
130
|
-
const buffer = Buffer.from(status.value, 'base64');
|
|
131
|
-
return (buffer.length === 3) && (buffer[0] === 1);
|
|
132
|
-
})
|
|
133
|
-
.onSet(async (value) => {
|
|
134
|
-
const status = this.getStatus(schema.code);
|
|
135
|
-
let buffer = Buffer.from(status.value, 'base64');
|
|
136
|
-
if (buffer.length !== 3) {
|
|
137
|
-
buffer = Buffer.alloc(3);
|
|
130
|
+
removeInternalSwitchServices() {
|
|
131
|
+
const switchOrOutletUUIDs = new Set([this.Service.Switch.UUID, this.Service.Outlet.UUID]);
|
|
132
|
+
for (const service of [...this.accessory.services]) {
|
|
133
|
+
if (switchOrOutletUUIDs.has(service.UUID)
|
|
134
|
+
&& service.subtype
|
|
135
|
+
&& INTERNAL_SWITCH_SCHEMA_CODES.has(service.subtype)) {
|
|
136
|
+
this.log.warn(`Removing internal Tuya switch config service from cache: ${service.displayName} (subtype: ${service.subtype})`);
|
|
137
|
+
this.accessory.removeService(service);
|
|
138
138
|
}
|
|
139
|
-
|
|
140
|
-
await this.sendCommands([{
|
|
141
|
-
code: schema.code,
|
|
142
|
-
value: buffer.toString('base64'),
|
|
143
|
-
}], true);
|
|
144
|
-
});
|
|
139
|
+
}
|
|
145
140
|
}
|
|
146
141
|
}
|
|
147
142
|
exports.default = SwitchAccessory;
|
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.13",
|
|
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",
|