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/nodes/nrchkb.html
CHANGED
|
@@ -1,22 +1,740 @@
|
|
|
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 input {
|
|
368
|
+
width: 16px;
|
|
369
|
+
height: 16px;
|
|
370
|
+
margin: 0 !important;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.nrchkb-sidebar-pairing label.nrchkb-sidebar-setting span {
|
|
374
|
+
display: block;
|
|
375
|
+
min-width: 0;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.nrchkb-sidebar-pairing-card {
|
|
379
|
+
display: grid;
|
|
380
|
+
grid-template-rows: auto auto auto 1fr;
|
|
381
|
+
margin: 0 0 12px;
|
|
382
|
+
padding: 10px;
|
|
383
|
+
border: 1px solid var(--nrchkb-section-border);
|
|
384
|
+
border-radius: 7px;
|
|
385
|
+
background: var(--nrchkb-section-bg);
|
|
386
|
+
container-type: inline-size;
|
|
387
|
+
min-width: 0;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.nrchkb-sidebar-pairing-card h3 {
|
|
391
|
+
margin: 0 0 8px;
|
|
392
|
+
font-size: 14px;
|
|
393
|
+
line-height: 1.35;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.nrchkb-sidebar-pairing-list {
|
|
397
|
+
display: grid;
|
|
398
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
|
|
399
|
+
gap: 10px;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.nrchkb-sidebar-pairing .nrchkb-pairing-setup-card {
|
|
403
|
+
margin: 0 auto 8px;
|
|
404
|
+
width: min(100%, 220px);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.nrchkb-sidebar-meta {
|
|
408
|
+
display: grid;
|
|
409
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
410
|
+
gap: 3px 8px;
|
|
411
|
+
min-height: 66px;
|
|
412
|
+
margin: 0 0 8px;
|
|
413
|
+
color: var(--nrchkb-muted-text);
|
|
414
|
+
font-size: 12px;
|
|
415
|
+
line-height: 1.35;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.nrchkb-sidebar-meta dt {
|
|
419
|
+
margin: 0;
|
|
420
|
+
font-weight: 600;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.nrchkb-sidebar-meta dd {
|
|
424
|
+
min-width: 0;
|
|
425
|
+
margin: 0;
|
|
426
|
+
overflow-wrap: anywhere;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.nrchkb-sidebar-accessories {
|
|
430
|
+
margin: 8px 0 0;
|
|
431
|
+
padding: 8px 0 0;
|
|
432
|
+
border-top: 1px solid var(--nrchkb-section-border);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.nrchkb-sidebar-accessories-title {
|
|
436
|
+
margin: 0 0 5px;
|
|
437
|
+
color: var(--nrchkb-muted-text);
|
|
438
|
+
font-size: 12px;
|
|
439
|
+
font-weight: 600;
|
|
440
|
+
line-height: 1.35;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.nrchkb-sidebar-accessories ul {
|
|
444
|
+
margin: 0;
|
|
445
|
+
padding: 0;
|
|
446
|
+
color: var(--nrchkb-muted-text);
|
|
447
|
+
line-height: 1.4;
|
|
448
|
+
list-style: none;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.nrchkb-sidebar-accessories li {
|
|
452
|
+
margin: 0 0 7px;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.nrchkb-sidebar-accessory-name {
|
|
456
|
+
display: block;
|
|
457
|
+
color: var(--red-ui-primary-text-color, #333);
|
|
458
|
+
font-weight: 600;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.nrchkb-sidebar-accessory-meta {
|
|
462
|
+
display: block;
|
|
463
|
+
overflow-wrap: anywhere;
|
|
464
|
+
font-size: 12px;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.nrchkb-sidebar-pairing-control {
|
|
468
|
+
margin: 0 0 10px;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.nrchkb-sidebar-toolbar {
|
|
472
|
+
display: flex;
|
|
473
|
+
justify-content: flex-end;
|
|
474
|
+
gap: 6px;
|
|
475
|
+
padding: 6px;
|
|
476
|
+
border-top: 1px solid var(--nrchkb-section-border);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.fa-nrchkb::before {
|
|
480
|
+
content: "";
|
|
481
|
+
display: inline-block;
|
|
482
|
+
width: 1.25em;
|
|
483
|
+
height: 1.25em;
|
|
484
|
+
vertical-align: -20%;
|
|
485
|
+
background-image: url("icons/node-red-contrib-homekit-bridged/nrchkb.png");
|
|
486
|
+
background-size: contain;
|
|
487
|
+
background-position: center;
|
|
488
|
+
background-repeat: no-repeat;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.nrchkb-help {
|
|
492
|
+
--nrchkb-help-border: var(--red-ui-secondary-border-color, #d8d8d8);
|
|
493
|
+
--nrchkb-help-bg: var(--red-ui-secondary-background, #fff);
|
|
494
|
+
--nrchkb-help-heading-bg: var(--red-ui-tertiary-background, #f6f6f6);
|
|
495
|
+
--nrchkb-help-muted: var(--red-ui-secondary-text-color, #666);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.nrchkb-help .nrchkb-help-section {
|
|
499
|
+
margin: 10px 0;
|
|
500
|
+
border: 1px solid var(--nrchkb-help-border);
|
|
501
|
+
border-radius: 7px;
|
|
502
|
+
background: var(--nrchkb-help-bg);
|
|
503
|
+
overflow: hidden;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.nrchkb-help .nrchkb-help-section > h4 {
|
|
507
|
+
margin: 0;
|
|
508
|
+
padding: 7px 10px;
|
|
509
|
+
font-size: 13px;
|
|
510
|
+
line-height: 1.35;
|
|
511
|
+
background: var(--nrchkb-help-heading-bg);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.nrchkb-help .nrchkb-help-section > h4 i {
|
|
515
|
+
width: 14px;
|
|
516
|
+
margin-right: 6px;
|
|
517
|
+
color: var(--nrchkb-help-muted);
|
|
518
|
+
text-align: center;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.nrchkb-help .nrchkb-help-section-body {
|
|
522
|
+
padding: 8px 10px;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.nrchkb-help .nrchkb-help-section-body > :first-child {
|
|
526
|
+
margin-top: 0;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
.nrchkb-help .nrchkb-help-section-body > :last-child {
|
|
530
|
+
margin-bottom: 0;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
.nrchkb-help .nrchkb-advertiser-dynamic {
|
|
534
|
+
margin: 8px 0;
|
|
535
|
+
padding: 7px 9px;
|
|
536
|
+
border-left: 3px solid var(--red-ui-primary-background, #8f0000);
|
|
537
|
+
border-radius: 4px;
|
|
538
|
+
background: color-mix(in srgb, var(--red-ui-tertiary-background, #f6f6f6) 78%, transparent);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.nrchkb-editor .nrchkb-characteristic-item {
|
|
542
|
+
overflow: hidden;
|
|
543
|
+
white-space: normal;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.nrchkb-editor .nrchkb-characteristic-row {
|
|
547
|
+
display: flex;
|
|
548
|
+
align-items: center;
|
|
549
|
+
gap: 10px;
|
|
550
|
+
min-width: 0;
|
|
551
|
+
margin-bottom: 8px;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.nrchkb-editor .nrchkb-characteristic-fields {
|
|
555
|
+
margin-top: 8px;
|
|
556
|
+
min-width: 0;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.nrchkb-editor .nrchkb-characteristic-label {
|
|
560
|
+
flex: 0 1 120px;
|
|
561
|
+
min-width: 98px;
|
|
562
|
+
max-width: min(120px, 42%);
|
|
563
|
+
padding-left: 10px;
|
|
564
|
+
text-align: left;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.nrchkb-editor .nrchkb-characteristic-label i,
|
|
568
|
+
.nrchkb-editor .nrchkb-characteristic-property-label i,
|
|
569
|
+
.nrchkb-editor .properties-accordion h3 i {
|
|
570
|
+
width: 14px;
|
|
571
|
+
color: var(--nrchkb-muted-text);
|
|
572
|
+
text-align: center;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.nrchkb-editor .nrchkb-characteristic-row input,
|
|
576
|
+
.nrchkb-editor .nrchkb-characteristic-row select,
|
|
577
|
+
.nrchkb-editor .nrchkb-characteristic-fields input,
|
|
578
|
+
.nrchkb-editor .nrchkb-characteristic-fields select {
|
|
579
|
+
flex: 1 1 0;
|
|
580
|
+
min-width: 0;
|
|
581
|
+
width: auto !important;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.nrchkb-editor .nrchkb-characteristic-fields .property-validValueRanges {
|
|
585
|
+
flex: 1 1 0;
|
|
586
|
+
min-width: 0;
|
|
587
|
+
max-width: 100%;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
.nrchkb-editor #node-input-customCharacteristics-container {
|
|
591
|
+
width: 100%;
|
|
592
|
+
min-width: 0;
|
|
593
|
+
min-height: 150px;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.nrchkb-editor ol#node-input-customCharacteristics-container .red-ui-typedInput-container {
|
|
597
|
+
flex: 1;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.nrchkb-editor .node-input-customCharacteristics-container-row {
|
|
601
|
+
display: block;
|
|
602
|
+
min-width: 0;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.nrchkb-editor .red-ui-editableList,
|
|
606
|
+
.nrchkb-editor .red-ui-editableList-border,
|
|
607
|
+
.nrchkb-editor .red-ui-editableList-container,
|
|
608
|
+
.nrchkb-editor .red-ui-editableList-list,
|
|
609
|
+
.nrchkb-editor .red-ui-editableList-list > li {
|
|
610
|
+
min-width: 0;
|
|
611
|
+
max-width: 100%;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
.nrchkb-editor .ui-slider .ui-slider-handle {
|
|
615
|
+
background: var(--nrchkb-accent);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
@container (max-width: 520px) {
|
|
619
|
+
.nrchkb-editor .form-row {
|
|
620
|
+
align-items: stretch;
|
|
621
|
+
flex-direction: column;
|
|
622
|
+
gap: 4px;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
.nrchkb-editor .form-row > label:first-child {
|
|
626
|
+
flex: 0 0 auto;
|
|
627
|
+
max-width: 100%;
|
|
628
|
+
min-width: 0;
|
|
629
|
+
width: auto;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
.nrchkb-editor .form-row > input[type="text"],
|
|
633
|
+
.nrchkb-editor .form-row > input[type="number"],
|
|
634
|
+
.nrchkb-editor .form-row > input:not([type]),
|
|
635
|
+
.nrchkb-editor .form-row > select,
|
|
636
|
+
.nrchkb-editor .form-row > .red-ui-typedInput-container,
|
|
637
|
+
.nrchkb-editor .form-row > div[style*="inline-flex"] {
|
|
638
|
+
flex: 0 0 auto;
|
|
639
|
+
width: 100% !important;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
.nrchkb-editor .nrchkb-checkbox-row {
|
|
643
|
+
display: block;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
.nrchkb-editor .nrchkb-characteristic-row {
|
|
647
|
+
align-items: stretch;
|
|
648
|
+
flex-direction: column;
|
|
649
|
+
gap: 4px;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
.nrchkb-editor .nrchkb-characteristic-label {
|
|
653
|
+
flex: 0 0 auto;
|
|
654
|
+
max-width: 100%;
|
|
655
|
+
min-width: 0;
|
|
656
|
+
padding-left: 0;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
.nrchkb-editor .nrchkb-pairing-card {
|
|
660
|
+
align-items: flex-start;
|
|
661
|
+
flex-direction: column;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
@media (max-width: 520px) {
|
|
666
|
+
.nrchkb-editor .form-row {
|
|
667
|
+
align-items: stretch;
|
|
668
|
+
flex-direction: column;
|
|
669
|
+
gap: 4px;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.nrchkb-editor .form-row > label:first-child {
|
|
673
|
+
flex: 0 0 auto;
|
|
674
|
+
max-width: 100%;
|
|
675
|
+
min-width: 0;
|
|
676
|
+
width: auto;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
.nrchkb-editor .form-row > input[type="text"],
|
|
680
|
+
.nrchkb-editor .form-row > input[type="number"],
|
|
681
|
+
.nrchkb-editor .form-row > input:not([type]),
|
|
682
|
+
.nrchkb-editor .form-row > select,
|
|
683
|
+
.nrchkb-editor .form-row > .red-ui-typedInput-container,
|
|
684
|
+
.nrchkb-editor .form-row > div[style*="inline-flex"] {
|
|
685
|
+
flex: 0 0 auto;
|
|
686
|
+
width: 100% !important;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
.nrchkb-editor .nrchkb-checkbox-row {
|
|
690
|
+
display: block;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
.nrchkb-editor .nrchkb-characteristic-row {
|
|
694
|
+
align-items: stretch;
|
|
695
|
+
flex-direction: column;
|
|
696
|
+
gap: 4px;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
.nrchkb-editor .nrchkb-characteristic-label {
|
|
700
|
+
flex: 0 0 auto;
|
|
701
|
+
max-width: 100%;
|
|
702
|
+
min-width: 0;
|
|
703
|
+
padding-left: 0;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
.nrchkb-editor .nrchkb-pairing-card {
|
|
707
|
+
align-items: flex-start;
|
|
708
|
+
flex-direction: column;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
.nrchkb-editor .alert {
|
|
3
713
|
position: relative;
|
|
4
714
|
padding: .75rem 1.25rem;
|
|
5
715
|
margin-bottom: 1rem;
|
|
6
|
-
border: 1px solid
|
|
716
|
+
border: 1px solid var(--nrchkb-section-border);
|
|
7
717
|
border-radius: .25rem;
|
|
8
|
-
margin-right:
|
|
718
|
+
margin-right: 0;
|
|
719
|
+
color: var(--red-ui-primary-text-color, #333);
|
|
720
|
+
background: var(--red-ui-tertiary-background, #f6f6f6);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
.nrchkb-editor .alert-warning {
|
|
724
|
+
color: var(--nrchkb-warning-text);
|
|
725
|
+
background: var(--nrchkb-warning-bg);
|
|
726
|
+
border-color: var(--nrchkb-warning-border);
|
|
9
727
|
}
|
|
10
728
|
|
|
11
|
-
.alert-
|
|
12
|
-
color:
|
|
13
|
-
background
|
|
14
|
-
border-color:
|
|
729
|
+
.nrchkb-editor .alert-info {
|
|
730
|
+
color: var(--nrchkb-info-text);
|
|
731
|
+
background: var(--nrchkb-info-bg);
|
|
732
|
+
border-color: var(--nrchkb-info-border);
|
|
15
733
|
}
|
|
16
734
|
</style>
|
|
17
735
|
|
|
18
736
|
<script type="text/javascript">
|
|
19
|
-
const
|
|
737
|
+
const initNRCHKBConfigNode = function () {
|
|
20
738
|
//NRCHKB Custom Characteristics
|
|
21
739
|
$.ajax({
|
|
22
740
|
url: 'nrchkb/config',
|
|
@@ -43,6 +761,9 @@
|
|
|
43
761
|
labelStyle: function () {
|
|
44
762
|
return 'node_label_italic'
|
|
45
763
|
},
|
|
764
|
+
onadd: function () {
|
|
765
|
+
applyDefaultNodeDocumentation(this, 'nrchkb')
|
|
766
|
+
},
|
|
46
767
|
oneditsave: function () {
|
|
47
768
|
const self = this
|
|
48
769
|
saveCustomCharacteristics(self)
|
|
@@ -55,38 +776,45 @@
|
|
|
55
776
|
types: ["json"]
|
|
56
777
|
})*/
|
|
57
778
|
|
|
58
|
-
$('#node-input-customCharacteristics-container').
|
|
779
|
+
$('#node-input-customCharacteristics-container').editableList({
|
|
59
780
|
addItem: function (container, i, opt) {
|
|
60
781
|
$('.properties-accordion').accordion('option', 'active', false)
|
|
61
782
|
|
|
62
783
|
const {name, UUID, ...props} = opt
|
|
63
784
|
|
|
64
|
-
container.
|
|
65
|
-
overflow: 'hidden',
|
|
66
|
-
whiteSpace: 'nowrap'
|
|
67
|
-
})
|
|
785
|
+
container.addClass('nrchkb-characteristic-item')
|
|
68
786
|
|
|
69
787
|
const fragment = document.createDocumentFragment()
|
|
70
|
-
const row1 = $('<div/>', {
|
|
71
|
-
const row2 = $('<div/>', {
|
|
72
|
-
const row3 = $('<div/>', {
|
|
788
|
+
const row1 = $('<div/>', {class: 'nrchkb-characteristic-row'}).appendTo(fragment)
|
|
789
|
+
const row2 = $('<div/>', {class: 'nrchkb-characteristic-row'}).appendTo(fragment)
|
|
790
|
+
const row3 = $('<div/>', {class: 'nrchkb-characteristic-fields'}).appendTo(fragment)
|
|
791
|
+
const setIconLabelText = function (target, iconClass, text) {
|
|
792
|
+
target.empty()
|
|
793
|
+
$('<i/>', {class: 'fa ' + iconClass}).appendTo(target)
|
|
794
|
+
target.append(document.createTextNode(' ' + text))
|
|
795
|
+
}
|
|
796
|
+
const createPropertyLabel = function (attrs, iconClass, text) {
|
|
797
|
+
const label = $('<label/>', attrs)
|
|
798
|
+
setIconLabelText(label, iconClass, text)
|
|
799
|
+
return label
|
|
800
|
+
}
|
|
73
801
|
|
|
74
|
-
$('<div/>', {
|
|
75
|
-
|
|
802
|
+
const uuidLabel = $('<div/>', {
|
|
803
|
+
class: 'nrchkb-characteristic-label',
|
|
76
804
|
required: 'required'
|
|
77
805
|
})
|
|
78
|
-
.text('UUID ')
|
|
79
806
|
.appendTo(row1)
|
|
807
|
+
setIconLabelText(uuidLabel, 'fa-key', 'UUID')
|
|
80
808
|
$('<input/>', {class: 'property-uuid', type: 'text'})
|
|
81
809
|
.val(UUID ? UUID : uuidv4)
|
|
82
810
|
.appendTo(row1)
|
|
83
811
|
|
|
84
|
-
$('<div/>', {
|
|
85
|
-
|
|
812
|
+
const nameLabel = $('<div/>', {
|
|
813
|
+
class: 'nrchkb-characteristic-label',
|
|
86
814
|
required: 'required'
|
|
87
815
|
})
|
|
88
|
-
.text('Name ')
|
|
89
816
|
.appendTo(row2)
|
|
817
|
+
setIconLabelText(nameLabel, 'fa-tag', 'Name')
|
|
90
818
|
$('<input/>', {class: 'property-name', type: 'text'})
|
|
91
819
|
.val(name)
|
|
92
820
|
.appendTo(row2)
|
|
@@ -94,15 +822,17 @@
|
|
|
94
822
|
const row3_properties_accordion = $('<div/>', {class: 'properties-accordion'})
|
|
95
823
|
.appendTo(row3)
|
|
96
824
|
|
|
97
|
-
$('<h3/>')
|
|
825
|
+
$('<h3/>')
|
|
826
|
+
.append($('<i/>', {class: 'fa fa-sliders'}), document.createTextNode(' Properties'))
|
|
827
|
+
.appendTo(row3_properties_accordion)
|
|
98
828
|
|
|
99
829
|
const row3_properties = $('<div/>', {class: 'properties'}).appendTo(row3_properties_accordion)
|
|
100
830
|
|
|
101
831
|
const formatRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
102
|
-
|
|
103
|
-
class: '
|
|
832
|
+
createPropertyLabel({
|
|
833
|
+
class: 'nrchkb-characteristic-property-label',
|
|
104
834
|
htmlFor: 'property-format'
|
|
105
|
-
}
|
|
835
|
+
}, 'fa-code', 'Format *').appendTo(formatRow)
|
|
106
836
|
const formatInput = $('<select/>', {
|
|
107
837
|
class: 'property-format',
|
|
108
838
|
required: 'required'
|
|
@@ -123,7 +853,10 @@
|
|
|
123
853
|
formatInput.val(props.format)
|
|
124
854
|
|
|
125
855
|
const unitRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
126
|
-
|
|
856
|
+
createPropertyLabel({
|
|
857
|
+
class: 'nrchkb-characteristic-property-label',
|
|
858
|
+
htmlFor: 'property-unit'
|
|
859
|
+
}, 'fa-balance-scale', 'Unit').appendTo(unitRow)
|
|
127
860
|
const unitSelect = $('<select/>', {class: 'property-unit'}).appendTo(unitRow)
|
|
128
861
|
$('<option/>').val(undefined).text('Choose...').appendTo(unitSelect)
|
|
129
862
|
$('<option/>').val('celsius').text('CELSIUS').appendTo(unitSelect)
|
|
@@ -134,10 +867,10 @@
|
|
|
134
867
|
unitSelect.val(props.unit)
|
|
135
868
|
|
|
136
869
|
const permsRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
137
|
-
|
|
138
|
-
class: '
|
|
870
|
+
createPropertyLabel({
|
|
871
|
+
class: 'nrchkb-characteristic-property-label',
|
|
139
872
|
htmlFor: 'property-perms'
|
|
140
|
-
}
|
|
873
|
+
}, 'fa-lock', 'Permissions').appendTo(permsRow)
|
|
141
874
|
const permsSelect = $('<select/>', {
|
|
142
875
|
class: 'property-perms',
|
|
143
876
|
multiple: 'multiple'
|
|
@@ -152,10 +885,10 @@
|
|
|
152
885
|
permsSelect.val(props.perms)
|
|
153
886
|
|
|
154
887
|
const evRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
155
|
-
|
|
156
|
-
class: '
|
|
888
|
+
createPropertyLabel({
|
|
889
|
+
class: 'nrchkb-characteristic-property-label',
|
|
157
890
|
htmlFor: 'property-ev'
|
|
158
|
-
}
|
|
891
|
+
}, 'fa-bell-o', 'Event Notifications').appendTo(evRow)
|
|
159
892
|
$('<input/>', {
|
|
160
893
|
class: 'property-ev',
|
|
161
894
|
type: 'checkbox',
|
|
@@ -163,80 +896,80 @@
|
|
|
163
896
|
}).appendTo(evRow)
|
|
164
897
|
|
|
165
898
|
const descriptionRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
166
|
-
|
|
167
|
-
class: '
|
|
899
|
+
createPropertyLabel({
|
|
900
|
+
class: 'nrchkb-characteristic-property-label',
|
|
168
901
|
htmlFor: 'property-description'
|
|
169
|
-
}
|
|
902
|
+
}, 'fa-align-left', 'Description').appendTo(descriptionRow)
|
|
170
903
|
$('<input/>', {
|
|
171
904
|
class: 'property-description',
|
|
172
905
|
type: 'text'
|
|
173
906
|
}).appendTo(descriptionRow).val(props.description)
|
|
174
907
|
|
|
175
908
|
const minValueRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
176
|
-
|
|
177
|
-
class: '
|
|
909
|
+
createPropertyLabel({
|
|
910
|
+
class: 'nrchkb-characteristic-property-label',
|
|
178
911
|
htmlFor: 'property-minValue'
|
|
179
|
-
}
|
|
912
|
+
}, 'fa-sort-numeric-asc', 'Minimum Value').appendTo(minValueRow)
|
|
180
913
|
$('<input/>', {
|
|
181
914
|
class: 'property-minValue',
|
|
182
915
|
type: 'number'
|
|
183
916
|
}).appendTo(minValueRow).val(props.minValue)
|
|
184
917
|
|
|
185
918
|
const maxValueRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
186
|
-
|
|
187
|
-
class: '
|
|
919
|
+
createPropertyLabel({
|
|
920
|
+
class: 'nrchkb-characteristic-property-label',
|
|
188
921
|
htmlFor: 'property-maxValue'
|
|
189
|
-
}
|
|
922
|
+
}, 'fa-sort-numeric-desc', 'Maximum Value').appendTo(maxValueRow)
|
|
190
923
|
$('<input/>', {
|
|
191
924
|
class: 'property-maxValue',
|
|
192
925
|
type: 'number'
|
|
193
926
|
}).appendTo(maxValueRow).val(props.maxValue)
|
|
194
927
|
|
|
195
928
|
const minStepRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
196
|
-
|
|
197
|
-
class: '
|
|
929
|
+
createPropertyLabel({
|
|
930
|
+
class: 'nrchkb-characteristic-property-label',
|
|
198
931
|
htmlFor: 'property-minStep'
|
|
199
|
-
}
|
|
932
|
+
}, 'fa-step-forward', 'Minimum Step').appendTo(minStepRow)
|
|
200
933
|
$('<input/>', {
|
|
201
934
|
class: 'property-minStep',
|
|
202
935
|
type: 'number'
|
|
203
936
|
}).appendTo(minStepRow).val(props.minStep)
|
|
204
937
|
|
|
205
938
|
const maxLenRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
206
|
-
|
|
207
|
-
class: '
|
|
939
|
+
createPropertyLabel({
|
|
940
|
+
class: 'nrchkb-characteristic-property-label',
|
|
208
941
|
htmlFor: 'property-maxLen'
|
|
209
|
-
}
|
|
942
|
+
}, 'fa-text-width', 'Maximum Length').appendTo(maxLenRow)
|
|
210
943
|
$('<input/>', {
|
|
211
944
|
class: 'property-maxLen',
|
|
212
945
|
type: 'number'
|
|
213
946
|
}).appendTo(maxLenRow).val(props.maxLen)
|
|
214
947
|
|
|
215
948
|
const maxDataLenRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
216
|
-
|
|
217
|
-
class: '
|
|
949
|
+
createPropertyLabel({
|
|
950
|
+
class: 'nrchkb-characteristic-property-label',
|
|
218
951
|
htmlFor: 'property-maxDataLen'
|
|
219
|
-
}
|
|
952
|
+
}, 'fa-database', 'Maximum Data Length').appendTo(maxDataLenRow)
|
|
220
953
|
$('<input/>', {
|
|
221
954
|
class: 'property-maxDataLen',
|
|
222
955
|
type: 'number'
|
|
223
956
|
}).appendTo(maxDataLenRow).val(props.maxDataLen)
|
|
224
957
|
|
|
225
958
|
const validValuesRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
226
|
-
|
|
227
|
-
class: '
|
|
959
|
+
createPropertyLabel({
|
|
960
|
+
class: 'nrchkb-characteristic-property-label',
|
|
228
961
|
htmlFor: 'property-validValues'
|
|
229
|
-
}
|
|
962
|
+
}, 'fa-check-square-o', 'Valid Values').appendTo(validValuesRow)
|
|
230
963
|
$('<input/>', {
|
|
231
964
|
class: 'property-validValues',
|
|
232
965
|
type: 'text'
|
|
233
966
|
}).appendTo(validValuesRow).val(props.validValues)
|
|
234
967
|
|
|
235
968
|
const validValueRangesRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
236
|
-
const validValueRangesLabel =
|
|
237
|
-
class: '
|
|
969
|
+
const validValueRangesLabel = createPropertyLabel({
|
|
970
|
+
class: 'nrchkb-characteristic-property-label property-validValueRanges-label',
|
|
238
971
|
htmlFor: 'property-validValueRanges'
|
|
239
|
-
}
|
|
972
|
+
}, 'fa-sliders', 'Valid Value Ranges').appendTo(validValueRangesRow)
|
|
240
973
|
const validValueRangesSlider = $('<div/>', {
|
|
241
974
|
class: 'property-validValueRanges',
|
|
242
975
|
type: 'text'
|
|
@@ -248,16 +981,16 @@
|
|
|
248
981
|
max: 500,
|
|
249
982
|
values: props.validValueRanges,
|
|
250
983
|
slide: function (event, ui) {
|
|
251
|
-
validValueRangesLabel
|
|
984
|
+
setIconLabelText(validValueRangesLabel, 'fa-sliders', 'Valid Value Ranges: [' + ui.values[0] + ', ' + ui.values[1] + ']')
|
|
252
985
|
}
|
|
253
986
|
})
|
|
254
|
-
validValueRangesLabel
|
|
987
|
+
setIconLabelText(validValueRangesLabel, 'fa-sliders', 'Valid Value Ranges: [' + validValueRangesSlider.slider('values', 0) + ', ' + validValueRangesSlider.slider('values', 1) + ']')
|
|
255
988
|
|
|
256
989
|
const adminOnlyAccessRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
|
|
257
|
-
|
|
258
|
-
class: '
|
|
990
|
+
createPropertyLabel({
|
|
991
|
+
class: 'nrchkb-characteristic-property-label',
|
|
259
992
|
htmlFor: 'property-adminOnlyAccess'
|
|
260
|
-
}
|
|
993
|
+
}, 'fa-user-secret', 'Admin Only Access').appendTo(adminOnlyAccessRow)
|
|
261
994
|
const adminOnlyAccessSelect = $('<select/>', {
|
|
262
995
|
class: 'property-adminOnlyAccess',
|
|
263
996
|
multiple: 'multiple'
|
|
@@ -285,15 +1018,15 @@
|
|
|
285
1018
|
}
|
|
286
1019
|
},
|
|
287
1020
|
oneditresize: function (size) {
|
|
288
|
-
const rows = $('#dialog-form>
|
|
1021
|
+
const rows = $('#dialog-form .nrchkb-section > summary, #dialog-form .nrchkb-section-body > .form-row:not(.node-input-customCharacteristics-container-row)')
|
|
289
1022
|
let height = size.height
|
|
290
1023
|
for (let i = 0; i < rows.length; i++) {
|
|
291
1024
|
height -= $(rows[i]).outerHeight(true)
|
|
292
1025
|
}
|
|
293
|
-
const editorRow = $('#dialog-form
|
|
1026
|
+
const editorRow = $('#dialog-form .node-input-customCharacteristics-container-row')
|
|
294
1027
|
height -= (parseInt(editorRow.css('marginTop')) + parseInt(editorRow.css('marginBottom')))
|
|
295
1028
|
height += 16
|
|
296
|
-
$('#node-input-customCharacteristics-container').editableList('height', height)
|
|
1029
|
+
$('#node-input-customCharacteristics-container').editableList('height', Math.max(150, height))
|
|
297
1030
|
}
|
|
298
1031
|
})
|
|
299
1032
|
}
|
|
@@ -304,15 +1037,18 @@
|
|
|
304
1037
|
return port !== 1880 && port >= 1 && port <= 65535 && port === port.toString()
|
|
305
1038
|
}
|
|
306
1039
|
|
|
1040
|
+
// biome-ignore lint/correctness/noUnusedVariables: Shared editor helper consumed by node templates loaded after nrchkb.html.
|
|
307
1041
|
const isValueDefined = function (value) {
|
|
308
1042
|
return 'undefined' === typeof value ? false : null !== value
|
|
309
1043
|
}
|
|
310
1044
|
|
|
311
|
-
|
|
312
|
-
let
|
|
1045
|
+
// biome-ignore lint/correctness/noUnusedVariables: Shared editor data consumed by service editors loaded after nrchkb.html.
|
|
1046
|
+
let serviceTypes = {}
|
|
1047
|
+
let accessoryCategories = {}
|
|
1048
|
+
// biome-ignore lint/correctness/noUnusedVariables: Shared editor data consumed by config and service editors loaded after nrchkb.html.
|
|
313
1049
|
let nrchkbVersion = '0.0.0'
|
|
314
|
-
let nrchkbExperimental = false
|
|
315
1050
|
let nrchkbConfig = {}
|
|
1051
|
+
let nrchkbAdvertiserRecommendation = null
|
|
316
1052
|
|
|
317
1053
|
//HomeKit Service Types
|
|
318
1054
|
$.getJSON('nrchkb/service/types', function (data) {
|
|
@@ -331,24 +1067,111 @@
|
|
|
331
1067
|
async: false,
|
|
332
1068
|
success: function (data) {
|
|
333
1069
|
nrchkbVersion = data.version
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
if (nrchkbExperimental) {
|
|
337
|
-
initExperimental()
|
|
338
|
-
}
|
|
1070
|
+
initNRCHKBConfigNode()
|
|
339
1071
|
},
|
|
340
1072
|
})
|
|
341
1073
|
|
|
1074
|
+
const renderAdvertiserRecommendation = function (recommendation) {
|
|
1075
|
+
if (!recommendation || !recommendation.recommended) {
|
|
1076
|
+
return 'Recommendation unavailable.'
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
const caveats = Array.isArray(recommendation.caveats)
|
|
1080
|
+
? recommendation.caveats
|
|
1081
|
+
: []
|
|
1082
|
+
const caveatText = caveats.length
|
|
1083
|
+
? ' ' + caveats.join(' ')
|
|
1084
|
+
: ''
|
|
1085
|
+
const reason = recommendation.reason || ''
|
|
1086
|
+
const reasonText = reason.replace(/[.!?]+$/, '') + '.'
|
|
1087
|
+
|
|
1088
|
+
return (
|
|
1089
|
+
'<strong>' +
|
|
1090
|
+
recommendation.title +
|
|
1091
|
+
'</strong>: ' +
|
|
1092
|
+
reasonText +
|
|
1093
|
+
caveatText
|
|
1094
|
+
)
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
const updateAdvertiserHelpTemplates = function () {
|
|
1098
|
+
const renderedRecommendation = renderAdvertiserRecommendation(
|
|
1099
|
+
nrchkbAdvertiserRecommendation,
|
|
1100
|
+
)
|
|
1101
|
+
|
|
1102
|
+
$('script[data-help-name="homekit-bridge"], script[data-help-name="homekit-standalone"]').each(function () {
|
|
1103
|
+
this.innerHTML = this.innerHTML.replace(
|
|
1104
|
+
/<p class="nrchkb-advertiser-dynamic">[\s\S]*?<\/p>/,
|
|
1105
|
+
'<p class="nrchkb-advertiser-dynamic">' +
|
|
1106
|
+
renderedRecommendation +
|
|
1107
|
+
'</p>',
|
|
1108
|
+
)
|
|
1109
|
+
})
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
const applyAdvertiserRecommendation = function (select) {
|
|
1113
|
+
const selectAdvertiser = $(select)
|
|
1114
|
+
|
|
1115
|
+
if (!selectAdvertiser.length) {
|
|
1116
|
+
return
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
const recommendation = nrchkbAdvertiserRecommendation
|
|
1120
|
+
|
|
1121
|
+
selectAdvertiser.find('option').each(function () {
|
|
1122
|
+
const option = $(this)
|
|
1123
|
+
const originalText = option.data('nrchkbOriginalText') || option.text()
|
|
1124
|
+
option.data('nrchkbOriginalText', originalText)
|
|
1125
|
+
option.text(originalText)
|
|
1126
|
+
})
|
|
1127
|
+
|
|
1128
|
+
selectAdvertiser
|
|
1129
|
+
.closest('.form-row')
|
|
1130
|
+
.next('.nrchkb-advertiser-recommendation')
|
|
1131
|
+
.remove()
|
|
1132
|
+
|
|
1133
|
+
if (!recommendation || !recommendation.recommended) {
|
|
1134
|
+
return
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
const recommendedOption = selectAdvertiser.find(
|
|
1138
|
+
'option[value="' + recommendation.recommended + '"]',
|
|
1139
|
+
)
|
|
1140
|
+
|
|
1141
|
+
if (recommendedOption.length) {
|
|
1142
|
+
recommendedOption.text(
|
|
1143
|
+
recommendedOption.data('nrchkbOriginalText') + ' (recommended)',
|
|
1144
|
+
)
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
selectAdvertiser
|
|
1148
|
+
.closest('.form-row')
|
|
1149
|
+
.after(
|
|
1150
|
+
'<div class="nrchkb-advertiser-recommendation">' +
|
|
1151
|
+
renderAdvertiserRecommendation(recommendation) +
|
|
1152
|
+
'</div>',
|
|
1153
|
+
)
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
$.getJSON('nrchkb/advertiser/recommendation', function (data) {
|
|
1157
|
+
nrchkbAdvertiserRecommendation = data
|
|
1158
|
+
updateAdvertiserHelpTemplates()
|
|
1159
|
+
applyAdvertiserRecommendation('#node-config-input-advertiser')
|
|
1160
|
+
})
|
|
1161
|
+
|
|
1162
|
+
// biome-ignore lint/correctness/noUnusedVariables: Shared editor validator consumed by node templates loaded after nrchkb.html.
|
|
342
1163
|
const versionValidator = function (value) {
|
|
343
1164
|
return value ? /^(\d+\.)?(\d+\.)?(\.|\d+)$/.test(value) : true
|
|
344
1165
|
}
|
|
345
1166
|
|
|
1167
|
+
// biome-ignore lint/correctness/noUnusedVariables: Shared editor validator consumed by config node templates loaded after nrchkb.html.
|
|
346
1168
|
const hostNameValidator = function (value) {
|
|
347
1169
|
return value ? /^[^.]{1,64}$/.test(value) : false
|
|
348
1170
|
}
|
|
349
1171
|
|
|
1172
|
+
// biome-ignore lint/correctness/noUnusedVariables: Shared editor validator consumed by service templates loaded after nrchkb.html.
|
|
350
1173
|
const cameraConfigRequiredField = function (value) {
|
|
351
|
-
return 'CameraControl'
|
|
1174
|
+
return ['CameraControl', 'Camera'].includes(this.serviceName) ? (value || '').toString().trim() : true
|
|
352
1175
|
}
|
|
353
1176
|
|
|
354
1177
|
const saveCustomCharacteristics = function (self) {
|
|
@@ -440,6 +1263,7 @@
|
|
|
440
1263
|
return `${a}${b}${c}${d}-${e}${f}${g}${h}`
|
|
441
1264
|
}
|
|
442
1265
|
|
|
1266
|
+
// biome-ignore lint/correctness/noUnusedVariables: Shared editor validator consumed by config node templates loaded after nrchkb.html.
|
|
443
1267
|
const validatePinCode = function (value) {
|
|
444
1268
|
if (!value) {
|
|
445
1269
|
return false
|
|
@@ -451,23 +1275,712 @@
|
|
|
451
1275
|
|
|
452
1276
|
return !forbiddenPinCodes.includes(value.replaceAll('-', ''))
|
|
453
1277
|
}
|
|
454
|
-
</script>
|
|
455
1278
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
1279
|
+
// biome-ignore lint/correctness/noUnusedVariables: Shared editor helper consumed by node templates loaded after nrchkb.html.
|
|
1280
|
+
const sortSelectOptionsByLabel = function (select, options) {
|
|
1281
|
+
const selectElement = $(select)
|
|
1282
|
+
const sortedOptions = (options || []).sort(function (left, right) {
|
|
1283
|
+
return left.text.localeCompare(right.text, undefined, {
|
|
1284
|
+
numeric: true,
|
|
1285
|
+
sensitivity: 'base'
|
|
1286
|
+
})
|
|
1287
|
+
})
|
|
1288
|
+
|
|
1289
|
+
sortedOptions.forEach(function (option) {
|
|
1290
|
+
selectElement.append(option.element)
|
|
1291
|
+
})
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
const nrchkbLegacyDefaultNodeInfo = {
|
|
1295
|
+
'homekit-status': [
|
|
1296
|
+
'# HomeKit Status',
|
|
1297
|
+
'',
|
|
1298
|
+
'> [!NOTE]',
|
|
1299
|
+
'> Any input message reads the selected HomeKit service and emits a serialized service description.',
|
|
1300
|
+
].join('\n'),
|
|
1301
|
+
nrchkb: [
|
|
1302
|
+
'# NRCHKB Custom Characteristics',
|
|
1303
|
+
'',
|
|
1304
|
+
'> [!CAUTION]',
|
|
1305
|
+
'> Custom characteristics should be used only when a standard HAP characteristic cannot represent the device state.',
|
|
1306
|
+
].join('\n'),
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
const nrchkbDefaultNodeInfo = {
|
|
1310
|
+
'homekit-service2': [
|
|
1311
|
+
'# HomeKit Service 2',
|
|
1312
|
+
'',
|
|
1313
|
+
'> [!IMPORTANT]',
|
|
1314
|
+
'> Send characteristic updates as `msg.payload` keys using HomeKit characteristic names, for example `{\"On\": true}`.',
|
|
1315
|
+
'',
|
|
1316
|
+
'> [!NOTE]',
|
|
1317
|
+
'> Use the node Help sidebar for the full characteristic, plugin, and migration reference.',
|
|
1318
|
+
].join('\n'),
|
|
1319
|
+
'homekit-bridge': [
|
|
1320
|
+
'# HomeKit Bridge',
|
|
1321
|
+
'',
|
|
1322
|
+
'> [!IMPORTANT]',
|
|
1323
|
+
'> Keep the bridge name, PIN, serial number, and model stable after pairing. HomeKit uses them as accessory identity.',
|
|
1324
|
+
'',
|
|
1325
|
+
'> [!WARNING]',
|
|
1326
|
+
'> Leave insecure requests disabled unless you are deliberately testing with a trusted local client.',
|
|
1327
|
+
].join('\n'),
|
|
1328
|
+
'homekit-standalone': [
|
|
1329
|
+
'# HomeKit Standalone Accessory',
|
|
1330
|
+
'',
|
|
1331
|
+
'> [!IMPORTANT]',
|
|
1332
|
+
'> Standalone accessories publish independently. Prefer a bridge for most multi-service setups to reduce mDNS load.',
|
|
1333
|
+
'',
|
|
1334
|
+
'> [!WARNING]',
|
|
1335
|
+
'> Leave insecure requests disabled unless you are deliberately testing with a trusted local client.',
|
|
1336
|
+
].join('\n'),
|
|
1337
|
+
'homekit-service': [
|
|
1338
|
+
'# HomeKit Service',
|
|
1339
|
+
'',
|
|
1340
|
+
'> [!WARNING]',
|
|
1341
|
+
'> This legacy node is kept for existing flows. Use `homekit-service2` for new work and migrate when practical.',
|
|
1342
|
+
'',
|
|
1343
|
+
'> [!NOTE]',
|
|
1344
|
+
'> The migration button prepares changes in the editor. Click Done and then Deploy to commit them.',
|
|
1345
|
+
].join('\n'),
|
|
1346
|
+
'homekit-status': [
|
|
1347
|
+
'# HomeKit Status',
|
|
1348
|
+
'',
|
|
1349
|
+
'Reads the selected `homekit-service` or `homekit-service2` node and outputs a serialized description of its current HomeKit service shape.',
|
|
1350
|
+
'',
|
|
1351
|
+
'> [!NOTE]',
|
|
1352
|
+
'> Any input message triggers a fresh read. The incoming payload is not used as a command.',
|
|
1353
|
+
'',
|
|
1354
|
+
'Use this node for diagnostics, dashboards, migration checks, or flows that need to inspect which characteristics and values a service currently exposes.',
|
|
1355
|
+
'',
|
|
1356
|
+
'The output includes the selected service metadata, characteristics, properties, and current values where the runtime can read them.',
|
|
1357
|
+
].join('\n'),
|
|
1358
|
+
'homekit-unifi-controller': [
|
|
1359
|
+
'# UniFi Controller',
|
|
1360
|
+
'',
|
|
1361
|
+
'> [!IMPORTANT]',
|
|
1362
|
+
'> Use a dedicated local UniFi account with the minimum permissions needed to read Protect camera data.',
|
|
1363
|
+
'',
|
|
1364
|
+
'> [!WARNING]',
|
|
1365
|
+
'> Allow self-signed certificates only for controllers you administer and trust on your local network.',
|
|
1366
|
+
].join('\n'),
|
|
1367
|
+
'homekit-plugin-instance': [
|
|
1368
|
+
'# HomeKit Plugin Instance',
|
|
1369
|
+
'',
|
|
1370
|
+
'> [!NOTE]',
|
|
1371
|
+
'> This internal config node lets Node-RED track plugin dependencies used by NRCHKB service nodes.',
|
|
1372
|
+
].join('\n'),
|
|
1373
|
+
nrchkb: [
|
|
1374
|
+
'# NRCHKB Custom Characteristics',
|
|
1375
|
+
'',
|
|
1376
|
+
'Defines custom HAP characteristics that can be attached to HomeKit services when the standard HAP catalog does not contain the value you need.',
|
|
1377
|
+
'',
|
|
1378
|
+
'> [!CAUTION]',
|
|
1379
|
+
'> Custom characteristics should be used only when a standard HAP characteristic cannot represent the device state.',
|
|
1380
|
+
'',
|
|
1381
|
+
'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.',
|
|
1382
|
+
'',
|
|
1383
|
+
'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.',
|
|
1384
|
+
].join('\n'),
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
const applyDefaultNodeDocumentation = function (node, type) {
|
|
1388
|
+
if ((!node.info || node.info === nrchkbLegacyDefaultNodeInfo[type]) && nrchkbDefaultNodeInfo[type]) {
|
|
1389
|
+
node.info = nrchkbDefaultNodeInfo[type]
|
|
1390
|
+
node.dirty = true
|
|
1391
|
+
return true
|
|
460
1392
|
}
|
|
461
|
-
|
|
462
|
-
|
|
1393
|
+
|
|
1394
|
+
return false
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
const backfillDefaultNodeDocumentation = function (nodes) {
|
|
1398
|
+
let changed = false
|
|
1399
|
+
const applyToNode = function (node) {
|
|
1400
|
+
if (node && applyDefaultNodeDocumentation(node, node.type)) {
|
|
1401
|
+
changed = true
|
|
1402
|
+
}
|
|
463
1403
|
}
|
|
464
|
-
</style>
|
|
465
1404
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
1405
|
+
if (Array.isArray(nodes)) {
|
|
1406
|
+
nodes.forEach(applyToNode)
|
|
1407
|
+
} else if (nodes) {
|
|
1408
|
+
applyToNode(nodes)
|
|
1409
|
+
} else if (RED.nodes) {
|
|
1410
|
+
RED.nodes.eachNode(applyToNode)
|
|
1411
|
+
|
|
1412
|
+
if (typeof RED.nodes.eachConfig === 'function') {
|
|
1413
|
+
RED.nodes.eachConfig(applyToNode)
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
if (changed && RED.view && typeof RED.view.redraw === 'function') {
|
|
1418
|
+
RED.view.redraw(true)
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
if (RED.events) {
|
|
1423
|
+
RED.events.on('flows:loaded', function () {
|
|
1424
|
+
setTimeout(backfillDefaultNodeDocumentation, 0)
|
|
1425
|
+
})
|
|
1426
|
+
|
|
1427
|
+
RED.events.on('nodes:add', function (node) {
|
|
1428
|
+
setTimeout(function () {
|
|
1429
|
+
backfillDefaultNodeDocumentation(node)
|
|
1430
|
+
}, 0)
|
|
1431
|
+
})
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
window.NRCHKBPairingQR = (function () {
|
|
1435
|
+
const stateByHost = {}
|
|
1436
|
+
const settingsStorageKey = 'nrchkb.pairingQR.settings'
|
|
1437
|
+
let initialized = false
|
|
1438
|
+
let refreshTimer
|
|
1439
|
+
let sidebarContent
|
|
1440
|
+
let sidebarList
|
|
1441
|
+
let sidebarStatus
|
|
1442
|
+
let settings = {
|
|
1443
|
+
autoShowWhenPairingAvailable: true,
|
|
1444
|
+
}
|
|
1445
|
+
const text = function (key, fallback) {
|
|
1446
|
+
const keys = [
|
|
1447
|
+
'node-red-contrib-homekit-bridged/' + key,
|
|
1448
|
+
'node-red-contrib-homekit-bridged:' + key,
|
|
1449
|
+
]
|
|
1450
|
+
try {
|
|
1451
|
+
for (let i = 0; i < keys.length; i++) {
|
|
1452
|
+
const value = RED._(keys[i])
|
|
1453
|
+
if (value && value !== keys[i] && value !== key) {
|
|
1454
|
+
return value
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
} catch (_error) {
|
|
1458
|
+
}
|
|
1459
|
+
return fallback
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
const loadSettings = function () {
|
|
1463
|
+
try {
|
|
1464
|
+
const storedSettings = JSON.parse(localStorage.getItem(settingsStorageKey) || '{}')
|
|
1465
|
+
settings = {
|
|
1466
|
+
...settings,
|
|
1467
|
+
...storedSettings,
|
|
1468
|
+
autoShowWhenPairingAvailable: storedSettings.autoShowWhenPairingAvailable !== false,
|
|
1469
|
+
}
|
|
1470
|
+
} catch (_error) {
|
|
1471
|
+
settings.autoShowWhenPairingAvailable = true
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
const saveSettings = function () {
|
|
1476
|
+
try {
|
|
1477
|
+
localStorage.setItem(settingsStorageKey, JSON.stringify(settings))
|
|
1478
|
+
} catch (_error) {
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
const resolveHostId = function (node) {
|
|
1483
|
+
if (!node) {
|
|
1484
|
+
return ''
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
if ((node.type === 'homekit-service' || node.type === 'homekit-service2') && node.bridge) {
|
|
1488
|
+
return node.bridge
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
if ((node.type === 'homekit-service' || node.type === 'homekit-service2') && node.accessoryId) {
|
|
1492
|
+
return node.accessoryId
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
if (node.type === 'homekit-status' && node.serviceNodeId) {
|
|
1496
|
+
return resolveHostId(RED.nodes.node(node.serviceNodeId))
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
return ''
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
const fetchHostState = function (hostId) {
|
|
1503
|
+
if (!hostId) {
|
|
1504
|
+
return Promise.resolve(undefined)
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
return new Promise(function (resolve) {
|
|
1508
|
+
$.ajax({
|
|
1509
|
+
dataType: 'json',
|
|
1510
|
+
url: 'nrchkb/bridge/' + encodeURIComponent(hostId) + '/pairing',
|
|
1511
|
+
})
|
|
1512
|
+
.done(function (data) {
|
|
1513
|
+
stateByHost[hostId] = data
|
|
1514
|
+
resolve(data)
|
|
1515
|
+
})
|
|
1516
|
+
.fail(function (xhr) {
|
|
1517
|
+
const data = xhr.responseJSON || {
|
|
1518
|
+
paired: false,
|
|
1519
|
+
published: false,
|
|
1520
|
+
status: xhr.status === 404 ? 'missing' : 'error',
|
|
1521
|
+
}
|
|
1522
|
+
stateByHost[hostId] = data
|
|
1523
|
+
resolve(data)
|
|
1524
|
+
})
|
|
1525
|
+
})
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
const getPairingHostNodes = function () {
|
|
1529
|
+
const hosts = []
|
|
1530
|
+
const hostTypes = {
|
|
1531
|
+
'homekit-bridge': true,
|
|
1532
|
+
'homekit-standalone': true,
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
if (typeof RED.nodes.eachConfig === 'function') {
|
|
1536
|
+
RED.nodes.eachConfig(function (node) {
|
|
1537
|
+
if (node && hostTypes[node.type]) {
|
|
1538
|
+
hosts.push(node)
|
|
1539
|
+
}
|
|
1540
|
+
})
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
if (hosts.length === 0) {
|
|
1544
|
+
return [
|
|
1545
|
+
...RED.nodes.filterNodes({type: 'homekit-bridge'}),
|
|
1546
|
+
...RED.nodes.filterNodes({type: 'homekit-standalone'}),
|
|
1547
|
+
]
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
return hosts
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
const getCandidateNodes = function () {
|
|
1554
|
+
return [
|
|
1555
|
+
...RED.nodes.filterNodes({type: 'homekit-service'}),
|
|
1556
|
+
...RED.nodes.filterNodes({type: 'homekit-service2'}),
|
|
1557
|
+
...RED.nodes.filterNodes({type: 'homekit-status'}),
|
|
1558
|
+
]
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
const getReferencingNodes = function (hostId) {
|
|
1562
|
+
return getCandidateNodes().filter(function (node) {
|
|
1563
|
+
return resolveHostId(node) === hostId
|
|
1564
|
+
})
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
const formatPinCode = function (formattedPinCode) {
|
|
1568
|
+
return formattedPinCode ? formattedPinCode.top + ' ' + formattedPinCode.bottom : ''
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
const createPairingSetupCard = function (bridgeState) {
|
|
1572
|
+
const setupCard = $('<div/>', {class: 'nrchkb-pairing-setup-card'})
|
|
1573
|
+
$('<img/>', {
|
|
1574
|
+
alt: text('qr.imageAlt', 'HomeKit pairing QR code'),
|
|
1575
|
+
class: 'nrchkb-pairing-qr',
|
|
1576
|
+
src: bridgeState.qrCodeDataUrl,
|
|
1577
|
+
}).appendTo(setupCard)
|
|
1578
|
+
return setupCard
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
const isPairingAvailable = function (bridgeState) {
|
|
1582
|
+
return !!bridgeState && bridgeState.published && !bridgeState.paired && !!bridgeState.qrCodeDataUrl
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
const nodeDisplayName = function (node) {
|
|
1586
|
+
return node.name || node.serviceName || node.id || text('qr.unknownAccessory', 'Accessory')
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
const serviceTypeName = function (node) {
|
|
1590
|
+
if (!node) {
|
|
1591
|
+
return ''
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
if (node.type === 'homekit-status' && node.serviceNodeId) {
|
|
1595
|
+
return serviceTypeName(RED.nodes.node(node.serviceNodeId))
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
return node.serviceName || node.service || node.type || ''
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
const hostCategoryName = function (hostNode) {
|
|
1602
|
+
if (!hostNode || hostNode.type !== 'homekit-standalone') {
|
|
1603
|
+
return ''
|
|
1604
|
+
}
|
|
469
1605
|
|
|
470
|
-
|
|
471
|
-
|
|
1606
|
+
return accessoryCategories[hostNode.accessoryCategory] || hostNode.accessoryCategory || ''
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
const hostTypeName = function (hostNode) {
|
|
1610
|
+
return hostNode && hostNode.type === 'homekit-standalone' ? 'Standalone accessory' : 'Bridge'
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
const appendMeta = function (container, items) {
|
|
1614
|
+
const meta = $('<dl/>', {class: 'nrchkb-sidebar-meta'}).appendTo(container)
|
|
1615
|
+
items.forEach(function (item) {
|
|
1616
|
+
if (!item.value) {
|
|
1617
|
+
return
|
|
1618
|
+
}
|
|
1619
|
+
$('<dt/>', {text: item.label}).appendTo(meta)
|
|
1620
|
+
$('<dd/>', {text: item.value}).appendTo(meta)
|
|
1621
|
+
})
|
|
1622
|
+
return meta
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
const renderAccessoryListItem = function (list, node) {
|
|
1626
|
+
const item = $('<li/>').appendTo(list)
|
|
1627
|
+
$('<span/>', {
|
|
1628
|
+
class: 'nrchkb-sidebar-accessory-name',
|
|
1629
|
+
text: nodeDisplayName(node),
|
|
1630
|
+
}).appendTo(item)
|
|
1631
|
+
$('<span/>', {
|
|
1632
|
+
class: 'nrchkb-sidebar-accessory-meta',
|
|
1633
|
+
text: 'id: ' + node.id + ', service: ' + serviceTypeName(node),
|
|
1634
|
+
}).appendTo(item)
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
const renderSidebarCardContent = function (card, hostNode, hostState) {
|
|
1638
|
+
card.empty()
|
|
1639
|
+
$('<h3/>', {
|
|
1640
|
+
text: hostState.bridgeName || hostNode.name || hostNode.bridgeName || hostNode.id,
|
|
1641
|
+
}).appendTo(card)
|
|
1642
|
+
appendMeta(card, [
|
|
1643
|
+
{label: 'Type', value: hostTypeName(hostNode)},
|
|
1644
|
+
{label: 'ID', value: hostNode.id},
|
|
1645
|
+
{label: 'Category', value: hostCategoryName(hostNode)},
|
|
1646
|
+
])
|
|
1647
|
+
createPairingSetupCard(hostState).appendTo(card)
|
|
1648
|
+
|
|
1649
|
+
const accessories = getReferencingNodes(hostNode.id)
|
|
1650
|
+
const accessorySection = $('<div/>', {class: 'nrchkb-sidebar-accessories'}).appendTo(card)
|
|
1651
|
+
$('<p/>', {
|
|
1652
|
+
class: 'nrchkb-sidebar-accessories-title',
|
|
1653
|
+
text: text('qr.accessories', 'Accessories'),
|
|
1654
|
+
}).appendTo(accessorySection)
|
|
1655
|
+
|
|
1656
|
+
if (accessories.length === 0) {
|
|
1657
|
+
$('<p/>', {
|
|
1658
|
+
class: 'nrchkb-pairing-message',
|
|
1659
|
+
text: text('qr.noAccessories', 'No flow nodes reference this pairing host.'),
|
|
1660
|
+
}).appendTo(accessorySection)
|
|
1661
|
+
} else {
|
|
1662
|
+
const list = $('<ul/>').appendTo(accessorySection)
|
|
1663
|
+
accessories.forEach(function (node) {
|
|
1664
|
+
renderAccessoryListItem(list, node)
|
|
1665
|
+
})
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
const createSidebarCard = function (hostNode, hostState) {
|
|
1670
|
+
const card = $('<div/>', {
|
|
1671
|
+
class: 'nrchkb-sidebar-pairing-card',
|
|
1672
|
+
'data-host-id': hostNode.id,
|
|
1673
|
+
role: 'listitem',
|
|
1674
|
+
})
|
|
1675
|
+
renderSidebarCardContent(card, hostNode, hostState)
|
|
1676
|
+
return card
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
const sidebarCardSignature = function (hostNode, hostState) {
|
|
1680
|
+
return JSON.stringify({
|
|
1681
|
+
accessories: getReferencingNodes(hostNode.id).map(function (node) {
|
|
1682
|
+
return {
|
|
1683
|
+
id: node.id,
|
|
1684
|
+
name: nodeDisplayName(node),
|
|
1685
|
+
serviceType: serviceTypeName(node),
|
|
1686
|
+
}
|
|
1687
|
+
}),
|
|
1688
|
+
category: hostCategoryName(hostNode),
|
|
1689
|
+
hostId: hostNode.id,
|
|
1690
|
+
hostName: hostState.bridgeName || hostNode.name || hostNode.bridgeName || hostNode.id,
|
|
1691
|
+
hostType: hostTypeName(hostNode),
|
|
1692
|
+
qrCodeDataUrl: hostState.qrCodeDataUrl,
|
|
1693
|
+
})
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
const findSidebarCard = function (hostId) {
|
|
1697
|
+
return sidebarList.children('.nrchkb-sidebar-pairing-card').filter(function () {
|
|
1698
|
+
return $(this).attr('data-host-id') === hostId
|
|
1699
|
+
})
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
const removeSidebarCard = function (card, onComplete) {
|
|
1703
|
+
if (card.data('nrchkbRemoving')) {
|
|
1704
|
+
return
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
card.data('nrchkbRemoving', true)
|
|
1708
|
+
card.stop(true, true).slideUp(180, function () {
|
|
1709
|
+
card.remove()
|
|
1710
|
+
if (onComplete) {
|
|
1711
|
+
onComplete()
|
|
1712
|
+
}
|
|
1713
|
+
})
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
const updateEmptySidebarState = function () {
|
|
1717
|
+
if (sidebarList.children('.nrchkb-sidebar-pairing-card').length === 0) {
|
|
1718
|
+
sidebarStatus.text(text('qr.noUnpaired', 'Nothing to be paired.'))
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
const refreshSidebar = function (options) {
|
|
1723
|
+
options = options || {}
|
|
1724
|
+
|
|
1725
|
+
if (!sidebarList || !sidebarStatus) {
|
|
1726
|
+
return Promise.resolve()
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
const pairingHosts = getPairingHostNodes()
|
|
1730
|
+
|
|
1731
|
+
if (pairingHosts.length === 0) {
|
|
1732
|
+
sidebarList.children('.nrchkb-sidebar-pairing-card').each(function () {
|
|
1733
|
+
removeSidebarCard($(this), updateEmptySidebarState)
|
|
1734
|
+
})
|
|
1735
|
+
updateEmptySidebarState()
|
|
1736
|
+
return Promise.resolve()
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
return Promise.all(pairingHosts.map(function (hostNode) {
|
|
1740
|
+
return fetchHostState(hostNode.id).then(function (hostState) {
|
|
1741
|
+
return {hostNode, hostState}
|
|
1742
|
+
})
|
|
1743
|
+
})).then(function (results) {
|
|
1744
|
+
const available = results.filter(function (result) {
|
|
1745
|
+
return isPairingAvailable(result.hostState)
|
|
1746
|
+
})
|
|
1747
|
+
const availableById = {}
|
|
1748
|
+
|
|
1749
|
+
available.forEach(function (result) {
|
|
1750
|
+
availableById[result.hostNode.id] = result
|
|
1751
|
+
})
|
|
1752
|
+
|
|
1753
|
+
sidebarList.children('.nrchkb-sidebar-pairing-card').each(function () {
|
|
1754
|
+
const card = $(this)
|
|
1755
|
+
const hostId = card.attr('data-host-id')
|
|
1756
|
+
|
|
1757
|
+
if (!availableById[hostId]) {
|
|
1758
|
+
removeSidebarCard(card, updateEmptySidebarState)
|
|
1759
|
+
}
|
|
1760
|
+
})
|
|
1761
|
+
|
|
1762
|
+
if (available.length === 0) {
|
|
1763
|
+
updateEmptySidebarState()
|
|
1764
|
+
} else {
|
|
1765
|
+
sidebarStatus.text('')
|
|
1766
|
+
available.forEach(function (result) {
|
|
1767
|
+
const signature = sidebarCardSignature(result.hostNode, result.hostState)
|
|
1768
|
+
const card = findSidebarCard(result.hostNode.id)
|
|
1769
|
+
|
|
1770
|
+
if (card.length > 0) {
|
|
1771
|
+
if (card.attr('data-signature') !== signature) {
|
|
1772
|
+
renderSidebarCardContent(card, result.hostNode, result.hostState)
|
|
1773
|
+
card.attr('data-signature', signature)
|
|
1774
|
+
}
|
|
1775
|
+
return
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
const newCard = createSidebarCard(result.hostNode, result.hostState)
|
|
1779
|
+
.attr('data-signature', signature)
|
|
1780
|
+
.hide()
|
|
1781
|
+
sidebarList.append(newCard)
|
|
1782
|
+
newCard.slideDown(180)
|
|
1783
|
+
})
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
if (options.show && available.length > 0 && settings.autoShowWhenPairingAvailable && RED.sidebar) {
|
|
1787
|
+
RED.sidebar.show('nrchkb')
|
|
1788
|
+
}
|
|
1789
|
+
})
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
const registerSidebar = function () {
|
|
1793
|
+
sidebarContent = $('<div/>', {class: 'nrchkb-editor nrchkb-sidebar-pairing'})
|
|
1794
|
+
const section = $('<details/>', {class: 'nrchkb-section', open: true}).appendTo(sidebarContent)
|
|
1795
|
+
$('<summary/>')
|
|
1796
|
+
.append($('<i/>', {class: 'fa fa-qrcode'}))
|
|
1797
|
+
.append(' ')
|
|
1798
|
+
.append($('<span/>', {text: text('qr.sectionTitle', 'Pairing QR Code')}))
|
|
1799
|
+
.appendTo(section)
|
|
1800
|
+
const sectionBody = $('<div/>', {class: 'nrchkb-section-body'}).appendTo(section)
|
|
1801
|
+
const autoShowLabel = $('<label/>', {
|
|
1802
|
+
class: 'nrchkb-sidebar-setting nrchkb-sidebar-pairing-control',
|
|
1803
|
+
}).appendTo(sectionBody)
|
|
1804
|
+
$('<input/>', {
|
|
1805
|
+
checked: settings.autoShowWhenPairingAvailable,
|
|
1806
|
+
type: 'checkbox',
|
|
1807
|
+
})
|
|
1808
|
+
.on('change', function () {
|
|
1809
|
+
settings.autoShowWhenPairingAvailable = this.checked
|
|
1810
|
+
saveSettings()
|
|
1811
|
+
})
|
|
1812
|
+
.appendTo(autoShowLabel)
|
|
1813
|
+
$('<span/>', {
|
|
1814
|
+
text: text('qr.autoShowWhenPairingAvailable', 'Auto show if something needs pairing'),
|
|
1815
|
+
}).appendTo(autoShowLabel)
|
|
1816
|
+
sidebarStatus = $('<p/>', {
|
|
1817
|
+
class: 'nrchkb-sidebar-pairing-status',
|
|
1818
|
+
'aria-live': 'polite',
|
|
1819
|
+
}).appendTo(sectionBody)
|
|
1820
|
+
sidebarList = $('<div/>', {
|
|
1821
|
+
class: 'nrchkb-sidebar-pairing-list',
|
|
1822
|
+
role: 'list',
|
|
1823
|
+
}).appendTo(sectionBody)
|
|
1824
|
+
|
|
1825
|
+
const toolbar = $('<div/>', {class: 'nrchkb-sidebar-toolbar'})
|
|
1826
|
+
$('<button/>', {
|
|
1827
|
+
class: 'red-ui-button red-ui-button-small',
|
|
1828
|
+
'aria-label': text('qr.refresh', 'Refresh'),
|
|
1829
|
+
title: text('qr.refresh', 'Refresh'),
|
|
1830
|
+
type: 'button',
|
|
1831
|
+
})
|
|
1832
|
+
.append($('<i/>', {class: 'fa fa-refresh'}))
|
|
1833
|
+
.on('click', function () {
|
|
1834
|
+
refreshSidebar()
|
|
1835
|
+
})
|
|
1836
|
+
.appendTo(toolbar)
|
|
1837
|
+
|
|
1838
|
+
RED.sidebar.addTab({
|
|
1839
|
+
action: 'nrchkb:show-pairing-tab',
|
|
1840
|
+
content: sidebarContent,
|
|
1841
|
+
enableOnEdit: true,
|
|
1842
|
+
iconClass: 'fa fa-nrchkb',
|
|
1843
|
+
id: 'nrchkb',
|
|
1844
|
+
label: text('qr.sidebarTitle', 'NRCHKB'),
|
|
1845
|
+
name: text('qr.sidebarTitle', 'NRCHKB'),
|
|
1846
|
+
pinned: true,
|
|
1847
|
+
toolbar,
|
|
1848
|
+
})
|
|
1849
|
+
|
|
1850
|
+
RED.actions.add('nrchkb:show-pairing-tab', function () {
|
|
1851
|
+
RED.sidebar.show('nrchkb')
|
|
1852
|
+
refreshSidebar()
|
|
1853
|
+
})
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
const renderHostEditor = function (hostId, selector) {
|
|
1857
|
+
const container = $(selector)
|
|
1858
|
+
container.empty().append($('<p/>', {
|
|
1859
|
+
class: 'nrchkb-pairing-message',
|
|
1860
|
+
text: text('qr.loading', 'Loading pairing state...'),
|
|
1861
|
+
}))
|
|
1862
|
+
|
|
1863
|
+
fetchHostState(hostId).then(function (hostState) {
|
|
1864
|
+
container.empty()
|
|
1865
|
+
|
|
1866
|
+
if (!hostState || hostState.status === 'missing' || !hostState.published) {
|
|
1867
|
+
container.append($('<p/>', {
|
|
1868
|
+
class: 'nrchkb-pairing-message',
|
|
1869
|
+
text: text('qr.deployFirst', 'Deploy this bridge or accessory to generate a pairing QR code.'),
|
|
1870
|
+
}))
|
|
1871
|
+
return
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
if (hostState.paired) {
|
|
1875
|
+
container.append($('<p/>', {
|
|
1876
|
+
class: 'nrchkb-pairing-message',
|
|
1877
|
+
text: text('qr.alreadyPaired', 'This bridge or accessory is already paired.'),
|
|
1878
|
+
}))
|
|
1879
|
+
return
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
const card = $('<div/>', {class: 'nrchkb-pairing-card'})
|
|
1883
|
+
createPairingSetupCard(hostState).appendTo(card)
|
|
1884
|
+
const details = $('<div/>', {class: 'nrchkb-pairing-details'}).appendTo(card)
|
|
1885
|
+
$('<p/>', {
|
|
1886
|
+
class: 'nrchkb-pairing-state',
|
|
1887
|
+
text: text('qr.ready', 'Ready to pair'),
|
|
1888
|
+
}).appendTo(details)
|
|
1889
|
+
$('<span/>', {
|
|
1890
|
+
class: 'nrchkb-pairing-code',
|
|
1891
|
+
text: formatPinCode(hostState.formattedPinCode),
|
|
1892
|
+
}).appendTo(details)
|
|
1893
|
+
$('<span/>', {
|
|
1894
|
+
class: 'nrchkb-pairing-uri',
|
|
1895
|
+
text: hostState.setupUri,
|
|
1896
|
+
}).appendTo(details)
|
|
1897
|
+
container.append(card)
|
|
1898
|
+
})
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
const init = function () {
|
|
1902
|
+
if (initialized) {
|
|
1903
|
+
return
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
initialized = true
|
|
1907
|
+
|
|
1908
|
+
loadSettings()
|
|
1909
|
+
registerSidebar()
|
|
1910
|
+
|
|
1911
|
+
if (RED.events) {
|
|
1912
|
+
RED.events.on('deploy', function () {
|
|
1913
|
+
setTimeout(function () {
|
|
1914
|
+
refreshSidebar({show: true})
|
|
1915
|
+
}, 1500)
|
|
1916
|
+
})
|
|
1917
|
+
|
|
1918
|
+
;['flows:loaded', 'nodes:add', 'nodes:change', 'nodes:remove'].forEach(function (eventName) {
|
|
1919
|
+
RED.events.on(eventName, function () {
|
|
1920
|
+
setTimeout(refreshSidebar, 250)
|
|
1921
|
+
})
|
|
1922
|
+
})
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
refreshTimer = setInterval(refreshSidebar, 10000)
|
|
1926
|
+
window.addEventListener('beforeunload', function () {
|
|
1927
|
+
clearInterval(refreshTimer)
|
|
1928
|
+
})
|
|
1929
|
+
setTimeout(function () {
|
|
1930
|
+
refreshSidebar({show: true})
|
|
1931
|
+
}, 1000)
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
return {
|
|
1935
|
+
init,
|
|
1936
|
+
refreshSidebar,
|
|
1937
|
+
renderBridgeEditor: renderHostEditor,
|
|
1938
|
+
renderHostEditor,
|
|
1939
|
+
}
|
|
1940
|
+
})()
|
|
1941
|
+
RED.NRCHKBPairingQR = window.NRCHKBPairingQR
|
|
1942
|
+
|
|
1943
|
+
setTimeout(function () {
|
|
1944
|
+
if (RED.NRCHKBPairingQR) {
|
|
1945
|
+
RED.NRCHKBPairingQR.init()
|
|
1946
|
+
}
|
|
1947
|
+
}, 1000)
|
|
1948
|
+
</script>
|
|
1949
|
+
|
|
1950
|
+
<script data-template-name="nrchkb" type="text/x-red">
|
|
1951
|
+
<div class="nrchkb-editor">
|
|
1952
|
+
<details class="nrchkb-section" open>
|
|
1953
|
+
<summary><i class="fa fa-list-alt"></i> Custom Characteristics</summary>
|
|
1954
|
+
<div class="nrchkb-section-body">
|
|
1955
|
+
<div class="form-row node-input-customCharacteristics-container-row">
|
|
1956
|
+
<ol id="node-input-customCharacteristics-container"></ol>
|
|
1957
|
+
</div>
|
|
1958
|
+
</div>
|
|
1959
|
+
</details>
|
|
472
1960
|
</div>
|
|
473
|
-
</script>
|
|
1961
|
+
</script>
|
|
1962
|
+
|
|
1963
|
+
<script data-help-name="nrchkb" type="text/markdown">
|
|
1964
|
+
# NRCHKB Custom Characteristics
|
|
1965
|
+
|
|
1966
|
+
Defines custom HAP characteristics that can be attached to HomeKit services when the standard HAP catalog does not contain the value you need.
|
|
1967
|
+
|
|
1968
|
+
> [!CAUTION]
|
|
1969
|
+
> Prefer standard HAP characteristics whenever possible. Custom characteristics may not be shown or interpreted consistently by every HomeKit client.
|
|
1970
|
+
|
|
1971
|
+
## Characteristic Identity
|
|
1972
|
+
|
|
1973
|
+
- **Name**: Display name used by NRCHKB when selecting or reporting the characteristic.
|
|
1974
|
+
- **UUID**: Stable HAP UUID for the custom characteristic. Keep it unchanged once HomeKit clients have seen it.
|
|
1975
|
+
|
|
1976
|
+
## Value Shape
|
|
1977
|
+
|
|
1978
|
+
- **Format**: HAP value format such as boolean, integer, float, string, or data.
|
|
1979
|
+
- **Unit**: Optional HomeKit unit metadata for numeric values.
|
|
1980
|
+
- **Minimum / Maximum / Step**: Numeric constraints exposed to HomeKit clients.
|
|
1981
|
+
- **Valid Values**: Optional list of allowed values for enumerated characteristics.
|
|
1982
|
+
|
|
1983
|
+
## Permissions
|
|
1984
|
+
|
|
1985
|
+
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.
|
|
1986
|
+
</script>
|