node-red-contrib-homekit-bridged 2.0.0-dev.1 → 2.0.0-dev.10
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 +185 -146
- package/build/lib/HAPServiceNode.js +200 -177
- package/build/lib/HAPServiceNode2.js +208 -177
- package/build/lib/NRCHKBError.js +23 -2
- package/build/lib/PairingQRCode.js +62 -0
- package/build/lib/Storage.js +152 -90
- package/build/lib/api.js +654 -290
- package/build/lib/camera/CameraControl.js +125 -0
- package/build/lib/camera/CameraDelegate.js +507 -0
- package/build/lib/camera/MP4StreamingServer.js +159 -0
- 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 -112
- package/build/lib/utils/BridgeUtils.js +95 -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 +433 -368
- package/build/lib/utils/ServiceUtils2.js +519 -304
- package/build/lib/utils/index.js +11 -12
- package/build/nodes/bridge.html +206 -168
- 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 +1753 -117
- package/build/nodes/nrchkb.js +66 -88
- package/build/nodes/plugin-instance.html +509 -0
- package/build/nodes/plugin-instance.js +46 -0
- package/build/nodes/service.html +557 -306
- package/build/nodes/service.js +5 -6
- package/build/nodes/service2.html +1735 -455
- package/build/nodes/service2.js +5 -8
- package/build/nodes/standalone.html +208 -176
- package/build/nodes/standalone.js +27 -9
- package/build/nodes/status.html +51 -18
- package/build/nodes/status.js +47 -41
- 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 -82
- 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/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/nodes/nrchkb.html
CHANGED
|
@@ -1,32 +1,780 @@
|
|
|
1
1
|
<style>
|
|
2
|
-
.
|
|
2
|
+
.nrchkb-editor {
|
|
3
|
+
--nrchkb-section-border: var(--red-ui-secondary-border-color, #d8d8d8);
|
|
4
|
+
--nrchkb-section-bg: var(--red-ui-secondary-background, #fff);
|
|
5
|
+
--nrchkb-section-header-bg: var(--red-ui-tertiary-background, #f6f6f6);
|
|
6
|
+
--nrchkb-muted-text: var(--red-ui-secondary-text-color, #666);
|
|
7
|
+
--nrchkb-focus-ring: var(--red-ui-focus-color, #1a73e8);
|
|
8
|
+
--nrchkb-accent: var(--red-ui-text-color-link, #ad1625);
|
|
9
|
+
--nrchkb-warning-border: var(--red-ui-message-warning-border-color, #d7a100);
|
|
10
|
+
--nrchkb-warning-bg: var(--red-ui-message-warning-background, var(--red-ui-tertiary-background, #fff7d6));
|
|
11
|
+
--nrchkb-warning-text: var(--red-ui-message-warning-color, var(--red-ui-primary-text-color, #333));
|
|
12
|
+
--nrchkb-info-border: var(--red-ui-message-info-border-color, var(--red-ui-secondary-border-color, #9ab));
|
|
13
|
+
--nrchkb-info-bg: var(--red-ui-message-info-background, var(--red-ui-tertiary-background, #eef6ff));
|
|
14
|
+
--nrchkb-info-text: var(--red-ui-message-info-color, var(--red-ui-primary-text-color, #333));
|
|
15
|
+
--nrchkb-toggle-on: #34c759;
|
|
16
|
+
--nrchkb-toggle-off: color-mix(in srgb, var(--red-ui-secondary-text-color, #666) 30%, var(--red-ui-tertiary-background, #f6f6f6));
|
|
17
|
+
--nrchkb-toggle-border: color-mix(in srgb, var(--red-ui-secondary-text-color, #666) 20%, transparent);
|
|
18
|
+
--nrchkb-toggle-thumb: #fff;
|
|
19
|
+
--nrchkb-toggle-shadow: 0 1px 2px rgba(0, 0, 0, .22), 0 1px 4px rgba(0, 0, 0, .12);
|
|
20
|
+
container-type: inline-size;
|
|
21
|
+
box-sizing: border-box;
|
|
22
|
+
width: 100%;
|
|
23
|
+
min-width: 0;
|
|
24
|
+
max-width: 100%;
|
|
25
|
+
overflow-x: hidden;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.nrchkb-editor *,
|
|
29
|
+
.nrchkb-editor *::before,
|
|
30
|
+
.nrchkb-editor *::after {
|
|
31
|
+
box-sizing: border-box;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.nrchkb-editor .nrchkb-section {
|
|
35
|
+
margin: 0 0 10px;
|
|
36
|
+
border: 1px solid var(--nrchkb-section-border);
|
|
37
|
+
border-radius: 7px;
|
|
38
|
+
background: var(--nrchkb-section-bg);
|
|
39
|
+
background: color-mix(in srgb, var(--red-ui-secondary-background, #fff) 88%, transparent);
|
|
40
|
+
max-width: 100%;
|
|
41
|
+
overflow: hidden;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.nrchkb-editor .nrchkb-section > summary {
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
gap: 8px;
|
|
48
|
+
min-height: 34px;
|
|
49
|
+
min-width: 0;
|
|
50
|
+
padding: 7px 10px;
|
|
51
|
+
list-style: none;
|
|
52
|
+
cursor: pointer;
|
|
53
|
+
font-weight: 600;
|
|
54
|
+
background: var(--nrchkb-section-header-bg);
|
|
55
|
+
background: color-mix(in srgb, var(--red-ui-tertiary-background, #f6f6f6) 82%, transparent);
|
|
56
|
+
user-select: none;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.nrchkb-editor .nrchkb-section > summary:focus-visible,
|
|
60
|
+
.nrchkb-editor button:focus-visible,
|
|
61
|
+
.nrchkb-editor [role="button"]:focus-visible,
|
|
62
|
+
.nrchkb-plugin-picker-item:focus-visible {
|
|
63
|
+
outline: 2px solid var(--nrchkb-focus-ring);
|
|
64
|
+
outline-offset: 2px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.nrchkb-editor .nrchkb-section > summary::-webkit-details-marker {
|
|
68
|
+
display: none;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.nrchkb-editor .nrchkb-section > summary::before {
|
|
72
|
+
width: 12px;
|
|
73
|
+
color: var(--nrchkb-muted-text);
|
|
74
|
+
content: "\f0da";
|
|
75
|
+
font-family: FontAwesome;
|
|
76
|
+
text-align: center;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.nrchkb-editor .nrchkb-section[open] > summary::before {
|
|
80
|
+
content: "\f0d7";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.nrchkb-editor .nrchkb-section > summary i {
|
|
84
|
+
flex: 0 0 14px;
|
|
85
|
+
width: 14px;
|
|
86
|
+
color: var(--nrchkb-muted-text);
|
|
87
|
+
text-align: center;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.nrchkb-editor .nrchkb-section-body {
|
|
91
|
+
min-width: 0;
|
|
92
|
+
padding: 10px 10px 2px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.nrchkb-editor .nrchkb-nested-panel {
|
|
96
|
+
margin: 4px 0 8px;
|
|
97
|
+
padding: 10px 10px 2px;
|
|
98
|
+
border: 1px solid var(--nrchkb-section-border);
|
|
99
|
+
border-radius: 6px;
|
|
100
|
+
background: var(--nrchkb-section-bg);
|
|
101
|
+
background: color-mix(in srgb, var(--red-ui-secondary-background, #fff) 72%, transparent);
|
|
102
|
+
min-width: 0;
|
|
103
|
+
max-width: 100%;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.nrchkb-editor .form-row {
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
gap: 10px;
|
|
110
|
+
min-width: 0;
|
|
111
|
+
margin-bottom: 8px;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.nrchkb-editor .form-row > label:first-child {
|
|
115
|
+
flex: 0 1 150px;
|
|
116
|
+
width: auto;
|
|
117
|
+
min-width: 98px;
|
|
118
|
+
max-width: min(150px, 42%);
|
|
119
|
+
margin: 0;
|
|
120
|
+
line-height: 1.25;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.nrchkb-editor .form-row > label:first-child:not(.nrchkb-checkbox-label) i {
|
|
124
|
+
display: inline-block;
|
|
125
|
+
width: 14px;
|
|
126
|
+
color: var(--nrchkb-muted-text);
|
|
127
|
+
text-align: center;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.nrchkb-editor .form-row > label.nrchkb-checkbox-label {
|
|
131
|
+
flex: 1 1 auto;
|
|
132
|
+
width: auto;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.nrchkb-editor input[type="text"],
|
|
136
|
+
.nrchkb-editor input[type="number"],
|
|
137
|
+
.nrchkb-editor input:not([type]),
|
|
138
|
+
.nrchkb-editor select,
|
|
139
|
+
.nrchkb-editor .red-ui-typedInput-container {
|
|
140
|
+
min-width: 0;
|
|
141
|
+
max-width: 100%;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.nrchkb-editor .form-row > input[type="text"],
|
|
145
|
+
.nrchkb-editor .form-row > input[type="number"],
|
|
146
|
+
.nrchkb-editor .form-row > input:not([type]),
|
|
147
|
+
.nrchkb-editor .form-row > select,
|
|
148
|
+
.nrchkb-editor .form-row > .red-ui-typedInput-container {
|
|
149
|
+
flex: 1 1 auto;
|
|
150
|
+
width: auto !important;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.nrchkb-editor .form-row > div {
|
|
154
|
+
min-width: 0;
|
|
155
|
+
max-width: 100%;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.nrchkb-editor .form-row > div[style*="inline-flex"] {
|
|
159
|
+
flex: 1 1 auto;
|
|
160
|
+
width: auto !important;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.nrchkb-editor .form-row > div[style*="inline-flex"] > select {
|
|
164
|
+
min-width: 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.nrchkb-editor .form-row > .red-ui-typedInput-container input {
|
|
168
|
+
width: 100% !important;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.nrchkb-editor .form-row > .red-ui-typedInput-container {
|
|
172
|
+
min-height: 34px;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.nrchkb-editor .nrchkb-checkbox-row {
|
|
176
|
+
align-items: flex-start;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.nrchkb-editor input[type="checkbox"] {
|
|
180
|
+
position: relative;
|
|
181
|
+
flex: 0 0 auto;
|
|
182
|
+
width: 36px;
|
|
183
|
+
height: 22px;
|
|
184
|
+
margin: 0;
|
|
185
|
+
border: 1px solid var(--nrchkb-toggle-border);
|
|
186
|
+
border-radius: 999px;
|
|
187
|
+
appearance: none;
|
|
188
|
+
-webkit-appearance: none;
|
|
189
|
+
background: var(--nrchkb-toggle-off);
|
|
190
|
+
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .03);
|
|
191
|
+
cursor: pointer;
|
|
192
|
+
vertical-align: middle;
|
|
193
|
+
transition: background-color .18s ease, border-color .18s ease, box-shadow .18s ease;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.nrchkb-editor input[type="checkbox"]::before {
|
|
197
|
+
position: absolute;
|
|
198
|
+
top: 2px;
|
|
199
|
+
left: 2px;
|
|
200
|
+
width: 16px;
|
|
201
|
+
height: 16px;
|
|
202
|
+
border-radius: 50%;
|
|
203
|
+
background: var(--nrchkb-toggle-thumb);
|
|
204
|
+
box-shadow: var(--nrchkb-toggle-shadow);
|
|
205
|
+
content: "";
|
|
206
|
+
transition: transform .18s ease;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.nrchkb-editor input[type="checkbox"]:checked {
|
|
210
|
+
border-color: color-mix(in srgb, var(--nrchkb-toggle-on) 82%, #000);
|
|
211
|
+
background: var(--nrchkb-toggle-on);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.nrchkb-editor input[type="checkbox"]:checked::before {
|
|
215
|
+
transform: translateX(14px);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.nrchkb-editor input[type="checkbox"]:focus-visible {
|
|
219
|
+
outline: 2px solid var(--nrchkb-focus-ring);
|
|
220
|
+
outline-offset: 2px;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.nrchkb-editor input[type="checkbox"]:disabled {
|
|
224
|
+
cursor: not-allowed;
|
|
225
|
+
opacity: .55;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.nrchkb-editor .nrchkb-checkbox-label {
|
|
229
|
+
position: relative;
|
|
230
|
+
display: inline-flex;
|
|
231
|
+
align-items: center;
|
|
232
|
+
gap: 8px;
|
|
233
|
+
width: auto;
|
|
234
|
+
margin: 0;
|
|
235
|
+
line-height: 1.35;
|
|
236
|
+
cursor: pointer;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.nrchkb-editor .nrchkb-checkbox-label input[type="checkbox"] {
|
|
240
|
+
margin-top: 0;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.nrchkb-editor .nrchkb-checkbox-label span {
|
|
244
|
+
min-width: 0;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.nrchkb-editor .nrchkb-checkbox-label i {
|
|
248
|
+
position: absolute;
|
|
249
|
+
top: 50%;
|
|
250
|
+
left: 6px;
|
|
251
|
+
z-index: 1;
|
|
252
|
+
width: 10px;
|
|
253
|
+
color: color-mix(in srgb, var(--nrchkb-muted-text) 72%, transparent);
|
|
254
|
+
font-size: 9px;
|
|
255
|
+
line-height: 1;
|
|
256
|
+
text-align: center;
|
|
257
|
+
pointer-events: none;
|
|
258
|
+
transform: translateY(-50%);
|
|
259
|
+
transition: transform .18s ease, color .18s ease;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.nrchkb-editor .nrchkb-checkbox-label input[type="checkbox"]:checked + span i {
|
|
263
|
+
color: color-mix(in srgb, var(--nrchkb-toggle-on) 72%, #1f6f35);
|
|
264
|
+
transform: translate(14px, -50%);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.nrchkb-editor .form-row > label:not(.nrchkb-checkbox-label) i {
|
|
268
|
+
color: var(--nrchkb-muted-text);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.nrchkb-editor .nrchkb-warning {
|
|
272
|
+
margin: 0 0 10px;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.nrchkb-editor .nrchkb-advertiser-recommendation {
|
|
276
|
+
display: block;
|
|
277
|
+
flex: 1 1 100%;
|
|
278
|
+
margin: -2px 0 8px;
|
|
279
|
+
padding: 7px 9px;
|
|
280
|
+
border: 1px solid var(--nrchkb-section-border);
|
|
281
|
+
border-radius: 6px;
|
|
282
|
+
color: var(--nrchkb-muted-text);
|
|
283
|
+
background: color-mix(in srgb, var(--red-ui-tertiary-background, #f6f6f6) 78%, transparent);
|
|
284
|
+
line-height: 1.35;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.nrchkb-editor .nrchkb-advertiser-recommendation strong {
|
|
288
|
+
color: var(--red-ui-primary-text-color, #333);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.nrchkb-editor .nrchkb-pairing-card {
|
|
292
|
+
display: flex;
|
|
293
|
+
align-items: center;
|
|
294
|
+
gap: 12px;
|
|
295
|
+
min-width: 0;
|
|
296
|
+
margin: 0 0 8px;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.nrchkb-editor .nrchkb-pairing-setup-card,
|
|
300
|
+
.nrchkb-sidebar-pairing .nrchkb-pairing-setup-card {
|
|
301
|
+
flex: 0 0 220px;
|
|
302
|
+
width: 220px;
|
|
303
|
+
min-width: 0;
|
|
304
|
+
height: auto;
|
|
305
|
+
padding: 0;
|
|
306
|
+
border: 0;
|
|
307
|
+
background: transparent;
|
|
308
|
+
box-sizing: border-box;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.nrchkb-editor .nrchkb-pairing-qr,
|
|
312
|
+
.nrchkb-sidebar-pairing .nrchkb-pairing-qr {
|
|
313
|
+
display: block;
|
|
314
|
+
width: 100%;
|
|
315
|
+
height: auto;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.nrchkb-editor .nrchkb-pairing-details {
|
|
319
|
+
flex: 1 1 auto;
|
|
320
|
+
min-width: 0;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.nrchkb-editor .nrchkb-pairing-state {
|
|
324
|
+
margin: 0 0 6px;
|
|
325
|
+
font-weight: 600;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.nrchkb-editor .nrchkb-pairing-code,
|
|
329
|
+
.nrchkb-editor .nrchkb-pairing-uri {
|
|
330
|
+
display: block;
|
|
331
|
+
min-width: 0;
|
|
332
|
+
overflow-wrap: anywhere;
|
|
333
|
+
color: var(--nrchkb-muted-text);
|
|
334
|
+
font-family: monospace;
|
|
335
|
+
line-height: 1.35;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.nrchkb-editor .nrchkb-pairing-message {
|
|
339
|
+
margin: 0 0 8px;
|
|
340
|
+
color: var(--nrchkb-muted-text);
|
|
341
|
+
line-height: 1.35;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.nrchkb-sidebar-pairing {
|
|
345
|
+
height: 100%;
|
|
346
|
+
overflow-y: auto;
|
|
347
|
+
min-width: 0;
|
|
348
|
+
padding: 10px;
|
|
349
|
+
box-sizing: border-box;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.nrchkb-sidebar-pairing-status {
|
|
353
|
+
margin: 0 0 10px;
|
|
354
|
+
color: var(--nrchkb-muted-text);
|
|
355
|
+
line-height: 1.35;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.nrchkb-sidebar-pairing label.nrchkb-sidebar-setting {
|
|
359
|
+
display: grid !important;
|
|
360
|
+
grid-template-columns: 16px minmax(0, 1fr);
|
|
361
|
+
align-items: center;
|
|
362
|
+
column-gap: 6px;
|
|
363
|
+
margin: 0 0 10px !important;
|
|
364
|
+
line-height: 16px;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.nrchkb-sidebar-pairing label.nrchkb-sidebar-setting.nrchkb-checkbox-label {
|
|
368
|
+
display: inline-flex !important;
|
|
369
|
+
gap: 8px;
|
|
370
|
+
line-height: 1.35;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.nrchkb-sidebar-pairing label.nrchkb-sidebar-setting input {
|
|
374
|
+
width: 16px;
|
|
375
|
+
height: 16px;
|
|
376
|
+
margin: 0 !important;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.nrchkb-sidebar-pairing label.nrchkb-sidebar-setting.nrchkb-checkbox-label input[type="checkbox"] {
|
|
380
|
+
width: 36px;
|
|
381
|
+
height: 22px;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.nrchkb-sidebar-pairing label.nrchkb-sidebar-setting span {
|
|
385
|
+
display: block;
|
|
386
|
+
min-width: 0;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.nrchkb-sidebar-pairing-card {
|
|
390
|
+
display: grid;
|
|
391
|
+
grid-template-rows: auto auto auto 1fr;
|
|
392
|
+
margin: 0 0 12px;
|
|
393
|
+
padding: 10px;
|
|
394
|
+
border: 1px solid var(--nrchkb-section-border);
|
|
395
|
+
border-radius: 7px;
|
|
396
|
+
background: var(--nrchkb-section-bg);
|
|
397
|
+
container-type: inline-size;
|
|
398
|
+
min-width: 0;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.nrchkb-sidebar-pairing-card h3 {
|
|
402
|
+
margin: 0 0 8px;
|
|
403
|
+
font-size: 14px;
|
|
404
|
+
line-height: 1.35;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.nrchkb-sidebar-pairing-list {
|
|
408
|
+
display: grid;
|
|
409
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
|
|
410
|
+
gap: 10px;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.nrchkb-sidebar-pairing .nrchkb-pairing-setup-card {
|
|
414
|
+
margin: 0 auto 8px;
|
|
415
|
+
width: min(100%, 220px);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.nrchkb-sidebar-meta {
|
|
419
|
+
display: grid;
|
|
420
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
421
|
+
gap: 3px 8px;
|
|
422
|
+
min-height: 66px;
|
|
423
|
+
margin: 0 0 8px;
|
|
424
|
+
color: var(--nrchkb-muted-text);
|
|
425
|
+
font-size: 12px;
|
|
426
|
+
line-height: 1.35;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.nrchkb-sidebar-meta dt {
|
|
430
|
+
margin: 0;
|
|
431
|
+
font-weight: 600;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.nrchkb-sidebar-meta dd {
|
|
435
|
+
min-width: 0;
|
|
436
|
+
margin: 0;
|
|
437
|
+
overflow-wrap: anywhere;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.nrchkb-sidebar-accessories {
|
|
441
|
+
margin: 8px 0 0;
|
|
442
|
+
padding: 8px 0 0;
|
|
443
|
+
border-top: 1px solid var(--nrchkb-section-border);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.nrchkb-sidebar-accessories-title {
|
|
447
|
+
margin: 0 0 5px;
|
|
448
|
+
color: var(--nrchkb-muted-text);
|
|
449
|
+
font-size: 12px;
|
|
450
|
+
font-weight: 600;
|
|
451
|
+
line-height: 1.35;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.nrchkb-sidebar-accessories ul {
|
|
455
|
+
margin: 0;
|
|
456
|
+
padding: 0;
|
|
457
|
+
color: var(--nrchkb-muted-text);
|
|
458
|
+
line-height: 1.4;
|
|
459
|
+
list-style: none;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.nrchkb-sidebar-accessories li {
|
|
463
|
+
margin: 0 0 7px;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.nrchkb-sidebar-accessory-name {
|
|
467
|
+
display: block;
|
|
468
|
+
color: var(--red-ui-primary-text-color, #333);
|
|
469
|
+
font-weight: 600;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.nrchkb-sidebar-accessory-meta {
|
|
473
|
+
display: block;
|
|
474
|
+
overflow-wrap: anywhere;
|
|
475
|
+
font-size: 12px;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.nrchkb-sidebar-pairing-control {
|
|
479
|
+
margin: 0 0 10px;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.nrchkb-sidebar-toolbar {
|
|
483
|
+
display: flex;
|
|
484
|
+
justify-content: flex-end;
|
|
485
|
+
gap: 6px;
|
|
486
|
+
padding: 6px;
|
|
487
|
+
border-top: 1px solid var(--nrchkb-section-border);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.fa-nrchkb::before {
|
|
491
|
+
content: "";
|
|
492
|
+
display: inline-block;
|
|
493
|
+
width: 1.25em;
|
|
494
|
+
height: 1.25em;
|
|
495
|
+
vertical-align: -20%;
|
|
496
|
+
background-image: url("icons/node-red-contrib-homekit-bridged/nrchkb.png");
|
|
497
|
+
background-size: contain;
|
|
498
|
+
background-position: center;
|
|
499
|
+
background-repeat: no-repeat;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.nrchkb-help {
|
|
503
|
+
--nrchkb-help-border: var(--red-ui-secondary-border-color, #d8d8d8);
|
|
504
|
+
--nrchkb-help-bg: var(--red-ui-secondary-background, #fff);
|
|
505
|
+
--nrchkb-help-heading-bg: var(--red-ui-tertiary-background, #f6f6f6);
|
|
506
|
+
--nrchkb-help-muted: var(--red-ui-secondary-text-color, #666);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
.nrchkb-help .nrchkb-help-section {
|
|
510
|
+
margin: 10px 0;
|
|
511
|
+
border: 1px solid var(--nrchkb-help-border);
|
|
512
|
+
border-radius: 7px;
|
|
513
|
+
background: var(--nrchkb-help-bg);
|
|
514
|
+
overflow: hidden;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.nrchkb-help .nrchkb-help-section > h4 {
|
|
518
|
+
margin: 0;
|
|
519
|
+
padding: 7px 10px;
|
|
520
|
+
font-size: 13px;
|
|
521
|
+
line-height: 1.35;
|
|
522
|
+
background: var(--nrchkb-help-heading-bg);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.nrchkb-help .nrchkb-help-section > h4 i {
|
|
526
|
+
width: 14px;
|
|
527
|
+
margin-right: 6px;
|
|
528
|
+
color: var(--nrchkb-help-muted);
|
|
529
|
+
text-align: center;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.nrchkb-help .nrchkb-help-section-body {
|
|
533
|
+
padding: 8px 10px;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
.nrchkb-help .nrchkb-help-section-body > :first-child {
|
|
537
|
+
margin-top: 0;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
.nrchkb-help .nrchkb-help-section-body > :last-child {
|
|
541
|
+
margin-bottom: 0;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.nrchkb-help .nrchkb-advertiser-dynamic {
|
|
545
|
+
margin: 8px 0;
|
|
546
|
+
padding: 7px 9px;
|
|
547
|
+
border-left: 3px solid var(--red-ui-primary-background, #8f0000);
|
|
548
|
+
border-radius: 4px;
|
|
549
|
+
background: color-mix(in srgb, var(--red-ui-tertiary-background, #f6f6f6) 78%, transparent);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.nrchkb-editor .nrchkb-characteristic-item {
|
|
553
|
+
overflow: hidden;
|
|
554
|
+
white-space: normal;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.nrchkb-editor .nrchkb-characteristic-row {
|
|
558
|
+
display: flex;
|
|
559
|
+
align-items: center;
|
|
560
|
+
gap: 10px;
|
|
561
|
+
min-width: 0;
|
|
562
|
+
margin-bottom: 8px;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.nrchkb-editor .nrchkb-characteristic-fields {
|
|
566
|
+
margin-top: 8px;
|
|
567
|
+
min-width: 0;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.nrchkb-editor .nrchkb-characteristic-label {
|
|
571
|
+
flex: 0 1 120px;
|
|
572
|
+
min-width: 98px;
|
|
573
|
+
max-width: min(120px, 42%);
|
|
574
|
+
padding-left: 10px;
|
|
575
|
+
text-align: left;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
.nrchkb-editor .nrchkb-characteristic-label i,
|
|
579
|
+
.nrchkb-editor .nrchkb-characteristic-property-label i,
|
|
580
|
+
.nrchkb-editor .properties-accordion h3 i {
|
|
581
|
+
width: 14px;
|
|
582
|
+
color: var(--nrchkb-muted-text);
|
|
583
|
+
text-align: center;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
.nrchkb-editor .nrchkb-characteristic-row input,
|
|
587
|
+
.nrchkb-editor .nrchkb-characteristic-row select,
|
|
588
|
+
.nrchkb-editor .nrchkb-characteristic-fields input,
|
|
589
|
+
.nrchkb-editor .nrchkb-characteristic-fields select {
|
|
590
|
+
flex: 1 1 0;
|
|
591
|
+
min-width: 0;
|
|
592
|
+
width: auto !important;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.nrchkb-editor .nrchkb-characteristic-fields .property-validValueRanges {
|
|
596
|
+
flex: 1 1 0;
|
|
597
|
+
min-width: 0;
|
|
598
|
+
max-width: 100%;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
.nrchkb-editor #node-input-customCharacteristics-container {
|
|
602
|
+
width: 100%;
|
|
603
|
+
min-width: 0;
|
|
604
|
+
min-height: 150px;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
.nrchkb-editor ol#node-input-customCharacteristics-container .red-ui-typedInput-container {
|
|
608
|
+
flex: 1;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
.nrchkb-editor .node-input-customCharacteristics-container-row {
|
|
612
|
+
display: block;
|
|
613
|
+
min-width: 0;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
.nrchkb-editor .red-ui-editableList,
|
|
617
|
+
.nrchkb-editor .red-ui-editableList-border,
|
|
618
|
+
.nrchkb-editor .red-ui-editableList-container,
|
|
619
|
+
.nrchkb-editor .red-ui-editableList-list,
|
|
620
|
+
.nrchkb-editor .red-ui-editableList-list > li {
|
|
621
|
+
min-width: 0;
|
|
622
|
+
max-width: 100%;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
.nrchkb-editor .ui-slider .ui-slider-handle {
|
|
626
|
+
background: var(--nrchkb-accent);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
@supports not (color: color-mix(in srgb, #000 50%, #fff)) {
|
|
630
|
+
.nrchkb-editor {
|
|
631
|
+
--nrchkb-toggle-off: var(--red-ui-tertiary-background, #f6f6f6);
|
|
632
|
+
--nrchkb-toggle-border: var(--red-ui-secondary-border-color, #d8d8d8);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
.nrchkb-editor .nrchkb-section,
|
|
636
|
+
.nrchkb-editor .nrchkb-nested-panel,
|
|
637
|
+
.nrchkb-editor .nrchkb-section > summary,
|
|
638
|
+
.nrchkb-editor .nrchkb-advertiser-recommendation,
|
|
639
|
+
.nrchkb-help .nrchkb-advertiser-dynamic {
|
|
640
|
+
background: var(--red-ui-secondary-background, #fff);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.nrchkb-editor input[type="checkbox"]:checked {
|
|
644
|
+
border-color: #1f8f3d;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
.nrchkb-editor .nrchkb-checkbox-label i {
|
|
648
|
+
color: var(--nrchkb-muted-text);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.nrchkb-editor .nrchkb-checkbox-label input[type="checkbox"]:checked + span i {
|
|
652
|
+
color: #1f6f35;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
@container (max-width: 520px) {
|
|
657
|
+
.nrchkb-editor .form-row {
|
|
658
|
+
align-items: stretch;
|
|
659
|
+
flex-direction: column;
|
|
660
|
+
gap: 4px;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
.nrchkb-editor .form-row > label:first-child {
|
|
664
|
+
flex: 0 0 auto;
|
|
665
|
+
max-width: 100%;
|
|
666
|
+
min-width: 0;
|
|
667
|
+
width: auto;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
.nrchkb-editor .form-row > input[type="text"],
|
|
671
|
+
.nrchkb-editor .form-row > input[type="number"],
|
|
672
|
+
.nrchkb-editor .form-row > input:not([type]),
|
|
673
|
+
.nrchkb-editor .form-row > select,
|
|
674
|
+
.nrchkb-editor .form-row > .red-ui-typedInput-container,
|
|
675
|
+
.nrchkb-editor .form-row > div[style*="inline-flex"] {
|
|
676
|
+
flex: 0 0 auto;
|
|
677
|
+
width: 100% !important;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
.nrchkb-editor .nrchkb-checkbox-row {
|
|
681
|
+
display: block;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.nrchkb-editor .nrchkb-characteristic-row {
|
|
685
|
+
align-items: stretch;
|
|
686
|
+
flex-direction: column;
|
|
687
|
+
gap: 4px;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.nrchkb-editor .nrchkb-characteristic-label {
|
|
691
|
+
flex: 0 0 auto;
|
|
692
|
+
max-width: 100%;
|
|
693
|
+
min-width: 0;
|
|
694
|
+
padding-left: 0;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.nrchkb-editor .nrchkb-pairing-card {
|
|
698
|
+
align-items: flex-start;
|
|
699
|
+
flex-direction: column;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
@supports not (container-type: inline-size) {
|
|
704
|
+
@media (max-width: 520px) {
|
|
705
|
+
.nrchkb-editor .form-row {
|
|
706
|
+
align-items: stretch;
|
|
707
|
+
flex-direction: column;
|
|
708
|
+
gap: 4px;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
.nrchkb-editor .form-row > label:first-child {
|
|
712
|
+
flex: 0 0 auto;
|
|
713
|
+
max-width: 100%;
|
|
714
|
+
min-width: 0;
|
|
715
|
+
width: auto;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
.nrchkb-editor .form-row > input[type="text"],
|
|
719
|
+
.nrchkb-editor .form-row > input[type="number"],
|
|
720
|
+
.nrchkb-editor .form-row > input:not([type]),
|
|
721
|
+
.nrchkb-editor .form-row > select,
|
|
722
|
+
.nrchkb-editor .form-row > .red-ui-typedInput-container,
|
|
723
|
+
.nrchkb-editor .form-row > div[style*="inline-flex"] {
|
|
724
|
+
flex: 0 0 auto;
|
|
725
|
+
width: 100% !important;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
.nrchkb-editor .nrchkb-checkbox-row {
|
|
729
|
+
display: block;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
.nrchkb-editor .nrchkb-characteristic-row {
|
|
733
|
+
align-items: stretch;
|
|
734
|
+
flex-direction: column;
|
|
735
|
+
gap: 4px;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
.nrchkb-editor .nrchkb-characteristic-label {
|
|
739
|
+
flex: 0 0 auto;
|
|
740
|
+
max-width: 100%;
|
|
741
|
+
min-width: 0;
|
|
742
|
+
padding-left: 0;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
.nrchkb-editor .nrchkb-pairing-card {
|
|
746
|
+
align-items: flex-start;
|
|
747
|
+
flex-direction: column;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
.nrchkb-editor .alert {
|
|
3
753
|
position: relative;
|
|
4
754
|
padding: .75rem 1.25rem;
|
|
5
755
|
margin-bottom: 1rem;
|
|
6
|
-
border: 1px solid
|
|
756
|
+
border: 1px solid var(--nrchkb-section-border);
|
|
7
757
|
border-radius: .25rem;
|
|
8
|
-
margin-right:
|
|
758
|
+
margin-right: 0;
|
|
759
|
+
color: var(--red-ui-primary-text-color, #333);
|
|
760
|
+
background: var(--red-ui-tertiary-background, #f6f6f6);
|
|
9
761
|
}
|
|
10
762
|
|
|
11
|
-
.alert-warning {
|
|
12
|
-
color:
|
|
13
|
-
background
|
|
14
|
-
border-color:
|
|
763
|
+
.nrchkb-editor .alert-warning {
|
|
764
|
+
color: var(--nrchkb-warning-text);
|
|
765
|
+
background: var(--nrchkb-warning-bg);
|
|
766
|
+
border-color: var(--nrchkb-warning-border);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
.nrchkb-editor .alert-info {
|
|
770
|
+
color: var(--nrchkb-info-text);
|
|
771
|
+
background: var(--nrchkb-info-bg);
|
|
772
|
+
border-color: var(--nrchkb-info-border);
|
|
15
773
|
}
|
|
16
774
|
</style>
|
|
17
775
|
|
|
18
776
|
<script type="text/javascript">
|
|
19
|
-
const
|
|
20
|
-
//NRCHKB Custom Characteristics
|
|
21
|
-
$.ajax({
|
|
22
|
-
url: 'nrchkb/config',
|
|
23
|
-
dataType: 'json',
|
|
24
|
-
async: false,
|
|
25
|
-
success: function (data) {
|
|
26
|
-
nrchkbConfig = data
|
|
27
|
-
},
|
|
28
|
-
})
|
|
29
|
-
|
|
777
|
+
const registerNRCHKBConfigNode = function () {
|
|
30
778
|
RED.nodes.registerType('nrchkb', {
|
|
31
779
|
category: 'Apple HomeKit',
|
|
32
780
|
icon: 'nrchkb.png',
|
|
@@ -43,6 +791,9 @@
|
|
|
43
791
|
labelStyle: function () {
|
|
44
792
|
return 'node_label_italic'
|
|
45
793
|
},
|
|
794
|
+
onadd: function () {
|
|
795
|
+
applyDefaultNodeDocumentation(this, 'nrchkb')
|
|
796
|
+
},
|
|
46
797
|
oneditsave: function () {
|
|
47
798
|
const self = this
|
|
48
799
|
saveCustomCharacteristics(self)
|
|
@@ -55,56 +806,75 @@
|
|
|
55
806
|
types: ["json"]
|
|
56
807
|
})*/
|
|
57
808
|
|
|
58
|
-
$('#node-input-customCharacteristics-container').
|
|
809
|
+
$('#node-input-customCharacteristics-container').editableList({
|
|
59
810
|
addItem: function (container, i, opt) {
|
|
60
811
|
$('.properties-accordion').accordion('option', 'active', false)
|
|
61
812
|
|
|
62
813
|
const {name, UUID, ...props} = opt
|
|
63
814
|
|
|
64
|
-
container.
|
|
65
|
-
overflow: 'hidden',
|
|
66
|
-
whiteSpace: 'nowrap'
|
|
67
|
-
})
|
|
815
|
+
container.addClass('nrchkb-characteristic-item')
|
|
68
816
|
|
|
69
817
|
const fragment = document.createDocumentFragment()
|
|
70
|
-
const row1 = $('<div/>', {
|
|
71
|
-
const row2 = $('<div/>', {
|
|
72
|
-
const row3 = $('<div/>', {
|
|
818
|
+
const row1 = $('<div/>', {class: 'nrchkb-characteristic-row'}).appendTo(fragment)
|
|
819
|
+
const row2 = $('<div/>', {class: 'nrchkb-characteristic-row'}).appendTo(fragment)
|
|
820
|
+
const row3 = $('<div/>', {class: 'nrchkb-characteristic-fields'}).appendTo(fragment)
|
|
821
|
+
const fieldIdPrefix = 'node-input-customCharacteristic-' + i + '-'
|
|
822
|
+
const createFieldId = function (name) {
|
|
823
|
+
return fieldIdPrefix + name
|
|
824
|
+
}
|
|
825
|
+
const setIconLabelText = function (target, iconClass, text) {
|
|
826
|
+
target.empty()
|
|
827
|
+
$('<i/>', {class: 'fa ' + iconClass}).appendTo(target)
|
|
828
|
+
target.append(document.createTextNode(' ' + text))
|
|
829
|
+
}
|
|
830
|
+
const createPropertyLabel = function (attrs, iconClass, text) {
|
|
831
|
+
const label = $('<label/>', attrs)
|
|
832
|
+
setIconLabelText(label, iconClass, text)
|
|
833
|
+
return label
|
|
834
|
+
}
|
|
73
835
|
|
|
74
|
-
|
|
75
|
-
|
|
836
|
+
const uuidFieldId = createFieldId('uuid')
|
|
837
|
+
const uuidLabel = $('<label/>', {
|
|
838
|
+
class: 'nrchkb-characteristic-label',
|
|
839
|
+
for: uuidFieldId,
|
|
76
840
|
required: 'required'
|
|
77
841
|
})
|
|
78
|
-
.text('UUID ')
|
|
79
842
|
.appendTo(row1)
|
|
80
|
-
|
|
843
|
+
setIconLabelText(uuidLabel, 'fa-key', 'UUID')
|
|
844
|
+
$('<input/>', {class: 'property-uuid', id: uuidFieldId, type: 'text'})
|
|
81
845
|
.val(UUID ? UUID : uuidv4)
|
|
82
846
|
.appendTo(row1)
|
|
83
847
|
|
|
84
|
-
|
|
85
|
-
|
|
848
|
+
const nameFieldId = createFieldId('name')
|
|
849
|
+
const nameLabel = $('<label/>', {
|
|
850
|
+
class: 'nrchkb-characteristic-label',
|
|
851
|
+
for: nameFieldId,
|
|
86
852
|
required: 'required'
|
|
87
853
|
})
|
|
88
|
-
.text('Name ')
|
|
89
854
|
.appendTo(row2)
|
|
90
|
-
|
|
855
|
+
setIconLabelText(nameLabel, 'fa-tag', 'Name')
|
|
856
|
+
$('<input/>', {class: 'property-name', id: nameFieldId, type: 'text'})
|
|
91
857
|
.val(name)
|
|
92
858
|
.appendTo(row2)
|
|
93
859
|
|
|
94
860
|
const row3_properties_accordion = $('<div/>', {class: 'properties-accordion'})
|
|
95
861
|
.appendTo(row3)
|
|
96
862
|
|
|
97
|
-
$('<h3/>')
|
|
863
|
+
$('<h3/>')
|
|
864
|
+
.append($('<i/>', {class: 'fa fa-sliders'}), document.createTextNode(' Properties'))
|
|
865
|
+
.appendTo(row3_properties_accordion)
|
|
98
866
|
|
|
99
867
|
const row3_properties = $('<div/>', {class: 'properties'}).appendTo(row3_properties_accordion)
|
|
100
868
|
|
|
101
869
|
const formatRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
870
|
+
const formatFieldId = createFieldId('format')
|
|
871
|
+
createPropertyLabel({
|
|
872
|
+
class: 'nrchkb-characteristic-property-label',
|
|
873
|
+
for: formatFieldId
|
|
874
|
+
}, 'fa-code', 'Format *').appendTo(formatRow)
|
|
106
875
|
const formatInput = $('<select/>', {
|
|
107
876
|
class: 'property-format',
|
|
877
|
+
id: formatFieldId,
|
|
108
878
|
required: 'required'
|
|
109
879
|
}).appendTo(formatRow)
|
|
110
880
|
$('<option/>').val(undefined).text('Choose...').appendTo(formatInput)
|
|
@@ -123,8 +893,12 @@
|
|
|
123
893
|
formatInput.val(props.format)
|
|
124
894
|
|
|
125
895
|
const unitRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
126
|
-
|
|
127
|
-
|
|
896
|
+
const unitFieldId = createFieldId('unit')
|
|
897
|
+
createPropertyLabel({
|
|
898
|
+
class: 'nrchkb-characteristic-property-label',
|
|
899
|
+
for: unitFieldId
|
|
900
|
+
}, 'fa-balance-scale', 'Unit').appendTo(unitRow)
|
|
901
|
+
const unitSelect = $('<select/>', {class: 'property-unit', id: unitFieldId}).appendTo(unitRow)
|
|
128
902
|
$('<option/>').val(undefined).text('Choose...').appendTo(unitSelect)
|
|
129
903
|
$('<option/>').val('celsius').text('CELSIUS').appendTo(unitSelect)
|
|
130
904
|
$('<option/>').val('percentage').text('PERCENTAGE').appendTo(unitSelect)
|
|
@@ -134,12 +908,14 @@
|
|
|
134
908
|
unitSelect.val(props.unit)
|
|
135
909
|
|
|
136
910
|
const permsRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
911
|
+
const permsFieldId = createFieldId('perms')
|
|
912
|
+
createPropertyLabel({
|
|
913
|
+
class: 'nrchkb-characteristic-property-label',
|
|
914
|
+
for: permsFieldId
|
|
915
|
+
}, 'fa-lock', 'Permissions').appendTo(permsRow)
|
|
141
916
|
const permsSelect = $('<select/>', {
|
|
142
917
|
class: 'property-perms',
|
|
918
|
+
id: permsFieldId,
|
|
143
919
|
multiple: 'multiple'
|
|
144
920
|
}).appendTo(permsRow)
|
|
145
921
|
$('<option/>').val('pr').text('PAIRED_READ / READ').appendTo(permsSelect)
|
|
@@ -152,93 +928,113 @@
|
|
|
152
928
|
permsSelect.val(props.perms)
|
|
153
929
|
|
|
154
930
|
const evRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
931
|
+
const evFieldId = createFieldId('ev')
|
|
932
|
+
createPropertyLabel({
|
|
933
|
+
class: 'nrchkb-characteristic-property-label',
|
|
934
|
+
for: evFieldId
|
|
935
|
+
}, 'fa-bell-o', 'Event Notifications').appendTo(evRow)
|
|
159
936
|
$('<input/>', {
|
|
160
937
|
class: 'property-ev',
|
|
938
|
+
id: evFieldId,
|
|
161
939
|
type: 'checkbox',
|
|
162
940
|
checked: props.ev
|
|
163
941
|
}).appendTo(evRow)
|
|
164
942
|
|
|
165
943
|
const descriptionRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
944
|
+
const descriptionFieldId = createFieldId('description')
|
|
945
|
+
createPropertyLabel({
|
|
946
|
+
class: 'nrchkb-characteristic-property-label',
|
|
947
|
+
for: descriptionFieldId
|
|
948
|
+
}, 'fa-align-left', 'Description').appendTo(descriptionRow)
|
|
170
949
|
$('<input/>', {
|
|
171
950
|
class: 'property-description',
|
|
951
|
+
id: descriptionFieldId,
|
|
172
952
|
type: 'text'
|
|
173
953
|
}).appendTo(descriptionRow).val(props.description)
|
|
174
954
|
|
|
175
955
|
const minValueRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
956
|
+
const minValueFieldId = createFieldId('minValue')
|
|
957
|
+
createPropertyLabel({
|
|
958
|
+
class: 'nrchkb-characteristic-property-label',
|
|
959
|
+
for: minValueFieldId
|
|
960
|
+
}, 'fa-sort-numeric-asc', 'Minimum Value').appendTo(minValueRow)
|
|
180
961
|
$('<input/>', {
|
|
181
962
|
class: 'property-minValue',
|
|
963
|
+
id: minValueFieldId,
|
|
182
964
|
type: 'number'
|
|
183
965
|
}).appendTo(minValueRow).val(props.minValue)
|
|
184
966
|
|
|
185
967
|
const maxValueRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
968
|
+
const maxValueFieldId = createFieldId('maxValue')
|
|
969
|
+
createPropertyLabel({
|
|
970
|
+
class: 'nrchkb-characteristic-property-label',
|
|
971
|
+
for: maxValueFieldId
|
|
972
|
+
}, 'fa-sort-numeric-desc', 'Maximum Value').appendTo(maxValueRow)
|
|
190
973
|
$('<input/>', {
|
|
191
974
|
class: 'property-maxValue',
|
|
975
|
+
id: maxValueFieldId,
|
|
192
976
|
type: 'number'
|
|
193
977
|
}).appendTo(maxValueRow).val(props.maxValue)
|
|
194
978
|
|
|
195
979
|
const minStepRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
980
|
+
const minStepFieldId = createFieldId('minStep')
|
|
981
|
+
createPropertyLabel({
|
|
982
|
+
class: 'nrchkb-characteristic-property-label',
|
|
983
|
+
for: minStepFieldId
|
|
984
|
+
}, 'fa-step-forward', 'Minimum Step').appendTo(minStepRow)
|
|
200
985
|
$('<input/>', {
|
|
201
986
|
class: 'property-minStep',
|
|
987
|
+
id: minStepFieldId,
|
|
202
988
|
type: 'number'
|
|
203
989
|
}).appendTo(minStepRow).val(props.minStep)
|
|
204
990
|
|
|
205
991
|
const maxLenRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
992
|
+
const maxLenFieldId = createFieldId('maxLen')
|
|
993
|
+
createPropertyLabel({
|
|
994
|
+
class: 'nrchkb-characteristic-property-label',
|
|
995
|
+
for: maxLenFieldId
|
|
996
|
+
}, 'fa-text-width', 'Maximum Length').appendTo(maxLenRow)
|
|
210
997
|
$('<input/>', {
|
|
211
998
|
class: 'property-maxLen',
|
|
999
|
+
id: maxLenFieldId,
|
|
212
1000
|
type: 'number'
|
|
213
1001
|
}).appendTo(maxLenRow).val(props.maxLen)
|
|
214
1002
|
|
|
215
1003
|
const maxDataLenRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
1004
|
+
const maxDataLenFieldId = createFieldId('maxDataLen')
|
|
1005
|
+
createPropertyLabel({
|
|
1006
|
+
class: 'nrchkb-characteristic-property-label',
|
|
1007
|
+
for: maxDataLenFieldId
|
|
1008
|
+
}, 'fa-database', 'Maximum Data Length').appendTo(maxDataLenRow)
|
|
220
1009
|
$('<input/>', {
|
|
221
1010
|
class: 'property-maxDataLen',
|
|
1011
|
+
id: maxDataLenFieldId,
|
|
222
1012
|
type: 'number'
|
|
223
1013
|
}).appendTo(maxDataLenRow).val(props.maxDataLen)
|
|
224
1014
|
|
|
225
1015
|
const validValuesRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
1016
|
+
const validValuesFieldId = createFieldId('validValues')
|
|
1017
|
+
createPropertyLabel({
|
|
1018
|
+
class: 'nrchkb-characteristic-property-label',
|
|
1019
|
+
for: validValuesFieldId
|
|
1020
|
+
}, 'fa-check-square-o', 'Valid Values').appendTo(validValuesRow)
|
|
230
1021
|
$('<input/>', {
|
|
231
1022
|
class: 'property-validValues',
|
|
1023
|
+
id: validValuesFieldId,
|
|
232
1024
|
type: 'text'
|
|
233
1025
|
}).appendTo(validValuesRow).val(props.validValues)
|
|
234
1026
|
|
|
235
1027
|
const validValueRangesRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
1028
|
+
const validValueRangesFieldId = createFieldId('validValueRanges')
|
|
1029
|
+
const validValueRangesLabelId = validValueRangesFieldId + '-label'
|
|
1030
|
+
const validValueRangesLabel = createPropertyLabel({
|
|
1031
|
+
class: 'nrchkb-characteristic-property-label property-validValueRanges-label',
|
|
1032
|
+
id: validValueRangesLabelId,
|
|
1033
|
+
}, 'fa-sliders', 'Valid Value Ranges').appendTo(validValueRangesRow)
|
|
240
1034
|
const validValueRangesSlider = $('<div/>', {
|
|
241
1035
|
class: 'property-validValueRanges',
|
|
1036
|
+
id: validValueRangesFieldId,
|
|
1037
|
+
'aria-labelledby': validValueRangesLabelId,
|
|
242
1038
|
type: 'text'
|
|
243
1039
|
}).appendTo(validValueRangesRow)
|
|
244
1040
|
|
|
@@ -248,18 +1044,22 @@
|
|
|
248
1044
|
max: 500,
|
|
249
1045
|
values: props.validValueRanges,
|
|
250
1046
|
slide: function (event, ui) {
|
|
251
|
-
validValueRangesLabel
|
|
1047
|
+
setIconLabelText(validValueRangesLabel, 'fa-sliders', 'Valid Value Ranges: [' + ui.values[0] + ', ' + ui.values[1] + ']')
|
|
252
1048
|
}
|
|
253
1049
|
})
|
|
254
|
-
|
|
1050
|
+
validValueRangesSlider.find('.ui-slider-handle')
|
|
1051
|
+
.attr('aria-labelledby', validValueRangesLabelId)
|
|
1052
|
+
setIconLabelText(validValueRangesLabel, 'fa-sliders', 'Valid Value Ranges: [' + validValueRangesSlider.slider('values', 0) + ', ' + validValueRangesSlider.slider('values', 1) + ']')
|
|
255
1053
|
|
|
256
1054
|
const adminOnlyAccessRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
1055
|
+
const adminOnlyAccessFieldId = createFieldId('adminOnlyAccess')
|
|
1056
|
+
createPropertyLabel({
|
|
1057
|
+
class: 'nrchkb-characteristic-property-label',
|
|
1058
|
+
for: adminOnlyAccessFieldId
|
|
1059
|
+
}, 'fa-user-secret', 'Admin Only Access').appendTo(adminOnlyAccessRow)
|
|
261
1060
|
const adminOnlyAccessSelect = $('<select/>', {
|
|
262
1061
|
class: 'property-adminOnlyAccess',
|
|
1062
|
+
id: adminOnlyAccessFieldId,
|
|
263
1063
|
multiple: 'multiple'
|
|
264
1064
|
}).appendTo(adminOnlyAccessRow)
|
|
265
1065
|
$('<option/>').val(0).text('READ').appendTo(adminOnlyAccessSelect)
|
|
@@ -279,24 +1079,47 @@
|
|
|
279
1079
|
sortable: true
|
|
280
1080
|
})
|
|
281
1081
|
|
|
282
|
-
|
|
283
|
-
|
|
1082
|
+
const customCharacteristics = Array.isArray(nrchkbConfig.customCharacteristics)
|
|
1083
|
+
? nrchkbConfig.customCharacteristics
|
|
1084
|
+
: []
|
|
1085
|
+
|
|
1086
|
+
for (let i = 0; i < customCharacteristics.length; i++) {
|
|
1087
|
+
const customCharacteristic = customCharacteristics[i]
|
|
284
1088
|
$('#node-input-customCharacteristics-container').editableList('addItem', customCharacteristic)
|
|
285
1089
|
}
|
|
286
1090
|
},
|
|
287
1091
|
oneditresize: function (size) {
|
|
288
|
-
const rows = $('#dialog-form>
|
|
1092
|
+
const rows = $('#dialog-form .nrchkb-section > summary, #dialog-form .nrchkb-section-body > .form-row:not(.node-input-customCharacteristics-container-row)')
|
|
289
1093
|
let height = size.height
|
|
290
1094
|
for (let i = 0; i < rows.length; i++) {
|
|
291
1095
|
height -= $(rows[i]).outerHeight(true)
|
|
292
1096
|
}
|
|
293
|
-
const editorRow = $('#dialog-form
|
|
1097
|
+
const editorRow = $('#dialog-form .node-input-customCharacteristics-container-row')
|
|
294
1098
|
height -= (parseInt(editorRow.css('marginTop')) + parseInt(editorRow.css('marginBottom')))
|
|
295
1099
|
height += 16
|
|
296
|
-
$('#node-input-customCharacteristics-container').editableList('height', height)
|
|
1100
|
+
$('#node-input-customCharacteristics-container').editableList('height', Math.max(150, height))
|
|
297
1101
|
}
|
|
298
1102
|
})
|
|
299
1103
|
}
|
|
1104
|
+
|
|
1105
|
+
const initNRCHKBConfigNode = function () {
|
|
1106
|
+
//NRCHKB Custom Characteristics
|
|
1107
|
+
$.ajax({
|
|
1108
|
+
url: 'nrchkb/config',
|
|
1109
|
+
dataType: 'json',
|
|
1110
|
+
})
|
|
1111
|
+
.done(function (data) {
|
|
1112
|
+
nrchkbConfig = data || {}
|
|
1113
|
+
if (!Array.isArray(nrchkbConfig.customCharacteristics)) {
|
|
1114
|
+
nrchkbConfig.customCharacteristics = []
|
|
1115
|
+
}
|
|
1116
|
+
})
|
|
1117
|
+
.fail(function () {
|
|
1118
|
+
nrchkbConfig = {customCharacteristics: []}
|
|
1119
|
+
RED.notify('Unable to load NRCHKB custom characteristics.', 'warning')
|
|
1120
|
+
})
|
|
1121
|
+
.always(registerNRCHKBConfigNode)
|
|
1122
|
+
}
|
|
300
1123
|
</script>
|
|
301
1124
|
|
|
302
1125
|
<script type="text/javascript">
|
|
@@ -304,15 +1127,18 @@
|
|
|
304
1127
|
return port !== 1880 && port >= 1 && port <= 65535 && port === port.toString()
|
|
305
1128
|
}
|
|
306
1129
|
|
|
1130
|
+
// biome-ignore lint/correctness/noUnusedVariables: Shared editor helper consumed by node templates loaded after nrchkb.html.
|
|
307
1131
|
const isValueDefined = function (value) {
|
|
308
1132
|
return 'undefined' === typeof value ? false : null !== value
|
|
309
1133
|
}
|
|
310
1134
|
|
|
311
|
-
|
|
312
|
-
let
|
|
1135
|
+
// biome-ignore lint/correctness/noUnusedVariables: Shared editor data consumed by service editors loaded after nrchkb.html.
|
|
1136
|
+
let serviceTypes = {}
|
|
1137
|
+
let accessoryCategories = {}
|
|
1138
|
+
// biome-ignore lint/correctness/noUnusedVariables: Shared editor data consumed by config and service editors loaded after nrchkb.html.
|
|
313
1139
|
let nrchkbVersion = '0.0.0'
|
|
314
|
-
let nrchkbExperimental = false
|
|
315
1140
|
let nrchkbConfig = {}
|
|
1141
|
+
let nrchkbAdvertiserRecommendation = null
|
|
316
1142
|
|
|
317
1143
|
//HomeKit Service Types
|
|
318
1144
|
$.getJSON('nrchkb/service/types', function (data) {
|
|
@@ -328,27 +1154,116 @@
|
|
|
328
1154
|
$.ajax({
|
|
329
1155
|
url: 'nrchkb/info',
|
|
330
1156
|
dataType: 'json',
|
|
331
|
-
|
|
332
|
-
|
|
1157
|
+
})
|
|
1158
|
+
.done(function (data) {
|
|
333
1159
|
nrchkbVersion = data.version
|
|
334
|
-
|
|
1160
|
+
})
|
|
1161
|
+
.fail(function () {
|
|
1162
|
+
RED.notify('Unable to load NRCHKB version information.', 'warning')
|
|
1163
|
+
})
|
|
1164
|
+
.always(initNRCHKBConfigNode)
|
|
335
1165
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
1166
|
+
const renderAdvertiserRecommendation = function (recommendation) {
|
|
1167
|
+
if (!recommendation || !recommendation.recommended) {
|
|
1168
|
+
return 'Recommendation unavailable.'
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
const caveats = Array.isArray(recommendation.caveats)
|
|
1172
|
+
? recommendation.caveats
|
|
1173
|
+
: []
|
|
1174
|
+
const caveatText = caveats.length
|
|
1175
|
+
? ' ' + caveats.join(' ')
|
|
1176
|
+
: ''
|
|
1177
|
+
const reason = recommendation.reason || ''
|
|
1178
|
+
const reasonText = reason.replace(/[.!?]+$/, '') + '.'
|
|
1179
|
+
|
|
1180
|
+
return (
|
|
1181
|
+
'<strong>' +
|
|
1182
|
+
recommendation.title +
|
|
1183
|
+
'</strong>: ' +
|
|
1184
|
+
reasonText +
|
|
1185
|
+
caveatText
|
|
1186
|
+
)
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
const updateAdvertiserHelpTemplates = function () {
|
|
1190
|
+
const renderedRecommendation = renderAdvertiserRecommendation(
|
|
1191
|
+
nrchkbAdvertiserRecommendation,
|
|
1192
|
+
)
|
|
1193
|
+
|
|
1194
|
+
$('script[data-help-name="homekit-bridge"], script[data-help-name="homekit-standalone"]').each(function () {
|
|
1195
|
+
this.innerHTML = this.innerHTML.replace(
|
|
1196
|
+
/<p class="nrchkb-advertiser-dynamic">[\s\S]*?<\/p>/,
|
|
1197
|
+
'<p class="nrchkb-advertiser-dynamic">' +
|
|
1198
|
+
renderedRecommendation +
|
|
1199
|
+
'</p>',
|
|
1200
|
+
)
|
|
1201
|
+
})
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
const applyAdvertiserRecommendation = function (select) {
|
|
1205
|
+
const selectAdvertiser = $(select)
|
|
1206
|
+
|
|
1207
|
+
if (!selectAdvertiser.length) {
|
|
1208
|
+
return
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
const recommendation = nrchkbAdvertiserRecommendation
|
|
1212
|
+
|
|
1213
|
+
selectAdvertiser.find('option').each(function () {
|
|
1214
|
+
const option = $(this)
|
|
1215
|
+
const originalText = option.data('nrchkbOriginalText') || option.text()
|
|
1216
|
+
option.data('nrchkbOriginalText', originalText)
|
|
1217
|
+
option.text(originalText)
|
|
1218
|
+
})
|
|
1219
|
+
|
|
1220
|
+
selectAdvertiser
|
|
1221
|
+
.closest('.form-row')
|
|
1222
|
+
.next('.nrchkb-advertiser-recommendation')
|
|
1223
|
+
.remove()
|
|
1224
|
+
|
|
1225
|
+
if (!recommendation || !recommendation.recommended) {
|
|
1226
|
+
return
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
const recommendedOption = selectAdvertiser.find(
|
|
1230
|
+
'option[value="' + recommendation.recommended + '"]',
|
|
1231
|
+
)
|
|
1232
|
+
|
|
1233
|
+
if (recommendedOption.length) {
|
|
1234
|
+
recommendedOption.text(
|
|
1235
|
+
recommendedOption.data('nrchkbOriginalText') + ' (recommended)',
|
|
1236
|
+
)
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
selectAdvertiser
|
|
1240
|
+
.closest('.form-row')
|
|
1241
|
+
.after(
|
|
1242
|
+
'<div class="nrchkb-advertiser-recommendation">' +
|
|
1243
|
+
renderAdvertiserRecommendation(recommendation) +
|
|
1244
|
+
'</div>',
|
|
1245
|
+
)
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
$.getJSON('nrchkb/advertiser/recommendation', function (data) {
|
|
1249
|
+
nrchkbAdvertiserRecommendation = data
|
|
1250
|
+
updateAdvertiserHelpTemplates()
|
|
1251
|
+
applyAdvertiserRecommendation('#node-config-input-advertiser')
|
|
340
1252
|
})
|
|
341
1253
|
|
|
1254
|
+
// biome-ignore lint/correctness/noUnusedVariables: Shared editor validator consumed by node templates loaded after nrchkb.html.
|
|
342
1255
|
const versionValidator = function (value) {
|
|
343
1256
|
return value ? /^(\d+\.)?(\d+\.)?(\.|\d+)$/.test(value) : true
|
|
344
1257
|
}
|
|
345
1258
|
|
|
1259
|
+
// biome-ignore lint/correctness/noUnusedVariables: Shared editor validator consumed by config node templates loaded after nrchkb.html.
|
|
346
1260
|
const hostNameValidator = function (value) {
|
|
347
1261
|
return value ? /^[^.]{1,64}$/.test(value) : false
|
|
348
1262
|
}
|
|
349
1263
|
|
|
1264
|
+
// biome-ignore lint/correctness/noUnusedVariables: Shared editor validator consumed by service templates loaded after nrchkb.html.
|
|
350
1265
|
const cameraConfigRequiredField = function (value) {
|
|
351
|
-
return 'CameraControl'
|
|
1266
|
+
return ['CameraControl', 'Camera'].includes(this.serviceName) ? (value || '').toString().trim() : true
|
|
352
1267
|
}
|
|
353
1268
|
|
|
354
1269
|
const saveCustomCharacteristics = function (self) {
|
|
@@ -440,6 +1355,7 @@
|
|
|
440
1355
|
return `${a}${b}${c}${d}-${e}${f}${g}${h}`
|
|
441
1356
|
}
|
|
442
1357
|
|
|
1358
|
+
// biome-ignore lint/correctness/noUnusedVariables: Shared editor validator consumed by config node templates loaded after nrchkb.html.
|
|
443
1359
|
const validatePinCode = function (value) {
|
|
444
1360
|
if (!value) {
|
|
445
1361
|
return false
|
|
@@ -451,23 +1367,743 @@
|
|
|
451
1367
|
|
|
452
1368
|
return !forbiddenPinCodes.includes(value.replaceAll('-', ''))
|
|
453
1369
|
}
|
|
454
|
-
</script>
|
|
455
1370
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
1371
|
+
// biome-ignore lint/correctness/noUnusedVariables: Shared editor helper consumed by node templates loaded after nrchkb.html.
|
|
1372
|
+
const sortSelectOptionsByLabel = function (select, options) {
|
|
1373
|
+
const selectElement = $(select)
|
|
1374
|
+
const sortedOptions = (options || []).sort(function (left, right) {
|
|
1375
|
+
return left.text.localeCompare(right.text, undefined, {
|
|
1376
|
+
numeric: true,
|
|
1377
|
+
sensitivity: 'base'
|
|
1378
|
+
})
|
|
1379
|
+
})
|
|
1380
|
+
|
|
1381
|
+
sortedOptions.forEach(function (option) {
|
|
1382
|
+
selectElement.append(option.element)
|
|
1383
|
+
})
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
const nrchkbLegacyDefaultNodeInfo = {
|
|
1387
|
+
'homekit-status': [
|
|
1388
|
+
'# HomeKit Status',
|
|
1389
|
+
'',
|
|
1390
|
+
'> [!NOTE]',
|
|
1391
|
+
'> Any input message reads the selected HomeKit service and emits a serialized service description.',
|
|
1392
|
+
].join('\n'),
|
|
1393
|
+
nrchkb: [
|
|
1394
|
+
'# NRCHKB Custom Characteristics',
|
|
1395
|
+
'',
|
|
1396
|
+
'> [!CAUTION]',
|
|
1397
|
+
'> Custom characteristics should be used only when a standard HAP characteristic cannot represent the device state.',
|
|
1398
|
+
].join('\n'),
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
const nrchkbDefaultNodeInfo = {
|
|
1402
|
+
'homekit-service2': [
|
|
1403
|
+
'# HomeKit Service 2',
|
|
1404
|
+
'',
|
|
1405
|
+
'> [!IMPORTANT]',
|
|
1406
|
+
'> Send characteristic updates as `msg.payload` keys using HomeKit characteristic names, for example `{\"On\": true}`.',
|
|
1407
|
+
'',
|
|
1408
|
+
'> [!NOTE]',
|
|
1409
|
+
'> Use the node Help sidebar for the full characteristic, plugin, and migration reference.',
|
|
1410
|
+
].join('\n'),
|
|
1411
|
+
'homekit-bridge': [
|
|
1412
|
+
'# HomeKit Bridge',
|
|
1413
|
+
'',
|
|
1414
|
+
'> [!IMPORTANT]',
|
|
1415
|
+
'> Keep the bridge name, PIN, serial number, and model stable after pairing. HomeKit uses them as accessory identity.',
|
|
1416
|
+
'',
|
|
1417
|
+
'> [!WARNING]',
|
|
1418
|
+
'> Leave insecure requests disabled unless you are deliberately testing with a trusted local client.',
|
|
1419
|
+
].join('\n'),
|
|
1420
|
+
'homekit-standalone': [
|
|
1421
|
+
'# HomeKit Standalone Accessory',
|
|
1422
|
+
'',
|
|
1423
|
+
'> [!IMPORTANT]',
|
|
1424
|
+
'> Standalone accessories publish independently. Prefer a bridge for most multi-service setups to reduce mDNS load.',
|
|
1425
|
+
'',
|
|
1426
|
+
'> [!WARNING]',
|
|
1427
|
+
'> Leave insecure requests disabled unless you are deliberately testing with a trusted local client.',
|
|
1428
|
+
].join('\n'),
|
|
1429
|
+
'homekit-service': [
|
|
1430
|
+
'# HomeKit Service',
|
|
1431
|
+
'',
|
|
1432
|
+
'> [!WARNING]',
|
|
1433
|
+
'> This legacy node is kept for existing flows. Use `homekit-service2` for new work and migrate when practical.',
|
|
1434
|
+
'',
|
|
1435
|
+
'> [!NOTE]',
|
|
1436
|
+
'> The migration button prepares changes in the editor. Click Done and then Deploy to commit them.',
|
|
1437
|
+
].join('\n'),
|
|
1438
|
+
'homekit-status': [
|
|
1439
|
+
'# HomeKit Status',
|
|
1440
|
+
'',
|
|
1441
|
+
'Reads the selected `homekit-service` or `homekit-service2` node and outputs a serialized description of its current HomeKit service shape.',
|
|
1442
|
+
'',
|
|
1443
|
+
'> [!NOTE]',
|
|
1444
|
+
'> Any input message triggers a fresh read. The incoming payload is not used as a command.',
|
|
1445
|
+
'',
|
|
1446
|
+
'Use this node for diagnostics, dashboards, migration checks, or flows that need to inspect which characteristics and values a service currently exposes.',
|
|
1447
|
+
'',
|
|
1448
|
+
'The output includes the selected service metadata, characteristics, properties, and current values where the runtime can read them.',
|
|
1449
|
+
].join('\n'),
|
|
1450
|
+
'homekit-unifi-controller': [
|
|
1451
|
+
'# UniFi Controller',
|
|
1452
|
+
'',
|
|
1453
|
+
'> [!IMPORTANT]',
|
|
1454
|
+
'> Use a dedicated local UniFi account with the minimum permissions needed to read Protect camera data.',
|
|
1455
|
+
'',
|
|
1456
|
+
'> [!WARNING]',
|
|
1457
|
+
'> Allow self-signed certificates only for controllers you administer and trust on your local network.',
|
|
1458
|
+
].join('\n'),
|
|
1459
|
+
'homekit-plugin-instance': [
|
|
1460
|
+
'# HomeKit Plugin Instance',
|
|
1461
|
+
'',
|
|
1462
|
+
'> [!NOTE]',
|
|
1463
|
+
'> This internal config node lets Node-RED track plugin dependencies used by NRCHKB service nodes.',
|
|
1464
|
+
].join('\n'),
|
|
1465
|
+
nrchkb: [
|
|
1466
|
+
'# NRCHKB Custom Characteristics',
|
|
1467
|
+
'',
|
|
1468
|
+
'Defines custom HAP characteristics that can be attached to HomeKit services when the standard HAP catalog does not contain the value you need.',
|
|
1469
|
+
'',
|
|
1470
|
+
'> [!CAUTION]',
|
|
1471
|
+
'> Custom characteristics should be used only when a standard HAP characteristic cannot represent the device state.',
|
|
1472
|
+
'',
|
|
1473
|
+
'Each entry needs a stable name and UUID. Changing either one after HomeKit has seen it can make the characteristic appear as a different field to clients.',
|
|
1474
|
+
'',
|
|
1475
|
+
'Configure value format, permissions, unit, min/max range, step size, and valid values to match the data your flow sends. The runtime loads these definitions before services are built.',
|
|
1476
|
+
].join('\n'),
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
const applyDefaultNodeDocumentation = function (node, type) {
|
|
1480
|
+
if ((!node.info || node.info === nrchkbLegacyDefaultNodeInfo[type]) && nrchkbDefaultNodeInfo[type]) {
|
|
1481
|
+
node.info = nrchkbDefaultNodeInfo[type]
|
|
1482
|
+
node.dirty = true
|
|
1483
|
+
return true
|
|
460
1484
|
}
|
|
461
|
-
|
|
462
|
-
|
|
1485
|
+
|
|
1486
|
+
return false
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
const backfillDefaultNodeDocumentation = function (nodes) {
|
|
1490
|
+
let changed = false
|
|
1491
|
+
const applyToNode = function (node) {
|
|
1492
|
+
if (node && applyDefaultNodeDocumentation(node, node.type)) {
|
|
1493
|
+
changed = true
|
|
1494
|
+
}
|
|
463
1495
|
}
|
|
464
|
-
</style>
|
|
465
1496
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
1497
|
+
if (Array.isArray(nodes)) {
|
|
1498
|
+
nodes.forEach(applyToNode)
|
|
1499
|
+
} else if (nodes) {
|
|
1500
|
+
applyToNode(nodes)
|
|
1501
|
+
} else if (RED.nodes) {
|
|
1502
|
+
RED.nodes.eachNode(applyToNode)
|
|
469
1503
|
|
|
470
|
-
|
|
471
|
-
|
|
1504
|
+
if (typeof RED.nodes.eachConfig === 'function') {
|
|
1505
|
+
RED.nodes.eachConfig(applyToNode)
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
if (changed && RED.view && typeof RED.view.redraw === 'function') {
|
|
1510
|
+
RED.view.redraw(true)
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
if (RED.events) {
|
|
1515
|
+
RED.events.on('flows:loaded', function () {
|
|
1516
|
+
setTimeout(backfillDefaultNodeDocumentation, 0)
|
|
1517
|
+
})
|
|
1518
|
+
|
|
1519
|
+
RED.events.on('nodes:add', function (node) {
|
|
1520
|
+
setTimeout(function () {
|
|
1521
|
+
backfillDefaultNodeDocumentation(node)
|
|
1522
|
+
}, 0)
|
|
1523
|
+
})
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
window.NRCHKBPairingQR = (function () {
|
|
1527
|
+
const stateByHost = {}
|
|
1528
|
+
const settingsStorageKey = 'nrchkb.pairingQR.settings'
|
|
1529
|
+
let initialized = false
|
|
1530
|
+
let refreshTimer
|
|
1531
|
+
let refreshRequestTimer
|
|
1532
|
+
let sidebarContent
|
|
1533
|
+
let sidebarList
|
|
1534
|
+
let sidebarStatus
|
|
1535
|
+
let settings = {
|
|
1536
|
+
autoShowWhenPairingAvailable: true,
|
|
1537
|
+
}
|
|
1538
|
+
const prefersReducedMotion = function () {
|
|
1539
|
+
return window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
1540
|
+
}
|
|
1541
|
+
const slideDownElement = function (element, duration) {
|
|
1542
|
+
if (prefersReducedMotion()) {
|
|
1543
|
+
element.stop(true, true).show()
|
|
1544
|
+
return
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
element.stop(true, true).slideDown(duration)
|
|
1548
|
+
}
|
|
1549
|
+
const slideUpElement = function (element, duration, onComplete) {
|
|
1550
|
+
if (prefersReducedMotion()) {
|
|
1551
|
+
element.stop(true, true).hide()
|
|
1552
|
+
if (onComplete) {
|
|
1553
|
+
onComplete()
|
|
1554
|
+
}
|
|
1555
|
+
return
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
element.stop(true, true).slideUp(duration, onComplete)
|
|
1559
|
+
}
|
|
1560
|
+
const text = function (key, fallback) {
|
|
1561
|
+
const keys = [
|
|
1562
|
+
'node-red-contrib-homekit-bridged/' + key,
|
|
1563
|
+
'node-red-contrib-homekit-bridged:' + key,
|
|
1564
|
+
]
|
|
1565
|
+
try {
|
|
1566
|
+
for (let i = 0; i < keys.length; i++) {
|
|
1567
|
+
const value = RED._(keys[i])
|
|
1568
|
+
if (value && value !== keys[i] && value !== key) {
|
|
1569
|
+
return value
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
} catch (_error) {
|
|
1573
|
+
}
|
|
1574
|
+
return fallback
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
const loadSettings = function () {
|
|
1578
|
+
try {
|
|
1579
|
+
const storedSettings = JSON.parse(localStorage.getItem(settingsStorageKey) || '{}')
|
|
1580
|
+
settings = {
|
|
1581
|
+
...settings,
|
|
1582
|
+
...storedSettings,
|
|
1583
|
+
autoShowWhenPairingAvailable: storedSettings.autoShowWhenPairingAvailable !== false,
|
|
1584
|
+
}
|
|
1585
|
+
} catch (_error) {
|
|
1586
|
+
settings.autoShowWhenPairingAvailable = true
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
const saveSettings = function () {
|
|
1591
|
+
try {
|
|
1592
|
+
localStorage.setItem(settingsStorageKey, JSON.stringify(settings))
|
|
1593
|
+
} catch (_error) {
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
const resolveHostId = function (node) {
|
|
1598
|
+
if (!node) {
|
|
1599
|
+
return ''
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
if ((node.type === 'homekit-service' || node.type === 'homekit-service2') && node.bridge) {
|
|
1603
|
+
return node.bridge
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
if ((node.type === 'homekit-service' || node.type === 'homekit-service2') && node.accessoryId) {
|
|
1607
|
+
return node.accessoryId
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
if (node.type === 'homekit-status' && node.serviceNodeId) {
|
|
1611
|
+
return resolveHostId(RED.nodes.node(node.serviceNodeId))
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
return ''
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
const fetchHostState = function (hostId) {
|
|
1618
|
+
if (!hostId) {
|
|
1619
|
+
return Promise.resolve(undefined)
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
return new Promise(function (resolve) {
|
|
1623
|
+
$.ajax({
|
|
1624
|
+
dataType: 'json',
|
|
1625
|
+
url: 'nrchkb/bridge/' + encodeURIComponent(hostId) + '/pairing',
|
|
1626
|
+
})
|
|
1627
|
+
.done(function (data) {
|
|
1628
|
+
stateByHost[hostId] = data
|
|
1629
|
+
resolve(data)
|
|
1630
|
+
})
|
|
1631
|
+
.fail(function (xhr) {
|
|
1632
|
+
const data = xhr.responseJSON || {
|
|
1633
|
+
paired: false,
|
|
1634
|
+
published: false,
|
|
1635
|
+
status: xhr.status === 404 ? 'missing' : 'error',
|
|
1636
|
+
}
|
|
1637
|
+
stateByHost[hostId] = data
|
|
1638
|
+
resolve(data)
|
|
1639
|
+
})
|
|
1640
|
+
})
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
const getPairingHostNodes = function () {
|
|
1644
|
+
const hosts = []
|
|
1645
|
+
const hostTypes = {
|
|
1646
|
+
'homekit-bridge': true,
|
|
1647
|
+
'homekit-standalone': true,
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
if (typeof RED.nodes.eachConfig === 'function') {
|
|
1651
|
+
RED.nodes.eachConfig(function (node) {
|
|
1652
|
+
if (node && hostTypes[node.type]) {
|
|
1653
|
+
hosts.push(node)
|
|
1654
|
+
}
|
|
1655
|
+
})
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
if (hosts.length === 0) {
|
|
1659
|
+
return [
|
|
1660
|
+
...RED.nodes.filterNodes({type: 'homekit-bridge'}),
|
|
1661
|
+
...RED.nodes.filterNodes({type: 'homekit-standalone'}),
|
|
1662
|
+
]
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
return hosts
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
const getCandidateNodes = function () {
|
|
1669
|
+
return [
|
|
1670
|
+
...RED.nodes.filterNodes({type: 'homekit-service'}),
|
|
1671
|
+
...RED.nodes.filterNodes({type: 'homekit-service2'}),
|
|
1672
|
+
...RED.nodes.filterNodes({type: 'homekit-status'}),
|
|
1673
|
+
]
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
const getReferencingNodes = function (hostId) {
|
|
1677
|
+
return getCandidateNodes().filter(function (node) {
|
|
1678
|
+
return resolveHostId(node) === hostId
|
|
1679
|
+
})
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
const formatPinCode = function (formattedPinCode) {
|
|
1683
|
+
return formattedPinCode ? formattedPinCode.top + ' ' + formattedPinCode.bottom : ''
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
const createPairingSetupCard = function (bridgeState) {
|
|
1687
|
+
const setupCard = $('<div/>', {class: 'nrchkb-pairing-setup-card'})
|
|
1688
|
+
$('<img/>', {
|
|
1689
|
+
alt: text('qr.imageAlt', 'HomeKit pairing QR code'),
|
|
1690
|
+
class: 'nrchkb-pairing-qr',
|
|
1691
|
+
src: bridgeState.qrCodeDataUrl,
|
|
1692
|
+
}).appendTo(setupCard)
|
|
1693
|
+
return setupCard
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
const isPairingAvailable = function (bridgeState) {
|
|
1697
|
+
return !!bridgeState && bridgeState.published && !bridgeState.paired && !!bridgeState.qrCodeDataUrl
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
const nodeDisplayName = function (node) {
|
|
1701
|
+
return node.name || node.serviceName || node.id || text('qr.unknownAccessory', 'Accessory')
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
const serviceTypeName = function (node) {
|
|
1705
|
+
if (!node) {
|
|
1706
|
+
return ''
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
if (node.type === 'homekit-status' && node.serviceNodeId) {
|
|
1710
|
+
return serviceTypeName(RED.nodes.node(node.serviceNodeId))
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
return node.serviceName || node.service || node.type || ''
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
const hostCategoryName = function (hostNode) {
|
|
1717
|
+
if (!hostNode || hostNode.type !== 'homekit-standalone') {
|
|
1718
|
+
return ''
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
return accessoryCategories[hostNode.accessoryCategory] || hostNode.accessoryCategory || ''
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
const hostTypeName = function (hostNode) {
|
|
1725
|
+
return hostNode && hostNode.type === 'homekit-standalone' ? 'Standalone accessory' : 'Bridge'
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
const appendMeta = function (container, items) {
|
|
1729
|
+
const meta = $('<dl/>', {class: 'nrchkb-sidebar-meta'}).appendTo(container)
|
|
1730
|
+
items.forEach(function (item) {
|
|
1731
|
+
if (!item.value) {
|
|
1732
|
+
return
|
|
1733
|
+
}
|
|
1734
|
+
$('<dt/>', {text: item.label}).appendTo(meta)
|
|
1735
|
+
$('<dd/>', {text: item.value}).appendTo(meta)
|
|
1736
|
+
})
|
|
1737
|
+
return meta
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
const renderAccessoryListItem = function (list, node) {
|
|
1741
|
+
const item = $('<li/>').appendTo(list)
|
|
1742
|
+
$('<span/>', {
|
|
1743
|
+
class: 'nrchkb-sidebar-accessory-name',
|
|
1744
|
+
text: nodeDisplayName(node),
|
|
1745
|
+
}).appendTo(item)
|
|
1746
|
+
$('<span/>', {
|
|
1747
|
+
class: 'nrchkb-sidebar-accessory-meta',
|
|
1748
|
+
text: 'id: ' + node.id + ', service: ' + serviceTypeName(node),
|
|
1749
|
+
}).appendTo(item)
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
const renderSidebarCardContent = function (card, hostNode, hostState) {
|
|
1753
|
+
card.empty()
|
|
1754
|
+
$('<h3/>', {
|
|
1755
|
+
text: hostState.bridgeName || hostNode.name || hostNode.bridgeName || hostNode.id,
|
|
1756
|
+
}).appendTo(card)
|
|
1757
|
+
appendMeta(card, [
|
|
1758
|
+
{label: 'Type', value: hostTypeName(hostNode)},
|
|
1759
|
+
{label: 'ID', value: hostNode.id},
|
|
1760
|
+
{label: 'Category', value: hostCategoryName(hostNode)},
|
|
1761
|
+
])
|
|
1762
|
+
createPairingSetupCard(hostState).appendTo(card)
|
|
1763
|
+
|
|
1764
|
+
const accessories = getReferencingNodes(hostNode.id)
|
|
1765
|
+
const accessorySection = $('<div/>', {class: 'nrchkb-sidebar-accessories'}).appendTo(card)
|
|
1766
|
+
$('<p/>', {
|
|
1767
|
+
class: 'nrchkb-sidebar-accessories-title',
|
|
1768
|
+
text: text('qr.accessories', 'Accessories'),
|
|
1769
|
+
}).appendTo(accessorySection)
|
|
1770
|
+
|
|
1771
|
+
if (accessories.length === 0) {
|
|
1772
|
+
$('<p/>', {
|
|
1773
|
+
class: 'nrchkb-pairing-message',
|
|
1774
|
+
text: text('qr.noAccessories', 'No flow nodes reference this pairing host.'),
|
|
1775
|
+
}).appendTo(accessorySection)
|
|
1776
|
+
} else {
|
|
1777
|
+
const list = $('<ul/>').appendTo(accessorySection)
|
|
1778
|
+
accessories.forEach(function (node) {
|
|
1779
|
+
renderAccessoryListItem(list, node)
|
|
1780
|
+
})
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
const createSidebarCard = function (hostNode, hostState) {
|
|
1785
|
+
const card = $('<div/>', {
|
|
1786
|
+
class: 'nrchkb-sidebar-pairing-card',
|
|
1787
|
+
'data-host-id': hostNode.id,
|
|
1788
|
+
role: 'listitem',
|
|
1789
|
+
})
|
|
1790
|
+
renderSidebarCardContent(card, hostNode, hostState)
|
|
1791
|
+
return card
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
const sidebarCardSignature = function (hostNode, hostState) {
|
|
1795
|
+
return JSON.stringify({
|
|
1796
|
+
accessories: getReferencingNodes(hostNode.id).map(function (node) {
|
|
1797
|
+
return {
|
|
1798
|
+
id: node.id,
|
|
1799
|
+
name: nodeDisplayName(node),
|
|
1800
|
+
serviceType: serviceTypeName(node),
|
|
1801
|
+
}
|
|
1802
|
+
}),
|
|
1803
|
+
category: hostCategoryName(hostNode),
|
|
1804
|
+
hostId: hostNode.id,
|
|
1805
|
+
hostName: hostState.bridgeName || hostNode.name || hostNode.bridgeName || hostNode.id,
|
|
1806
|
+
hostType: hostTypeName(hostNode),
|
|
1807
|
+
qrCodeDataUrl: hostState.qrCodeDataUrl,
|
|
1808
|
+
})
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
const findSidebarCard = function (hostId) {
|
|
1812
|
+
return sidebarList.children('.nrchkb-sidebar-pairing-card').filter(function () {
|
|
1813
|
+
return $(this).attr('data-host-id') === hostId
|
|
1814
|
+
})
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
const removeSidebarCard = function (card, onComplete) {
|
|
1818
|
+
if (card.data('nrchkbRemoving')) {
|
|
1819
|
+
return
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
card.data('nrchkbRemoving', true)
|
|
1823
|
+
slideUpElement(card, 180, function () {
|
|
1824
|
+
card.remove()
|
|
1825
|
+
if (onComplete) {
|
|
1826
|
+
onComplete()
|
|
1827
|
+
}
|
|
1828
|
+
})
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
const updateEmptySidebarState = function () {
|
|
1832
|
+
if (sidebarList.children('.nrchkb-sidebar-pairing-card').length === 0) {
|
|
1833
|
+
sidebarStatus.text(text('qr.noUnpaired', 'Nothing to be paired.'))
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
const refreshSidebar = function (options) {
|
|
1838
|
+
options = options || {}
|
|
1839
|
+
|
|
1840
|
+
if (!sidebarList || !sidebarStatus) {
|
|
1841
|
+
return Promise.resolve()
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
const pairingHosts = getPairingHostNodes()
|
|
1845
|
+
|
|
1846
|
+
if (pairingHosts.length === 0) {
|
|
1847
|
+
sidebarList.children('.nrchkb-sidebar-pairing-card').each(function () {
|
|
1848
|
+
removeSidebarCard($(this), updateEmptySidebarState)
|
|
1849
|
+
})
|
|
1850
|
+
updateEmptySidebarState()
|
|
1851
|
+
return Promise.resolve()
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
return Promise.all(pairingHosts.map(function (hostNode) {
|
|
1855
|
+
return fetchHostState(hostNode.id).then(function (hostState) {
|
|
1856
|
+
return {hostNode, hostState}
|
|
1857
|
+
})
|
|
1858
|
+
})).then(function (results) {
|
|
1859
|
+
const available = results.filter(function (result) {
|
|
1860
|
+
return isPairingAvailable(result.hostState)
|
|
1861
|
+
})
|
|
1862
|
+
const availableById = {}
|
|
1863
|
+
|
|
1864
|
+
available.forEach(function (result) {
|
|
1865
|
+
availableById[result.hostNode.id] = result
|
|
1866
|
+
})
|
|
1867
|
+
|
|
1868
|
+
sidebarList.children('.nrchkb-sidebar-pairing-card').each(function () {
|
|
1869
|
+
const card = $(this)
|
|
1870
|
+
const hostId = card.attr('data-host-id')
|
|
1871
|
+
|
|
1872
|
+
if (!availableById[hostId]) {
|
|
1873
|
+
removeSidebarCard(card, updateEmptySidebarState)
|
|
1874
|
+
}
|
|
1875
|
+
})
|
|
1876
|
+
|
|
1877
|
+
if (available.length === 0) {
|
|
1878
|
+
updateEmptySidebarState()
|
|
1879
|
+
} else {
|
|
1880
|
+
sidebarStatus.text('')
|
|
1881
|
+
available.forEach(function (result) {
|
|
1882
|
+
const signature = sidebarCardSignature(result.hostNode, result.hostState)
|
|
1883
|
+
const card = findSidebarCard(result.hostNode.id)
|
|
1884
|
+
|
|
1885
|
+
if (card.length > 0) {
|
|
1886
|
+
if (card.attr('data-signature') !== signature) {
|
|
1887
|
+
renderSidebarCardContent(card, result.hostNode, result.hostState)
|
|
1888
|
+
card.attr('data-signature', signature)
|
|
1889
|
+
}
|
|
1890
|
+
return
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
const newCard = createSidebarCard(result.hostNode, result.hostState)
|
|
1894
|
+
.attr('data-signature', signature)
|
|
1895
|
+
.hide()
|
|
1896
|
+
sidebarList.append(newCard)
|
|
1897
|
+
slideDownElement(newCard, 180)
|
|
1898
|
+
})
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
if (options.show && available.length > 0 && settings.autoShowWhenPairingAvailable && RED.sidebar) {
|
|
1902
|
+
RED.sidebar.show('nrchkb')
|
|
1903
|
+
}
|
|
1904
|
+
})
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
const requestRefreshSidebar = function (delay, options) {
|
|
1908
|
+
clearTimeout(refreshRequestTimer)
|
|
1909
|
+
refreshRequestTimer = setTimeout(function () {
|
|
1910
|
+
refreshSidebar(options)
|
|
1911
|
+
}, delay)
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
const registerSidebar = function () {
|
|
1915
|
+
sidebarContent = $('<div/>', {class: 'nrchkb-editor nrchkb-sidebar-pairing'})
|
|
1916
|
+
const section = $('<details/>', {class: 'nrchkb-section', open: true}).appendTo(sidebarContent)
|
|
1917
|
+
$('<summary/>')
|
|
1918
|
+
.append($('<i/>', {class: 'fa fa-qrcode'}))
|
|
1919
|
+
.append(' ')
|
|
1920
|
+
.append($('<span/>', {text: text('qr.sectionTitle', 'Pairing QR Code')}))
|
|
1921
|
+
.appendTo(section)
|
|
1922
|
+
const sectionBody = $('<div/>', {class: 'nrchkb-section-body'}).appendTo(section)
|
|
1923
|
+
const autoShowLabel = $('<label/>', {
|
|
1924
|
+
class: 'nrchkb-checkbox-label nrchkb-sidebar-setting nrchkb-sidebar-pairing-control',
|
|
1925
|
+
}).appendTo(sectionBody)
|
|
1926
|
+
$('<input/>', {
|
|
1927
|
+
checked: settings.autoShowWhenPairingAvailable,
|
|
1928
|
+
type: 'checkbox',
|
|
1929
|
+
})
|
|
1930
|
+
.on('change', function () {
|
|
1931
|
+
settings.autoShowWhenPairingAvailable = this.checked
|
|
1932
|
+
saveSettings()
|
|
1933
|
+
})
|
|
1934
|
+
.appendTo(autoShowLabel)
|
|
1935
|
+
$('<span/>')
|
|
1936
|
+
.append($('<i/>', {class: 'fa fa-eye'}))
|
|
1937
|
+
.append(' ')
|
|
1938
|
+
.append(text('qr.autoShowWhenPairingAvailable', 'Auto show if something needs pairing'))
|
|
1939
|
+
.appendTo(autoShowLabel)
|
|
1940
|
+
sidebarStatus = $('<p/>', {
|
|
1941
|
+
class: 'nrchkb-sidebar-pairing-status',
|
|
1942
|
+
'aria-live': 'polite',
|
|
1943
|
+
}).appendTo(sectionBody)
|
|
1944
|
+
sidebarList = $('<div/>', {
|
|
1945
|
+
class: 'nrchkb-sidebar-pairing-list',
|
|
1946
|
+
role: 'list',
|
|
1947
|
+
}).appendTo(sectionBody)
|
|
1948
|
+
|
|
1949
|
+
const toolbar = $('<div/>', {class: 'nrchkb-sidebar-toolbar'})
|
|
1950
|
+
$('<button/>', {
|
|
1951
|
+
class: 'red-ui-button red-ui-button-small',
|
|
1952
|
+
'aria-label': text('qr.refresh', 'Refresh'),
|
|
1953
|
+
title: text('qr.refresh', 'Refresh'),
|
|
1954
|
+
type: 'button',
|
|
1955
|
+
})
|
|
1956
|
+
.append($('<i/>', {class: 'fa fa-refresh'}))
|
|
1957
|
+
.on('click', function () {
|
|
1958
|
+
refreshSidebar()
|
|
1959
|
+
})
|
|
1960
|
+
.appendTo(toolbar)
|
|
1961
|
+
|
|
1962
|
+
RED.sidebar.addTab({
|
|
1963
|
+
action: 'nrchkb:show-pairing-tab',
|
|
1964
|
+
content: sidebarContent,
|
|
1965
|
+
enableOnEdit: true,
|
|
1966
|
+
iconClass: 'fa fa-nrchkb',
|
|
1967
|
+
id: 'nrchkb',
|
|
1968
|
+
label: text('qr.sidebarTitle', 'NRCHKB'),
|
|
1969
|
+
name: text('qr.sidebarTitle', 'NRCHKB'),
|
|
1970
|
+
pinned: true,
|
|
1971
|
+
toolbar,
|
|
1972
|
+
})
|
|
1973
|
+
|
|
1974
|
+
RED.actions.add('nrchkb:show-pairing-tab', function () {
|
|
1975
|
+
RED.sidebar.show('nrchkb')
|
|
1976
|
+
refreshSidebar()
|
|
1977
|
+
})
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
const renderHostEditor = function (hostId, selector) {
|
|
1981
|
+
const container = $(selector)
|
|
1982
|
+
container.empty().append($('<p/>', {
|
|
1983
|
+
class: 'nrchkb-pairing-message',
|
|
1984
|
+
text: text('qr.loading', 'Loading pairing state...'),
|
|
1985
|
+
}))
|
|
1986
|
+
|
|
1987
|
+
fetchHostState(hostId).then(function (hostState) {
|
|
1988
|
+
container.empty()
|
|
1989
|
+
|
|
1990
|
+
if (!hostState || hostState.status === 'missing' || !hostState.published) {
|
|
1991
|
+
container.append($('<p/>', {
|
|
1992
|
+
class: 'nrchkb-pairing-message',
|
|
1993
|
+
text: text('qr.deployFirst', 'Deploy this bridge or accessory to generate a pairing QR code.'),
|
|
1994
|
+
}))
|
|
1995
|
+
return
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
if (hostState.paired) {
|
|
1999
|
+
container.append($('<p/>', {
|
|
2000
|
+
class: 'nrchkb-pairing-message',
|
|
2001
|
+
text: text('qr.alreadyPaired', 'This bridge or accessory is already paired.'),
|
|
2002
|
+
}))
|
|
2003
|
+
return
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
const card = $('<div/>', {class: 'nrchkb-pairing-card'})
|
|
2007
|
+
createPairingSetupCard(hostState).appendTo(card)
|
|
2008
|
+
const details = $('<div/>', {class: 'nrchkb-pairing-details'}).appendTo(card)
|
|
2009
|
+
$('<p/>', {
|
|
2010
|
+
class: 'nrchkb-pairing-state',
|
|
2011
|
+
text: text('qr.ready', 'Ready to pair'),
|
|
2012
|
+
}).appendTo(details)
|
|
2013
|
+
$('<span/>', {
|
|
2014
|
+
class: 'nrchkb-pairing-code',
|
|
2015
|
+
text: formatPinCode(hostState.formattedPinCode),
|
|
2016
|
+
}).appendTo(details)
|
|
2017
|
+
$('<span/>', {
|
|
2018
|
+
class: 'nrchkb-pairing-uri',
|
|
2019
|
+
text: hostState.setupUri,
|
|
2020
|
+
}).appendTo(details)
|
|
2021
|
+
container.append(card)
|
|
2022
|
+
})
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
const init = function () {
|
|
2026
|
+
if (initialized) {
|
|
2027
|
+
return
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
initialized = true
|
|
2031
|
+
|
|
2032
|
+
loadSettings()
|
|
2033
|
+
registerSidebar()
|
|
2034
|
+
|
|
2035
|
+
if (RED.events) {
|
|
2036
|
+
RED.events.on('deploy', function () {
|
|
2037
|
+
requestRefreshSidebar(1500, {show: true})
|
|
2038
|
+
})
|
|
2039
|
+
|
|
2040
|
+
;['flows:loaded', 'nodes:add', 'nodes:change', 'nodes:remove'].forEach(function (eventName) {
|
|
2041
|
+
RED.events.on(eventName, function () {
|
|
2042
|
+
requestRefreshSidebar(250)
|
|
2043
|
+
})
|
|
2044
|
+
})
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
refreshTimer = setInterval(refreshSidebar, 10000)
|
|
2048
|
+
window.addEventListener('beforeunload', function () {
|
|
2049
|
+
clearInterval(refreshTimer)
|
|
2050
|
+
clearTimeout(refreshRequestTimer)
|
|
2051
|
+
})
|
|
2052
|
+
setTimeout(function () {
|
|
2053
|
+
refreshSidebar({show: true})
|
|
2054
|
+
}, 1000)
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
return {
|
|
2058
|
+
init,
|
|
2059
|
+
refreshSidebar,
|
|
2060
|
+
renderBridgeEditor: renderHostEditor,
|
|
2061
|
+
renderHostEditor,
|
|
2062
|
+
}
|
|
2063
|
+
})()
|
|
2064
|
+
RED.NRCHKBPairingQR = window.NRCHKBPairingQR
|
|
2065
|
+
|
|
2066
|
+
setTimeout(function () {
|
|
2067
|
+
if (RED.NRCHKBPairingQR) {
|
|
2068
|
+
RED.NRCHKBPairingQR.init()
|
|
2069
|
+
}
|
|
2070
|
+
}, 1000)
|
|
2071
|
+
</script>
|
|
2072
|
+
|
|
2073
|
+
<script data-template-name="nrchkb" type="text/x-red">
|
|
2074
|
+
<div class="nrchkb-editor">
|
|
2075
|
+
<details class="nrchkb-section" open>
|
|
2076
|
+
<summary><i class="fa fa-list-alt"></i> Custom Characteristics</summary>
|
|
2077
|
+
<div class="nrchkb-section-body">
|
|
2078
|
+
<div class="form-row node-input-customCharacteristics-container-row">
|
|
2079
|
+
<ol id="node-input-customCharacteristics-container"></ol>
|
|
2080
|
+
</div>
|
|
2081
|
+
</div>
|
|
2082
|
+
</details>
|
|
472
2083
|
</div>
|
|
473
|
-
</script>
|
|
2084
|
+
</script>
|
|
2085
|
+
|
|
2086
|
+
<script data-help-name="nrchkb" type="text/markdown">
|
|
2087
|
+
# NRCHKB Custom Characteristics
|
|
2088
|
+
|
|
2089
|
+
Defines custom HAP characteristics that can be attached to HomeKit services when the standard HAP catalog does not contain the value you need.
|
|
2090
|
+
|
|
2091
|
+
> [!CAUTION]
|
|
2092
|
+
> Prefer standard HAP characteristics whenever possible. Custom characteristics may not be shown or interpreted consistently by every HomeKit client.
|
|
2093
|
+
|
|
2094
|
+
## Characteristic Identity
|
|
2095
|
+
|
|
2096
|
+
- **Name**: Display name used by NRCHKB when selecting or reporting the characteristic.
|
|
2097
|
+
- **UUID**: Stable HAP UUID for the custom characteristic. Keep it unchanged once HomeKit clients have seen it.
|
|
2098
|
+
|
|
2099
|
+
## Value Shape
|
|
2100
|
+
|
|
2101
|
+
- **Format**: HAP value format such as boolean, integer, float, string, or data.
|
|
2102
|
+
- **Unit**: Optional HomeKit unit metadata for numeric values.
|
|
2103
|
+
- **Minimum / Maximum / Step**: Numeric constraints exposed to HomeKit clients.
|
|
2104
|
+
- **Valid Values**: Optional list of allowed values for enumerated characteristics.
|
|
2105
|
+
|
|
2106
|
+
## Permissions
|
|
2107
|
+
|
|
2108
|
+
Use read, write, and notify permissions to describe how HomeKit can interact with the value. Runtime flows must still send values matching the configured format and constraints.
|
|
2109
|
+
</script>
|