@mp-consulting/homebridge-unifi-access 1.0.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 (74) hide show
  1. package/.claude/settings.local.json +91 -0
  2. package/CHANGELOG.md +13 -0
  3. package/LICENSE.md +22 -0
  4. package/README.md +159 -0
  5. package/config.schema.json +202 -0
  6. package/dist/access-controller.d.ts +41 -0
  7. package/dist/access-controller.js +342 -0
  8. package/dist/access-controller.js.map +1 -0
  9. package/dist/access-device-catalog.d.ts +43 -0
  10. package/dist/access-device-catalog.js +151 -0
  11. package/dist/access-device-catalog.js.map +1 -0
  12. package/dist/access-device.d.ts +68 -0
  13. package/dist/access-device.js +330 -0
  14. package/dist/access-device.js.map +1 -0
  15. package/dist/access-events.d.ts +27 -0
  16. package/dist/access-events.js +152 -0
  17. package/dist/access-events.js.map +1 -0
  18. package/dist/access-options.d.ts +32 -0
  19. package/dist/access-options.js +65 -0
  20. package/dist/access-options.js.map +1 -0
  21. package/dist/access-platform.d.ts +15 -0
  22. package/dist/access-platform.js +74 -0
  23. package/dist/access-platform.js.map +1 -0
  24. package/dist/access-types.d.ts +30 -0
  25. package/dist/access-types.js +42 -0
  26. package/dist/access-types.js.map +1 -0
  27. package/dist/hub/access-hub-api.d.ts +13 -0
  28. package/dist/hub/access-hub-api.js +140 -0
  29. package/dist/hub/access-hub-api.js.map +1 -0
  30. package/dist/hub/access-hub-events.d.ts +2 -0
  31. package/dist/hub/access-hub-events.js +229 -0
  32. package/dist/hub/access-hub-events.js.map +1 -0
  33. package/dist/hub/access-hub-mqtt.d.ts +2 -0
  34. package/dist/hub/access-hub-mqtt.js +137 -0
  35. package/dist/hub/access-hub-mqtt.js.map +1 -0
  36. package/dist/hub/access-hub-services.d.ts +4 -0
  37. package/dist/hub/access-hub-services.js +451 -0
  38. package/dist/hub/access-hub-services.js.map +1 -0
  39. package/dist/hub/access-hub-types.d.ts +145 -0
  40. package/dist/hub/access-hub-types.js +35 -0
  41. package/dist/hub/access-hub-types.js.map +1 -0
  42. package/dist/hub/access-hub-utils.d.ts +20 -0
  43. package/dist/hub/access-hub-utils.js +128 -0
  44. package/dist/hub/access-hub-utils.js.map +1 -0
  45. package/dist/hub/access-hub.d.ts +39 -0
  46. package/dist/hub/access-hub.js +185 -0
  47. package/dist/hub/access-hub.js.map +1 -0
  48. package/dist/hub/index.d.ts +4 -0
  49. package/dist/hub/index.js +7 -0
  50. package/dist/hub/index.js.map +1 -0
  51. package/dist/index.d.ts +3 -0
  52. package/dist/index.js +11 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/settings.d.ts +16 -0
  55. package/dist/settings.js +49 -0
  56. package/dist/settings.js.map +1 -0
  57. package/docs/FeatureOptions.md +120 -0
  58. package/docs/MQTT.md +116 -0
  59. package/docs/api_reference.pdf +0 -0
  60. package/docs/media/homebridge-unifi-access.png +0 -0
  61. package/docs/media/homebridge-unifi-access.svg +21 -0
  62. package/eslint.config.mjs +99 -0
  63. package/homebridge-ui/public/app.js +104 -0
  64. package/homebridge-ui/public/index.html +267 -0
  65. package/homebridge-ui/public/modules/constants.js +22 -0
  66. package/homebridge-ui/public/modules/controllers.js +202 -0
  67. package/homebridge-ui/public/modules/discovery.js +89 -0
  68. package/homebridge-ui/public/modules/dom-helpers.js +41 -0
  69. package/homebridge-ui/public/modules/feature-options.js +625 -0
  70. package/homebridge-ui/public/modules/state.js +26 -0
  71. package/homebridge-ui/public/styles.css +533 -0
  72. package/homebridge-ui/server.js +374 -0
  73. package/package.json +83 -0
  74. package/scripts/event-schema-monitor.ts +350 -0
