node-red-contrib-homekit-bridged 2.0.0-dev.5 → 2.0.0-dev.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/lib/HAPHostNode.js +183 -141
- package/build/lib/HAPServiceNode.js +199 -172
- package/build/lib/HAPServiceNode2.js +207 -172
- package/build/lib/NRCHKBError.js +23 -2
- package/build/lib/PairingQRCode.js +62 -0
- package/build/lib/Storage.js +157 -92
- package/build/lib/api.js +654 -288
- package/build/lib/camera/CameraControl.js +119 -84
- package/build/lib/camera/CameraDelegate.js +481 -404
- package/build/lib/camera/MP4StreamingServer.js +148 -139
- package/build/lib/hap/HAPCharacteristic.js +25 -4
- package/build/lib/hap/HAPService.js +25 -4
- package/build/lib/hap/eve-app/EveCharacteristics.js +124 -81
- package/build/lib/hap/eve-app/EveServices.js +50 -17
- package/build/lib/hap/hap-nodejs.js +32 -0
- package/build/lib/migration/HomeKitService2Migration.js +34 -0
- package/build/lib/migration/NodeMigration.js +75 -0
- package/build/lib/types/AccessoryInformationType.js +15 -1
- package/build/lib/types/CameraConfigType.js +15 -1
- package/build/lib/types/CustomCharacteristicType.js +15 -1
- package/build/lib/types/HAPHostConfigType.js +15 -1
- package/build/lib/types/HAPHostNodeType.js +15 -1
- package/build/lib/types/HAPService2ConfigType.js +15 -1
- package/build/lib/types/HAPService2NodeType.js +15 -1
- package/build/lib/types/HAPServiceConfigType.js +15 -1
- package/build/lib/types/HAPServiceNodeType.js +15 -1
- package/build/lib/types/HAPStatusConfigType.js +15 -1
- package/build/lib/types/HAPStatusNodeType.js +15 -1
- package/build/lib/types/HostType.js +28 -7
- package/build/lib/types/NodeType.js +15 -1
- package/build/lib/types/PublishTimersType.js +15 -1
- package/build/lib/types/UniFiControllerConfigType.js +16 -0
- package/build/lib/types/hap-nodejs/HapAdaptiveLightingControllerMode.js +28 -7
- package/build/lib/types/hap-nodejs/HapCategories.js +64 -43
- package/build/lib/types/storage/SerializedHostType.js +15 -1
- package/build/lib/types/storage/StorageType.js +34 -10
- package/build/lib/unifi/ProtectDiscovery.js +80 -0
- package/build/lib/utils/AccessoryUtils.js +152 -110
- package/build/lib/utils/BridgeUtils.js +82 -39
- package/build/lib/utils/CharacteristicUtils.js +5 -49
- package/build/lib/utils/CharacteristicUtils2.js +5 -49
- package/build/lib/utils/CharacteristicUtilsBase.js +81 -0
- package/build/lib/utils/NodeStatusUtils.js +89 -40
- package/build/lib/utils/ServiceUtils.js +434 -375
- package/build/lib/utils/ServiceUtils2.js +514 -309
- package/build/lib/utils/index.js +10 -11
- package/build/nodes/bridge.html +184 -166
- package/build/nodes/bridge.js +27 -9
- package/build/nodes/locales/en-US/node-red-contrib-homekit-bridged.json +22 -0
- package/build/nodes/nrchkb.html +1601 -88
- package/build/nodes/nrchkb.js +66 -88
- package/build/nodes/plugin-instance.html +499 -0
- package/build/nodes/plugin-instance.js +46 -0
- package/build/nodes/service.html +517 -299
- package/build/nodes/service.js +5 -6
- package/build/nodes/service2.html +1683 -460
- package/build/nodes/service2.js +5 -8
- package/build/nodes/standalone.html +187 -174
- package/build/nodes/standalone.js +27 -9
- package/build/nodes/status.html +51 -18
- package/build/nodes/status.js +47 -40
- package/build/nodes/unifi-controller.html +92 -0
- package/build/nodes/unifi-controller.js +20 -0
- package/build/plugins/embedded/homebridge-camera-ffmpeg/index.js +479 -0
- package/build/plugins/embedded/homebridge-unifi-protect/index.js +521 -0
- package/build/plugins/embedded/index.js +58 -0
- package/build/plugins/nrchkb-homekit-plugins.js +17 -0
- package/build/plugins/registry/index.js +203 -0
- package/build/plugins/registry/types.js +16 -0
- package/build/scripts/migrate-homekit-service-flows.js +47 -0
- package/examples/demo/01 - ALL Demos single import.json +1885 -1885
- package/examples/demo/02 - Air Purifier.json +279 -279
- package/examples/demo/03 - Air Quality sensor with Battery.json +254 -254
- package/examples/demo/04 - Dimmable Bulb.json +172 -172
- package/examples/demo/05 - Color Bulb (HSV).json +195 -195
- package/examples/demo/06 - Fan (simple, 3 speeds).json +240 -240
- package/examples/demo/07 - Fan (with speed, oscillate, rotation direction).json +175 -175
- package/examples/demo/08 - CO2 detector.json +224 -224
- package/examples/demo/09 - CO (carbon monoxide) example.json +255 -255
- package/examples/demo/10 - Door window contact sensor.json +234 -234
- package/examples/demos (advanced)/01 - Television with inputs and speaker.json +541 -541
- package/examples/switch/01 - Plain Switch.json +178 -178
- package/package.json +95 -84
- package/build/lib/HAPHostNode.d.ts +0 -1
- package/build/lib/HAPServiceNode.d.ts +0 -1
- package/build/lib/HAPServiceNode2.d.ts +0 -1
- package/build/lib/NRCHKBError.d.ts +0 -3
- package/build/lib/Storage.d.ts +0 -30
- package/build/lib/api.d.ts +0 -1
- package/build/lib/camera/CameraControl.d.ts +0 -3
- package/build/lib/camera/CameraDelegate.d.ts +0 -38
- package/build/lib/camera/MP4StreamingServer.d.ts +0 -26
- package/build/lib/hap/HAPCharacteristic.d.ts +0 -9
- package/build/lib/hap/HAPService.d.ts +0 -6
- package/build/lib/hap/eve-app/EveCharacteristics.d.ts +0 -20
- package/build/lib/hap/eve-app/EveServices.d.ts +0 -5
- package/build/lib/types/AccessoryInformationType.d.ts +0 -11
- package/build/lib/types/CameraConfigType.d.ts +0 -24
- package/build/lib/types/CustomCharacteristicType.d.ts +0 -6
- package/build/lib/types/HAPHostConfigType.d.ts +0 -22
- package/build/lib/types/HAPHostNodeType.d.ts +0 -14
- package/build/lib/types/HAPService2ConfigType.d.ts +0 -6
- package/build/lib/types/HAPService2NodeType.d.ts +0 -7
- package/build/lib/types/HAPServiceConfigType.d.ts +0 -26
- package/build/lib/types/HAPServiceNodeType.d.ts +0 -38
- package/build/lib/types/HAPStatusConfigType.d.ts +0 -5
- package/build/lib/types/HAPStatusNodeType.d.ts +0 -12
- package/build/lib/types/HostType.d.ts +0 -5
- package/build/lib/types/NodeType.d.ts +0 -3
- package/build/lib/types/PublishTimersType.d.ts +0 -4
- package/build/lib/types/hap-nodejs/HapAdaptiveLightingControllerMode.d.ts +0 -5
- package/build/lib/types/hap-nodejs/HapCategories.d.ts +0 -41
- package/build/lib/types/storage/SerializedHostType.d.ts +0 -5
- package/build/lib/types/storage/StorageType.d.ts +0 -8
- package/build/lib/utils/AccessoryUtils.d.ts +0 -1
- package/build/lib/utils/BridgeUtils.d.ts +0 -1
- package/build/lib/utils/CharacteristicUtils.d.ts +0 -1
- package/build/lib/utils/CharacteristicUtils2.d.ts +0 -1
- package/build/lib/utils/NodeStatusUtils.d.ts +0 -17
- package/build/lib/utils/ServiceUtils.d.ts +0 -1
- package/build/lib/utils/ServiceUtils2.d.ts +0 -1
- package/build/lib/utils/index.d.ts +0 -1
- package/build/nodes/bridge.d.ts +0 -1
- package/build/nodes/nrchkb.d.ts +0 -1
- package/build/nodes/service.d.ts +0 -1
- package/build/nodes/service2.d.ts +0 -1
- package/build/nodes/standalone.d.ts +0 -1
- package/build/nodes/status.d.ts +0 -1
package/build/lib/api.js
CHANGED
|
@@ -1,306 +1,672 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
16
|
-
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
17
|
-
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
18
|
-
t[p[i]] = s[p[i]];
|
|
19
|
-
}
|
|
20
|
-
return t;
|
|
21
|
-
};
|
|
22
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
23
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
24
15
|
};
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
var fs = __toESM(require("node:fs"));
|
|
25
|
+
var os = __toESM(require("node:os"));
|
|
26
|
+
var import_hap_nodejs = require("@homebridge/hap-nodejs");
|
|
27
|
+
var import_logger = require("@nrchkb/logger");
|
|
28
|
+
var import_embedded = require("../plugins/embedded");
|
|
29
|
+
var import_registry = require("../plugins/registry");
|
|
30
|
+
var import_EveCharacteristics = __toESM(require("./hap/eve-app/EveCharacteristics"));
|
|
31
|
+
var import_NodeMigration = require("./migration/NodeMigration");
|
|
32
|
+
var import_PairingQRCode = require("./PairingQRCode");
|
|
33
|
+
var import_Storage = require("./Storage");
|
|
34
|
+
var import_HapCategories = __toESM(require("./types/hap-nodejs/HapCategories"));
|
|
35
|
+
var import_ProtectDiscovery = require("./unifi/ProtectDiscovery");
|
|
36
|
+
const version = require("../../package.json").version.trim();
|
|
32
37
|
module.exports = (RED) => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
38
|
+
const log = (0, import_logger.logger)("NRCHKB", "API");
|
|
39
|
+
const registeredCustomCharacteristicKeys = /* @__PURE__ */ new Set();
|
|
40
|
+
const pathExists = (path) => {
|
|
41
|
+
try {
|
|
42
|
+
return fs.existsSync(path);
|
|
43
|
+
} catch (_error) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const detectAdvertiserRecommendation = () => {
|
|
48
|
+
const platform = os.platform();
|
|
49
|
+
const container = pathExists("/.dockerenv") || pathExists("/run/.containerenv") || process.env.container !== void 0 || process.env.KUBERNETES_SERVICE_HOST !== void 0;
|
|
50
|
+
const dbusAvailable = pathExists("/run/dbus/system_bus_socket") || pathExists("/var/run/dbus/system_bus_socket");
|
|
51
|
+
const avahiAvailable = dbusAvailable && (pathExists("/run/avahi-daemon/socket") || pathExists("/var/run/avahi-daemon/socket"));
|
|
52
|
+
const resolvedAvailable = dbusAvailable && (pathExists("/run/systemd/resolve") || pathExists("/run/systemd/resolve/io.systemd.Resolve"));
|
|
53
|
+
const caveats = [];
|
|
54
|
+
if (platform === "linux") {
|
|
55
|
+
if (avahiAvailable) {
|
|
56
|
+
if (container) {
|
|
57
|
+
caveats.push(
|
|
58
|
+
"Container detected. HomeKit discovery still depends on host networking and multicast visibility."
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
caveats,
|
|
63
|
+
detected: {
|
|
64
|
+
avahiAvailable,
|
|
65
|
+
container,
|
|
66
|
+
dbusAvailable,
|
|
67
|
+
platform,
|
|
68
|
+
resolvedAvailable
|
|
69
|
+
},
|
|
70
|
+
reason: "Linux host with Avahi and D-Bus available.",
|
|
71
|
+
recommended: "avahi",
|
|
72
|
+
title: "AVAHI recommended"
|
|
64
73
|
};
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
74
|
+
}
|
|
75
|
+
if (container) {
|
|
76
|
+
caveats.push(
|
|
77
|
+
"Container detected without usable host Avahi. Docker bridge networking can prevent mDNS discovery even with the recommended advertiser."
|
|
78
|
+
);
|
|
79
|
+
} else if (resolvedAvailable) {
|
|
80
|
+
caveats.push(
|
|
81
|
+
"systemd-resolved mDNS appears available, but RESOLVED is an advanced experimental choice. CIAO is safer unless you intentionally manage mDNS through systemd-resolved."
|
|
82
|
+
);
|
|
83
|
+
} else {
|
|
84
|
+
caveats.push(
|
|
85
|
+
"Install and run avahi-daemon with D-Bus if you prefer the Linux-native advertiser."
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
caveats,
|
|
90
|
+
detected: {
|
|
91
|
+
avahiAvailable,
|
|
92
|
+
container,
|
|
93
|
+
dbusAvailable,
|
|
94
|
+
platform,
|
|
95
|
+
resolvedAvailable
|
|
96
|
+
},
|
|
97
|
+
reason: container ? "Linux container without usable host Avahi detected." : "Linux host without usable Avahi detected.",
|
|
98
|
+
recommended: "ciao",
|
|
99
|
+
title: "CIAO recommended"
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (platform === "darwin" || platform === "win32") {
|
|
103
|
+
return {
|
|
104
|
+
caveats,
|
|
105
|
+
detected: {
|
|
106
|
+
avahiAvailable,
|
|
107
|
+
container,
|
|
108
|
+
dbusAvailable,
|
|
109
|
+
platform,
|
|
110
|
+
resolvedAvailable
|
|
111
|
+
},
|
|
112
|
+
reason: platform === "darwin" ? "macOS host detected." : "Windows host detected.",
|
|
113
|
+
recommended: "ciao",
|
|
114
|
+
title: "CIAO recommended"
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
caveats.push(
|
|
118
|
+
"Host platform is uncommon for HomeKit. Verify multicast and mDNS behavior on this system."
|
|
119
|
+
);
|
|
120
|
+
return {
|
|
121
|
+
caveats,
|
|
122
|
+
detected: {
|
|
123
|
+
avahiAvailable,
|
|
124
|
+
container,
|
|
125
|
+
dbusAvailable,
|
|
126
|
+
platform,
|
|
127
|
+
resolvedAvailable
|
|
128
|
+
},
|
|
129
|
+
reason: `Unsupported or uncommon host platform: ${platform}.`,
|
|
130
|
+
recommended: "ciao",
|
|
131
|
+
title: "CIAO recommended"
|
|
79
132
|
};
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
133
|
+
};
|
|
134
|
+
const _initServiceAPI = () => {
|
|
135
|
+
log.debug("Initialize Service API");
|
|
136
|
+
const serviceData = {
|
|
137
|
+
Camera: {
|
|
138
|
+
displayName: "Camera",
|
|
139
|
+
constructorName: "Camera"
|
|
140
|
+
},
|
|
141
|
+
BatteryService: {
|
|
142
|
+
nrchkbDisabledText: "BatteryService (deprecated, replaced by Battery)"
|
|
143
|
+
},
|
|
144
|
+
BridgeConfiguration: {
|
|
145
|
+
nrchkbDisabledText: "BridgeConfiguration (deprecated, unused)"
|
|
146
|
+
},
|
|
147
|
+
BridgingState: {
|
|
148
|
+
nrchkbDisabledText: "BridgingState (deprecated, unused)"
|
|
149
|
+
},
|
|
150
|
+
CameraControl: {
|
|
151
|
+
nrchkbDisabledText: "CameraControl (deprecated, replaced by Camera)",
|
|
152
|
+
nrchkbHiddenInService2: true
|
|
153
|
+
},
|
|
154
|
+
CameraEventRecordingManagement: {
|
|
155
|
+
nrchkbDisabledText: "CameraEventRecordingManagement (deprecated, replaced by CameraRecordingManagement)"
|
|
156
|
+
},
|
|
157
|
+
Relay: {
|
|
158
|
+
nrchkbDisabledText: "Relay (deprecated, replaced by CloudRelay)"
|
|
159
|
+
},
|
|
160
|
+
Slat: {
|
|
161
|
+
nrchkbDisabledText: "Slat (deprecated, replaced by Slats)"
|
|
162
|
+
},
|
|
163
|
+
TimeInformation: {
|
|
164
|
+
nrchkbDisabledText: "TimeInformation (deprecated, unused)"
|
|
165
|
+
},
|
|
166
|
+
TunneledBTLEAccessoryService: {
|
|
167
|
+
nrchkbDisabledText: "TunneledBTLEAccessoryService (deprecated, replaced by Tunnel)"
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
Object.values(import_hap_nodejs.Service).filter((service) => service.prototype instanceof import_hap_nodejs.Service).map((service) => {
|
|
171
|
+
const newService = import_hap_nodejs.Service.serialize(new service());
|
|
172
|
+
newService.displayName = service.name;
|
|
173
|
+
return newService;
|
|
174
|
+
}).forEach((serialized) => {
|
|
175
|
+
serviceData[serialized.displayName] = {
|
|
176
|
+
...serviceData?.[serialized.displayName],
|
|
177
|
+
...serialized
|
|
178
|
+
};
|
|
179
|
+
});
|
|
180
|
+
RED.httpAdmin.get(
|
|
181
|
+
"/nrchkb/service/types",
|
|
182
|
+
RED.auth.needsPermission("nrchkb.read"),
|
|
183
|
+
(_req, res) => {
|
|
184
|
+
res.setHeader("Content-Type", "application/json");
|
|
185
|
+
res.json(serviceData);
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
RED.httpAdmin.get(
|
|
189
|
+
"/nrchkb/plugins",
|
|
190
|
+
RED.auth.needsPermission("nrchkb.read"),
|
|
191
|
+
(_req, res) => {
|
|
192
|
+
(0, import_registry.registerNodeRedPlugins)(RED);
|
|
193
|
+
(0, import_embedded.registerEmbeddedPlugins)();
|
|
194
|
+
res.setHeader("Content-Type", "application/json");
|
|
195
|
+
res.json((0, import_registry.listPlugins)().map((plugin) => plugin.metadata));
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
RED.httpAdmin.get(
|
|
199
|
+
"/nrchkb/unifi/controllers/:id/test",
|
|
200
|
+
RED.auth.needsPermission("nrchkb.read"),
|
|
201
|
+
async (req, res) => {
|
|
202
|
+
const controller = RED.nodes.getNode(
|
|
203
|
+
req.params.id
|
|
204
|
+
);
|
|
205
|
+
if (!controller) {
|
|
206
|
+
res.status(404).json({
|
|
207
|
+
error: "UniFi controller config node was not found."
|
|
208
|
+
});
|
|
209
|
+
return;
|
|
99
210
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
211
|
+
try {
|
|
212
|
+
(0, import_ProtectDiscovery.assertProtectControllerConfig)(
|
|
213
|
+
controller,
|
|
214
|
+
controller.credentials ?? {}
|
|
215
|
+
);
|
|
216
|
+
await (0, import_ProtectDiscovery.discoverProtectCameras)(
|
|
217
|
+
controller,
|
|
218
|
+
controller.credentials ?? {}
|
|
219
|
+
);
|
|
220
|
+
res.json({ ok: true });
|
|
221
|
+
} catch (error) {
|
|
222
|
+
res.status(400).json({
|
|
223
|
+
error: error instanceof Error ? error.message : "Unable to connect to UniFi Protect."
|
|
224
|
+
});
|
|
113
225
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
RED.httpAdmin.get(
|
|
229
|
+
"/nrchkb/unifi/controllers/:id/protect/cameras",
|
|
230
|
+
RED.auth.needsPermission("nrchkb.read"),
|
|
231
|
+
async (req, res) => {
|
|
232
|
+
const controller = RED.nodes.getNode(
|
|
233
|
+
req.params.id
|
|
234
|
+
);
|
|
235
|
+
if (!controller) {
|
|
236
|
+
res.status(404).json({
|
|
237
|
+
error: "UniFi controller config node was not found."
|
|
238
|
+
});
|
|
239
|
+
return;
|
|
117
240
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
241
|
+
try {
|
|
242
|
+
const cameras = await (0, import_ProtectDiscovery.discoverProtectCameras)(
|
|
243
|
+
controller,
|
|
244
|
+
controller.credentials ?? {}
|
|
245
|
+
);
|
|
246
|
+
res.json(
|
|
247
|
+
cameras.map((camera) => ({
|
|
248
|
+
value: camera.mac,
|
|
249
|
+
label: `${camera.name ?? camera.marketName ?? camera.mac} (${camera.mac})`,
|
|
250
|
+
id: camera.id,
|
|
251
|
+
mac: camera.mac,
|
|
252
|
+
state: camera.state
|
|
253
|
+
}))
|
|
254
|
+
);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
res.status(400).json({
|
|
257
|
+
error: error instanceof Error ? error.message : "Unable to discover UniFi Protect cameras."
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
RED.httpAdmin.post(
|
|
263
|
+
"/nrchkb/migration/node",
|
|
264
|
+
RED.auth.needsPermission("nrchkb.write"),
|
|
265
|
+
(req, res) => {
|
|
266
|
+
if (!req.body || typeof req.body !== "object") {
|
|
267
|
+
res.status(400).json({
|
|
268
|
+
error: "Request body must be a Node-RED node object."
|
|
269
|
+
});
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
res.setHeader("Content-Type", "application/json");
|
|
273
|
+
res.json((0, import_NodeMigration.migrateNode)(req.body));
|
|
274
|
+
}
|
|
275
|
+
);
|
|
276
|
+
RED.httpAdmin.post(
|
|
277
|
+
"/nrchkb/migration/flow",
|
|
278
|
+
RED.auth.needsPermission("nrchkb.write"),
|
|
279
|
+
(req, res) => {
|
|
280
|
+
if (!Array.isArray(req.body)) {
|
|
281
|
+
res.status(400).json({
|
|
282
|
+
error: "Request body must be an array of Node-RED nodes."
|
|
283
|
+
});
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
res.setHeader("Content-Type", "application/json");
|
|
287
|
+
res.json((0, import_NodeMigration.migrateFlow)(req.body));
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
};
|
|
291
|
+
const stringifyVersion = (version2) => {
|
|
292
|
+
const releaseVersionRegex = /(\d+)\.(\d+)\.(\d+)/;
|
|
293
|
+
const devVersionRegex = /(\d+)\.(\d+)\.(\d+)-dev\.(\d+)/;
|
|
294
|
+
const releaseVersionFound = releaseVersionRegex.test(version2);
|
|
295
|
+
const devVersionFound = devVersionRegex.test(version2);
|
|
296
|
+
let xyzVersion = "0.0.0";
|
|
297
|
+
if (devVersionFound) {
|
|
298
|
+
try {
|
|
299
|
+
const match = devVersionRegex.exec(version2);
|
|
300
|
+
if (match) {
|
|
301
|
+
xyzVersion = `0.${match[1]}${match[2]}${match[3]}.${match[4]}`;
|
|
302
|
+
} else {
|
|
303
|
+
log.debug("Could not match dev version");
|
|
304
|
+
}
|
|
305
|
+
} catch (e) {
|
|
306
|
+
log.error(e);
|
|
307
|
+
}
|
|
308
|
+
} else if (releaseVersionFound) {
|
|
309
|
+
try {
|
|
310
|
+
const match = releaseVersionRegex.exec(version2);
|
|
311
|
+
if (match) {
|
|
312
|
+
xyzVersion = match[0];
|
|
313
|
+
} else {
|
|
314
|
+
log.debug("Could not match release version");
|
|
315
|
+
}
|
|
316
|
+
} catch (e) {
|
|
317
|
+
log.error(e);
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
log.debug("Bad version format");
|
|
321
|
+
xyzVersion = "0.0.0";
|
|
322
|
+
}
|
|
323
|
+
return xyzVersion;
|
|
324
|
+
};
|
|
325
|
+
const _initNRCHKBInfoAPI = () => {
|
|
326
|
+
log.debug("Initialize NRCHKB Info API");
|
|
327
|
+
log.debug(`Running version: ${version}`);
|
|
328
|
+
const xyzVersion = stringifyVersion(version);
|
|
329
|
+
log.debug(`Evaluated as: ${xyzVersion}`);
|
|
330
|
+
const experimental = process.env.NRCHKB_EXPERIMENTAL === "true";
|
|
331
|
+
log.debug(`Running experimental: ${experimental}`);
|
|
332
|
+
RED.httpAdmin.get(
|
|
333
|
+
"/nrchkb/info",
|
|
334
|
+
RED.auth.needsPermission("nrchkb.read"),
|
|
335
|
+
(_req, res) => {
|
|
336
|
+
res.setHeader("Content-Type", "application/json");
|
|
337
|
+
res.json({
|
|
338
|
+
version: xyzVersion,
|
|
339
|
+
experimental
|
|
133
340
|
});
|
|
341
|
+
}
|
|
342
|
+
);
|
|
343
|
+
};
|
|
344
|
+
const _initAdvertiserRecommendationAPI = () => {
|
|
345
|
+
log.debug("Initialize Advertiser Recommendation API");
|
|
346
|
+
RED.httpAdmin.get(
|
|
347
|
+
"/nrchkb/advertiser/recommendation",
|
|
348
|
+
RED.auth.needsPermission("nrchkb.read"),
|
|
349
|
+
(_req, res) => {
|
|
350
|
+
res.setHeader("Content-Type", "application/json");
|
|
351
|
+
res.json(detectAdvertiserRecommendation());
|
|
352
|
+
}
|
|
353
|
+
);
|
|
354
|
+
};
|
|
355
|
+
const getHostPairedState = (hostNode) => {
|
|
356
|
+
const accessoryInfo = hostNode.host._accessoryInfo;
|
|
357
|
+
if (accessoryInfo) {
|
|
358
|
+
hostNode.paired = accessoryInfo.paired();
|
|
359
|
+
}
|
|
360
|
+
return !!hostNode.paired;
|
|
361
|
+
};
|
|
362
|
+
const _initBridgePairingAPI = () => {
|
|
363
|
+
log.debug("Initialize Bridge Pairing API");
|
|
364
|
+
RED.httpAdmin.get(
|
|
365
|
+
"/nrchkb/bridge/:id/pairing",
|
|
366
|
+
RED.auth.needsPermission("nrchkb.read"),
|
|
367
|
+
async (req, res) => {
|
|
368
|
+
const hostId = req.params.id;
|
|
369
|
+
const node = RED.nodes.getNode(hostId);
|
|
370
|
+
res.setHeader("Content-Type", "application/json");
|
|
371
|
+
if (!node || node.type !== "homekit-bridge" && node.type !== "homekit-standalone") {
|
|
372
|
+
res.status(404).json({
|
|
373
|
+
error: "Pairing host not found.",
|
|
374
|
+
paired: false,
|
|
375
|
+
published: false
|
|
376
|
+
});
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const bridgeName = node.config.bridgeName;
|
|
380
|
+
const pinCode = node.config.pinCode;
|
|
381
|
+
const formattedPinCode = (0, import_PairingQRCode.formatPinCodeForPairing)(pinCode);
|
|
382
|
+
const paired = getHostPairedState(node);
|
|
383
|
+
if (!node.published) {
|
|
384
|
+
res.status(409).json({
|
|
385
|
+
bridgeName,
|
|
386
|
+
formattedPinCode,
|
|
387
|
+
paired,
|
|
388
|
+
pinCode,
|
|
389
|
+
published: false,
|
|
390
|
+
status: "unpublished"
|
|
391
|
+
});
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (paired) {
|
|
395
|
+
res.json({
|
|
396
|
+
bridgeName,
|
|
397
|
+
formattedPinCode,
|
|
398
|
+
paired: true,
|
|
399
|
+
pinCode,
|
|
400
|
+
published: true,
|
|
401
|
+
status: "paired"
|
|
402
|
+
});
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
try {
|
|
406
|
+
const setupUri = node.host.setupURI();
|
|
407
|
+
const qrCodeDataUrl = await (0, import_PairingQRCode.createPairingQRCodeDataURL)(
|
|
408
|
+
setupUri,
|
|
409
|
+
pinCode
|
|
410
|
+
);
|
|
411
|
+
res.json({
|
|
412
|
+
bridgeName,
|
|
413
|
+
formattedPinCode,
|
|
414
|
+
paired: false,
|
|
415
|
+
pinCode,
|
|
416
|
+
published: true,
|
|
417
|
+
qrCodeDataUrl,
|
|
418
|
+
setupUri,
|
|
419
|
+
status: "unpaired"
|
|
420
|
+
});
|
|
421
|
+
} catch (error) {
|
|
422
|
+
log.error(
|
|
423
|
+
`Failed to generate pairing QR code for host ${hostId}: ${error}`
|
|
424
|
+
);
|
|
425
|
+
res.status(500).json({
|
|
426
|
+
bridgeName,
|
|
427
|
+
error: "Failed to generate pairing QR code.",
|
|
428
|
+
formattedPinCode,
|
|
429
|
+
paired: false,
|
|
430
|
+
pinCode,
|
|
431
|
+
published: true
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
);
|
|
436
|
+
};
|
|
437
|
+
const _initNRCHKBCustomCharacteristicsAPI = async () => {
|
|
438
|
+
const getCustomCharacteristics = async () => {
|
|
439
|
+
try {
|
|
440
|
+
const value = await import_Storage.Storage.loadCustomCharacteristics();
|
|
441
|
+
log.trace("loadCustomCharacteristics()");
|
|
442
|
+
log.trace(value);
|
|
443
|
+
if (Array.isArray(value)) {
|
|
444
|
+
return value;
|
|
445
|
+
} else {
|
|
446
|
+
log.debug(
|
|
447
|
+
"customCharacteristics is not Array, returning empty value"
|
|
448
|
+
);
|
|
449
|
+
return import_EveCharacteristics.default;
|
|
450
|
+
}
|
|
451
|
+
} catch (error) {
|
|
452
|
+
log.error(
|
|
453
|
+
`Failed to get customCharacteristics in nrchkbStorage due to ${error}`
|
|
454
|
+
);
|
|
455
|
+
return import_EveCharacteristics.default;
|
|
456
|
+
}
|
|
134
457
|
};
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
458
|
+
const characteristicNameToKey = (name) => {
|
|
459
|
+
return name.replace(/\s+/g, "");
|
|
460
|
+
};
|
|
461
|
+
const toNumber = (value, optional = void 0) => {
|
|
462
|
+
const num = Number(value);
|
|
463
|
+
if (Number.isNaN(num)) {
|
|
464
|
+
return optional;
|
|
465
|
+
} else return num;
|
|
466
|
+
};
|
|
467
|
+
const rebindCharacteristicListeners = (characteristic, serviceNode) => {
|
|
468
|
+
characteristic.removeListener(
|
|
469
|
+
"get",
|
|
470
|
+
serviceNode.onCharacteristicGet
|
|
471
|
+
);
|
|
472
|
+
characteristic.removeListener(
|
|
473
|
+
"set",
|
|
474
|
+
serviceNode.onCharacteristicSet
|
|
475
|
+
);
|
|
476
|
+
characteristic.removeListener(
|
|
477
|
+
"change",
|
|
478
|
+
serviceNode.onCharacteristicChange
|
|
479
|
+
);
|
|
480
|
+
characteristic.on("get", serviceNode.onCharacteristicGet);
|
|
481
|
+
characteristic.on("set", serviceNode.onCharacteristicSet);
|
|
482
|
+
characteristic.on("change", serviceNode.onCharacteristicChange);
|
|
483
|
+
};
|
|
484
|
+
const refreshCustomCharacteristics = (customCharacteristics) => {
|
|
485
|
+
log.debug("Refreshing Custom Characteristics");
|
|
486
|
+
const customCharacteristicKeys = /* @__PURE__ */ new Set();
|
|
487
|
+
customCharacteristics.forEach(({ name, UUID, ...props }) => {
|
|
488
|
+
if (UUID && name) {
|
|
489
|
+
const key = characteristicNameToKey(name);
|
|
490
|
+
log.debug(
|
|
491
|
+
`Adding Custom Characteristic ${name} using key ${key}`
|
|
492
|
+
);
|
|
493
|
+
if (customCharacteristicKeys.has(key)) {
|
|
494
|
+
log.error(
|
|
495
|
+
`Cannot add ${name}. Another Custom Characteristic already defined using key ${key}`
|
|
496
|
+
);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const validatedProps = { ...props };
|
|
500
|
+
if (validatedProps.validValues?.length === 0) {
|
|
501
|
+
validatedProps.validValues = void 0;
|
|
502
|
+
}
|
|
503
|
+
if (validatedProps.validValueRanges?.length) {
|
|
504
|
+
const [minRange, maxRange] = validatedProps.validValueRanges;
|
|
505
|
+
if (minRange === void 0 || maxRange === void 0) {
|
|
506
|
+
validatedProps.validValueRanges = void 0;
|
|
507
|
+
} else {
|
|
508
|
+
const minRangeNumber = Number(minRange);
|
|
509
|
+
const maxRangeNumber = Number(maxRange);
|
|
510
|
+
if (Number.isNaN(minRangeNumber) || Number.isNaN(maxRangeNumber)) {
|
|
511
|
+
validatedProps.validValueRanges = void 0;
|
|
512
|
+
} else {
|
|
513
|
+
validatedProps.validValueRanges = [
|
|
514
|
+
minRangeNumber,
|
|
515
|
+
maxRangeNumber
|
|
516
|
+
];
|
|
517
|
+
}
|
|
148
518
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
519
|
+
}
|
|
520
|
+
if (validatedProps.adminOnlyAccess?.length === 0) {
|
|
521
|
+
validatedProps.adminOnlyAccess = void 0;
|
|
522
|
+
}
|
|
523
|
+
if (validatedProps.minValue !== void 0) {
|
|
524
|
+
validatedProps.minValue = toNumber(
|
|
525
|
+
validatedProps.minValue
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
if (validatedProps.maxValue !== void 0) {
|
|
529
|
+
validatedProps.maxValue = toNumber(
|
|
530
|
+
validatedProps.maxValue
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
if (validatedProps.minStep !== void 0) {
|
|
534
|
+
validatedProps.minStep = toNumber(
|
|
535
|
+
validatedProps.minStep
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
class CustomCharacteristic extends import_hap_nodejs.Characteristic {
|
|
539
|
+
static {
|
|
540
|
+
this.UUID = UUID;
|
|
152
541
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
542
|
+
constructor() {
|
|
543
|
+
super(name, CustomCharacteristic.UUID, {
|
|
544
|
+
...validatedProps,
|
|
545
|
+
perms: validatedProps.perms ?? [
|
|
546
|
+
import_hap_nodejs.Perms.PAIRED_READ,
|
|
547
|
+
import_hap_nodejs.Perms.PAIRED_WRITE,
|
|
548
|
+
import_hap_nodejs.Perms.NOTIFY
|
|
549
|
+
]
|
|
550
|
+
});
|
|
551
|
+
this.value = this.getDefaultValue();
|
|
161
552
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
validatedProps.minValue = toNumber(validatedProps.minValue);
|
|
191
|
-
}
|
|
192
|
-
if (validatedProps.maxValue) {
|
|
193
|
-
validatedProps.maxValue = toNumber(validatedProps.maxValue);
|
|
194
|
-
}
|
|
195
|
-
if (validatedProps.minStep) {
|
|
196
|
-
validatedProps.minStep = toNumber(validatedProps.minStep);
|
|
197
|
-
}
|
|
198
|
-
class CustomCharacteristic extends hap_nodejs_1.Characteristic {
|
|
199
|
-
constructor() {
|
|
200
|
-
var _a;
|
|
201
|
-
super(name, CustomCharacteristic.UUID, Object.assign(Object.assign({}, validatedProps), { perms: (_a = validatedProps.perms) !== null && _a !== void 0 ? _a : [
|
|
202
|
-
"pr",
|
|
203
|
-
"pw",
|
|
204
|
-
"ev"
|
|
205
|
-
] }));
|
|
206
|
-
this.value = this.getDefaultValue();
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
CustomCharacteristic.UUID = UUID;
|
|
210
|
-
Object.defineProperty(CustomCharacteristic, 'name', {
|
|
211
|
-
value: key,
|
|
212
|
-
configurable: true
|
|
213
|
-
});
|
|
214
|
-
Object.defineProperty(hap_nodejs_1.Characteristic, key, {
|
|
215
|
-
value: CustomCharacteristic,
|
|
216
|
-
configurable: true
|
|
217
|
-
});
|
|
218
|
-
customCharacteristicKeys.push(key);
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
new Promise((resolve) => {
|
|
222
|
-
const isRedInitialized = () => {
|
|
223
|
-
try {
|
|
224
|
-
RED.nodes.eachNode(() => {
|
|
225
|
-
return;
|
|
226
|
-
});
|
|
227
|
-
resolve(true);
|
|
228
|
-
}
|
|
229
|
-
catch (_) {
|
|
230
|
-
log.debug('Waiting for RED to be initialized');
|
|
231
|
-
setTimeout(isRedInitialized, 1000);
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
isRedInitialized();
|
|
235
|
-
}).then(() => {
|
|
236
|
-
RED.nodes.eachNode((node) => {
|
|
237
|
-
if (node.type === 'homekit-service' ||
|
|
238
|
-
node.type === 'homekit-service2') {
|
|
239
|
-
const serviceNodeConfig = node;
|
|
240
|
-
const serviceNode = RED.nodes.getNode(serviceNodeConfig.id);
|
|
241
|
-
if ((serviceNode === null || serviceNode === void 0 ? void 0 : serviceNode.characteristicProperties) && serviceNode.service) {
|
|
242
|
-
for (const key in serviceNode.characteristicProperties) {
|
|
243
|
-
if (customCharacteristicKeys.includes(key)) {
|
|
244
|
-
const characteristic = serviceNode.service
|
|
245
|
-
.getCharacteristic(hap_nodejs_1.Characteristic[key])
|
|
246
|
-
.setProps(serviceNode.characteristicProperties[key]);
|
|
247
|
-
serviceNode.supported.push(key);
|
|
248
|
-
characteristic.on('get', serviceNode.onCharacteristicGet);
|
|
249
|
-
characteristic.on('set', serviceNode.onCharacteristicSet);
|
|
250
|
-
characteristic.on('change', serviceNode.onCharacteristicChange);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
});
|
|
553
|
+
}
|
|
554
|
+
Object.defineProperty(CustomCharacteristic, "name", {
|
|
555
|
+
value: key,
|
|
556
|
+
configurable: true
|
|
557
|
+
});
|
|
558
|
+
Object.defineProperty(import_hap_nodejs.Characteristic, key, {
|
|
559
|
+
value: CustomCharacteristic,
|
|
560
|
+
configurable: true
|
|
561
|
+
});
|
|
562
|
+
customCharacteristicKeys.add(key);
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
registeredCustomCharacteristicKeys.forEach((key) => {
|
|
566
|
+
if (customCharacteristicKeys.has(key)) {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
log.debug(`Removing stale Custom Characteristic ${key}`);
|
|
570
|
+
delete import_hap_nodejs.Characteristic[key];
|
|
571
|
+
registeredCustomCharacteristicKeys.delete(key);
|
|
572
|
+
});
|
|
573
|
+
customCharacteristicKeys.forEach((key) => {
|
|
574
|
+
registeredCustomCharacteristicKeys.add(key);
|
|
575
|
+
});
|
|
576
|
+
new Promise((resolve) => {
|
|
577
|
+
const isRedInitialized = () => {
|
|
578
|
+
try {
|
|
579
|
+
RED.nodes.eachNode(() => {
|
|
580
|
+
return;
|
|
256
581
|
});
|
|
582
|
+
resolve(true);
|
|
583
|
+
} catch (_) {
|
|
584
|
+
log.debug("Waiting for RED to be initialized");
|
|
585
|
+
setTimeout(isRedInitialized, 1e3);
|
|
586
|
+
}
|
|
257
587
|
};
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
RED.
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const accessoryCategoriesData = {};
|
|
282
|
-
Object.keys(HapCategories_1.default)
|
|
283
|
-
.sort()
|
|
284
|
-
.filter((x) => parseInt(x, 10) >= 0)
|
|
285
|
-
.forEach((key) => {
|
|
286
|
-
const keyNumber = key;
|
|
287
|
-
accessoryCategoriesData[keyNumber] = HapCategories_1.default[keyNumber];
|
|
288
|
-
});
|
|
289
|
-
RED.httpAdmin.get('/nrchkb/accessory/categories', RED.auth.needsPermission('nrchkb.read'), (_req, res) => {
|
|
290
|
-
res.setHeader('Content-Type', 'application/json');
|
|
291
|
-
res.json(accessoryCategoriesData);
|
|
588
|
+
isRedInitialized();
|
|
589
|
+
}).then(() => {
|
|
590
|
+
RED.nodes.eachNode((node) => {
|
|
591
|
+
if (node.type === "homekit-service" || node.type === "homekit-service2") {
|
|
592
|
+
const serviceNodeConfig = node;
|
|
593
|
+
const serviceNode = RED.nodes.getNode(
|
|
594
|
+
serviceNodeConfig.id
|
|
595
|
+
);
|
|
596
|
+
if (serviceNode?.characteristicProperties && serviceNode.service) {
|
|
597
|
+
for (const key in serviceNode.characteristicProperties) {
|
|
598
|
+
if (customCharacteristicKeys.has(key)) {
|
|
599
|
+
const characteristic = serviceNode.service.getCharacteristic(import_hap_nodejs.Characteristic[key]).setProps(
|
|
600
|
+
serviceNode.characteristicProperties[key]
|
|
601
|
+
);
|
|
602
|
+
serviceNode.supported.add(key);
|
|
603
|
+
rebindCharacteristicListeners(
|
|
604
|
+
characteristic,
|
|
605
|
+
serviceNode
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
292
611
|
});
|
|
612
|
+
});
|
|
293
613
|
};
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
614
|
+
log.debug("Initialize NRCHKBCustomCharacteristicsAPI");
|
|
615
|
+
getCustomCharacteristics().then(
|
|
616
|
+
(value) => refreshCustomCharacteristics(value)
|
|
617
|
+
);
|
|
618
|
+
RED.httpAdmin.get(
|
|
619
|
+
"/nrchkb/config",
|
|
620
|
+
RED.auth.needsPermission("nrchkb.read"),
|
|
621
|
+
async (_req, res) => {
|
|
622
|
+
res.setHeader("Content-Type", "application/json");
|
|
623
|
+
res.json({
|
|
624
|
+
customCharacteristics: await getCustomCharacteristics()
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
);
|
|
628
|
+
RED.httpAdmin.post(
|
|
629
|
+
"/nrchkb/config",
|
|
630
|
+
RED.auth.needsPermission("nrchkb.write"),
|
|
631
|
+
async (req, res) => {
|
|
632
|
+
const customCharacteristics = req.body.customCharacteristics || import_EveCharacteristics.default;
|
|
633
|
+
import_Storage.Storage.saveCustomCharacteristics(customCharacteristics).then(() => {
|
|
634
|
+
res.sendStatus(200);
|
|
635
|
+
refreshCustomCharacteristics(customCharacteristics);
|
|
636
|
+
}).catch((error) => {
|
|
637
|
+
log.error(error);
|
|
638
|
+
res.sendStatus(500);
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
);
|
|
642
|
+
};
|
|
643
|
+
const _initAccessoryAPI = () => {
|
|
644
|
+
log.debug("Initialize Accessory API");
|
|
645
|
+
const accessoryCategoriesData = {};
|
|
646
|
+
Object.keys(import_HapCategories.default).sort().filter((x) => parseInt(x, 10) >= 0).forEach((key) => {
|
|
647
|
+
const keyNumber = key;
|
|
648
|
+
accessoryCategoriesData[keyNumber] = import_HapCategories.default[keyNumber];
|
|
649
|
+
});
|
|
650
|
+
RED.httpAdmin.get(
|
|
651
|
+
"/nrchkb/accessory/categories",
|
|
652
|
+
RED.auth.needsPermission("nrchkb.read"),
|
|
653
|
+
(_req, res) => {
|
|
654
|
+
res.setHeader("Content-Type", "application/json");
|
|
655
|
+
res.json(accessoryCategoriesData);
|
|
656
|
+
}
|
|
657
|
+
);
|
|
658
|
+
};
|
|
659
|
+
const init = () => {
|
|
660
|
+
_initServiceAPI();
|
|
661
|
+
_initNRCHKBInfoAPI();
|
|
662
|
+
_initAdvertiserRecommendationAPI();
|
|
663
|
+
_initBridgePairingAPI();
|
|
664
|
+
_initAccessoryAPI();
|
|
665
|
+
_initNRCHKBCustomCharacteristicsAPI().then();
|
|
666
|
+
};
|
|
667
|
+
return {
|
|
668
|
+
detectAdvertiserRecommendation,
|
|
669
|
+
init,
|
|
670
|
+
stringifyVersion
|
|
671
|
+
};
|
|
306
672
|
};
|