homebridge-unifi-access 0.0.1 → 1.1.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.
Files changed (49) hide show
  1. package/LICENSE.md +3 -3
  2. package/README.md +88 -3
  3. package/config.schema.json +202 -0
  4. package/dist/access-controller.d.ts +45 -0
  5. package/dist/access-controller.js +387 -0
  6. package/dist/access-controller.js.map +1 -0
  7. package/dist/access-device.d.ts +53 -0
  8. package/dist/access-device.js +362 -0
  9. package/dist/access-device.js.map +1 -0
  10. package/dist/access-events.d.ts +24 -0
  11. package/dist/access-events.js +151 -0
  12. package/dist/access-events.js.map +1 -0
  13. package/dist/access-hub.d.ts +21 -0
  14. package/dist/access-hub.js +277 -0
  15. package/dist/access-hub.js.map +1 -0
  16. package/dist/access-mqtt.d.ts +20 -0
  17. package/dist/access-mqtt.js +163 -0
  18. package/dist/access-mqtt.js.map +1 -0
  19. package/dist/access-options.d.ts +38 -0
  20. package/dist/access-options.js +167 -0
  21. package/dist/access-options.js.map +1 -0
  22. package/dist/access-platform.d.ts +16 -0
  23. package/dist/access-platform.js +103 -0
  24. package/dist/access-platform.js.map +1 -0
  25. package/dist/access-types.d.ts +11 -0
  26. package/dist/access-types.js +13 -0
  27. package/dist/access-types.js.map +1 -0
  28. package/dist/index.d.ts +3 -0
  29. package/dist/index.js +9 -5
  30. package/dist/index.js.map +1 -1
  31. package/dist/settings.d.ts +10 -0
  32. package/dist/settings.js +23 -10
  33. package/dist/settings.js.map +1 -1
  34. package/homebridge-ui/public/access-featureoptions.mjs +748 -0
  35. package/homebridge-ui/public/index.html +151 -0
  36. package/homebridge-ui/public/lib/featureoptions.mjs +201 -0
  37. package/homebridge-ui/public/ui.mjs +182 -0
  38. package/homebridge-ui/server.js +153 -0
  39. package/package.json +55 -23
  40. package/.eslintrc.json +0 -45
  41. package/dist/platform.js +0 -98
  42. package/dist/platform.js.map +0 -1
  43. package/dist/platformAccessory.js +0 -104
  44. package/dist/platformAccessory.js.map +0 -1
  45. package/src/index.ts +0 -11
  46. package/src/platform.ts +0 -116
  47. package/src/platformAccessory.ts +0 -130
  48. package/src/settings.ts +0 -9
  49. package/tsconfig.json +0 -20
