homebridge-unifi-access 1.9.1 → 1.10.0
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/dist/access-controller.d.ts +7 -6
- package/dist/access-controller.js +96 -126
- package/dist/access-controller.js.map +1 -1
- package/dist/access-device.d.ts +15 -1
- package/dist/access-device.js +17 -42
- package/dist/access-device.js.map +1 -1
- package/dist/access-events.js +21 -33
- package/dist/access-events.js.map +1 -1
- package/dist/access-hub.d.ts +22 -6
- package/dist/access-hub.js +333 -62
- package/dist/access-hub.js.map +1 -1
- package/dist/access-options.d.ts +3 -2
- package/dist/access-options.js +19 -3
- package/dist/access-options.js.map +1 -1
- package/dist/access-platform.d.ts +1 -1
- package/dist/access-platform.js +5 -9
- package/dist/access-platform.js.map +1 -1
- package/dist/access-types.d.ts +9 -0
- package/dist/access-types.js +9 -0
- package/dist/access-types.js.map +1 -1
- package/homebridge-ui/public/index.html +54 -51
- package/homebridge-ui/public/lib/featureoptions.js +7 -8
- package/homebridge-ui/public/lib/featureoptions.js.map +1 -1
- package/homebridge-ui/public/lib/webUi-featureoptions.mjs +3484 -558
- package/homebridge-ui/public/lib/webUi.mjs +13 -5
- package/homebridge-ui/public/ui.mjs +76 -105
- package/package.json +11 -11
package/dist/access-hub.js
CHANGED
|
@@ -1,6 +1,51 @@
|
|
|
1
|
-
|
|
1
|
+
/* Copyright(C) 2019-2025, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
|
+
*
|
|
3
|
+
* access-hub.ts: Unified hub and reader device class for UniFi Access.
|
|
4
|
+
*/
|
|
2
5
|
import { AccessDevice } from "./access-device.js";
|
|
6
|
+
import { acquireService, validService } from "homebridge-plugin-utils";
|
|
3
7
|
import { AccessReservedNames } from "./access-types.js";
|
|
8
|
+
// Access methods available to us for readers.
|
|
9
|
+
const accessMethods = [
|
|
10
|
+
{ capability: "identity_face_unlock", key: "face", name: "Face Unlock", option: "AccessMethod.Face", subtype: AccessReservedNames.SWITCH_ACCESSMETHOD_FACE },
|
|
11
|
+
{ capability: "hand_wave", key: "wave", name: "Hand Wave", option: "AccessMethod.Hand", subtype: AccessReservedNames.SWITCH_ACCESSMETHOD_HAND },
|
|
12
|
+
{ capability: "mobile_unlock_ver2", key: "bt_button", name: "Mobile", option: "AccessMethod.Mobile", subtype: AccessReservedNames.SWITCH_ACCESSMETHOD_MOBILE },
|
|
13
|
+
{ capability: "nfc_card_easy_provision", key: "nfc", name: "NFC", option: "AccessMethod.NFC", subtype: AccessReservedNames.SWITCH_ACCESSMETHOD_NFC },
|
|
14
|
+
{ capability: "pin_code", key: "pin_code", name: "PIN", option: "AccessMethod.PIN", subtype: AccessReservedNames.SWITCH_ACCESSMETHOD_PIN },
|
|
15
|
+
{ capability: "qr_code", key: "qr_code", name: "QR Code", option: "AccessMethod.QR", subtype: AccessReservedNames.SWITCH_ACCESSMETHOD_QR }
|
|
16
|
+
];
|
|
17
|
+
// Define the dry contact inputs we're interested in for Access hubs.
|
|
18
|
+
const sensorInputs = ["Dps", "Rel", "Ren", "Rex"];
|
|
19
|
+
// Define the sensor wiring. It's a bit convoluted because there's a lot of inconsistency at the API level across device types in Access:
|
|
20
|
+
// - For UA-ULTRA, we look at rex_button_mode = proxyMode.
|
|
21
|
+
// - For other models, we look at per-device wiring keys.
|
|
22
|
+
const sensorWiring = {
|
|
23
|
+
Dps: {
|
|
24
|
+
proxyMode: "dps",
|
|
25
|
+
wiring: {
|
|
26
|
+
"UA-Hub-Door-Mini": ["wiring_state_d1-dps-neg", "wiring_state_d1-dps-pos"],
|
|
27
|
+
UAH: ["wiring_state_dps-neg", "wiring_state_dps-pos"],
|
|
28
|
+
UGT: ["wiring_state_gate-dps-neg", "wiring_state_gate-dps-pos"]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
Rel: {
|
|
32
|
+
wiring: {
|
|
33
|
+
UAH: ["wiring_state_rel-neg", "wiring_state_rel-pos"]
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
Ren: {
|
|
37
|
+
wiring: {
|
|
38
|
+
UAH: ["wiring_state_ren-neg", "wiring_state_ren-pos"]
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
Rex: {
|
|
42
|
+
proxyMode: "rex",
|
|
43
|
+
wiring: {
|
|
44
|
+
"UA-Hub-Door-Mini": ["wiring_state_d1-button-neg", "wiring_state_d1-button-pos"],
|
|
45
|
+
UAH: ["wiring_state_rex-neg", "wiring_state_rex-pos"]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
4
49
|
export class AccessHub extends AccessDevice {
|
|
5
50
|
_hkLockState;
|
|
6
51
|
doorbellRingRequestId;
|
|
@@ -24,10 +69,21 @@ export class AccessHub extends AccessDevice {
|
|
|
24
69
|
configureHints() {
|
|
25
70
|
// Configure our parent's hints.
|
|
26
71
|
super.configureHints();
|
|
27
|
-
this.hints.
|
|
72
|
+
this.hints.hasWiringDps = ["UA Ultra", "UA Hub", "UA Hub Door Mini"].includes(this.uda.display_model ?? "") && this.hasFeature("Hub.DPS");
|
|
73
|
+
this.hints.hasWiringRel = ["UA Hub"].includes(this.uda.display_model ?? "") && this.hasFeature("Hub.REL");
|
|
74
|
+
this.hints.hasWiringRen = ["UA Hub"].includes(this.uda.display_model ?? "") && this.hasFeature("Hub.REN");
|
|
75
|
+
this.hints.hasWiringRex = ["UA Ultra", "UA Hub", "UA Hub Door Mini"].includes(this.uda.display_model ?? "") && this.hasFeature("Hub.REX");
|
|
28
76
|
this.hints.logDoorbell = this.hasFeature("Log.Doorbell");
|
|
29
77
|
this.hints.logDps = this.hasFeature("Log.DPS");
|
|
30
78
|
this.hints.logLock = this.hasFeature("Log.Lock");
|
|
79
|
+
this.hints.logRel = this.hasFeature("Log.REL");
|
|
80
|
+
this.hints.logRen = this.hasFeature("Log.REN");
|
|
81
|
+
this.hints.logRex = this.hasFeature("Log.REX");
|
|
82
|
+
// The Ultra has a single terminal input that's selectable between DPS and REX modes. We detect which mode it's operating in, and adjust accordingly. We've
|
|
83
|
+
// over-engineered this a bit for future-proofing.
|
|
84
|
+
if (this.uda.display_model === "UA Ultra") {
|
|
85
|
+
this.checkUltraInputs();
|
|
86
|
+
}
|
|
31
87
|
return true;
|
|
32
88
|
}
|
|
33
89
|
// Initialize and configure the light accessory for HomeKit.
|
|
@@ -45,14 +101,16 @@ export class AccessHub extends AccessDevice {
|
|
|
45
101
|
}
|
|
46
102
|
// Configure accessory information.
|
|
47
103
|
this.configureInfo();
|
|
48
|
-
// Configure
|
|
104
|
+
// Configure access method switches, if we're a reader device.
|
|
105
|
+
this.configureAccessMethodSwitches();
|
|
106
|
+
// Configure the lock, if we're a hub device.
|
|
49
107
|
this.configureLock();
|
|
50
108
|
this.configureLockTrigger();
|
|
51
|
-
// Configure the doorbell.
|
|
109
|
+
// Configure the doorbell, if we have one.
|
|
52
110
|
this.configureDoorbell();
|
|
53
111
|
this.configureDoorbellTrigger();
|
|
54
|
-
// Configure the
|
|
55
|
-
this.
|
|
112
|
+
// Configure the sensors connected to terminal inputs.
|
|
113
|
+
this.configureTerminalInputs();
|
|
56
114
|
// Configure MQTT services.
|
|
57
115
|
this.configureMqtt();
|
|
58
116
|
// Listen for events.
|
|
@@ -61,6 +119,43 @@ export class AccessHub extends AccessDevice {
|
|
|
61
119
|
this.controller.events.on("access.remote_view.change", this.listeners["access.remote_view.change"] = this.eventHandler.bind(this));
|
|
62
120
|
return true;
|
|
63
121
|
}
|
|
122
|
+
// Configure the access method switches for HomeKit.
|
|
123
|
+
configureAccessMethodSwitches() {
|
|
124
|
+
for (const accessMethod of accessMethods) {
|
|
125
|
+
// Validate whether we should have this service enabled.
|
|
126
|
+
if (!validService(this.accessory, this.hap.Service.Switch, this.hasCapability("is_reader") && this.hasCapability(accessMethod.capability) && this.hasFeature(accessMethod.option), accessMethod.subtype)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
// Acquire the service.
|
|
130
|
+
const service = acquireService(this.accessory, this.hap.Service.Switch, this.accessoryName + " " + accessMethod.name, accessMethod.subtype);
|
|
131
|
+
if (!service) {
|
|
132
|
+
this.log.error("Unable to add the %s access method switch.", accessMethod.name);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
// Retrieve the state when requested.
|
|
136
|
+
service.getCharacteristic(this.hap.Characteristic.On).onGet(() => Boolean(this.uda.configs?.find(entry => entry.key === accessMethod.key)?.value === "yes"));
|
|
137
|
+
// Set the state when requested.
|
|
138
|
+
service.getCharacteristic(this.hap.Characteristic.On).onSet(async (value) => {
|
|
139
|
+
const entry = this.uda.configs?.find(entry => entry.key === accessMethod.key);
|
|
140
|
+
let success;
|
|
141
|
+
if (entry) {
|
|
142
|
+
const response = await this.controller.udaApi.retrieve(this.controller.udaApi.getApiEndpoint("device") + "/" + this.id + "/settings", {
|
|
143
|
+
body: JSON.stringify([{ key: entry.key, tag: "open_door_mode", value: value ? "yes" : "no" }]),
|
|
144
|
+
method: "PUT"
|
|
145
|
+
});
|
|
146
|
+
success = this.controller.udaApi.responseOk(response?.statusCode);
|
|
147
|
+
}
|
|
148
|
+
// If we didn't find the configuration entry or we didn't succeed in setting the value, revert our switch state.
|
|
149
|
+
if (!success) {
|
|
150
|
+
this.log.error("Unable to %s the %s access method.", value ? "activate" : "deactivate", accessMethod.name);
|
|
151
|
+
setTimeout(() => service.updateCharacteristic(this.hap.Characteristic.On, !value), 50);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
// Initialize the switch.
|
|
155
|
+
service.updateCharacteristic(this.hap.Characteristic.On, Boolean(this.uda.configs?.find(entry => entry.key === accessMethod.key)?.value === "yes"));
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
64
159
|
// Configure the doorbell service for HomeKit.
|
|
65
160
|
configureDoorbell() {
|
|
66
161
|
// Validate whether we should have this service enabled.
|
|
@@ -68,7 +163,7 @@ export class AccessHub extends AccessDevice {
|
|
|
68
163
|
return false;
|
|
69
164
|
}
|
|
70
165
|
// Acquire the service.
|
|
71
|
-
const service = acquireService(this.
|
|
166
|
+
const service = acquireService(this.accessory, this.hap.Service.Doorbell, this.accessoryName, undefined, () => this.log.info("Enabling the doorbell."));
|
|
72
167
|
if (!service) {
|
|
73
168
|
this.log.error("Unable to add the doorbell.");
|
|
74
169
|
return false;
|
|
@@ -76,33 +171,62 @@ export class AccessHub extends AccessDevice {
|
|
|
76
171
|
service.setPrimaryService(true);
|
|
77
172
|
return true;
|
|
78
173
|
}
|
|
79
|
-
// Configure
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
174
|
+
// Configure our contact sensors for HomeKit. Availability is determined by a combination of hub model, what's been configured on the hub, and feature options.
|
|
175
|
+
configureTerminalInputs() {
|
|
176
|
+
const terminalInputs = [
|
|
177
|
+
{ input: "Dps", label: "Door Position Sensor" },
|
|
178
|
+
{ input: "Rel", label: "Remote Release" },
|
|
179
|
+
{ input: "Ren", label: "Request to Enter Sensor" },
|
|
180
|
+
{ input: "Rex", label: "Request to Exit Sensor" }
|
|
181
|
+
];
|
|
182
|
+
for (const { input, label } of terminalInputs) {
|
|
183
|
+
const hint = ("hasWiring" + input);
|
|
184
|
+
const reservedId = AccessReservedNames[("CONTACT_" + input.toUpperCase())];
|
|
185
|
+
const state = ("hub" + input + "State");
|
|
186
|
+
// Validate whether we should have this service enabled.
|
|
187
|
+
if (!validService(this.accessory, this.hap.Service.ContactSensor, (hasService) => {
|
|
188
|
+
if (!this.hints[hint] && hasService) {
|
|
189
|
+
this.log.info("Disabling the " + label.toLowerCase() + ".");
|
|
190
|
+
}
|
|
191
|
+
return this.hints[hint];
|
|
192
|
+
}, reservedId)) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
// Acquire the service.
|
|
196
|
+
const service = acquireService(this.accessory, this.hap.Service.ContactSensor, this.accessoryName + " " + label, reservedId, () => this.log.info("Enabling the " + label.toLowerCase() + "."));
|
|
197
|
+
if (!service) {
|
|
198
|
+
this.log.error("Unable to add the " + label.toLowerCase() + ".");
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
// Initialize the sensor state.
|
|
202
|
+
service.updateCharacteristic(this.hap.Characteristic.ContactSensorState, this[state]);
|
|
203
|
+
service.updateCharacteristic(this.hap.Characteristic.StatusActive, !!this.uda.is_online);
|
|
204
|
+
// If the hub has tamper indicator capabilities, let's reflect that in HomeKit.
|
|
205
|
+
if (this.hasCapability("tamper_proofing")) {
|
|
206
|
+
const tamperedEntry = this.uda.configs?.find(entry => entry.key === "tamper_event");
|
|
207
|
+
if (tamperedEntry) {
|
|
208
|
+
service.updateCharacteristic(this.hap.Characteristic.StatusTampered, (tamperedEntry.value === "true") ? this.hap.Characteristic.StatusTampered.TAMPERED :
|
|
209
|
+
this.hap.Characteristic.StatusTampered.NOT_TAMPERED);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
90
212
|
}
|
|
91
|
-
// Initialize the light.
|
|
92
|
-
service.updateCharacteristic(this.hap.Characteristic.ContactSensorState, this.hubDpsState);
|
|
93
213
|
return true;
|
|
94
214
|
}
|
|
95
215
|
// Configure the lock for HomeKit.
|
|
96
216
|
configureLock() {
|
|
217
|
+
// Validate whether we should have this service enabled.
|
|
218
|
+
if (!validService(this.accessory, this.hap.Service.LockMechanism, this.hasCapability("is_hub"))) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
97
221
|
// Acquire the service.
|
|
98
|
-
const service = acquireService(this.
|
|
222
|
+
const service = acquireService(this.accessory, this.hap.Service.LockMechanism, this.accessoryName);
|
|
99
223
|
if (!service) {
|
|
100
224
|
this.log.error("Unable to add the lock.");
|
|
101
225
|
return false;
|
|
102
226
|
}
|
|
103
227
|
// Return the lock state.
|
|
104
|
-
service.getCharacteristic(this.hap.Characteristic.LockCurrentState)
|
|
105
|
-
service.getCharacteristic(this.hap.Characteristic.LockTargetState)
|
|
228
|
+
service.getCharacteristic(this.hap.Characteristic.LockCurrentState).onGet(() => this.hkLockState);
|
|
229
|
+
service.getCharacteristic(this.hap.Characteristic.LockTargetState).onSet(async (value) => {
|
|
106
230
|
if (!(await this.hubLockCommand(value === this.hap.Characteristic.LockTargetState.SECURED))) {
|
|
107
231
|
// Revert our target state.
|
|
108
232
|
setTimeout(() => service.updateCharacteristic(this.hap.Characteristic.LockTargetState, !value), 50);
|
|
@@ -124,15 +248,15 @@ export class AccessHub extends AccessDevice {
|
|
|
124
248
|
return false;
|
|
125
249
|
}
|
|
126
250
|
// Acquire the service.
|
|
127
|
-
const service = acquireService(this.
|
|
251
|
+
const service = acquireService(this.accessory, this.hap.Service.Switch, this.accessoryName + " Doorbell Trigger", AccessReservedNames.SWITCH_DOORBELL_TRIGGER, () => this.log.info("Enabling the doorbell automation trigger."));
|
|
128
252
|
if (!service) {
|
|
129
253
|
this.log.error("Unable to add the doorbell automation trigger.");
|
|
130
254
|
return false;
|
|
131
255
|
}
|
|
132
256
|
// Trigger the doorbell.
|
|
133
|
-
service.getCharacteristic(this.hap.Characteristic.On)
|
|
257
|
+
service.getCharacteristic(this.hap.Characteristic.On).onGet(() => this.doorbellRingRequestId !== null);
|
|
134
258
|
// The state isn't really user-triggerable. We have no way, currently, to trigger a ring event on the hub.
|
|
135
|
-
service.getCharacteristic(this.hap.Characteristic.On)
|
|
259
|
+
service.getCharacteristic(this.hap.Characteristic.On).onSet(() => {
|
|
136
260
|
setTimeout(() => service.updateCharacteristic(this.hap.Characteristic.On, this.doorbellRingRequestId !== null), 50);
|
|
137
261
|
});
|
|
138
262
|
// Initialize the switch.
|
|
@@ -143,19 +267,19 @@ export class AccessHub extends AccessDevice {
|
|
|
143
267
|
// Configure a switch to automate lock and unlock events in HomeKit beyond what HomeKit might allow for a lock service that gets treated as a secure service.
|
|
144
268
|
configureLockTrigger() {
|
|
145
269
|
// Validate whether we should have this service enabled.
|
|
146
|
-
if (!validService(this.accessory, this.hap.Service.Switch, this.hasFeature("Hub.Lock.Trigger"), AccessReservedNames.SWITCH_LOCK_TRIGGER)) {
|
|
270
|
+
if (!validService(this.accessory, this.hap.Service.Switch, this.hasCapability("is_hub") && this.hasFeature("Hub.Lock.Trigger"), AccessReservedNames.SWITCH_LOCK_TRIGGER)) {
|
|
147
271
|
return false;
|
|
148
272
|
}
|
|
149
273
|
// Acquire the service.
|
|
150
|
-
const service = acquireService(this.
|
|
274
|
+
const service = acquireService(this.accessory, this.hap.Service.Switch, this.accessoryName + " Lock Trigger", AccessReservedNames.SWITCH_LOCK_TRIGGER, () => this.log.info("Enabling the lock automation trigger."));
|
|
151
275
|
if (!service) {
|
|
152
276
|
this.log.error("Unable to add the lock automation trigger.");
|
|
153
277
|
return false;
|
|
154
278
|
}
|
|
155
279
|
// Trigger the doorbell.
|
|
156
|
-
service.getCharacteristic(this.hap.Characteristic.On)
|
|
280
|
+
service.getCharacteristic(this.hap.Characteristic.On).onGet(() => this.hkLockState !== this.hap.Characteristic.LockCurrentState.SECURED);
|
|
157
281
|
// The state isn't really user-triggerable. We have no way, currently, to trigger a lock or unlock event on the hub.
|
|
158
|
-
service.getCharacteristic(this.hap.Characteristic.On)
|
|
282
|
+
service.getCharacteristic(this.hap.Characteristic.On).onSet(async (value) => {
|
|
159
283
|
// If we are on, we are in an unlocked state. If we are off, we are in a locked state.
|
|
160
284
|
if (!(await this.hubLockCommand(!value))) {
|
|
161
285
|
// Revert our state.
|
|
@@ -218,6 +342,23 @@ export class AccessHub extends AccessDevice {
|
|
|
218
342
|
});
|
|
219
343
|
return true;
|
|
220
344
|
}
|
|
345
|
+
// Check and validate Ultra inputs with what the user has configured in HomeKit.
|
|
346
|
+
checkUltraInputs() {
|
|
347
|
+
for (const input of ["Dps", "Rex"]) {
|
|
348
|
+
const hint = ("hasWiring" + input);
|
|
349
|
+
const mode = input.toLowerCase();
|
|
350
|
+
// Is the mode enabled on the hub?
|
|
351
|
+
const isEnabled = this.uda.extensions?.[0]?.target_config?.some(entry => (entry.config_key === "rex_button_mode") && entry.config_value === mode);
|
|
352
|
+
if (this.hints[hint] && !isEnabled) {
|
|
353
|
+
// The hub has disabled this input.
|
|
354
|
+
this.hints[hint] = false;
|
|
355
|
+
}
|
|
356
|
+
else if (!this.hints[hint] && isEnabled && this.hasFeature("Hub." + input.toUpperCase())) {
|
|
357
|
+
// The hub has the input enabled, and we want it enabled in HomeKit.
|
|
358
|
+
this.hints[hint] = true;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
221
362
|
// Utility function to execute lock and unlock actions on a hub.
|
|
222
363
|
async hubLockCommand(isLocking) {
|
|
223
364
|
const action = isLocking ? "lock" : "unlock";
|
|
@@ -238,16 +379,6 @@ export class AccessHub extends AccessDevice {
|
|
|
238
379
|
}
|
|
239
380
|
return true;
|
|
240
381
|
}
|
|
241
|
-
// Return the current HomeKit DPS state that we are tracking for this hub.
|
|
242
|
-
get hkDpsState() {
|
|
243
|
-
return this.accessory.getService(this.hap.Service.ContactSensor)?.getCharacteristic(this.hap.Characteristic.ContactSensorState).value ??
|
|
244
|
-
this.hap.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED;
|
|
245
|
-
}
|
|
246
|
-
// Set the current HomeKit DPS state for this hub.
|
|
247
|
-
set hkDpsState(value) {
|
|
248
|
-
// Update the state of the contact service.
|
|
249
|
-
this.accessory.getService(this.hap.Service.ContactSensor)?.updateCharacteristic(this.hap.Characteristic.ContactSensorState, value);
|
|
250
|
-
}
|
|
251
382
|
// Return the current HomeKit lock state that we are tracking for this hub.
|
|
252
383
|
get hkLockState() {
|
|
253
384
|
return this._hkLockState;
|
|
@@ -274,7 +405,7 @@ export class AccessHub extends AccessDevice {
|
|
|
274
405
|
// Return the current state of the DPS on the hub.
|
|
275
406
|
get hubDpsState() {
|
|
276
407
|
// If we don't have the wiring connected for the DPS, we report our default closed state.
|
|
277
|
-
if (this.isDpsWired) {
|
|
408
|
+
if (!this.isDpsWired) {
|
|
278
409
|
return this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED;
|
|
279
410
|
}
|
|
280
411
|
let relayType;
|
|
@@ -283,12 +414,15 @@ export class AccessHub extends AccessDevice {
|
|
|
283
414
|
case "UA-ULTRA":
|
|
284
415
|
relayType = "input_d1_dps";
|
|
285
416
|
break;
|
|
417
|
+
case "UGT":
|
|
418
|
+
relayType = "input_gate_dps";
|
|
419
|
+
break;
|
|
286
420
|
default:
|
|
287
421
|
relayType = "input_state_dps";
|
|
288
422
|
break;
|
|
289
423
|
}
|
|
290
424
|
// Return our DPS state. If it's anything other than on, we assume it's open.
|
|
291
|
-
return (this.uda.configs?.find(
|
|
425
|
+
return (this.uda.configs?.find(entry => entry.key === relayType)?.value === "on") ? this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED :
|
|
292
426
|
this.hap.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED;
|
|
293
427
|
}
|
|
294
428
|
// Return the current state of the relay lock on the hub.
|
|
@@ -299,35 +433,109 @@ export class AccessHub extends AccessDevice {
|
|
|
299
433
|
case "UA-ULTRA":
|
|
300
434
|
relayType = "output_d1_lock_relay";
|
|
301
435
|
break;
|
|
436
|
+
case "UGT":
|
|
437
|
+
relayType = "output_oper1_relay";
|
|
438
|
+
break;
|
|
302
439
|
default:
|
|
303
440
|
relayType = "input_state_rly-lock_dry";
|
|
304
441
|
break;
|
|
305
442
|
}
|
|
306
|
-
const lockRelay = this.uda.configs?.find(
|
|
307
|
-
return (
|
|
308
|
-
|
|
443
|
+
const lockRelay = this.uda.configs?.find(entry => entry.key === relayType);
|
|
444
|
+
return (lockRelay?.value === "off") ? this.hap.Characteristic.LockCurrentState.SECURED : this.hap.Characteristic.LockCurrentState.UNSECURED;
|
|
445
|
+
}
|
|
446
|
+
// Return the current state of the REL on the hub.
|
|
447
|
+
get hubRelState() {
|
|
448
|
+
// If we don't have the wiring connected for the REL, we report our default closed state.
|
|
449
|
+
if (!this.isRelWired) {
|
|
450
|
+
return this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED;
|
|
451
|
+
}
|
|
452
|
+
let relayType;
|
|
453
|
+
switch (this.uda.device_type) {
|
|
454
|
+
case "UAH":
|
|
455
|
+
relayType = "input_state_rel";
|
|
456
|
+
break;
|
|
457
|
+
default:
|
|
458
|
+
return this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED;
|
|
459
|
+
}
|
|
460
|
+
// Return our REL state. If it's anything other than on, we assume it's open.
|
|
461
|
+
return (this.uda.configs?.find(relay => relay.key === relayType)?.value === "on") ? this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED :
|
|
462
|
+
this.hap.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED;
|
|
463
|
+
}
|
|
464
|
+
// Return the current state of the REN on the hub.
|
|
465
|
+
get hubRenState() {
|
|
466
|
+
// If we don't have the wiring connected for the REN, we report our default closed state.
|
|
467
|
+
if (!this.isRenWired) {
|
|
468
|
+
return this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED;
|
|
469
|
+
}
|
|
470
|
+
let relayType;
|
|
471
|
+
switch (this.uda.device_type) {
|
|
472
|
+
case "UAH":
|
|
473
|
+
relayType = "input_state_ren";
|
|
474
|
+
break;
|
|
475
|
+
default:
|
|
476
|
+
return this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED;
|
|
477
|
+
}
|
|
478
|
+
// Return our REN state. If it's anything other than on, we assume it's open.
|
|
479
|
+
return (this.uda.configs?.find(relay => relay.key === relayType)?.value === "on") ? this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED :
|
|
480
|
+
this.hap.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED;
|
|
309
481
|
}
|
|
310
|
-
// Return
|
|
311
|
-
get
|
|
312
|
-
|
|
482
|
+
// Return the current state of the REX on the hub.
|
|
483
|
+
get hubRexState() {
|
|
484
|
+
// If we don't have the wiring connected for the REX, we report our default closed state.
|
|
485
|
+
if (!this.isRexWired) {
|
|
486
|
+
return this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED;
|
|
487
|
+
}
|
|
488
|
+
let relayType;
|
|
313
489
|
switch (this.uda.device_type) {
|
|
314
490
|
case "UA-Hub-Door-Mini":
|
|
315
491
|
case "UA-ULTRA":
|
|
316
|
-
|
|
492
|
+
relayType = "input_d1_button";
|
|
317
493
|
break;
|
|
318
|
-
|
|
319
|
-
|
|
494
|
+
case "UAH":
|
|
495
|
+
relayType = "input_state_rex";
|
|
320
496
|
break;
|
|
497
|
+
default:
|
|
498
|
+
return this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED;
|
|
499
|
+
}
|
|
500
|
+
// Return our REX state. If it's anything other than on, we assume it's open.
|
|
501
|
+
return (this.uda.configs?.find(relay => relay.key === relayType)?.value === "on") ? this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED :
|
|
502
|
+
this.hap.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED;
|
|
503
|
+
}
|
|
504
|
+
// Utility to check the wiring state of a given terminal input.
|
|
505
|
+
isWired(input) {
|
|
506
|
+
// UA-ULTRA proxies via button mode.
|
|
507
|
+
if ((this.uda.device_type === "UA-ULTRA") && sensorWiring[input].proxyMode) {
|
|
508
|
+
return this.uda.extensions?.[0]?.target_config?.some(e => e.config_key === "rex_button_mode" && e.config_value === sensorWiring[input].proxyMode) ?? false;
|
|
509
|
+
}
|
|
510
|
+
// Find the wiring keys for this model.
|
|
511
|
+
const wires = sensorWiring[input].wiring?.[this.uda.device_type];
|
|
512
|
+
if (!wires) {
|
|
513
|
+
return false;
|
|
321
514
|
}
|
|
322
|
-
//
|
|
323
|
-
return
|
|
515
|
+
// All wires must be on for us to return true.
|
|
516
|
+
return wires.every(wire => this.uda.configs?.some(e => (e.key === wire) && (e.value === "on")));
|
|
517
|
+
}
|
|
518
|
+
// Utility to retrieve a contact sensor state.
|
|
519
|
+
getContactSensorState(name) {
|
|
520
|
+
return this.accessory.getServiceById(this.hap.Service.ContactSensor, name)?.getCharacteristic(this.hap.Characteristic.ContactSensorState).value ??
|
|
521
|
+
this.hap.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED;
|
|
522
|
+
}
|
|
523
|
+
// Utility to set a contact sensor state.
|
|
524
|
+
setContactSensorState(name, value) {
|
|
525
|
+
this.accessory.getServiceById(this.hap.Service.ContactSensor, name)?.updateCharacteristic(this.hap.Characteristic.ContactSensorState, value);
|
|
324
526
|
}
|
|
325
527
|
// Utility to validate hub capabilities.
|
|
326
528
|
hasCapability(capability) {
|
|
327
|
-
return Array.isArray(capability) ? capability.some(c => this.uda
|
|
529
|
+
return Array.isArray(capability) ? capability.some(c => this.uda.capabilities.includes(c)) : this.uda.capabilities.includes(capability);
|
|
328
530
|
}
|
|
329
531
|
// Handle hub-related events.
|
|
330
532
|
eventHandler(packet) {
|
|
533
|
+
const terminalInputs = [
|
|
534
|
+
{ input: "Dps", label: "Door position sensor", topic: "dps" },
|
|
535
|
+
{ input: "Rel", label: "Remote release", topic: "rel" },
|
|
536
|
+
{ input: "Ren", label: "Request to enter sensor", topic: "ren" },
|
|
537
|
+
{ input: "Rex", label: "Request to exit sensor", topic: "rex" }
|
|
538
|
+
];
|
|
331
539
|
switch (packet.event) {
|
|
332
540
|
case "access.data.device.remote_unlock":
|
|
333
541
|
// Process an Access unlock event.
|
|
@@ -347,15 +555,49 @@ export class AccessHub extends AccessDevice {
|
|
|
347
555
|
this.log.info(this.hkLockState === this.hap.Characteristic.LockCurrentState.SECURED ? "Locked." : "Unlocked.");
|
|
348
556
|
}
|
|
349
557
|
}
|
|
350
|
-
// Process
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
558
|
+
// Process any terminal input update events if our state has changed.
|
|
559
|
+
for (const { input, topic, label } of terminalInputs) {
|
|
560
|
+
const hasKey = ("hasWiring" + input);
|
|
561
|
+
const hkKey = ("hk" + input + "State");
|
|
562
|
+
const hubKey = ("hub" + input + "State");
|
|
563
|
+
const logKey = ("log" + input);
|
|
564
|
+
const wiredKey = ("is" + input + "Wired");
|
|
565
|
+
if (this.hints[hasKey] && this[hubKey] !== this[hkKey]) {
|
|
566
|
+
this[hkKey] = this[hubKey];
|
|
567
|
+
if (this[wiredKey]) {
|
|
568
|
+
const contactDetected = this[hkKey] === this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED;
|
|
569
|
+
this.controller.mqtt?.publish(this.id, topic, contactDetected ? "false" : "true");
|
|
570
|
+
if (this.hints[logKey]) {
|
|
571
|
+
this.log.info(label + " " + (contactDetected ? "closed" : "open") + ".");
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
// Process any changes to terminal input configuration.
|
|
577
|
+
if (packet.data.extensions?.[0]?.target_config && (this.uda.display_model === "UA Ultra")) {
|
|
578
|
+
// Ensure we sync our state with HomeKit.
|
|
579
|
+
this.checkUltraInputs();
|
|
580
|
+
this.configureTerminalInputs();
|
|
581
|
+
}
|
|
582
|
+
// Process any changes to our online status.
|
|
583
|
+
if (packet.data.is_online !== undefined) {
|
|
584
|
+
for (const sensor of Object.keys(AccessReservedNames).filter(key => key.startsWith("CONTACT_"))) {
|
|
585
|
+
this.accessory.getServiceById(this.hap.Service.ContactSensor, AccessReservedNames[sensor])?.
|
|
586
|
+
updateCharacteristic(this.hap.Characteristic.StatusActive, !!packet.data.is_online);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
break;
|
|
590
|
+
case "access.data.v2.device.update":
|
|
591
|
+
if (packet.data.access_method) {
|
|
592
|
+
const accessMethodData = packet.data.access_method;
|
|
593
|
+
// Process access method updates.
|
|
594
|
+
for (const [key, value] of Object.entries(accessMethodData)) {
|
|
595
|
+
const accessMethod = accessMethods.find(entry => entry.key === key);
|
|
596
|
+
if (!accessMethod) {
|
|
597
|
+
continue;
|
|
358
598
|
}
|
|
599
|
+
// Update any access method switches we have enabled with the current value.
|
|
600
|
+
this.accessory.getServiceById(this.hap.Service.Switch, accessMethod.subtype)?.updateCharacteristic(this.hap.Characteristic.On, value === "yes");
|
|
359
601
|
}
|
|
360
602
|
}
|
|
361
603
|
break;
|
|
@@ -394,5 +636,34 @@ export class AccessHub extends AccessDevice {
|
|
|
394
636
|
break;
|
|
395
637
|
}
|
|
396
638
|
}
|
|
639
|
+
// We dynamically define our getters and setters for terminal inputs so we can streamline redundancies. Yes, this is fancy...but it's meant to future-proof a bit
|
|
640
|
+
// against whatever Ubiquiti may do in the future given the inconsistencies in their API implementation for Access across devices of even similar types.
|
|
641
|
+
static {
|
|
642
|
+
// We define the specific sensor input properties we need.
|
|
643
|
+
for (const input of sensorInputs) {
|
|
644
|
+
let propName = "hk" + input + "State";
|
|
645
|
+
const enumKey = "CONTACT_" + input.toUpperCase();
|
|
646
|
+
Object.defineProperty(AccessHub.prototype, propName, {
|
|
647
|
+
configurable: true,
|
|
648
|
+
enumerable: true,
|
|
649
|
+
get() {
|
|
650
|
+
// Delegate to our individual helper functions.
|
|
651
|
+
return this.getContactSensorState(AccessReservedNames[enumKey]);
|
|
652
|
+
},
|
|
653
|
+
set(value) {
|
|
654
|
+
this.setContactSensorState(AccessReservedNames[enumKey], value);
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
// Now define our wiring getters.
|
|
658
|
+
propName = "is" + input + "Wired";
|
|
659
|
+
Object.defineProperty(AccessHub.prototype, propName, {
|
|
660
|
+
configurable: true,
|
|
661
|
+
enumerable: true,
|
|
662
|
+
get() {
|
|
663
|
+
return this.isWired(input);
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
}
|
|
397
668
|
}
|
|
398
669
|
//# sourceMappingURL=access-hub.js.map
|