@@ -0,0 +1,91 @@
1
+ {
2
+ "includeCoAuthoredBy": false,
3
+ "permissions": {
4
+ "allow": [
5
+ "Bash(npm audit)",
6
+ "Bash(npm install --save-dev homebridge-config-ui-x)",
7
+ "Bash(npm ls homebridge-config-ui-x --depth=0)",
8
+ "Bash(npm run start)",
9
+ "Bash(npm run build)",
10
+ "Bash(npm run lint)",
11
+ "Bash(lsof -i :8581)",
12
+ "Bash(npm install --save-dev homebridge-config-ui-x@^4.6.7)",
13
+ "Bash(find /Users/mickael/Documents/dev/homebridge-unifi-access/src -type f -name \"*.ts\" -exec wc -l {} +)",
14
+ "Bash(kill 77941)",
15
+ "Bash(npm run --workspaces list)",
16
+ "Bash(npm script)",
17
+ "Bash(wc -l src/*.ts)",
18
+ "Bash(npx tsc --noEmit)",
19
+ "Bash(npx eslint src/access-hub.ts src/access-hub-types.ts)",
20
+ "Bash(npm ls -g --depth=0)",
21
+ "Bash(npx eslint src/access-hub.ts src/access-events.ts src/access-controller.ts src/access-types.ts)",
22
+ "Bash(claude mcp add chrome-devtools --scope user npx chrome-devtools-mcp@latest)",
23
+ "Bash(npm root -g)",
24
+ "Bash(mdfind -name \"claude\" -onlyin /usr/local)",
25
+ "Bash(mdfind -name \"claude\" -onlyin /opt)",
26
+ "Bash(ls -la /Users/mickael/.npm/_npx/*/node_modules/.bin/claude)",
27
+ "Bash(ls -la /Users/mickael/.npm/_npx/*/node_modules/@anthropic-ai/claude-code/cli.js)",
28
+ "Bash(npm install -g @anthropic-ai/claude-code)",
29
+ "Bash(npx eslint src/access-hub.ts src/access-events.ts src/access-controller.ts src/access-types.ts src/access-hub-types.ts)",
30
+ "Bash(npm install -D vitest @vitest/coverage-v8)",
31
+ "Bash(sudo chown -R 501:20 \"/Users/mickael/.npm\")",
32
+ "Bash(npm install -D vitest @vitest/coverage-v8 --cache /tmp/npm-cache)",
33
+ "Bash(npm test)",
34
+ "Bash(npx chrome-devtools-mcp@latest --help 2>&1 | head -20)",
35
+ "Bash(source ~/.nvm/nvm.sh && nvm install 22.12.0 && nvm use 22.12.0 && node --version)",
36
+ "Bash(brew upgrade node 2>&1)",
37
+ "Bash(npx vitest run tests/access-device.test.ts)",
38
+ "Bash(npx vitest run)",
39
+ "Bash(npx vitest run tests/access-platform.test.ts)",
40
+ "Bash(npx vitest run tests/access-controller.test.ts tests/access-events.test.ts)",
41
+ "Bash(grep -r \"service\\\\|broadcast\\\\|mdns\\\\|discover\" /Users/mickael/Documents/dev/homebridge-unifi-access/node_modules/unifi-access/dist/*.js 2>/dev/null | head -20)",
42
+ "WebSearch",
43
+ "Bash(npm install bonjour-service 2>&1)",
44
+ "Bash(npx vitest run tests/access-hub.test.ts)",
45
+ "Bash(node -e \"import Bonjour from 'bonjour-service'; console.log\\(typeof Bonjour, Object.keys\\(Bonjour\\)\\)\" 2>&1 || node -e \"const b = await import\\('bonjour-service'\\); console.log\\(Object.keys\\(b\\)\\)\" 2>&1)",
46
+ "Bash(node -e \"\nimport { Bonjour } from 'bonjour-service';\nconst bonjour = new Bonjour\\(\\);\nconst browser = bonjour.find\\({ type: 'http' }\\);\nbrowser.on\\('up', \\(service\\) => {\n const ip = service.addresses?.find\\(a => /^\\\\d+\\\\.\\\\d+\\\\.\\\\d+\\\\.\\\\d+$/.test\\(a\\)\\);\n console.log\\('Found:', service.name, ip, service.host\\);\n}\\);\nsetTimeout\\(\\(\\) => { browser.stop\\(\\); bonjour.destroy\\(\\); console.log\\('Done'\\); process.exit\\(0\\); }, 6000\\);\n\" 2>&1)",
47
+ "Bash(wc -l /Users/mickael/Documents/dev/homebridge-unifi-access/src/*.ts)",
48
+ "Bash(node -e \"\nimport { Bonjour } from 'bonjour-service';\nconst bonjour = new Bonjour\\(\\);\n// Try all common UniFi service types\nconst types = ['unifi', 'ubnt', 'ubiquiti', 'unifi-access'];\ntypes.forEach\\(type => {\n const browser = bonjour.find\\({ type }\\);\n browser.on\\('up', \\(s\\) => console.log\\('Found [' + type + ']:', s.name, s.addresses, s.host\\)\\);\n}\\);\nsetTimeout\\(\\(\\) => { bonjour.destroy\\(\\); console.log\\('Done scanning specific types'\\); process.exit\\(0\\); }, 6000\\);\n\" 2>&1)",
49
+ "Bash(npm search ubiquiti discovery 2>&1 | head -20; echo \"---\"; npm search ubnt discovery 2>&1 | head -10; echo \"---\"; npm search unifi discover 2>&1 | head -10)",
50
+ "Bash(node -e \"\nimport dgram from 'node:dgram';\nconst socket = dgram.createSocket\\({ type: 'udp4', reuseAddr: true }\\);\nconst packet = Buffer.from\\([0x01, 0x00, 0x00, 0x00]\\);\nconst TLV = { MAC_IP: 0x02, FIRMWARE: 0x03, HOSTNAME: 0x0B, MODEL_SHORT: 0x0C, MODEL_LONG: 0x14 };\n\nsocket.on\\('message', \\(msg\\) => {\n if \\(msg.length < 4\\) return;\n const device = {};\n let offset = 4;\n while \\(offset + 3 <= msg.length\\) {\n const type = msg[offset];\n const length = msg.readUInt16BE\\(offset + 1\\);\n offset += 3;\n if \\(offset + length > msg.length\\) break;\n const value = msg.subarray\\(offset, offset + length\\);\n if \\(type === TLV.MAC_IP && length >= 10\\) {\n device.mac = [...value.subarray\\(0, 6\\)].map\\(b => b.toString\\(16\\).padStart\\(2, '0'\\)\\).join\\(':'\\);\n device.ip = value[6] + '.' + value[7] + '.' + value[8] + '.' + value[9];\n }\n if \\(type === TLV.HOSTNAME\\) device.hostname = value.toString\\('utf8'\\);\n if \\(type === TLV.MODEL_SHORT\\) device.model = value.toString\\('utf8'\\);\n if \\(type === TLV.MODEL_LONG\\) device.modelLong = value.toString\\('utf8'\\);\n if \\(type === TLV.FIRMWARE\\) device.firmware = value.toString\\('utf8'\\);\n offset += length;\n }\n console.log\\('Found:', JSON.stringify\\(device\\)\\);\n}\\);\n\nsocket.on\\('error', \\(e\\) => console.error\\('Error:', e.message\\)\\);\n\nsocket.bind\\(\\(\\) => {\n socket.setBroadcast\\(true\\);\n socket.send\\(packet, 0, packet.length, 10001, '255.255.255.255'\\);\n console.log\\('Sent discovery packet...'\\);\n}\\);\n\nsetTimeout\\(\\(\\) => { socket.close\\(\\); console.log\\('Done'\\); process.exit\\(0\\); }, 5000\\);\n\" 2>&1)",
51
+ "Bash(npm uninstall bonjour-service 2>&1)",
52
+ "Bash(grep \"bonjour\" /Users/mickael/Documents/dev/homebridge-unifi-access/package.json; echo \"exit: $?\")",
53
+ "Bash(node -e \"\nimport dgram from 'node:dgram';\nconst socket = dgram.createSocket\\({ type: 'udp4', reuseAddr: true }\\);\nconst packet = Buffer.from\\([0x01, 0x00, 0x00, 0x00]\\);\nconst TLV = { MAC_IP: 0x02, FIRMWARE: 0x03, HOSTNAME: 0x0B, MODEL_SHORT: 0x0C, MODEL_LONG: 0x14 };\n\nsocket.on\\('message', \\(msg, rinfo\\) => {\n if \\(msg.length < 4\\) return;\n const device = {};\n let offset = 4;\n while \\(offset + 3 <= msg.length\\) {\n const type = msg[offset];\n const length = msg.readUInt16BE\\(offset + 1\\);\n offset += 3;\n if \\(offset + length > msg.length\\) break;\n const value = msg.subarray\\(offset, offset + length\\);\n if \\(type === TLV.MAC_IP && length >= 10\\) {\n device.ip = value[6] + '.' + value[7] + '.' + value[8] + '.' + value[9];\n }\n if \\(type === TLV.HOSTNAME\\) device.hostname = value.toString\\('utf8'\\);\n if \\(type === TLV.MODEL_SHORT\\) device.model = value.toString\\('utf8'\\);\n offset += length;\n }\n console.log\\('Response from', rinfo.address, ':', JSON.stringify\\(device\\)\\);\n}\\);\n\nsocket.bind\\(\\(\\) => {\n // Try direct probe and subnet broadcast\n socket.setBroadcast\\(true\\);\n socket.send\\(packet, 0, packet.length, 10001, '192.168.1.119'\\);\n socket.send\\(packet, 0, packet.length, 10001, '192.168.1.255'\\);\n console.log\\('Sent to 192.168.1.119 and 192.168.1.255...'\\);\n}\\);\n\nsetTimeout\\(\\(\\) => { socket.close\\(\\); console.log\\('Done'\\); process.exit\\(0\\); }, 5000\\);\n\" 2>&1)",
54
+ "Bash(node -e \"console.log\\(process.version\\)\")",
55
+ "Bash(npx eslint --fix homebridge-ui/server.js)",
56
+ "Bash(echo \"EXIT: $?\")",
57
+ "Bash(wc -l /Users/mickael/Documents/dev/homebridge-unifi-access/homebridge-ui/public/*.js /Users/mickael/Documents/dev/homebridge-unifi-access/homebridge-ui/public/*.mjs /Users/mickael/Documents/dev/homebridge-unifi-access/homebridge-ui/server.js)",
58
+ "Bash(grep -l \"homebridge-ui\\\\|public/\" /Users/mickael/Documents/dev/homebridge-unifi-access/tests/*.ts)",
59
+ "Bash(npx eslint --fix eslint.config.mjs src homebridge-ui/*.js homebridge-ui/public/*.js homebridge-ui/public/modules/*.js)",
60
+ "Bash(npx tsc --noEmit --skipLibCheck)",
61
+ "Bash(npx tsc --noEmit --skipLibCheck --exclude src/hub)",
62
+ "Bash(ls -la /Users/mickael/Documents/dev/homebridge-unifi-access/src/access-hub*)",
63
+ "Bash(grep -rn \"access-hub\" /Users/mickael/Documents/dev/homebridge-unifi-access/src/*.ts)",
64
+ "Bash(grep -rn \"AccessHub\" /Users/mickael/Documents/dev/homebridge-unifi-access/src/*.ts)",
65
+ "Bash(npx vitest run tests/access-event-schemas.test.ts)",
66
+ "Bash(npx tsc --noEmit --esModuleInterop --module ES2022 --moduleResolution node --target ES2023 --strict scripts/event-schema-monitor.ts)",
67
+ "Bash(npx tsx --version)",
68
+ "Bash(brew install poppler)",
69
+ "Bash(git -C /Users/mickael/Documents/dev/homebridge-unifi-access status)",
70
+ "Bash(git -C /Users/mickael/Documents/dev/homebridge-unifi-access diff --stat)",
71
+ "Bash(git -C /Users/mickael/Documents/dev/homebridge-unifi-access log --oneline -5)",
72
+ "Bash(git add .github/ .gitignore .npmignore LICENSE.md README.md config.schema.json docs/ eslint.config.mjs homebridge-ui/ nodemon.json package-lock.json package.json scripts/ src/ tests/ tsconfig.json vitest.config.ts)",
73
+ "Bash(git -C:*)",
74
+ "Bash(git branch -M main)",
75
+ "Bash(git remote add origin https://github.com/mp-consulting/homebridge-unifi-access.git)",
76
+ "Bash(git push -u origin main)",
77
+ "Bash(gh repo create mp-consulting/homebridge-unifi-access --public --description \"Complete HomeKit support for UniFi Access devices — locks, doorbells, sensors, and readers — with automatic device discovery and realtime events.\" --source . --push)",
78
+ "Bash(gh repo view mp-consulting/homebridge-unifi-access --json isEmpty,defaultBranchRef)",
79
+ "Bash(while read f)",
80
+ "Bash(do sed -i '' 's|Copyright\\(C\\) [0-9]\\\\{4\\\\}-2026, HJD \\(https://github.com/hjdhjd\\). All rights reserved.|Copyright\\(C\\) 2026, Mickael Palma. All rights reserved.|' \"$f\")",
81
+ "Bash(done)",
82
+ "Bash(python3 -c \"\nimport io, sys\n\n# Try to extract text using built-in approach\nwith open\\('/Users/mickael/Documents/dev/homebridge-unifi-access/docs/api_reference.pdf', 'rb'\\) as f:\n content = f.read\\(\\)\n # Look for text streams in the PDF\n text_parts = []\n i = 0\n while i < len\\(content\\):\n # Find text between BT and ET markers\n bt = content.find\\(b'BT', i\\)\n if bt == -1:\n break\n et = content.find\\(b'ET', bt\\)\n if et == -1:\n break\n segment = content[bt:et+2]\n # Extract text from Tj and TJ operators\n import re\n matches = re.findall\\(rb'\\\\\\(\\([^\\)]*\\)\\\\\\)', segment\\)\n for m in matches:\n try:\n text_parts.append\\(m.decode\\('utf-8', errors='replace'\\)\\)\n except:\n pass\n i = et + 2\n print\\('\\\\\\\\n'.join\\(text_parts[:500]\\)\\)\n\" 2>&1 | head -200)",
83
+ "Bash(git commit:*)",
84
+ "Bash(git add CHANGELOG.md)",
85
+ "Bash(npx eslint .)",
86
+ "Bash(git add tests/access-event-schemas.test.ts)",
87
+ "Bash(npx eslint --no-warn-ignored src/ tests/ homebridge-ui/ scripts/ eslint.config.mjs)",
88
+ "Bash(git add eslint.config.mjs homebridge-ui/public/modules/feature-options.js)"
89
+ ]
90
+ }
91
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0 (2026-02-28)
4
+
5
+ ### Initial Release
6
+
7
+ - Full HomeKit support for UniFi Access devices: locks, doorbells, door position sensors, terminal inputs (REL, REN, REX), and access method switches.
8
+ - Automatic device discovery and realtime event handling via the UniFi Access events API.
9
+ - Support for UA Hub, UA Hub Door Mini, UA Ultra, and UA Gate (including side door / pedestrian gate).
10
+ - Access method switches for Face, Hand Wave, Mobile, NFC, PIN, QR, and Touch Pass.
11
+ - Feature options system for granular per-device and per-controller control.
12
+ - MQTT support for publishing and subscribing to device events.
13
+ - Homebridge webUI plugin with controller discovery, setup wizard, and feature options editor.
package/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+ ===========
3
+
4
+ Copyright (c) 2026 Mickael Palma
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,159 @@
1
+ <div align="center">
2
+
3
+ ![homebridge-unifi-access: Native HomeKit support for UniFi Access](docs/media/homebridge-unifi-access.svg)
4
+
5
+ # Homebridge UniFi Access
6
+
7
+ [![Downloads](https://img.shields.io/npm/dt/@mp-consulting/homebridge-unifi-access?color=%230559C9&logo=icloud&logoColor=%23FFFFFF&style=for-the-badge)](https://www.npmjs.com/package/@mp-consulting/homebridge-unifi-access)
8
+ [![Version](https://img.shields.io/npm/v/@mp-consulting/homebridge-unifi-access?color=%230559C9&label=Latest%20Version&logo=ubiquiti&logoColor=%23FFFFFF&style=for-the-badge)](https://www.npmjs.com/package/@mp-consulting/homebridge-unifi-access)
9
+ [![verified-by-homebridge](https://img.shields.io/badge/homebridge-verified-blueviolet?color=%23491F59&style=for-the-badge&logoColor=%23FFFFFF&logo=homebridge)](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
10
+ [![License](https://img.shields.io/npm/l/@mp-consulting/homebridge-unifi-access?color=%230559C9&logo=open%20source%20initiative&logoColor=%23FFFFFF&style=for-the-badge)](LICENSE.md)
11
+
12
+ Complete HomeKit support for the [UniFi Access](https://ui.com/door-access) ecosystem using [Homebridge](https://homebridge.io).
13
+
14
+ </div>
15
+
16
+ ## Table of Contents
17
+
18
+ - [About](#about)
19
+ - [Supported Devices](#supported-devices)
20
+ - [Features](#features)
21
+ - [Prerequisites](#prerequisites)
22
+ - [Installation](#installation)
23
+ - [Configuration](#configuration)
24
+ - [Documentation](#documentation)
25
+ - [Contributing](#contributing)
26
+ - [License](#license)
27
+
28
+ ## About
29
+
30
+ `@mp-consulting/homebridge-unifi-access` is a [Homebridge](https://homebridge.io) plugin that provides HomeKit support for [Ubiquiti's](https://www.ui.com) [UniFi Access](https://ui.com/door-access) door access security platform — including doorbell, reader, lock, and controller hardware.
31
+
32
+ This plugin discovers all your supported UniFi Access devices and makes them available in HomeKit with minimal configuration. It supports all known UniFi Access controller configurations (UniFi CloudKey Gen2+, UniFi Dream Machine Pro/SE, UniFi NVR, etc.) and uses the native UniFi Access events API for realtime event capturing.
33
+
34
+ ## Supported Devices
35
+
36
+ | Device | Capabilities |
37
+ |--------|-------------|
38
+ | **UA Hub** | Lock, doorbell, door position sensor (DPS), terminal inputs (REL, REN, REX) |
39
+ | **UA Hub Door Mini** | Lock, door position sensor (DPS), request to exit sensor (REX) |
40
+ | **UA Ultra** | Lock, door position sensor (DPS), request to exit sensor (REX) |
41
+ | **UA Gate** | Main gate (lock or garage door opener), side door (pedestrian gate) with separate lock, door position sensors for both doors |
42
+ | **UA Readers** | Access method switches for Face, Hand Wave, Mobile, NFC, PIN, and QR |
43
+
44
+ ## Features
45
+
46
+ - **Easy configuration** - provide your controller IP address, username, and password to get started
47
+ - **Full HomeKit support** - locks, doorbells, door position sensors, terminal inputs, access method switches, and automation accelerators
48
+ - **Multiple controllers** - seamlessly integrate several UniFi Access controllers into HomeKit
49
+ - **Realtime device detection** - automatically adds and removes devices in HomeKit as they change on your controller, without restarting Homebridge
50
+ - **Customizable** - [feature options](docs/FeatureOptions.md) let you show/hide specific devices and tailor behavior via the built-in webUI
51
+ - **MQTT support** - publish events to an [MQTT broker](docs/MQTT.md) for further automation
52
+
53
+ ## Prerequisites
54
+
55
+ - [Homebridge](https://homebridge.io) >= 1.8.0
56
+ - Node.js >= 18
57
+ - A UniFi Access controller running the **latest stable firmware**
58
+ - A local user account on the controller (recommended over Ubiquiti cloud credentials; 2FA is not supported)
59
+
60
+ ## Installation
61
+
62
+ ### Via Homebridge UI (recommended)
63
+
64
+ 1. Open the Homebridge UI
65
+ 2. Go to the **Plugins** tab
66
+ 3. Search for `@mp-consulting/homebridge-unifi-access`
67
+ 4. Click **Install**
68
+
69
+ ### Via CLI
70
+
71
+ ```sh
72
+ npm install -g @mp-consulting/homebridge-unifi-access
73
+ ```
74
+
75
+ ## Configuration
76
+
77
+ The recommended way to configure the plugin is through the [Homebridge UI](https://github.com/homebridge/homebridge-config-ui-x). For manual configuration, add the following to the `platforms` array in your Homebridge `config.json`:
78
+
79
+ ```json
80
+ {
81
+ "platform": "UniFi Access",
82
+ "name": "UniFi Access",
83
+ "controllers": [
84
+ {
85
+ "address": "1.2.3.4",
86
+ "username": "homebridge",
87
+ "password": "your-password-here"
88
+ }
89
+ ]
90
+ }
91
+ ```
92
+
93
+ > A sample configuration file is available at [`config.sample.json`](tests/fixtures/config.sample.json).
94
+
95
+ ### Optional Settings
96
+
97
+ | Setting | Description |
98
+ |---------|-------------|
99
+ | `controllers[].name` | Custom name for the controller (used in logs) |
100
+ | `controllers[].mqttUrl` | MQTT broker URL (e.g. `mqtt://1.2.3.4`) |
101
+ | `controllers[].mqttTopic` | MQTT base topic (default: `unifi/access`) |
102
+ | `options` | Array of [feature options](docs/FeatureOptions.md) for granular control |
103
+ | `ringDelay` | Delay in seconds between doorbell rings (default: `0`) |
104
+
105
+ ## Documentation
106
+
107
+ - [Feature Options](docs/FeatureOptions.md) - show/hide devices and customize behavior
108
+ - [MQTT](docs/MQTT.md) - configure MQTT event publishing
109
+ - [UniFi Access API](https://www.npmjs.com/package/unifi-access) - native API library
110
+ - [Changelog](CHANGELOG.md) - release notes and version history
111
+
112
+ ## Contributing
113
+
114
+ ```sh
115
+ # Install dependencies
116
+ npm install
117
+
118
+ # Build
119
+ npm run build
120
+
121
+ # Run linter
122
+ npm run lint
123
+
124
+ # Run tests
125
+ npm test
126
+
127
+ # Start in dev mode with live reload
128
+ npm run watch
129
+ ```
130
+
131
+ ### Event Schema Monitor
132
+
133
+ A live monitoring script is included to detect UniFi Access API changes across firmware updates. It connects to a controller, listens to real-time events, and validates each message against known schemas — reporting any new fields, missing fields, or type changes.
134
+
135
+ ```sh
136
+ # Uses credentials from tests/hbConfig/config.json
137
+ npm run monitor:events
138
+
139
+ # Or point to a different Homebridge config
140
+ npm run monitor:events -- --config /path/to/config.json
141
+
142
+ # Or specify credentials directly
143
+ npm run monitor:events -- --address 192.168.1.1 --username admin --password secret
144
+ ```
145
+
146
+ Sample output:
147
+
148
+ ```
149
+ 2026-02-28T23:02:09.004Z OK DEVICE_REMOTE_UNLOCK event_object_id=6c63f8431750
150
+ 2026-02-28T23:02:09.007Z OK DEVICE_UPDATE_V2 event_object_id=3de83e52-...
151
+ 2026-02-28T23:02:09.008Z MISMATCH LOCATION_UPDATE event_object_id=c87d8d81-...
152
+ + data.new_field: unexpected_field — Type: string
153
+ ```
154
+
155
+ When mismatches are found, update the schemas in `tests/event-schemas.ts` (the single source of truth) and the type definitions in `src/hub/access-hub-types.ts` to match the new API.
156
+
157
+ ## License
158
+
159
+ [MIT](LICENSE.md)
@@ -0,0 +1,202 @@
1
+ {
2
+ "pluginAlias": "UniFi Access",
3
+ "pluginType": "platform",
4
+ "headerDisplay": "Full HomeKit support UniFi Access devices. See the [homebridge-unifi-access](https://github.com/mp-consulting/homebridge-unifi-access) developer page for detailed documentation.",
5
+ "singular": true,
6
+ "customUi": true,
7
+
8
+ "schema": {
9
+ "type": "object",
10
+ "properties": {
11
+
12
+ "controllers": {
13
+ "type": "array",
14
+ "title": "UniFi Access Controllers",
15
+
16
+ "items": {
17
+ "type": "object",
18
+ "title": "UniFi Access Controller",
19
+ "properties": {
20
+
21
+ "address": {
22
+ "type": "string",
23
+ "title": "Controller Address",
24
+ "required": true,
25
+ "format": "hostname",
26
+ "placeholder": "e.g. 1.2.3.4",
27
+ "description": "Hostname or IP address of your UniFi Access controller."
28
+ },
29
+
30
+ "mqttTopic": {
31
+ "type": "string",
32
+ "title": "MQTT Base Topic",
33
+ "required": false,
34
+ "placeholder": "e.g. unifi/access",
35
+ "description": "The base MQTT topic to publish to. Default: unifi/access."
36
+ },
37
+
38
+ "mqttUrl": {
39
+ "type": "string",
40
+ "title": "MQTT Broker URL",
41
+ "required": false,
42
+ "format": "uri",
43
+ "placeholder": "e.g. mqtt://1.2.3.4",
44
+ "description": "URL for the MQTT broker you'd like to publish event messages to. Default: None."
45
+ },
46
+
47
+ "name": {
48
+ "type": "string",
49
+ "title": "Controller Name",
50
+ "required": false,
51
+ "placeholder": "e.g. UNVR",
52
+ "description": "Name for this UniFi Access controller to be used for logging purposes. Default: Defined by the controller."
53
+ },
54
+
55
+ "password": {
56
+ "type": "string",
57
+ "title": "Controller Password",
58
+ "required": true,
59
+ "placeholder": "e.g. unifi-access-password",
60
+ "description": "UniFi Access password for this controller. Creating a homebridge-specific local user is strongly encouraged for security and sanity."
61
+ },
62
+
63
+ "username": {
64
+ "type": "string",
65
+ "title": "Controller Username",
66
+ "required": true,
67
+ "placeholder": "e.g. some-unifi-access-user",
68
+ "description": "UniFi Access username for this controller. Creating a homebridge-specific local user is strongly encouraged for security and sanity."
69
+ }
70
+ }
71
+ }
72
+ },
73
+
74
+ "name": {
75
+
76
+ "type": "string",
77
+ "title": "Plugin Name",
78
+ "required": true,
79
+ "default": "UniFi Access",
80
+ "description": "Name to use for Homebridge logging purposes. Default: UniFi Access."
81
+ },
82
+
83
+ "options": {
84
+
85
+ "type": "array",
86
+ "title": "Feature Options",
87
+
88
+ "items": {
89
+ "type": "string",
90
+ "title": "Feature Option",
91
+ "required": false,
92
+ "description": "Enter only one option per entry. See the plugin documentation for the complete list of available options or use the feature options webUI tab above.",
93
+ "placeholder": "e.g. Disable.Device"
94
+ }
95
+ },
96
+
97
+ "ringDelay": {
98
+
99
+ "type": "integer",
100
+ "title": "Doorbell Ring Delay (seconds)",
101
+ "required": false,
102
+ "minimum": 0,
103
+ "maximum": 60,
104
+ "placeholder": "e.g. 5",
105
+ "description": "Delay between doorbell rings. Setting this to a non-zero value will prevent multiple rings of a doorbell over the specified duration. Default: 0."
106
+ }
107
+ }
108
+ },
109
+
110
+ "layout": [
111
+ {
112
+ "type": "section",
113
+ "title": "UniFi Access Controllers",
114
+ "expandable": true,
115
+ "expanded": false,
116
+ "items": [
117
+ {
118
+
119
+ "key": "controllers",
120
+ "type": "array",
121
+ "name": " ",
122
+ "description": "Provide the IP address and login details of your UniFi Access controllers.",
123
+ "orderable": false,
124
+ "buttonText": "Add UniFi Access Controller",
125
+ "items": [
126
+
127
+ "controllers[].address",
128
+ "controllers[].username",
129
+ "controllers[].password",
130
+
131
+ {
132
+ "key": "controllers[]",
133
+ "type": "section",
134
+ "title": "Optional Settings",
135
+ "expandable": true,
136
+ "expanded": false,
137
+ "items": [
138
+ {
139
+ "description": "These settings are optional. The defaults work well for almost everyone.",
140
+ "items": [
141
+ "controllers[].name"
142
+ ]
143
+ }
144
+ ]
145
+ },
146
+ {
147
+ "key": "controllers[]",
148
+ "type": "section",
149
+ "title": "MQTT Settings",
150
+ "expandable": true,
151
+ "expanded": false,
152
+ "items": [
153
+ {
154
+ "description": "MQTT support will only be enabled if an MQTT broker URL is specified below.",
155
+ "items": [
156
+ "controllers[].mqttUrl",
157
+ "controllers[].mqttTopic"
158
+ ]
159
+ }
160
+ ]
161
+ }
162
+ ]
163
+ }
164
+ ]
165
+ },
166
+
167
+ {
168
+ "type": "section",
169
+ "title": "Plugin Feature Options (Optional)",
170
+ "expandable": true,
171
+ "expanded": false,
172
+ "items": [
173
+ {
174
+ "key": "options",
175
+ "type": "array",
176
+ "name": " ",
177
+ "description": "Feature options allow you to further customize the behavior of this plugin such as the ability to show or hide devices. You can use the the feature options tab above (recommended) to customize these settings or enter them manually below.",
178
+ "orderable": false,
179
+ "buttonText": "Add Feature Option",
180
+ "items": [
181
+ "options[]"
182
+ ]
183
+ }
184
+ ]
185
+ },
186
+
187
+ {
188
+ "type": "section",
189
+ "title": "Additional Settings (Optional)",
190
+ "expandable": true,
191
+ "expanded": false,
192
+ "items": [
193
+ {
194
+ "description": "These settings should be rarely used or needed by most people. Use these with caution.",
195
+ "items": [
196
+ "name"
197
+ ]
198
+ }
199
+ ]
200
+ }
201
+ ]
202
+ }
@@ -0,0 +1,41 @@
1
+ import type { PlatformAccessory } from "homebridge";
2
+ import { AccessApi, type AccessControllerConfig, type AccessDeviceConfig } from "unifi-access";
3
+ import { type HomebridgePluginLogging, MqttClient, type Nullable } from "homebridge-plugin-utils";
4
+ import type { AccessControllerOptions } from "./access-options.js";
5
+ import type { AccessDevice } from "./access-device.js";
6
+ import { AccessEvents } from "./access-events.js";
7
+ import type { AccessPlatform } from "./access-platform.js";
8
+ export declare class AccessController {
9
+ private api;
10
+ config: AccessControllerOptions;
11
+ private deviceRemovalQueue;
12
+ readonly configuredDevices: Record<string, AccessDevice | undefined>;
13
+ events: AccessEvents;
14
+ private hap;
15
+ logApiErrors: boolean;
16
+ readonly log: HomebridgePluginLogging;
17
+ mqtt: MqttClient | null;
18
+ private name;
19
+ platform: AccessPlatform;
20
+ uda: AccessControllerConfig;
21
+ udaApi: AccessApi;
22
+ private bootstrapRefreshTimer;
23
+ private unsupportedDevices;
24
+ constructor(platform: AccessPlatform, accessOptions: AccessControllerOptions);
25
+ private bootstrapController;
26
+ login(): Promise<void>;
27
+ private addAccessDevice;
28
+ private discoverDevices;
29
+ addHomeKitDevice(device: AccessDeviceConfig): boolean;
30
+ private discoverAndSyncAccessories;
31
+ private cleanupDevices;
32
+ private resolveDeviceName;
33
+ private resolveDeviceModel;
34
+ removeHomeKitDevice(accessory: PlatformAccessory, noRemovalDelay?: boolean): void;
35
+ resetControllerConnection(): Promise<void>;
36
+ deviceLookup(deviceId: string): AccessDevice | null;
37
+ getFeatureFloat(option: string): Nullable<number | undefined>;
38
+ getFeatureNumber(option: string): Nullable<number | undefined>;
39
+ hasFeature(option: string, device?: AccessControllerConfig | AccessDeviceConfig): boolean;
40
+ get id(): string | undefined;
41
+ }