@@ -0,0 +1,167 @@
1
+ /* Copyright(C) 2020-2024, HJD (https://github.com/hjdhjd). All rights reserved.
2
+ *
3
+ * access-options.ts: Feature option and type definitions for UniFi Access.
4
+ */
5
+ import { ACCESS_DEVICE_REMOVAL_DELAY_INTERVAL, ACCESS_DEVICE_UNLOCK_INTERVAL } from "./settings.js";
6
+ // Feature option categories.
7
+ export const featureOptionCategories = [
8
+ { description: "Device feature options.", modelKey: ["all"], name: "Device" },
9
+ { description: "Controller feature options.", modelKey: ["controller"], name: "Controller" },
10
+ { description: "Hub feature options.", modelKey: ["UA Hub"], name: "Hub" },
11
+ { description: "Logging feature options.", modelKey: ["all"], name: "Log" }
12
+ ];
13
+ /* eslint-disable @stylistic/max-len */
14
+ // Individual feature options, broken out by category.
15
+ export const featureOptions = {
16
+ // Controller options.
17
+ "Controller": [
18
+ { default: false, defaultValue: ACCESS_DEVICE_REMOVAL_DELAY_INTERVAL, description: "Delay, in seconds, before removing devices that are no longer detected on the Access controller. By default, devices are added and removed in realtime.", name: "DelayDeviceRemoval" },
19
+ { default: false, description: "Publish all the realtime telemetry received from the Access controller to MQTT.", name: "Publish.Telemetry" }
20
+ ],
21
+ // Device options.
22
+ "Device": [
23
+ { default: true, description: "Make this device available in HomeKit.", name: "" },
24
+ { default: false, description: "Synchronize the UniFi Access name of this device with HomeKit. Synchronization is one-way only, syncing the device name from UniFi Access to HomeKit.", name: "SyncName" }
25
+ ],
26
+ // Hub options.
27
+ "Hub": [
28
+ { default: false, defaultValue: ACCESS_DEVICE_UNLOCK_INTERVAL, description: "Delay, in minutes, before locking the door lock relay, once it's been unlocked by HomeKit. If set to 0, it will remain unlocked indefinitely. By default, the door lock relay will lock five seconds after unlocking.", name: "LockDelayInterval" },
29
+ { default: true, description: "Add a doorbell accessory to handle doorbell ring events in HomeKit.", hasFeature: ["door_bell"], name: "Doorbell" },
30
+ { default: false, description: "Add a switch accessory for automation scenarios to reflect (but not trigger) doorbell ring events on an Access doorbell.", hasFeature: ["door_bell"], name: "Doorbell.Trigger" }
31
+ ],
32
+ // Logging options.
33
+ "Log": [
34
+ { default: true, description: "Log doorbell ring events in Homebridge.", hasFeature: ["door_bell"], name: "Doorbell" },
35
+ { default: true, description: "Log lock events in Homebridge.", modelKey: ["UA Hub"], name: "Lock" }
36
+ ]
37
+ };
38
+ // Utility function to let us know whether a feature option should be enabled or not, traversing the scope hierarchy.
39
+ export function isOptionEnabled(configOptions, controller, device, option = "", defaultReturnValue = true) {
40
+ // There are a couple of ways to enable and disable options. The rules of the road are:
41
+ //
42
+ // 1. Explicitly disabling, or enabling an option on the controller propagates to all the devices that are managed by that controller. Why might you want to do this?
43
+ // Because...
44
+ //
45
+ // 2. Explicitly disabling, or enabling an option on a device by its MAC address will always override the above. This means that it's possible to disable an option for a
46
+ // controller, and all the devices that are managed by it, and then override that behavior on a single device that it's managing.
47
+ // Nothing configured - we assume the default return value.
48
+ if (!configOptions.length) {
49
+ return defaultReturnValue;
50
+ }
51
+ const isOptionSet = (checkOption, checkMac = undefined) => {
52
+ // This regular expression is a bit more intricate than you might think it should be due to the need to ensure we capture values at the very end of the option.
53
+ const optionRegex = new RegExp("^(Enable|Disable)\\." + checkOption + (!checkMac ? "" : "\\." + checkMac.replace(/:/g, "")) + "$", "gi");
54
+ // Get the option value, if we have one.
55
+ for (const entry of configOptions) {
56
+ const regexMatch = optionRegex.exec(entry);
57
+ if (regexMatch) {
58
+ return regexMatch[1].toLowerCase() === "enable";
59
+ }
60
+ }
61
+ return undefined;
62
+ };
63
+ // Escape out our option to ensure we have no inadvertent issues in matching the regular expression.
64
+ option = option.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
65
+ // Check to see if we have a device-level option first.
66
+ let deviceMac = null;
67
+ // Let's figure out if we've been passed a controller configuration or a device one and extract the MAC.
68
+ if (device && ("host" in device)) {
69
+ deviceMac = device.host.mac;
70
+ }
71
+ else {
72
+ deviceMac = device?.mac;
73
+ }
74
+ if (deviceMac) {
75
+ const value = isOptionSet(option, deviceMac);
76
+ if (value !== undefined) {
77
+ return value;
78
+ }
79
+ }
80
+ // Now check to see if we have a controller-level option.
81
+ if (controller?.host?.mac) {
82
+ const value = isOptionSet(option, controller.host.mac);
83
+ if (value !== undefined) {
84
+ return value;
85
+ }
86
+ }
87
+ // Finally, we check for a global-level value.
88
+ const value = isOptionSet(option);
89
+ if (value !== undefined) {
90
+ return value;
91
+ }
92
+ // The option hasn't been set at any scope, return our default value.
93
+ return defaultReturnValue;
94
+ }
95
+ // Utility function to return a value-based feature option for an Access device.
96
+ export function getOptionValue(configOptions, controller, device, option) {
97
+ // Nothing configured - we assume there's nothing.
98
+ if (!configOptions.length || !option) {
99
+ return undefined;
100
+ }
101
+ const getValue = (checkOption, checkMac = undefined) => {
102
+ // This regular expression is a bit more intricate than you might think it should be due to the need to ensure we capture values at the very end of the option.
103
+ const optionRegex = new RegExp("^Enable\\." + checkOption + (!checkMac ? "" : "\\." + checkMac.replace(/:/g, "")) + "\\.([^\\.]+)$", "gi");
104
+ // Get the option value, if we have one.
105
+ for (const entry of configOptions) {
106
+ const regexMatch = optionRegex.exec(entry);
107
+ if (regexMatch) {
108
+ return regexMatch[1];
109
+ }
110
+ }
111
+ return undefined;
112
+ };
113
+ // Escape out our option to ensure we have no inadvertent issues in matching the regular expression.
114
+ option = option.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
115
+ // Check to see if we have a device-level option first.
116
+ let deviceMac = null;
117
+ // Let's figure out if we've been passed a controller configuration or a device one and extract the MAC.
118
+ if (device && ("host" in device)) {
119
+ deviceMac = device.host.mac;
120
+ }
121
+ else {
122
+ deviceMac = device?.mac;
123
+ }
124
+ if (deviceMac) {
125
+ const value = getValue(option, deviceMac);
126
+ if (value !== undefined) {
127
+ return value;
128
+ }
129
+ }
130
+ // Now check to see if we have an controller-level value.
131
+ if (controller?.host?.mac) {
132
+ const value = getValue(option, controller.host.mac);
133
+ if (value) {
134
+ return value;
135
+ }
136
+ }
137
+ // Finally, we check for a global-level value.
138
+ return getValue(option);
139
+ }
140
+ // Utility function to parse and return a numeric configuration parameter.
141
+ function parseOptionNumeric(optionValue, convert) {
142
+ // We don't have the option configured -- we're done.
143
+ if (optionValue === undefined) {
144
+ return undefined;
145
+ }
146
+ // Convert it to a number, if needed.
147
+ const convertedValue = convert(optionValue);
148
+ // Let's validate to make sure it's really a number.
149
+ if (isNaN(convertedValue) || (convertedValue < 0)) {
150
+ return undefined;
151
+ }
152
+ // Return the value.
153
+ return convertedValue;
154
+ }
155
+ // Utility function to return a floating point configuration parameter.
156
+ export function getOptionFloat(optionValue) {
157
+ return parseOptionNumeric(optionValue, (value) => {
158
+ return parseFloat(value);
159
+ });
160
+ }
161
+ // Utility function to return an integer configuration parameter on a device.
162
+ export function getOptionNumber(optionValue) {
163
+ return parseOptionNumeric(optionValue, (value) => {
164
+ return parseInt(value);
165
+ });
166
+ }
167
+ //# sourceMappingURL=access-options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-options.js","sourceRoot":"","sources":["../src/access-options.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { API, DynamicPlatformPlugin, Logging, PlatformAccessory, PlatformConfig } from "homebridge";
2
+ import { AccessOptions } from "./access-options.js";
3
+ export declare class AccessPlatform implements DynamicPlatformPlugin {
4
+ accessories: PlatformAccessory[];
5
+ readonly api: API;
6
+ readonly config: AccessOptions;
7
+ private readonly controllers;
8
+ private featureOptionDefaults;
9
+ readonly featureOptions: string[];
10
+ readonly log: Logging;
11
+ constructor(log: Logging, config: PlatformConfig, api: API);
12
+ configureAccessory(accessory: PlatformAccessory): void;
13
+ private launchControllers;
14
+ featureOptionDefault(option: string): boolean;
15
+ debug(message: string, ...parameters: unknown[]): void;
16
+ }
@@ -0,0 +1,103 @@
1
+ import { featureOptionCategories, featureOptions } from "./access-options.js";
2
+ import { ACCESS_MQTT_TOPIC } from "./settings.js";
3
+ import { AccessController } from "./access-controller.js";
4
+ import util from "node:util";
5
+ export class AccessPlatform {
6
+ accessories;
7
+ api;
8
+ config;
9
+ controllers;
10
+ featureOptionDefaults;
11
+ featureOptions;
12
+ log;
13
+ constructor(log, config, api) {
14
+ this.accessories = [];
15
+ this.api = api;
16
+ this.controllers = [];
17
+ this.featureOptionDefaults = {};
18
+ this.featureOptions = [];
19
+ this.log = log;
20
+ // We can't start without being configured.
21
+ if (!config) {
22
+ return;
23
+ }
24
+ // Plugin options into our config variables.
25
+ this.config = {
26
+ controllers: config.controllers,
27
+ debugAll: false,
28
+ options: config.options,
29
+ ringDelay: config.ringDelay ?? 0
30
+ };
31
+ // We need a UniFi Access controller configured to do anything.
32
+ if (!this.config.controllers) {
33
+ this.log.info("No UniFi Access controllers have been configured.");
34
+ return;
35
+ }
36
+ // Debugging - most people shouldn't enable this.
37
+ this.debug("Debug logging on. Expect a lot of data.");
38
+ // Build our list of default values for our feature options.
39
+ for (const category of featureOptionCategories) {
40
+ for (const options of featureOptions[category.name]) {
41
+ this.featureOptionDefaults[(category.name + (options.name.length ? "." + options.name : "")).toLowerCase()] = options.default;
42
+ }
43
+ }
44
+ // If we have feature options, put them into their own array, lower-cased for future reference.
45
+ if (this.config.options) {
46
+ for (const featureOption of this.config.options) {
47
+ this.featureOptions.push(featureOption.toLowerCase());
48
+ }
49
+ }
50
+ // Loop through each configured NVR and instantiate it.
51
+ for (const controllerConfig of this.config.controllers) {
52
+ // We need an address, or there's nothing to do.
53
+ if (!controllerConfig.address) {
54
+ this.log.info("No host or IP address has been configured.");
55
+ continue;
56
+ }
57
+ // We need login credentials or we're skipping this one.
58
+ if (!controllerConfig.username || !controllerConfig.password) {
59
+ this.log.info("No UniFi Access login credentials have been configured.");
60
+ continue;
61
+ }
62
+ // MQTT topic to use.
63
+ if (!controllerConfig.mqttTopic) {
64
+ controllerConfig.mqttTopic = ACCESS_MQTT_TOPIC;
65
+ }
66
+ this.controllers.push(new AccessController(this, controllerConfig));
67
+ }
68
+ // Avoid a prospective race condition by waiting to configure our controllers until Homebridge is done loading all the cached accessories it knows about, and calling
69
+ // configureAccessory() on each.
70
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
71
+ api.on("didFinishLaunching" /* APIEvent.DID_FINISH_LAUNCHING */, this.launchControllers.bind(this));
72
+ }
73
+ // This gets called when homebridge restores cached accessories at startup. We intentionally avoid doing anything significant here, and save all that logic
74
+ // for device discovery.
75
+ configureAccessory(accessory) {
76
+ // Add this to the accessory array so we can track it.
77
+ this.accessories.push(accessory);
78
+ }
79
+ // Launch our configured controllers once all accessories have been loaded. Once we do, they will sustain themselves.
80
+ launchControllers() {
81
+ // Iterate through all our controllers and startup.
82
+ for (const controller of this.controllers) {
83
+ // Login to the Access controller.
84
+ void controller.login();
85
+ }
86
+ }
87
+ // Utility to return the default value for a feature option.
88
+ featureOptionDefault(option) {
89
+ const defaultValue = this.featureOptionDefaults[option.toLowerCase()];
90
+ // If it's a feature that's unknown to us, assume it's false.
91
+ if (defaultValue === undefined) {
92
+ return false;
93
+ }
94
+ return defaultValue;
95
+ }
96
+ // Utility for debug logging.
97
+ debug(message, ...parameters) {
98
+ if (this.config.debugAll) {
99
+ this.log.info(util.format(message, ...parameters));
100
+ }
101
+ }
102
+ }
103
+ //# sourceMappingURL=access-platform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-platform.js","sourceRoot":"","sources":["../src/access-platform.ts"],"names":[],"mappings":"AAKA,OAAO,EAA0C,uBAAuB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACtH,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,OAAO,cAAc;IAElB,WAAW,CAAsB;IACxB,GAAG,CAAM;IACT,MAAM,CAAiB;IACtB,WAAW,CAAqB;IACzC,qBAAqB,CAA+B;IAC5C,cAAc,CAAW;IACzB,GAAG,CAAU;IAE7B,YAAY,GAAY,EAAE,MAAsB,EAAE,GAAQ;QAExD,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC;QACtB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC;QACtB,IAAI,CAAC,qBAAqB,GAAG,EAAE,CAAC;QAChC,IAAI,CAAC,cAAc,GAAG,EAAE,CAAC;QACzB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QAEf,2CAA2C;QAC3C,IAAG,CAAC,MAAM,EAAE,CAAC;YAEX,OAAO;QACT,CAAC;QAED,4CAA4C;QAC5C,IAAI,CAAC,MAAM,GAAG;YAEZ,WAAW,EAAE,MAAM,CAAC,WAAwC;YAC5D,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE,MAAM,CAAC,OAAmB;YACnC,SAAS,EAAE,MAAM,CAAC,SAAmB,IAAI,CAAC;SAC3C,CAAC;QAEF,+DAA+D;QAC/D,IAAG,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YAE5B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;YACnE,OAAO;QACT,CAAC;QAED,iDAAiD;QACjD,IAAI,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;QAEtD,4DAA4D;QAC5D,KAAI,MAAM,QAAQ,IAAI,uBAAuB,EAAE,CAAC;YAE9C,KAAI,MAAM,OAAO,IAAI,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBAEnD,IAAI,CAAC,qBAAqB,CAAC,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;YAChI,CAAC;QACH,CAAC;QAED,+FAA+F;QAC/F,IAAG,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAEvB,KAAI,MAAM,aAAa,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBAE/C,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,CAAC,CAAC;YACxD,CAAC;QACH,CAAC;QAED,uDAAuD;QACvD,KAAI,MAAM,gBAAgB,IAAI,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YAEtD,gDAAgD;YAChD,IAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC;gBAE7B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;gBAC5D,SAAS;YACX,CAAC;YAED,wDAAwD;YACxD,IAAG,CAAC,gBAAgB,CAAC,QAAQ,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC;gBAE5D,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,yDAAyD,CAAC,CAAC;gBACzE,SAAS;YACX,CAAC;YAED,qBAAqB;YACrB,IAAG,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC;gBAE/B,gBAAgB,CAAC,SAAS,GAAG,iBAAiB,CAAC;YACjD,CAAC;YAED,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC;QACtE,CAAC;QAED,qKAAqK;QACrK,gCAAgC;QAChC,kEAAkE;QAClE,GAAG,CAAC,EAAE,2DAAgC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3E,CAAC;IAED,2JAA2J;IAC3J,wBAAwB;IACjB,kBAAkB,CAAC,SAA4B;QAEpD,sDAAsD;QACtD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACnC,CAAC;IAED,qHAAqH;IAC7G,iBAAiB;QAEvB,mDAAmD;QACnD,KAAI,MAAM,UAAU,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YAEzC,kCAAkC;YAClC,KAAK,UAAU,CAAC,KAAK,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,4DAA4D;IACrD,oBAAoB,CAAC,MAAc;QAExC,MAAM,YAAY,GAAG,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;QAEtE,6DAA6D;QAC7D,IAAG,YAAY,KAAK,SAAS,EAAE,CAAC;YAE9B,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,6BAA6B;IACtB,KAAK,CAAC,OAAe,EAAE,GAAG,UAAqB;QAEpD,IAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YAExB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,11 @@
1
+ export interface AccessLogging {
2
+ debug: (message: string, ...parameters: unknown[]) => void;
3
+ error: (message: string, ...parameters: unknown[]) => void;
4
+ info: (message: string, ...parameters: unknown[]) => void;
5
+ warn: (message: string, ...parameters: unknown[]) => void;
6
+ }
7
+ export declare enum AccessReservedNames {
8
+ SWITCH_DOORBELL_TRIGGER = "DoorbellTrigger",
9
+ SWITCH_MOTION_SENSOR = "MotionSensorSwitch",
10
+ SWITCH_MOTION_TRIGGER = "MotionSensorTrigger"
11
+ }
@@ -0,0 +1,13 @@
1
+ /* Copyright(C) 2020-2024, HJD (https://github.com/hjdhjd). All rights reserved.
2
+ *
3
+ * access-types.ts: Interface and type definitions for UniFi Access.
4
+ */
5
+ // HBUA reserved names.
6
+ export var AccessReservedNames;
7
+ (function (AccessReservedNames) {
8
+ // Manage our switch types.
9
+ AccessReservedNames["SWITCH_DOORBELL_TRIGGER"] = "DoorbellTrigger";
10
+ AccessReservedNames["SWITCH_MOTION_SENSOR"] = "MotionSensorSwitch";
11
+ AccessReservedNames["SWITCH_MOTION_TRIGGER"] = "MotionSensorTrigger";
12
+ })(AccessReservedNames || (AccessReservedNames = {}));
13
+ //# sourceMappingURL=access-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-types.js","sourceRoot":"","sources":["../src/access-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,uBAAuB;AACvB,MAAM,CAAN,IAAY,mBAMX;AAND,WAAY,mBAAmB;IAE7B,2BAA2B;IAC3B,kEAA2C,CAAA;IAC3C,kEAA2C,CAAA;IAC3C,oEAA6C,CAAA;AAC/C,CAAC,EANW,mBAAmB,KAAnB,mBAAmB,QAM9B"}
@@ -0,0 +1,3 @@
1
+ import { API } from "homebridge";
2
+ declare const _default: (api: API) => void;
3
+ export default _default;
package/dist/index.js CHANGED
@@ -1,7 +1,11 @@
1
- "use strict";
2
- const settings_1 = require("./settings");
3
- const platform_1 = require("./platform");
4
- module.exports = (api) => {
5
- api.registerPlatform(settings_1.PLATFORM_NAME, platform_1.ExampleHomebridgePlatform);
1
+ /* Copyright(C) 2017-2024, HJD (https://github.com/hjdhjd). All rights reserved.
2
+ *
3
+ * index.ts: homebridge-unifi-access plugin registration.
4
+ */
5
+ import { PLATFORM_NAME, PLUGIN_NAME } from "./settings.js";
6
+ import { AccessPlatform } from "./access-platform.js";
7
+ // Register our platform with homebridge.
8
+ export default (api) => {
9
+ api.registerPlatform(PLUGIN_NAME, PLATFORM_NAME, AccessPlatform);
6
10
  };
7
11
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,yCAA2C;AAC3C,yCAAuD;AAKvD,iBAAS,CAAC,GAAQ,EAAE,EAAE;IACpB,GAAG,CAAC,gBAAgB,CAAC,wBAAa,EAAE,oCAAyB,CAAC,CAAC;AACjE,CAAC,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE3D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,yCAAyC;AACzC,eAAe,CAAC,GAAQ,EAAQ,EAAE;IAEhC,GAAG,CAAC,gBAAgB,CAAC,WAAW,EAAE,aAAa,EAAE,cAAc,CAAC,CAAC;AACnE,CAAC,CAAC"}
@@ -0,0 +1,10 @@
1
+ export declare const PLUGIN_NAME = "homebridge-unifi-access";
2
+ export declare const PLATFORM_NAME = "UniFi Access";
3
+ export declare const ACCESS_CONTROLLER_REFRESH_INTERVAL = 120;
4
+ export declare const ACCESS_CONTROLLER_RETRY_INTERVAL = 10;
5
+ export declare const ACCESS_DEVICE_REMOVAL_DELAY_INTERVAL = 60;
6
+ export declare const ACCESS_DEVICE_UNLOCK_INTERVAL = 0;
7
+ export declare const ACCESS_MOTION_DURATION = 10;
8
+ export declare const ACCESS_MQTT_RECONNECT_INTERVAL = 60;
9
+ export declare const ACCESS_MQTT_TOPIC = "unifi/access";
10
+ export declare const ACCESS_OCCUPANCY_DURATION = 300;
package/dist/settings.js CHANGED
@@ -1,12 +1,25 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PLUGIN_NAME = exports.PLATFORM_NAME = void 0;
4
- /**
5
- * This is the name of the platform that users will use to register the plugin in the Homebridge config.json
1
+ /* Copyright(C) 2022-2024, HJD (https://github.com/hjdhjd). All rights reserved.
2
+ *
3
+ * settings.ts: Settings and constants for homebridge-unifi-access.
6
4
  */
7
- exports.PLATFORM_NAME = "ExampleHomebridgePlugin";
8
- /**
9
- * This must match the name of your plugin as defined the package.json
10
- */
11
- exports.PLUGIN_NAME = "homebridge-plugin-name";
5
+ // The name of our plugin.
6
+ export const PLUGIN_NAME = "homebridge-unifi-access";
7
+ // The platform the plugin creates.
8
+ export const PLATFORM_NAME = "UniFi Access";
9
+ // How often, in seconds, should we check Access controllers for new or removed devices.
10
+ export const ACCESS_CONTROLLER_REFRESH_INTERVAL = 120;
11
+ // How often, in seconds, should we retry getting our bootstrap configuration from the Access controller.
12
+ export const ACCESS_CONTROLLER_RETRY_INTERVAL = 10;
13
+ // Default delay, in seconds, before removing Access devices that no longer exist.
14
+ export const ACCESS_DEVICE_REMOVAL_DELAY_INTERVAL = 60;
15
+ // Default delay, in minutes, before locking an unlocked door relay.
16
+ export const ACCESS_DEVICE_UNLOCK_INTERVAL = 0;
17
+ // Default duration, in seconds, of motion events. Setting this too low will potentially cause a lot of notification spam.
18
+ export const ACCESS_MOTION_DURATION = 10;
19
+ // How often, in seconds, should we try to reconnect with an MQTT broker, if we have one configured.
20
+ export const ACCESS_MQTT_RECONNECT_INTERVAL = 60;
21
+ // Default MQTT topic to use when publishing events. This is in the form of: unifi/access/MAC/event
22
+ export const ACCESS_MQTT_TOPIC = "unifi/access";
23
+ // Default duration, in seconds, of occupancy events.
24
+ export const ACCESS_OCCUPANCY_DURATION = 300;
12
25
  //# sourceMappingURL=settings.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACU,QAAA,aAAa,GAAG,yBAAyB,CAAC;AAEvD;;GAEG;AACU,QAAA,WAAW,GAAG,wBAAwB,CAAC"}
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,0BAA0B;AAC1B,MAAM,CAAC,MAAM,WAAW,GAAG,yBAAyB,CAAC;AAErD,mCAAmC;AACnC,MAAM,CAAC,MAAM,aAAa,GAAG,cAAc,CAAC;AAE5C,wFAAwF;AACxF,MAAM,CAAC,MAAM,kCAAkC,GAAG,GAAG,CAAC;AAEtD,yGAAyG;AACzG,MAAM,CAAC,MAAM,gCAAgC,GAAG,EAAE,CAAC;AAEnD,kFAAkF;AAClF,MAAM,CAAC,MAAM,oCAAoC,GAAG,EAAE,CAAC;AAEvD,oEAAoE;AACpE,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC;AAE/C,0HAA0H;AAC1H,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAEzC,oGAAoG;AACpG,MAAM,CAAC,MAAM,8BAA8B,GAAG,EAAE,CAAC;AAEjD,mGAAmG;AACnG,MAAM,CAAC,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAEhD,qDAAqD;AACrD,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAC"}