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
|
@@ -1,326 +1,472 @@
|
|
|
1
1
|
<!--suppress EqualityComparisonWithCoercionJS -->
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
<style>
|
|
3
|
+
.nrchkb-editor .nrchkb-plugins-summary {
|
|
4
|
+
display: flex !important;
|
|
5
|
+
align-items: center;
|
|
6
|
+
gap: 6px;
|
|
7
|
+
min-width: 0;
|
|
8
|
+
padding-right: 112px;
|
|
9
|
+
}
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Service</label>
|
|
15
|
-
<select id="node-input-serviceName" style="width: 70%">
|
|
16
|
-
<option value="">Choose...</option>
|
|
17
|
-
</select>
|
|
18
|
-
</div>
|
|
11
|
+
.nrchkb-editor .nrchkb-plugins-summary::-webkit-details-marker {
|
|
12
|
+
display: none;
|
|
13
|
+
}
|
|
19
14
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
15
|
+
.nrchkb-editor .nrchkb-plugin-add {
|
|
16
|
+
position: absolute;
|
|
17
|
+
top: 5px;
|
|
18
|
+
right: 8px;
|
|
19
|
+
z-index: 1;
|
|
20
|
+
white-space: nowrap;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.nrchkb-editor .nrchkb-plugin-label {
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: baseline;
|
|
26
|
+
gap: 5px;
|
|
27
|
+
min-width: 0;
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
white-space: nowrap;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.nrchkb-editor .nrchkb-plugin-count {
|
|
33
|
+
color: var(--red-ui-secondary-text-color, #666);
|
|
34
|
+
font-weight: normal;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.nrchkb-editor .nrchkb-plugin-summary-list {
|
|
38
|
+
display: inline;
|
|
39
|
+
min-width: 0;
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
color: var(--red-ui-secondary-text-color, #666);
|
|
42
|
+
font-size: 12px;
|
|
43
|
+
font-weight: normal;
|
|
44
|
+
text-overflow: ellipsis;
|
|
45
|
+
white-space: nowrap;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.nrchkb-editor .nrchkb-plugin-empty {
|
|
49
|
+
margin: 0;
|
|
50
|
+
padding: 8px 0;
|
|
51
|
+
color: var(--red-ui-secondary-text-color, #666);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.nrchkb-editor .nrchkb-plugin-overview {
|
|
55
|
+
display: grid;
|
|
56
|
+
gap: 6px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.nrchkb-editor .nrchkb-plugin-overview-item {
|
|
60
|
+
display: grid;
|
|
61
|
+
grid-template-columns: 1fr auto;
|
|
62
|
+
align-items: center;
|
|
63
|
+
gap: 8px;
|
|
64
|
+
padding: 5px 8px;
|
|
65
|
+
border: 1px solid var(--red-ui-secondary-border-color, #d8d8d8);
|
|
66
|
+
border-radius: 6px;
|
|
67
|
+
background: var(--red-ui-primary-background, #fff);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.nrchkb-editor .nrchkb-plugin-overview-name {
|
|
71
|
+
min-width: 0;
|
|
72
|
+
overflow: hidden;
|
|
73
|
+
text-overflow: ellipsis;
|
|
74
|
+
white-space: nowrap;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.nrchkb-editor .nrchkb-plugin-overview-name {
|
|
78
|
+
font-weight: 600;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.nrchkb-editor .nrchkb-plugin-overview-actions {
|
|
82
|
+
display: flex;
|
|
83
|
+
gap: 6px;
|
|
84
|
+
flex-shrink: 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.nrchkb-editor .nrchkb-plugin-section {
|
|
88
|
+
margin-top: 8px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.nrchkb-editor .nrchkb-plugin-shell {
|
|
92
|
+
position: relative;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.nrchkb-editor .nrchkb-plugin-section-toolbar {
|
|
96
|
+
display: flex;
|
|
97
|
+
justify-content: flex-end;
|
|
98
|
+
margin: 0 0 8px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.nrchkb-editor .nrchkb-plugin-section summary {
|
|
102
|
+
display: flex;
|
|
103
|
+
align-items: center;
|
|
104
|
+
gap: 6px;
|
|
105
|
+
min-width: 0;
|
|
106
|
+
list-style: none;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.nrchkb-editor .nrchkb-plugin-section summary::-webkit-details-marker {
|
|
110
|
+
display: none;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.nrchkb-editor .nrchkb-plugin-title {
|
|
114
|
+
flex: 1 1 auto;
|
|
115
|
+
min-width: 0;
|
|
116
|
+
overflow: hidden;
|
|
117
|
+
text-overflow: ellipsis;
|
|
118
|
+
white-space: nowrap;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.nrchkb-editor .nrchkb-plugin-remove {
|
|
122
|
+
justify-self: end;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.nrchkb-editor .nrchkb-plugin-about {
|
|
126
|
+
margin-bottom: 10px;
|
|
127
|
+
padding: 7px 9px;
|
|
128
|
+
border: 1px solid var(--red-ui-secondary-border-color, #d8d8d8);
|
|
129
|
+
border-radius: 6px;
|
|
130
|
+
background: var(--red-ui-tertiary-background, #f7f7f7);
|
|
131
|
+
color: var(--red-ui-secondary-text-color, #666);
|
|
132
|
+
font-size: 12px;
|
|
133
|
+
line-height: 1.35;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.nrchkb-editor .nrchkb-plugin-about strong {
|
|
137
|
+
display: block;
|
|
138
|
+
color: var(--red-ui-primary-text-color, #333);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.nrchkb-plugin-picker {
|
|
142
|
+
display: grid;
|
|
143
|
+
gap: 8px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.nrchkb-plugin-picker-item {
|
|
147
|
+
display: block;
|
|
148
|
+
width: 100%;
|
|
149
|
+
padding: 9px 10px;
|
|
150
|
+
border: 1px solid var(--red-ui-secondary-border-color, #d0d0d0);
|
|
151
|
+
border-radius: 6px;
|
|
152
|
+
background: var(--red-ui-primary-background, #fff);
|
|
153
|
+
color: var(--red-ui-primary-text-color, #333);
|
|
154
|
+
cursor: pointer;
|
|
155
|
+
font: inherit;
|
|
156
|
+
text-align: left;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.nrchkb-plugin-picker-item:not(:disabled):hover,
|
|
160
|
+
.nrchkb-plugin-picker-item:not(:disabled):focus-visible {
|
|
161
|
+
border-color: var(--red-ui-focus-color, var(--red-ui-text-color-link, #1a73e8));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.nrchkb-plugin-picker-item:disabled {
|
|
165
|
+
cursor: default;
|
|
166
|
+
opacity: 0.55;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.nrchkb-plugin-picker-title {
|
|
170
|
+
display: flex;
|
|
171
|
+
justify-content: space-between;
|
|
172
|
+
gap: 8px;
|
|
173
|
+
font-weight: 600;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.nrchkb-plugin-picker-description,
|
|
177
|
+
.nrchkb-plugin-picker-meta {
|
|
178
|
+
margin-top: 3px;
|
|
179
|
+
color: var(--red-ui-secondary-text-color, #666);
|
|
180
|
+
font-size: 12px;
|
|
181
|
+
line-height: 1.35;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.nrchkb-plugin-picker-description {
|
|
185
|
+
display: -webkit-box;
|
|
186
|
+
-webkit-line-clamp: 2;
|
|
187
|
+
-webkit-box-orient: vertical;
|
|
188
|
+
overflow: hidden;
|
|
189
|
+
}
|
|
190
|
+
</style>
|
|
191
|
+
|
|
192
|
+
<script data-template-name="homekit-service2" type="text/x-red">
|
|
193
|
+
<div class="nrchkb-editor">
|
|
194
|
+
<details id="plugins-configuration" class="nrchkb-section nrchkb-plugin-shell">
|
|
195
|
+
<summary class="nrchkb-plugins-summary">
|
|
196
|
+
<i class="fa fa-plug"></i>
|
|
197
|
+
<span class="nrchkb-plugin-label">
|
|
198
|
+
Plugins <span id="node-input-plugin-count" class="nrchkb-plugin-count">0</span>
|
|
199
|
+
<span id="node-input-plugin-summary-list" class="nrchkb-plugin-summary-list">No plugins attached</span>
|
|
200
|
+
</span>
|
|
201
|
+
</summary>
|
|
202
|
+
<button type="button" id="node-input-add-plugin" class="red-ui-button red-ui-button-small nrchkb-plugin-add">
|
|
203
|
+
<i class="fa fa-plus"></i>
|
|
204
|
+
Add plugin
|
|
205
|
+
</button>
|
|
206
|
+
<div class="nrchkb-section-body">
|
|
207
|
+
<div class="nrchkb-plugin-slot-fields" style="display: none;">
|
|
208
|
+
<input type="hidden" id="node-input-plugins">
|
|
209
|
+
<input type="hidden" id="node-input-plugin1">
|
|
210
|
+
<input type="hidden" id="node-input-plugin2">
|
|
211
|
+
<input type="hidden" id="node-input-plugin3">
|
|
212
|
+
<input type="hidden" id="node-input-plugin4">
|
|
213
|
+
<input type="hidden" id="node-input-plugin5">
|
|
214
|
+
<input type="hidden" id="node-input-plugin6">
|
|
215
|
+
<input type="hidden" id="node-input-plugin7">
|
|
216
|
+
<input type="hidden" id="node-input-plugin8">
|
|
217
|
+
</div>
|
|
218
|
+
<div id="node-input-plugin-overview" class="nrchkb-plugin-overview"></div>
|
|
45
219
|
</div>
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
220
|
+
</details>
|
|
221
|
+
|
|
222
|
+
<details class="nrchkb-section" open>
|
|
223
|
+
<summary><i class="fa fa-sliders"></i> Essentials</summary>
|
|
224
|
+
<div class="nrchkb-section-body">
|
|
225
|
+
<div class="form-row">
|
|
226
|
+
<label for="node-config-input-isParent"><i class="fa fa-sitemap"></i> Service Hierarchy</label>
|
|
227
|
+
<select id="node-config-input-isParent">
|
|
228
|
+
<option value="true" selected="selected">Parent</option>
|
|
229
|
+
<option value="false">Linked</option>
|
|
230
|
+
</select>
|
|
231
|
+
</div>
|
|
232
|
+
|
|
233
|
+
<div class="form-row">
|
|
234
|
+
<label for="node-input-serviceName">
|
|
235
|
+
<i class="fa fa-puzzle-piece"></i>
|
|
236
|
+
Service
|
|
237
|
+
</label>
|
|
238
|
+
<select id="node-input-serviceName">
|
|
239
|
+
<option value="">Choose...</option>
|
|
240
|
+
</select>
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
<div class="form-row">
|
|
244
|
+
<label for="node-input-name">
|
|
245
|
+
<i class="fa fa-tag"></i>
|
|
246
|
+
Name
|
|
247
|
+
</label>
|
|
248
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
249
|
+
</div>
|
|
49
250
|
</div>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
251
|
+
</details>
|
|
252
|
+
|
|
253
|
+
<details class="nrchkb-section" open>
|
|
254
|
+
<summary><i class="fa fa-map-marker"></i> Placement</summary>
|
|
255
|
+
<div class="nrchkb-section-body">
|
|
256
|
+
<div id="isParent">
|
|
257
|
+
<div class="form-row">
|
|
258
|
+
<label for="node-config-input-hostType"><i class="fa fa-server"></i> Host Type</label>
|
|
259
|
+
<select id="node-config-input-hostType">
|
|
260
|
+
<option value="0" selected="selected">Bridge</option>
|
|
261
|
+
<option value="1">Accessory</option>
|
|
262
|
+
</select>
|
|
263
|
+
</div>
|
|
264
|
+
<div id="isOnBridge">
|
|
265
|
+
<div id="isBridgeInSubflow" class="alert alert-warning nrchkb-warning" role="alert">
|
|
266
|
+
Read more <b><a href="#" id="bridgeInSubflowNotice">here</a></b> about adding Bridge in a Subflow.
|
|
267
|
+
</div>
|
|
268
|
+
<div class="form-row">
|
|
269
|
+
<label for="node-input-bridge">
|
|
270
|
+
<i class="fa fa-link"></i>
|
|
271
|
+
Bridge
|
|
272
|
+
</label>
|
|
273
|
+
<input id="node-input-bridge">
|
|
274
|
+
</div>
|
|
275
|
+
<details class="nrchkb-section">
|
|
276
|
+
<summary><i class="fa fa-id-card-o"></i> Accessory Metadata</summary>
|
|
277
|
+
<div class="nrchkb-section-body">
|
|
278
|
+
<div class="form-row">
|
|
279
|
+
<label for="node-input-manufacturer"><i class="fa fa-industry"></i> Manufacturer</label>
|
|
280
|
+
<input type="text" id="node-input-manufacturer" placeholder="Manufacturer">
|
|
281
|
+
</div>
|
|
282
|
+
<div class="form-row">
|
|
283
|
+
<label for="node-input-serialNo"><i class="fa fa-barcode"></i> Serial Number</label>
|
|
284
|
+
<input type="text" id="node-input-serialNo" placeholder="Serial Number">
|
|
285
|
+
</div>
|
|
286
|
+
<div class="form-row">
|
|
287
|
+
<label for="node-input-model"><i class="fa fa-cube"></i> Model</label>
|
|
288
|
+
<input type="text" id="node-input-model" placeholder="Model">
|
|
289
|
+
</div>
|
|
290
|
+
<div class="form-row">
|
|
291
|
+
<label for="node-input-firmwareRev"><i class="fa fa-microchip"></i> Firmware Revision</label>
|
|
292
|
+
<input type="text" id="node-input-firmwareRev" placeholder="Firmware Revision">
|
|
293
|
+
</div>
|
|
294
|
+
<div class="form-row">
|
|
295
|
+
<label for="node-input-hardwareRev"><i class="fa fa-hdd-o"></i> Hardware Revision</label>
|
|
296
|
+
<input type="text" id="node-input-hardwareRev" placeholder="Hardware Revision">
|
|
297
|
+
</div>
|
|
298
|
+
<div class="form-row">
|
|
299
|
+
<label for="node-input-softwareRev"><i class="fa fa-code"></i> Software Revision</label>
|
|
300
|
+
<input type="text" id="node-input-softwareRev" placeholder="Software Revision">
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
</details>
|
|
304
|
+
</div>
|
|
305
|
+
<div id="isAccessory">
|
|
306
|
+
<div class="form-row">
|
|
307
|
+
<label for="node-input-accessoryId">
|
|
308
|
+
<i class="fa fa-home"></i>
|
|
309
|
+
Accessory
|
|
310
|
+
</label>
|
|
311
|
+
<input id="node-input-accessoryId">
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
|
|
316
|
+
<div id="isLinked" style="display: none;">
|
|
317
|
+
<div class="form-row">
|
|
318
|
+
<label for="node-input-parentService">
|
|
319
|
+
<i class="fa fa-level-up"></i>
|
|
320
|
+
Parent Service
|
|
321
|
+
</label>
|
|
322
|
+
<select id="node-input-parentService">
|
|
323
|
+
<option value="">Choose...</option>
|
|
324
|
+
</select>
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
53
327
|
</div>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
328
|
+
</details>
|
|
329
|
+
|
|
330
|
+
<details class="nrchkb-section" open>
|
|
331
|
+
<summary><i class="fa fa-exchange"></i> Message Routing</summary>
|
|
332
|
+
<div class="nrchkb-section-body">
|
|
333
|
+
<div class="form-row">
|
|
334
|
+
<label for="node-input-topic"><i class="fa fa-comment-o"></i> Topic</label>
|
|
335
|
+
<input type="text" id="node-input-topic" placeholder="Topic">
|
|
336
|
+
</div>
|
|
337
|
+
<div class="form-row nrchkb-checkbox-row">
|
|
338
|
+
<label class="nrchkb-checkbox-label" for="node-input-filter">
|
|
339
|
+
<input type="checkbox" id="node-input-filter">
|
|
340
|
+
<span><i class="fa fa-filter"></i> Filter on Topic</span>
|
|
341
|
+
</label>
|
|
342
|
+
</div>
|
|
343
|
+
<div class="form-row">
|
|
344
|
+
<label for="node-input-outputMode"><i class="fa fa-random"></i> Output mode</label>
|
|
345
|
+
<select id="node-input-outputMode">
|
|
346
|
+
<option value="events" selected="selected">Events</option>
|
|
347
|
+
<option value="legacy">Legacy onChange/onSet</option>
|
|
348
|
+
</select>
|
|
349
|
+
</div>
|
|
57
350
|
</div>
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
351
|
+
</details>
|
|
352
|
+
|
|
353
|
+
<details id="adaptive-lightning-configuration" class="nrchkb-section" open style="display: none;">
|
|
354
|
+
<summary><i class="fa fa-sun-o"></i> Adaptive Lighting Configuration</summary>
|
|
355
|
+
<div class="nrchkb-section-body">
|
|
356
|
+
<div class="form-row nrchkb-checkbox-row">
|
|
357
|
+
<label class="nrchkb-checkbox-label" for="node-input-adaptiveLightingOptionsEnable">
|
|
358
|
+
<input type="checkbox" id="node-input-adaptiveLightingOptionsEnable">
|
|
359
|
+
<span><i class="fa fa-toggle-on"></i> Enable</span>
|
|
360
|
+
</label>
|
|
361
|
+
</div>
|
|
362
|
+
<div class="form-row">
|
|
363
|
+
<label for="node-input-adaptiveLightingOptionsMode"><i class="fa fa-magic"></i> Mode</label>
|
|
364
|
+
<select id="node-input-adaptiveLightingOptionsMode">
|
|
365
|
+
<option value="" selected hidden disabled>AUTOMATIC</option>
|
|
366
|
+
<option value="1">AUTOMATIC</option>
|
|
367
|
+
<option value="2">MANUAL</option>
|
|
368
|
+
</select>
|
|
369
|
+
</div>
|
|
370
|
+
<div class="form-row">
|
|
371
|
+
<label for="node-input-adaptiveLightingOptionsCustomTemperatureAdjustment"><i class="fa fa-thermometer-half"></i> Custom Temperature Adjustment</label>
|
|
372
|
+
<input type="number" id="node-input-adaptiveLightingOptionsCustomTemperatureAdjustment" placeholder="0">
|
|
373
|
+
</div>
|
|
61
374
|
</div>
|
|
62
|
-
</
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
375
|
+
</details>
|
|
376
|
+
|
|
377
|
+
<details class="nrchkb-section">
|
|
378
|
+
<summary><i class="fa fa-sliders"></i> Advanced Behavior</summary>
|
|
379
|
+
<div class="nrchkb-section-body">
|
|
380
|
+
<div class="form-row">
|
|
381
|
+
<label for="node-input-characteristicProperties"><i class="fa fa-list-alt"></i> Characteristic Properties</label>
|
|
382
|
+
<input type="text" id="node-input-characteristicProperties">
|
|
383
|
+
</div>
|
|
384
|
+
|
|
385
|
+
<div class="form-row nrchkb-checkbox-row">
|
|
386
|
+
<label class="nrchkb-checkbox-label" for="node-input-waitForSetupMsg">
|
|
387
|
+
<input type="checkbox" id="node-input-waitForSetupMsg">
|
|
388
|
+
<span><i class="fa fa-hourglass-half"></i> Wait for Setup message</span>
|
|
389
|
+
</label>
|
|
390
|
+
</div>
|
|
391
|
+
|
|
392
|
+
<div class="form-row nrchkb-checkbox-row">
|
|
393
|
+
<label class="nrchkb-checkbox-label" for="node-input-useEventCallback">
|
|
394
|
+
<input type="checkbox" id="node-input-useEventCallback">
|
|
395
|
+
<span><i class="fa fa-code-fork"></i> Use Event callback</span>
|
|
396
|
+
</label>
|
|
397
|
+
</div>
|
|
69
398
|
</div>
|
|
70
|
-
</
|
|
71
|
-
</div>
|
|
399
|
+
</details>
|
|
72
400
|
|
|
73
|
-
|
|
74
|
-
<div class="form-row">
|
|
75
|
-
<label for="node-input-parentService">
|
|
76
|
-
<i class="fa fa-cog"></i>
|
|
77
|
-
Parent Service</label>
|
|
78
|
-
<select id="node-input-parentService">
|
|
79
|
-
<option value="">Choose...</option>
|
|
80
|
-
</select>
|
|
81
|
-
</div>
|
|
401
|
+
<div id="node-input-plugin-sections"></div>
|
|
82
402
|
</div>
|
|
403
|
+
</script>
|
|
83
404
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
<input type="text" id="node-input-topic" placeholder="Topic">
|
|
87
|
-
</div>
|
|
88
|
-
<div class="form-row">
|
|
89
|
-
<label class="visibleDesktop"> </label>
|
|
90
|
-
<div style="display: inline;">
|
|
91
|
-
<input type="checkbox" id="node-input-filter" style="display: inline-block; width: auto; vertical-align: top;">
|
|
92
|
-
<label for="node-input-filter" style="width: 120px;"> <i class="fa fa-filter"></i> Filter on Topic</label>
|
|
93
|
-
</div>
|
|
94
|
-
</div>
|
|
405
|
+
<script data-help-name="homekit-service2" type="text/markdown">
|
|
406
|
+
# HomeKit Service 2
|
|
95
407
|
|
|
96
|
-
|
|
97
|
-
<label for="node-input-name">
|
|
98
|
-
<i class="fa fa-tag"></i>
|
|
99
|
-
Name</label>
|
|
100
|
-
<input type="text" id="node-input-name" placeholder="Name">
|
|
101
|
-
</div>
|
|
408
|
+
Represents one HomeKit service, such as a Switch, Outlet, Lightbulb, Sensor, or Camera. It receives Node-RED messages to update HomeKit characteristics and emits HomeKit events from the selected service.
|
|
102
409
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
<div class="form-row">
|
|
106
|
-
<label for="node-input-cameraConfigVideoProcessor"><i class="fa fa-cog"></i> Video Processor</label>
|
|
107
|
-
<input type="text" id="node-input-cameraConfigVideoProcessor" placeholder="ffmpeg">
|
|
108
|
-
</div>
|
|
109
|
-
<div class="form-row">
|
|
110
|
-
<label for="node-input-cameraConfigSource"><i class="fa fa-tint"></i> Source</label>
|
|
111
|
-
<input type="text" id="node-input-cameraConfigSource" placeholder="">
|
|
112
|
-
</div>
|
|
113
|
-
<div class="form-row">
|
|
114
|
-
<label for="node-input-cameraConfigStillImageSource"><i class="fa fa-picture-o"></i> Still Image Source</label>
|
|
115
|
-
<input type="text" id="node-input-cameraConfigStillImageSource" placeholder="">
|
|
116
|
-
</div>
|
|
117
|
-
<div class="form-row">
|
|
118
|
-
<label for="node-input-cameraConfigMaxStreams"><i class="fa fa-tint"></i> Max Streams</label>
|
|
119
|
-
<input type="text" id="node-input-cameraConfigMaxStreams" placeholder="">
|
|
120
|
-
</div>
|
|
121
|
-
<div class="form-row">
|
|
122
|
-
<label for="node-input-cameraConfigMaxWidth"><i class="fa fa-text-width"></i> Max Width</label>
|
|
123
|
-
<input type="text" id="node-input-cameraConfigMaxWidth" placeholder="">
|
|
124
|
-
</div>
|
|
125
|
-
<div class="form-row">
|
|
126
|
-
<label for="node-input-cameraConfigMaxHeight"><i class="fa fa-text-height"></i> Max Height</label>
|
|
127
|
-
<input type="text" id="node-input-cameraConfigMaxHeight" placeholder="">
|
|
128
|
-
</div>
|
|
129
|
-
<div class="form-row">
|
|
130
|
-
<label for="node-input-cameraConfigMaxFPS"><i class="fa fa-clock-o"></i> Max FPS</label>
|
|
131
|
-
<input type="text" id="node-input-cameraConfigMaxFPS" placeholder="">
|
|
132
|
-
</div>
|
|
133
|
-
<div class="form-row">
|
|
134
|
-
<label for="node-input-cameraConfigMaxBitrate"><i class="fa fa-clock-o"></i> Max Bitrate</label>
|
|
135
|
-
<input type="text" id="node-input-cameraConfigMaxBitrate" placeholder="">
|
|
136
|
-
</div>
|
|
137
|
-
<div class="form-row">
|
|
138
|
-
<label for="node-input-cameraConfigVideoCodec"><i class="fa fa-video-camera"></i> Video Codec</label>
|
|
139
|
-
<input type="text" id="node-input-cameraConfigVideoCodec" placeholder="">
|
|
140
|
-
</div>
|
|
141
|
-
<div class="form-row">
|
|
142
|
-
<label for="node-input-cameraConfigAudioCodec"><i class="fa fa-video-camera"></i> Audio Codec</label>
|
|
143
|
-
<input type="text" id="node-input-cameraConfigAudioCodec" placeholder="">
|
|
144
|
-
</div>
|
|
145
|
-
<div class="form-row">
|
|
146
|
-
<label for="node-input-cameraConfigAudio"><i class="fa fa-headphones"></i> Audio</label>
|
|
147
|
-
<input type="checkbox" id="node-input-cameraConfigAudio">
|
|
148
|
-
</div>
|
|
149
|
-
<div class="form-row">
|
|
150
|
-
<label for="node-input-cameraConfigPacketSize"><i class="fa fa-get-pocket"></i> Packet Size</label>
|
|
151
|
-
<input type="text" id="node-input-cameraConfigPacketSize" placeholder="">
|
|
152
|
-
</div>
|
|
153
|
-
<div class="form-row">
|
|
154
|
-
<label for="node-input-cameraConfigVerticalFlip"><i class="fa fa-undo"></i> Vertical Flip</label>
|
|
155
|
-
<input type="checkbox" id="node-input-cameraConfigVerticalFlip">
|
|
156
|
-
</div>
|
|
157
|
-
<div class="form-row">
|
|
158
|
-
<label for="node-input-cameraConfigHorizontalFlip"><i class="fa fa-undo"></i> Horizontal Flip</label>
|
|
159
|
-
<input type="checkbox" id="node-input-cameraConfigHorizontalFlip">
|
|
160
|
-
</div>
|
|
161
|
-
<div class="form-row">
|
|
162
|
-
<label for="node-input-cameraConfigMapVideo"><i class="fa fa-video-camera"></i> Map Video</label>
|
|
163
|
-
<input type="text" id="node-input-cameraConfigMapVideo" placeholder="">
|
|
164
|
-
</div>
|
|
165
|
-
<div class="form-row">
|
|
166
|
-
<label for="node-input-cameraConfigMapAudio"><i class="fa fa-headphones"></i> Map Audio</label>
|
|
167
|
-
<input type="text" id="node-input-cameraConfigMapAudio" placeholder="">
|
|
168
|
-
</div>
|
|
169
|
-
<div class="form-row">
|
|
170
|
-
<label for="node-input-cameraConfigVideoFilter"><i class="fa fa-filter"></i> Video Filter</label>
|
|
171
|
-
<input type="text" id="node-input-cameraConfigVideoFilter" placeholder="">
|
|
172
|
-
</div>
|
|
173
|
-
<div class="form-row">
|
|
174
|
-
<label for="node-input-cameraConfigAdditionalCommandLine"><i class="fa fa-plus"></i> Additional Command Line</label>
|
|
175
|
-
<input type="text" id="node-input-cameraConfigAdditionalCommandLine" placeholder="">
|
|
176
|
-
</div>
|
|
177
|
-
<div class="form-row">
|
|
178
|
-
<label for="node-input-cameraConfigDebug"><i class="fa fa-bug"></i> Debug</label>
|
|
179
|
-
<input type="checkbox" id="node-input-cameraConfigDebug">
|
|
180
|
-
</div>
|
|
181
|
-
|
|
182
|
-
<div class="form-row">
|
|
183
|
-
<label for="node-input-cameraConfigSnapshotOutput"><i class="fa fa-picture-o"></i> Snapshot output</label>
|
|
184
|
-
<select id="node-input-cameraConfigSnapshotOutput">
|
|
185
|
-
<option value="disabled" selected="selected">Disabled</option>
|
|
186
|
-
<option value="path">Path</option>
|
|
187
|
-
<option value="content">Content</option>
|
|
188
|
-
</select>
|
|
189
|
-
</div>
|
|
190
|
-
|
|
191
|
-
<div class="form-row">
|
|
192
|
-
<label for="node-input-cameraConfigInterfaceName"><i class="fa fa-smile-o"></i> Interface Name</label>
|
|
193
|
-
<input type="text" id="node-input-cameraConfigInterfaceName" placeholder="">
|
|
194
|
-
</div>
|
|
195
|
-
</div>
|
|
410
|
+
> [!IMPORTANT]
|
|
411
|
+
> Input payload keys must be HomeKit characteristic names. For example, send `{"On": true}` to turn on a Switch or Outlet.
|
|
196
412
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
<div class="form-row">
|
|
200
|
-
<label for="node-input-adaptiveLightingOptionsEnable"><i class="fa fa-toggle-on"></i> Enable</label>
|
|
201
|
-
<input type="checkbox" id="node-input-adaptiveLightingOptionsEnable">
|
|
202
|
-
</div>
|
|
203
|
-
<div class="form-row">
|
|
204
|
-
<label for="node-input-adaptiveLightingOptionsMode"><i class="fa fa-hand-o-up"></i> Mode</label>
|
|
205
|
-
<select id="node-input-adaptiveLightingOptionsMode">
|
|
206
|
-
<option value="" selected hidden disabled>AUTOMATIC</option>
|
|
207
|
-
<option value="1">AUTOMATIC</option>
|
|
208
|
-
<option value="2">MANUAL</option>
|
|
209
|
-
</select>
|
|
210
|
-
</div>
|
|
211
|
-
<div class="form-row">
|
|
212
|
-
<label for="node-input-adaptiveLightingOptionsCustomTemperatureAdjustment"><i class="fa fa-thermometer-quarter"></i> Custom Temperature Adjustment</label>
|
|
213
|
-
<input type="number" id="node-input-adaptiveLightingOptionsCustomTemperatureAdjustment" placeholder="0">
|
|
214
|
-
</div>
|
|
215
|
-
</div>
|
|
413
|
+
> [!NOTE]
|
|
414
|
+
> **Events** output mode is the preferred layout for new flows. **Legacy onChange/onSet** is available for migrated `homekit-service` flows.
|
|
216
415
|
|
|
217
|
-
|
|
218
|
-
<label for="node-input-characteristicProperties"><i class="fa fa-wrench"></i> Characteristic Properties</label>
|
|
219
|
-
<input type="text" id="node-input-characteristicProperties" style="width: 70%">
|
|
220
|
-
</div>
|
|
416
|
+
## Essentials
|
|
221
417
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
</div>
|
|
418
|
+
- **Service Hierarchy**: Select **Parent** for the main service of an accessory, or **Linked** for a secondary service attached to another service.
|
|
419
|
+
- **Service**: HAP service type exposed to HomeKit.
|
|
420
|
+
- **Name**: Service name shown in HomeKit and used as the default topic match when topic filtering is enabled without a configured topic.
|
|
226
421
|
|
|
227
|
-
|
|
228
|
-
<label for="node-input-useEventCallback"><i class="fa fa-code-fork"></i> Use Event callback</label>
|
|
229
|
-
<input type="checkbox" id="node-input-useEventCallback">
|
|
230
|
-
</div>
|
|
231
|
-
</script>
|
|
422
|
+
## Placement
|
|
232
423
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
<li><strong>Max Height</strong>: Maximum height reported to HomeKit, default <em>720</em>.</li>
|
|
260
|
-
<li><strong>Max FPS</strong>: Maximum frame rate of the stream, default <em>10</em>.</li>
|
|
261
|
-
<li><strong>Max Bitrate</strong>: Maximum bit rate of the stream in kbit/s, default <em>300</em>.</li>
|
|
262
|
-
<li><strong>Video Codec</strong>: If you're running on a RPi with the omx version of ffmpeg installed, you can change to the hardware accelerated video codec with this option, default <em>libx264</em>.</li>
|
|
263
|
-
<li><strong>Audio Codec</strong>: If you're running on a RPi with the omx version of ffmpeg installed, you can change to the hardware accelerated audio codec with this option, default <em>libfdk_aac</em>.</li>
|
|
264
|
-
<li><strong>Audio</strong>: Can be set to true to enable audio streaming from camera. To use audio ffmpeg must be compiled with --enable-libfdk-aac, default <em>false</em>.</li>
|
|
265
|
-
<li><strong>Packet Size</strong>: If audio or video is choppy try a smaller value, set to a multiple of 188, default <em>1316</em>.</li>
|
|
266
|
-
<li><strong>Vertical Flip</strong>: Flips the stream vertically, default <em>false</em>.</li>
|
|
267
|
-
<li><strong>Horizontal Flip</strong>: Flips the stream horizontally, default <em>false</em>.</li>
|
|
268
|
-
<li><strong>Map Video</strong>: Select the stream used for video, default <em>0:0</em>.</li>
|
|
269
|
-
<li><strong>Map Audio</strong>: Select the stream used for audio, default <em>0:1</em>.</li>
|
|
270
|
-
<li><strong>Video Filter</strong>: Allows a custom video filter to be passed to FFmpeg via -vf, defaults to <em>scale=1280:720</em> but is optional.</li>
|
|
271
|
-
<li><strong>Additional Command Line</strong>: Allows additional of extra command line options to FFmpeg, default <em>-tune zerolatency</em> but is optional.</li>
|
|
272
|
-
<li><strong>Debug</strong>: Show the output of ffmpeg in the log, default <em>false</em>.</li>
|
|
273
|
-
<li><strong>Snapshot output</strong>: Choose how to output camera snapshot</li>
|
|
274
|
-
<ul>
|
|
275
|
-
<li><strong>Disabled</strong>: there will be no output</li>
|
|
276
|
-
<li><strong>Path</strong>: file will be saved and path will be send to output, <em>msg.payload.cameraSnapshot</em> contains path value stored as a string.</li>
|
|
277
|
-
<li><strong>Content</strong>: file content will be send to output, <em>msg.payload.cameraSnapshot</em> contains Buffer object {"type":"Buffer","data":[]}.</li>
|
|
278
|
-
</ul>
|
|
279
|
-
<li><strong>Interface Name</strong>: Selects the IP address of a given network interface.</li>
|
|
280
|
-
</ul>
|
|
281
|
-
<li><strong>Characteristic Properties</strong>: Customize the properties of characteristics.</li>
|
|
282
|
-
<li><strong>Wait for Setup message</strong>: If yes then Service node will wait for a input message with appropriate payload.</li>
|
|
283
|
-
<li><strong>Use Event callback</strong>: If yes then Service node will wait for a callback message to respond to get event.</li>
|
|
284
|
-
</ul>
|
|
285
|
-
<h2 id="toc_6">Input Messages</h2>
|
|
286
|
-
<p>Input messages can be used to update any <em>Characteristic</em> that the selected <em>Service</em> provides. Simply pass the values-to-update as <code>msg.payload</code> object. </p>
|
|
287
|
-
<p><strong>Example</strong>: to signal that an <em>Outlet</em> is turned on and in use, send the following payload</p>
|
|
288
|
-
<div>
|
|
289
|
-
<pre><code class="language-javascript">
|
|
290
|
-
{
|
|
291
|
-
"On": 1,
|
|
292
|
-
"OutletInUse": 1
|
|
293
|
-
}
|
|
294
|
-
</code></pre>
|
|
295
|
-
</div>
|
|
296
|
-
<p><strong>Hint</strong>: to find out what <em>Characteristics</em> you can address, just send <code>{"foo":"bar"}</code> and watch the debug tab ;)</p>
|
|
297
|
-
<h2 id="toc_7">Output Messages</h2>
|
|
298
|
-
<p>Output messages are in the same format as input messages. They are emitted from the node when it receives <em>Characteristics</em> updates from a paired iOS device.</p>
|
|
299
|
-
<h2 id="toc_8">Characteristic Properties</h2>
|
|
300
|
-
<p><strong>Example</strong>: allow temperatures below 0°C</p>
|
|
301
|
-
<div>
|
|
302
|
-
<pre><code class="language-json">
|
|
424
|
+
- **Host Type**: Publish a parent service behind a shared **Bridge** or behind a standalone **Accessory**.
|
|
425
|
+
- **Bridge**: HomeKit bridge configuration that hosts this service.
|
|
426
|
+
- **Accessory**: Standalone accessory configuration that hosts this service.
|
|
427
|
+
- **Parent Service**: Parent service used by linked services.
|
|
428
|
+
|
|
429
|
+
## Message Routing
|
|
430
|
+
|
|
431
|
+
- **Topic**: Optional route key. If blank, incoming messages may provide `msg.topic`.
|
|
432
|
+
- **Filter on Topic**: Accept incoming messages only when `msg.topic` matches the configured Topic. If Topic is blank, `msg.topic` must match the node Name.
|
|
433
|
+
- **Output mode**: **Events** emits all HomeKit events through one output; **Legacy onChange/onSet** preserves the old output layout.
|
|
434
|
+
|
|
435
|
+
## Plugins
|
|
436
|
+
|
|
437
|
+
Plugins add service-specific behavior and render their own configuration fields from registered plugin metadata.
|
|
438
|
+
|
|
439
|
+
## Advanced Behavior
|
|
440
|
+
|
|
441
|
+
> [!WARNING]
|
|
442
|
+
> **Wait for Setup message** delays service setup until the node receives the required setup payload. The service will not be published until that message arrives.
|
|
443
|
+
|
|
444
|
+
- **Characteristic Properties**: JSON object that overrides HAP characteristic metadata such as `minValue`, `maxValue`, or `minStep`.
|
|
445
|
+
- **Use Event callback**: Lets a flow answer HomeKit read/get events by sending a callback response message.
|
|
446
|
+
|
|
447
|
+
## Input Messages
|
|
448
|
+
|
|
449
|
+
```json
|
|
303
450
|
{
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
451
|
+
"On": true,
|
|
452
|
+
"OutletInUse": true
|
|
307
453
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
To discover valid characteristic names for the selected service, send a test payload such as `{"foo":"bar"}` and check the Node-RED debug output.
|
|
457
|
+
|
|
458
|
+
## Characteristic Properties
|
|
459
|
+
|
|
460
|
+
```json
|
|
312
461
|
{
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
462
|
+
"CurrentTemperature": {
|
|
463
|
+
"minValue": -100
|
|
464
|
+
}
|
|
316
465
|
}
|
|
317
|
-
|
|
318
|
-
</div>
|
|
319
|
-
|
|
466
|
+
```
|
|
320
467
|
</script>
|
|
321
468
|
|
|
322
469
|
<script type="text/javascript">
|
|
323
|
-
if (nrchkbExperimental) {
|
|
324
470
|
RED.nodes.registerType('homekit-service2', {
|
|
325
471
|
category: "Apple HomeKit",
|
|
326
472
|
paletteLabel: 'service 2',
|
|
@@ -404,81 +550,6 @@ if (nrchkbExperimental) {
|
|
|
404
550
|
required: false,
|
|
405
551
|
validate: versionValidator,
|
|
406
552
|
},
|
|
407
|
-
cameraConfigVideoProcessor: {
|
|
408
|
-
value: 'ffmpeg',
|
|
409
|
-
validate: cameraConfigRequiredField,
|
|
410
|
-
},
|
|
411
|
-
cameraConfigSource: {
|
|
412
|
-
validate: cameraConfigRequiredField,
|
|
413
|
-
},
|
|
414
|
-
cameraConfigStillImageSource: {
|
|
415
|
-
required: false,
|
|
416
|
-
},
|
|
417
|
-
cameraConfigMaxStreams: {
|
|
418
|
-
value: 2,
|
|
419
|
-
validate: cameraConfigRequiredField,
|
|
420
|
-
},
|
|
421
|
-
cameraConfigMaxWidth: {
|
|
422
|
-
value: 1280,
|
|
423
|
-
validate: cameraConfigRequiredField,
|
|
424
|
-
},
|
|
425
|
-
cameraConfigMaxHeight: {
|
|
426
|
-
value: 720,
|
|
427
|
-
validate: cameraConfigRequiredField,
|
|
428
|
-
},
|
|
429
|
-
cameraConfigMaxFPS: {
|
|
430
|
-
value: 10,
|
|
431
|
-
validate: cameraConfigRequiredField,
|
|
432
|
-
},
|
|
433
|
-
cameraConfigMaxBitrate: {
|
|
434
|
-
value: 300,
|
|
435
|
-
validate: cameraConfigRequiredField,
|
|
436
|
-
},
|
|
437
|
-
cameraConfigVideoCodec: {
|
|
438
|
-
value: 'libx264',
|
|
439
|
-
validate: cameraConfigRequiredField,
|
|
440
|
-
},
|
|
441
|
-
cameraConfigAudioCodec: {
|
|
442
|
-
value: 'libfdk_aac',
|
|
443
|
-
validate: cameraConfigRequiredField,
|
|
444
|
-
},
|
|
445
|
-
cameraConfigAudio: {
|
|
446
|
-
value: false,
|
|
447
|
-
},
|
|
448
|
-
cameraConfigPacketSize: {
|
|
449
|
-
value: 1316,
|
|
450
|
-
validate: cameraConfigRequiredField,
|
|
451
|
-
},
|
|
452
|
-
cameraConfigVerticalFlip: {
|
|
453
|
-
value: false,
|
|
454
|
-
},
|
|
455
|
-
cameraConfigHorizontalFlip: {
|
|
456
|
-
value: false,
|
|
457
|
-
},
|
|
458
|
-
cameraConfigMapVideo: {
|
|
459
|
-
value: '0:0',
|
|
460
|
-
validate: cameraConfigRequiredField,
|
|
461
|
-
},
|
|
462
|
-
cameraConfigMapAudio: {
|
|
463
|
-
value: '0:1',
|
|
464
|
-
validate: cameraConfigRequiredField,
|
|
465
|
-
},
|
|
466
|
-
cameraConfigVideoFilter: {
|
|
467
|
-
value: 'scale=1280:720',
|
|
468
|
-
},
|
|
469
|
-
cameraConfigAdditionalCommandLine: {
|
|
470
|
-
value: '-tune zerolatency',
|
|
471
|
-
},
|
|
472
|
-
cameraConfigDebug: {
|
|
473
|
-
value: false,
|
|
474
|
-
},
|
|
475
|
-
cameraConfigSnapshotOutput: {
|
|
476
|
-
value: 'disabled',
|
|
477
|
-
validate: cameraConfigRequiredField,
|
|
478
|
-
},
|
|
479
|
-
cameraConfigInterfaceName: {
|
|
480
|
-
value: '',
|
|
481
|
-
},
|
|
482
553
|
characteristicProperties: {
|
|
483
554
|
value: '{}',
|
|
484
555
|
validate: function (value) {
|
|
@@ -488,8 +559,7 @@ if (nrchkbExperimental) {
|
|
|
488
559
|
|
|
489
560
|
try {
|
|
490
561
|
JSON.parse(value)
|
|
491
|
-
} catch (
|
|
492
|
-
console.log(e, value)
|
|
562
|
+
} catch (_) {
|
|
493
563
|
return false
|
|
494
564
|
}
|
|
495
565
|
|
|
@@ -499,9 +569,66 @@ if (nrchkbExperimental) {
|
|
|
499
569
|
waitForSetupMsg: {
|
|
500
570
|
value: false
|
|
501
571
|
},
|
|
572
|
+
outputMode: {
|
|
573
|
+
value: 'events'
|
|
574
|
+
},
|
|
502
575
|
useEventCallback: {
|
|
503
576
|
value: false
|
|
504
577
|
},
|
|
578
|
+
plugins: {
|
|
579
|
+
value: []
|
|
580
|
+
},
|
|
581
|
+
plugin1: {
|
|
582
|
+
value: '',
|
|
583
|
+
type: 'homekit-plugin-instance',
|
|
584
|
+
required: false,
|
|
585
|
+
validate: function () { return true }
|
|
586
|
+
},
|
|
587
|
+
plugin2: {
|
|
588
|
+
value: '',
|
|
589
|
+
type: 'homekit-plugin-instance',
|
|
590
|
+
required: false,
|
|
591
|
+
validate: function () { return true }
|
|
592
|
+
},
|
|
593
|
+
plugin3: {
|
|
594
|
+
value: '',
|
|
595
|
+
type: 'homekit-plugin-instance',
|
|
596
|
+
required: false,
|
|
597
|
+
validate: function () { return true }
|
|
598
|
+
},
|
|
599
|
+
plugin4: {
|
|
600
|
+
value: '',
|
|
601
|
+
type: 'homekit-plugin-instance',
|
|
602
|
+
required: false,
|
|
603
|
+
validate: function () { return true }
|
|
604
|
+
},
|
|
605
|
+
plugin5: {
|
|
606
|
+
value: '',
|
|
607
|
+
type: 'homekit-plugin-instance',
|
|
608
|
+
required: false,
|
|
609
|
+
validate: function () { return true }
|
|
610
|
+
},
|
|
611
|
+
plugin6: {
|
|
612
|
+
value: '',
|
|
613
|
+
type: 'homekit-plugin-instance',
|
|
614
|
+
required: false,
|
|
615
|
+
validate: function () { return true }
|
|
616
|
+
},
|
|
617
|
+
plugin7: {
|
|
618
|
+
value: '',
|
|
619
|
+
type: 'homekit-plugin-instance',
|
|
620
|
+
required: false,
|
|
621
|
+
validate: function () { return true }
|
|
622
|
+
},
|
|
623
|
+
plugin8: {
|
|
624
|
+
value: '',
|
|
625
|
+
type: 'homekit-plugin-instance',
|
|
626
|
+
required: false,
|
|
627
|
+
validate: function () { return true }
|
|
628
|
+
},
|
|
629
|
+
pluginSetupComplete: {
|
|
630
|
+
value: false
|
|
631
|
+
},
|
|
505
632
|
outputs: {
|
|
506
633
|
value: 1,
|
|
507
634
|
},
|
|
@@ -518,10 +645,32 @@ if (nrchkbExperimental) {
|
|
|
518
645
|
inputs: 1,
|
|
519
646
|
outputs: 1,
|
|
520
647
|
outputLabels: function (index) {
|
|
648
|
+
const outputMode = this.outputMode || 'events'
|
|
649
|
+
|
|
650
|
+
if (outputMode === 'legacy') {
|
|
651
|
+
if (index === 0) {
|
|
652
|
+
return 'onChange'
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (index === 1) {
|
|
656
|
+
return 'onSet'
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (index === 2) {
|
|
660
|
+
return 'camera snapshot'
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
return ''
|
|
664
|
+
}
|
|
665
|
+
|
|
521
666
|
if (index === 0) {
|
|
522
667
|
return 'events'
|
|
523
668
|
}
|
|
524
669
|
|
|
670
|
+
if (index === 1 && this.serviceName === 'Camera') {
|
|
671
|
+
return 'camera snapshot'
|
|
672
|
+
}
|
|
673
|
+
|
|
525
674
|
return ''
|
|
526
675
|
},
|
|
527
676
|
icon: 'homekit.png',
|
|
@@ -532,9 +681,42 @@ if (nrchkbExperimental) {
|
|
|
532
681
|
labelStyle: function () {
|
|
533
682
|
return this.name ? 'node_label_italic' : ''
|
|
534
683
|
},
|
|
684
|
+
onadd: function () {
|
|
685
|
+
applyDefaultNodeDocumentation(this, 'homekit-service2')
|
|
686
|
+
},
|
|
535
687
|
oneditprepare: function () {
|
|
536
688
|
let node = this
|
|
537
689
|
let isParentToggle = $('#node-config-input-isParent')
|
|
690
|
+
const cameraPluginId = 'node-red-contrib-homekit-bridged:homebridge-camera-ffmpeg'
|
|
691
|
+
const prefersReducedMotion = function () {
|
|
692
|
+
return window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
693
|
+
}
|
|
694
|
+
const showEditorSection = function (section) {
|
|
695
|
+
if (prefersReducedMotion()) {
|
|
696
|
+
section.stop(true, true).show()
|
|
697
|
+
return
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
section.stop(true, true).fadeIn('fast')
|
|
701
|
+
}
|
|
702
|
+
const hideEditorSection = function (section) {
|
|
703
|
+
if (prefersReducedMotion()) {
|
|
704
|
+
section.stop(true, true).hide()
|
|
705
|
+
return
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
section.stop(true, true).fadeOut('fast')
|
|
709
|
+
}
|
|
710
|
+
const scrollEditorSectionIntoView = function (element) {
|
|
711
|
+
if (!element || !element.scrollIntoView) {
|
|
712
|
+
return
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
element.scrollIntoView({
|
|
716
|
+
behavior: prefersReducedMotion() ? 'auto' : 'smooth',
|
|
717
|
+
block: 'start'
|
|
718
|
+
})
|
|
719
|
+
}
|
|
538
720
|
|
|
539
721
|
if (node.isParent === false) {
|
|
540
722
|
isParentToggle.val('false')
|
|
@@ -550,11 +732,11 @@ if (nrchkbExperimental) {
|
|
|
550
732
|
isParentToggle
|
|
551
733
|
.change(function () {
|
|
552
734
|
if (isParentToggle.val() === 'true') {
|
|
553
|
-
isLinkedDiv
|
|
554
|
-
isParentDiv
|
|
735
|
+
hideEditorSection(isLinkedDiv)
|
|
736
|
+
showEditorSection(isParentDiv)
|
|
555
737
|
} else {
|
|
556
|
-
isParentDiv
|
|
557
|
-
isLinkedDiv
|
|
738
|
+
hideEditorSection(isParentDiv)
|
|
739
|
+
showEditorSection(isLinkedDiv)
|
|
558
740
|
selectHostType.val("_ADD_")
|
|
559
741
|
}
|
|
560
742
|
})
|
|
@@ -595,95 +777,1154 @@ if (nrchkbExperimental) {
|
|
|
595
777
|
.change()
|
|
596
778
|
|
|
597
779
|
const selectServiceName = $('#node-input-serviceName')
|
|
780
|
+
let availablePlugins = []
|
|
598
781
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
.val(key)
|
|
604
|
-
.text(key)
|
|
782
|
+
const normalizePlugins = function (value) {
|
|
783
|
+
if (Array.isArray(value)) {
|
|
784
|
+
return value
|
|
785
|
+
}
|
|
605
786
|
|
|
606
|
-
if (
|
|
607
|
-
|
|
608
|
-
|
|
787
|
+
if (typeof value === 'string' && value.trim()) {
|
|
788
|
+
try {
|
|
789
|
+
const parsed = JSON.parse(value)
|
|
790
|
+
return Array.isArray(parsed) ? parsed : []
|
|
791
|
+
} catch (_) {
|
|
792
|
+
return []
|
|
793
|
+
}
|
|
609
794
|
}
|
|
610
795
|
|
|
611
|
-
|
|
612
|
-
}
|
|
796
|
+
return []
|
|
797
|
+
}
|
|
613
798
|
|
|
614
|
-
|
|
615
|
-
|
|
799
|
+
const initialServiceName = node.serviceName || ''
|
|
800
|
+
const initialPlugins = normalizePlugins(node.plugins)
|
|
801
|
+
let pluginSetupComplete =
|
|
802
|
+
node.pluginSetupComplete === true ||
|
|
803
|
+
(initialServiceName !== '' && initialPlugins.length === 0)
|
|
616
804
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
})
|
|
622
|
-
.attr('selected', true)
|
|
805
|
+
const pluginOverview = $('#node-input-plugin-overview')
|
|
806
|
+
const pluginSections = $('#node-input-plugin-sections')
|
|
807
|
+
const pluginCount = $('#node-input-plugin-count')
|
|
808
|
+
const pluginSummaryList = $('#node-input-plugin-summary-list')
|
|
623
809
|
|
|
624
|
-
|
|
625
|
-
.
|
|
626
|
-
|
|
627
|
-
cameraConfiguration.fadeIn('fast')
|
|
628
|
-
node.outputs = 2
|
|
629
|
-
} else {
|
|
630
|
-
cameraConfiguration.fadeOut('fast')
|
|
631
|
-
node.outputs = 1
|
|
632
|
-
}
|
|
810
|
+
const getPluginMetadata = function (id) {
|
|
811
|
+
return availablePlugins.find(function (entry) {
|
|
812
|
+
return entry.id === id
|
|
633
813
|
})
|
|
634
|
-
|
|
814
|
+
}
|
|
635
815
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
} else {
|
|
641
|
-
adaptiveLightningConfiguration.fadeOut('fast')
|
|
642
|
-
}
|
|
643
|
-
})
|
|
644
|
-
.change()
|
|
816
|
+
const getPluginDisplayLabel = function (plugin) {
|
|
817
|
+
if (!plugin) {
|
|
818
|
+
return ''
|
|
819
|
+
}
|
|
645
820
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
})
|
|
821
|
+
const hasDisplayNameCollision = availablePlugins.filter(function (entry) {
|
|
822
|
+
return entry.displayName === plugin.displayName
|
|
823
|
+
}).length > 1
|
|
650
824
|
|
|
651
|
-
|
|
825
|
+
return hasDisplayNameCollision
|
|
826
|
+
? plugin.displayName + ' (' + plugin.packageName + ')'
|
|
827
|
+
: plugin.displayName
|
|
828
|
+
}
|
|
652
829
|
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
type: 'homekit-service2',
|
|
659
|
-
})
|
|
660
|
-
]
|
|
830
|
+
const getPluginPrerequisiteServiceNames = function (plugin) {
|
|
831
|
+
return plugin && plugin.prerequisites && Array.isArray(plugin.prerequisites.serviceNames)
|
|
832
|
+
? plugin.prerequisites.serviceNames
|
|
833
|
+
: []
|
|
834
|
+
}
|
|
661
835
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
836
|
+
const isPluginCompatibleWithSelectedService = function (plugin) {
|
|
837
|
+
const serviceNames = getPluginPrerequisiteServiceNames(plugin)
|
|
838
|
+
return serviceNames.length === 0 || serviceNames.includes(selectServiceName.val())
|
|
839
|
+
}
|
|
666
840
|
|
|
667
|
-
|
|
668
|
-
|
|
841
|
+
const allowsAnotherPluginInstance = function (plugin, selectedId) {
|
|
842
|
+
if (!plugin || plugin.multipleInstances !== false || !selectedId) {
|
|
843
|
+
return true
|
|
669
844
|
}
|
|
670
845
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
846
|
+
return readPluginsFromList().filter(function (entry) {
|
|
847
|
+
return entry.id === selectedId
|
|
848
|
+
}).length === 0
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
const renderCompatibilityText = function (plugin) {
|
|
852
|
+
const serviceNames = getPluginPrerequisiteServiceNames(plugin)
|
|
853
|
+
|
|
854
|
+
if (!serviceNames.length) {
|
|
855
|
+
return 'Works with any service'
|
|
679
856
|
}
|
|
680
857
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
858
|
+
return 'Works with: ' + serviceNames.join(', ')
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
const renderMultipleInstancesText = function (plugin) {
|
|
862
|
+
return plugin && plugin.multipleInstances === false
|
|
863
|
+
? 'Single instance'
|
|
864
|
+
: 'Multiple instances allowed'
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
const getPluginSectionId = function (entry, index) {
|
|
868
|
+
return 'node-input-plugin-section-' + (entry.id || 'plugin')
|
|
869
|
+
.replace(/[^a-zA-Z0-9_-]/g, '-') + '-' + index
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
const renderPluginDetails = function (plugin) {
|
|
873
|
+
const upstream = plugin.upstream || {}
|
|
874
|
+
const capabilities = plugin.capabilities
|
|
875
|
+
? Object.keys(plugin.capabilities).filter(function (key) {
|
|
876
|
+
return plugin.capabilities[key] === true
|
|
877
|
+
}).join(', ')
|
|
878
|
+
: ''
|
|
879
|
+
|
|
880
|
+
return (
|
|
881
|
+
'<strong>' + $('<div>').text(plugin.displayName).html() + '</strong> ' +
|
|
882
|
+
'v' + $('<div>').text(plugin.version).html() + ' by ' +
|
|
883
|
+
$('<div>').text(plugin.author).html() + '<br>' +
|
|
884
|
+
$('<div>').text(plugin.description).html() + '<br>' +
|
|
885
|
+
'<small>' + $('<div>').text(renderCompatibilityText(plugin)).html() + '. ' +
|
|
886
|
+
$('<div>').text(renderMultipleInstancesText(plugin)).html() + '.</small><br>' +
|
|
887
|
+
(capabilities ? '<small>Features: ' + $('<div>').text(capabilities).html() + '</small><br>' : '') +
|
|
888
|
+
'<small>Package: ' + $('<div>').text(plugin.packageName).html() + '</small>' +
|
|
889
|
+
(upstream.packageName
|
|
890
|
+
? '<br><small>Uses upstream ' + $('<div>').text(upstream.packageName).html() +
|
|
891
|
+
(upstream.packageVersion ? ' v' + $('<div>').text(upstream.packageVersion).html() : '') +
|
|
892
|
+
'. Upstream authors are credited for the original plugin, not this NRCHKB integration.</small>'
|
|
893
|
+
: '')
|
|
894
|
+
)
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const cloneObject = function (value) {
|
|
898
|
+
return $.extend(true, {}, value || {})
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
const getPathParts = function (path) {
|
|
902
|
+
return typeof path === 'string' && path.length
|
|
903
|
+
? path.split('.').filter(function (part) {
|
|
904
|
+
return part.length > 0
|
|
905
|
+
})
|
|
906
|
+
: []
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const getConfigValue = function (config, path, defaultValue) {
|
|
910
|
+
const parts = getPathParts(path)
|
|
911
|
+
let current = config
|
|
912
|
+
|
|
913
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
914
|
+
if (!current || typeof current !== 'object' || !Object.prototype.hasOwnProperty.call(current, parts[index])) {
|
|
915
|
+
return defaultValue
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
current = current[parts[index]]
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
return current === undefined ? defaultValue : current
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
const setConfigValue = function (config, path, value) {
|
|
925
|
+
const parts = getPathParts(path)
|
|
926
|
+
let current = config
|
|
927
|
+
|
|
928
|
+
parts.forEach(function (part, index) {
|
|
929
|
+
if (index === parts.length - 1) {
|
|
930
|
+
current[part] = value
|
|
931
|
+
return
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
if (!current[part] || typeof current[part] !== 'object') {
|
|
935
|
+
current[part] = {}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
current = current[part]
|
|
939
|
+
})
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
const getPluginDefaultConfig = function (plugin) {
|
|
943
|
+
return cloneObject(plugin && plugin.editor && plugin.editor.defaultConfig)
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
const mergePluginConfig = function (plugin, config) {
|
|
947
|
+
return $.extend(true, getPluginDefaultConfig(plugin), config || {})
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
const readPluginConfig = function (section, entry, plugin) {
|
|
951
|
+
const config = mergePluginConfig(plugin, entry.config || {})
|
|
952
|
+
|
|
953
|
+
section.find('[data-plugin-config-path]').each(function () {
|
|
954
|
+
const input = $(this)
|
|
955
|
+
const path = input.attr('data-plugin-config-path')
|
|
956
|
+
const field = input.data('pluginField') || {}
|
|
957
|
+
|
|
958
|
+
if (!path) {
|
|
959
|
+
return
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
if (field.type === 'checkbox') {
|
|
963
|
+
setConfigValue(config, path, input.is(':checked'))
|
|
964
|
+
} else if (field.type === 'number') {
|
|
965
|
+
const value = input.val()
|
|
966
|
+
setConfigValue(config, path, value === '' || value === undefined ? undefined : Number(value))
|
|
967
|
+
} else if (field.type === 'dynamic-select') {
|
|
968
|
+
const value = input.val() || input.data('pluginDynamicValue') || ''
|
|
969
|
+
setConfigValue(config, path, value)
|
|
970
|
+
} else {
|
|
971
|
+
setConfigValue(config, path, input.val())
|
|
972
|
+
}
|
|
973
|
+
})
|
|
974
|
+
|
|
975
|
+
return config
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
const readPluginsFromList = function () {
|
|
979
|
+
const plugins = []
|
|
980
|
+
const sections = pluginSections.find('.nrchkb-plugin-section')
|
|
981
|
+
|
|
982
|
+
sections.each(function () {
|
|
983
|
+
const section = $(this)
|
|
984
|
+
const entry = section.data('pluginEntry') || {}
|
|
985
|
+
const id = entry.id
|
|
986
|
+
|
|
987
|
+
if (!id) {
|
|
988
|
+
return
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
const plugin = getPluginMetadata(id)
|
|
992
|
+
|
|
993
|
+
plugins.push({
|
|
994
|
+
id,
|
|
995
|
+
automatic: entry.automatic === true,
|
|
996
|
+
modified: entry.modified === true,
|
|
997
|
+
config: readPluginConfig(section, entry, plugin || {})
|
|
998
|
+
})
|
|
999
|
+
})
|
|
1000
|
+
|
|
1001
|
+
return plugins
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
const syncPluginsInput = function () {
|
|
1005
|
+
const plugins = readPluginsFromList()
|
|
1006
|
+
node.plugins = plugins
|
|
1007
|
+
$('#node-input-plugins').val(JSON.stringify(plugins))
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
const getCameraPluginEntry = function () {
|
|
1011
|
+
return readPluginsFromList().find(function (entry) {
|
|
1012
|
+
return entry.id === cameraPluginId
|
|
1013
|
+
})
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
const buildCameraPluginEntry = function () {
|
|
1017
|
+
const plugin = getPluginMetadata(cameraPluginId)
|
|
1018
|
+
const existing = getCameraPluginEntry()
|
|
1019
|
+
const defaultConfig = getPluginDefaultConfig(plugin || {})
|
|
1020
|
+
|
|
1021
|
+
// If we don't have an existing entry, merge service node's camera config into default config
|
|
1022
|
+
if (!existing && defaultConfig && defaultConfig.camera && defaultConfig.camera.videoConfig) {
|
|
1023
|
+
if (node.cameraConfigSource) {
|
|
1024
|
+
defaultConfig.camera.videoConfig.source = node.cameraConfigSource
|
|
1025
|
+
}
|
|
1026
|
+
if (node.cameraConfigStillImageSource) {
|
|
1027
|
+
defaultConfig.camera.videoConfig.stillImageSource = node.cameraConfigStillImageSource
|
|
1028
|
+
}
|
|
1029
|
+
if (node.cameraConfigMaxStreams !== undefined) {
|
|
1030
|
+
defaultConfig.camera.videoConfig.maxStreams = node.cameraConfigMaxStreams
|
|
1031
|
+
}
|
|
1032
|
+
if (node.cameraConfigMaxWidth !== undefined) {
|
|
1033
|
+
defaultConfig.camera.videoConfig.maxWidth = node.cameraConfigMaxWidth
|
|
1034
|
+
}
|
|
1035
|
+
if (node.cameraConfigMaxHeight !== undefined) {
|
|
1036
|
+
defaultConfig.camera.videoConfig.maxHeight = node.cameraConfigMaxHeight
|
|
1037
|
+
}
|
|
1038
|
+
if (node.cameraConfigMaxFPS !== undefined) {
|
|
1039
|
+
defaultConfig.camera.videoConfig.maxFPS = node.cameraConfigMaxFPS
|
|
1040
|
+
}
|
|
1041
|
+
if (node.cameraConfigMaxBitrate !== undefined) {
|
|
1042
|
+
defaultConfig.camera.videoConfig.maxBitrate = node.cameraConfigMaxBitrate
|
|
1043
|
+
}
|
|
1044
|
+
if (node.cameraConfigVideoCodec) {
|
|
1045
|
+
defaultConfig.camera.videoConfig.vcodec = node.cameraConfigVideoCodec
|
|
1046
|
+
}
|
|
1047
|
+
if (node.cameraConfigAudio !== undefined) {
|
|
1048
|
+
defaultConfig.camera.videoConfig.audio = node.cameraConfigAudio
|
|
1049
|
+
}
|
|
1050
|
+
if (node.cameraConfigPacketSize !== undefined) {
|
|
1051
|
+
defaultConfig.camera.videoConfig.packetSize = node.cameraConfigPacketSize
|
|
1052
|
+
}
|
|
1053
|
+
if (node.cameraConfigMapVideo) {
|
|
1054
|
+
defaultConfig.camera.videoConfig.mapvideo = node.cameraConfigMapVideo
|
|
1055
|
+
}
|
|
1056
|
+
if (node.cameraConfigMapAudio) {
|
|
1057
|
+
defaultConfig.camera.videoConfig.mapaudio = node.cameraConfigMapAudio
|
|
1058
|
+
}
|
|
1059
|
+
if (node.cameraConfigVideoFilter) {
|
|
1060
|
+
defaultConfig.camera.videoConfig.videoFilter = node.cameraConfigVideoFilter
|
|
1061
|
+
}
|
|
1062
|
+
if (node.cameraConfigAdditionalCommandLine) {
|
|
1063
|
+
defaultConfig.camera.videoConfig.encoderOptions = node.cameraConfigAdditionalCommandLine
|
|
1064
|
+
}
|
|
1065
|
+
if (node.cameraConfigDebug !== undefined) {
|
|
1066
|
+
defaultConfig.camera.videoConfig.debug = node.cameraConfigDebug
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
return {
|
|
1071
|
+
id: cameraPluginId,
|
|
1072
|
+
automatic: existing ? existing.automatic !== false : true,
|
|
1073
|
+
modified: existing ? existing.modified === true : false,
|
|
1074
|
+
config: existing ? existing.config : defaultConfig
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
const shouldSeedCameraPluginEntry = function () {
|
|
1079
|
+
return (
|
|
1080
|
+
!pluginSetupComplete &&
|
|
1081
|
+
initialServiceName === '' &&
|
|
1082
|
+
selectServiceName.val() === 'Camera' &&
|
|
1083
|
+
!getCameraPluginEntry()
|
|
1084
|
+
)
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
const ensureCameraPluginEntry = function () {
|
|
1088
|
+
if (!availablePlugins.length) {
|
|
1089
|
+
return
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
if (shouldSeedCameraPluginEntry()) {
|
|
1093
|
+
addPluginEntry(buildCameraPluginEntry())
|
|
1094
|
+
pluginSetupComplete = true
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
const getInitialPluginEntry = function (entry, index) {
|
|
1099
|
+
const indexedEntry = initialPlugins[index]
|
|
1100
|
+
|
|
1101
|
+
if (indexedEntry && indexedEntry.id === entry.id) {
|
|
1102
|
+
return indexedEntry
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
return initialPlugins.find(function (initialEntry) {
|
|
1106
|
+
return initialEntry.id === entry.id
|
|
1107
|
+
})
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
const createPluginAbout = function (plugin) {
|
|
1111
|
+
return $('<div/>', {class: 'nrchkb-plugin-about'})
|
|
1112
|
+
.html(renderPluginDetails(plugin))
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
const createPluginFieldId = function (sectionId, field) {
|
|
1116
|
+
return sectionId + '-' + (field.path || 'field').replace(/[^a-zA-Z0-9_-]/g, '-')
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
const appendPluginFieldHelp = function (row, input, fieldId, field) {
|
|
1120
|
+
const helpText = field.description || field.placeholder || ''
|
|
1121
|
+
|
|
1122
|
+
if (!helpText) {
|
|
1123
|
+
return
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
const helpId = fieldId + '-help'
|
|
1127
|
+
input.attr('aria-describedby', helpId)
|
|
1128
|
+
$('<div/>', {
|
|
1129
|
+
id: helpId,
|
|
1130
|
+
class: 'nrchkb-plugin-instance-help'
|
|
1131
|
+
}).text(helpText).appendTo(row)
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
const renderPluginField = function (body, section, field, config) {
|
|
1135
|
+
const sectionId = section.attr('id')
|
|
1136
|
+
const fieldId = createPluginFieldId(sectionId, field)
|
|
1137
|
+
let value = getConfigValue(config, field.path, field.default)
|
|
1138
|
+
if ((value === '' || value === undefined) && field.type === 'dynamic-select') {
|
|
1139
|
+
const initialEntry = section.data('pluginInitialEntry') || {}
|
|
1140
|
+
const initialValue = getConfigValue(initialEntry.config || {}, field.path, undefined)
|
|
1141
|
+
|
|
1142
|
+
if (initialValue !== '' && initialValue !== undefined) {
|
|
1143
|
+
value = initialValue
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
const row = $('<div/>', {
|
|
1147
|
+
class: 'form-row' + (field.type === 'checkbox'
|
|
1148
|
+
? ' nrchkb-checkbox-row nrchkb-plugin-instance-check-row'
|
|
1149
|
+
: field.type === 'textarea'
|
|
1150
|
+
? ' nrchkb-plugin-instance-textarea-row'
|
|
1151
|
+
: '')
|
|
1152
|
+
}).appendTo(body)
|
|
1153
|
+
|
|
1154
|
+
let input
|
|
1155
|
+
|
|
1156
|
+
if (field.type === 'checkbox') {
|
|
1157
|
+
const label = $('<label/>', {
|
|
1158
|
+
class: 'nrchkb-checkbox-label nrchkb-plugin-instance-check',
|
|
1159
|
+
for: fieldId
|
|
1160
|
+
}).appendTo(row)
|
|
1161
|
+
input = $('<input/>', {type: 'checkbox', id: fieldId}).appendTo(label)
|
|
1162
|
+
$('<span/>')
|
|
1163
|
+
.append(field.icon ? $('<i/>', {class: 'fa ' + field.icon}) : $())
|
|
1164
|
+
.append(field.icon ? ' ' : '')
|
|
1165
|
+
.append(document.createTextNode(field.label || field.path))
|
|
1166
|
+
.appendTo(label)
|
|
1167
|
+
input.prop('checked', !!value)
|
|
1168
|
+
} else {
|
|
1169
|
+
const label = $('<label/>', {for: fieldId}).appendTo(row)
|
|
1170
|
+
|
|
1171
|
+
if (field.icon) {
|
|
1172
|
+
$('<i/>', {class: 'fa ' + field.icon}).appendTo(label)
|
|
1173
|
+
label.append(' ')
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
label.append(document.createTextNode(field.label || field.path))
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
if (field.type === 'select' || field.type === 'dynamic-select') {
|
|
1180
|
+
input = $('<select/>', {id: fieldId, class: 'nrchkb-plugin-instance-select'}).appendTo(row)
|
|
1181
|
+
;(field.options || []).forEach(function (option) {
|
|
1182
|
+
$('<option/>')
|
|
1183
|
+
.val(option.value)
|
|
1184
|
+
.text(option.label)
|
|
1185
|
+
.appendTo(input)
|
|
1186
|
+
})
|
|
1187
|
+
input.val(value === undefined ? '' : value)
|
|
1188
|
+
} else if (field.type === 'textarea') {
|
|
1189
|
+
input = $('<textarea/>', {
|
|
1190
|
+
id: fieldId,
|
|
1191
|
+
class: 'nrchkb-plugin-instance-textarea',
|
|
1192
|
+
rows: 4
|
|
1193
|
+
}).appendTo(row)
|
|
1194
|
+
input.val(value === undefined ? '' : value)
|
|
1195
|
+
} else {
|
|
1196
|
+
input = $('<input/>', {
|
|
1197
|
+
type: field.type === 'number' ? 'number' : field.type === 'password' ? 'password' : 'text',
|
|
1198
|
+
id: fieldId,
|
|
1199
|
+
placeholder: field.placeholder || ''
|
|
1200
|
+
}).appendTo(row)
|
|
1201
|
+
input.val(value === undefined ? '' : value)
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
input
|
|
1205
|
+
.attr('data-plugin-config-path', field.path)
|
|
1206
|
+
.data('pluginField', field)
|
|
1207
|
+
|
|
1208
|
+
if (field.type === 'config-node' && field.configNodeType && RED.editor && RED.editor.prepareConfigNodeSelect) {
|
|
1209
|
+
const property = fieldId.replace(/^node-input-/, '')
|
|
1210
|
+
const node = {}
|
|
1211
|
+
node[property] = value === undefined ? '' : value
|
|
1212
|
+
|
|
1213
|
+
RED.editor.prepareConfigNodeSelect(
|
|
1214
|
+
node,
|
|
1215
|
+
property,
|
|
1216
|
+
field.configNodeType,
|
|
1217
|
+
'node-input'
|
|
1218
|
+
)
|
|
1219
|
+
|
|
1220
|
+
input = $('#' + fieldId)
|
|
1221
|
+
input
|
|
1222
|
+
.attr('data-plugin-config-path', field.path)
|
|
1223
|
+
.data('pluginField', field)
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
if (field.type === 'dynamic-select') {
|
|
1227
|
+
input.attr('data-plugin-dynamic-select', 'true')
|
|
1228
|
+
input.data('pluginDynamicValue', value === undefined ? '' : value)
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
appendPluginFieldHelp(row, input, fieldId, field)
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
const loadDynamicPluginSelect = function (section, input) {
|
|
1235
|
+
const field = input.data('pluginField') || {}
|
|
1236
|
+
const selectedValue = input.val() || input.data('pluginDynamicValue') || ''
|
|
1237
|
+
|
|
1238
|
+
if (!field.optionsUrl) {
|
|
1239
|
+
return
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
let url = field.optionsUrl
|
|
1243
|
+
if (field.optionsDependsOn) {
|
|
1244
|
+
const dependsInput = section.find('[data-plugin-config-path="' + field.optionsDependsOn + '"]')
|
|
1245
|
+
const dependsValue = dependsInput.val()
|
|
1246
|
+
|
|
1247
|
+
if (!dependsValue) {
|
|
1248
|
+
input.empty()
|
|
1249
|
+
$('<option/>').val('').text('Choose a controller first').appendTo(input)
|
|
1250
|
+
input.val('')
|
|
1251
|
+
syncPluginsInput()
|
|
1252
|
+
return
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
url = url.replace('{' + field.optionsDependsOn + '}', encodeURIComponent(dependsValue))
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
input.empty()
|
|
1259
|
+
$('<option/>').val('').text('Loading...').appendTo(input)
|
|
1260
|
+
|
|
1261
|
+
$.getJSON(url, function (options) {
|
|
1262
|
+
input.empty()
|
|
1263
|
+
$('<option/>').val('').text('Choose...').appendTo(input)
|
|
1264
|
+
;(options || []).forEach(function (option) {
|
|
1265
|
+
$('<option/>')
|
|
1266
|
+
.val(option.value)
|
|
1267
|
+
.text(option.label)
|
|
1268
|
+
.appendTo(input)
|
|
1269
|
+
})
|
|
1270
|
+
if (selectedValue && input.find('option').filter(function () {
|
|
1271
|
+
return $(this).val() === selectedValue
|
|
1272
|
+
}).length === 0) {
|
|
1273
|
+
$('<option/>')
|
|
1274
|
+
.val(selectedValue)
|
|
1275
|
+
.text('Saved camera (' + selectedValue + ')')
|
|
1276
|
+
.appendTo(input)
|
|
1277
|
+
}
|
|
1278
|
+
input.val(selectedValue)
|
|
1279
|
+
input.data('pluginDynamicValue', input.val() || selectedValue)
|
|
1280
|
+
syncPluginsInput()
|
|
1281
|
+
}).fail(function () {
|
|
1282
|
+
input.empty()
|
|
1283
|
+
$('<option/>').val('').text('Unable to load cameras').appendTo(input)
|
|
1284
|
+
if (selectedValue) {
|
|
1285
|
+
$('<option/>').val(selectedValue).text(selectedValue).appendTo(input)
|
|
1286
|
+
input.val(selectedValue)
|
|
1287
|
+
}
|
|
1288
|
+
input.data('pluginDynamicValue', input.val() || selectedValue)
|
|
1289
|
+
syncPluginsInput()
|
|
1290
|
+
})
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
const renderPluginEditor = function (body, entry, plugin, section) {
|
|
1294
|
+
const editor = plugin.editor || {}
|
|
1295
|
+
const editorSections = Array.isArray(editor.sections) ? editor.sections : []
|
|
1296
|
+
const config = mergePluginConfig(plugin, entry.config || {})
|
|
1297
|
+
|
|
1298
|
+
body.append(createPluginAbout(plugin))
|
|
1299
|
+
|
|
1300
|
+
if (!editorSections.length) {
|
|
1301
|
+
$('<div/>', {class: 'alert alert-info nrchkb-info'})
|
|
1302
|
+
.text('This plugin does not provide editor fields.')
|
|
1303
|
+
.appendTo(body)
|
|
1304
|
+
return
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
editorSections.forEach(function (editorSection) {
|
|
1308
|
+
if (editorSection.title) {
|
|
1309
|
+
$('<div/>', {class: 'nrchkb-plugin-overview-name'})
|
|
1310
|
+
.text(editorSection.title)
|
|
1311
|
+
.appendTo(body)
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
if (editorSection.description) {
|
|
1315
|
+
$('<div/>', {class: 'nrchkb-plugin-empty'})
|
|
1316
|
+
.text(editorSection.description)
|
|
1317
|
+
.appendTo(body)
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
;(editorSection.fields || []).forEach(function (field) {
|
|
1321
|
+
renderPluginField(body, section, field, config)
|
|
1322
|
+
})
|
|
1323
|
+
})
|
|
1324
|
+
|
|
1325
|
+
section.data('pluginEntry', $.extend(true, {}, entry, {config}))
|
|
1326
|
+
section.find('[data-plugin-dynamic-select="true"]').each(function () {
|
|
1327
|
+
loadDynamicPluginSelect(section, $(this))
|
|
1328
|
+
})
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
const renderPluginSections = function () {
|
|
1332
|
+
pluginOverview.empty()
|
|
1333
|
+
pluginSections.empty()
|
|
1334
|
+
|
|
1335
|
+
const entries = normalizePlugins(node.plugins)
|
|
1336
|
+
const configNodeEntries = readPluginSlotEntries()
|
|
1337
|
+
const allEntries = entries.concat(configNodeEntries)
|
|
1338
|
+
pluginCount.text(allEntries.length)
|
|
1339
|
+
|
|
1340
|
+
if (!allEntries.length) {
|
|
1341
|
+
pluginSummaryList.text('No plugins attached')
|
|
1342
|
+
$('<div/>', {class: 'nrchkb-plugin-empty'})
|
|
1343
|
+
.text('No plugins attached.')
|
|
1344
|
+
.appendTo(pluginOverview)
|
|
1345
|
+
return
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
const summaryNames = allEntries.map(function (entry) {
|
|
1349
|
+
const plugin = getPluginMetadata(entry.id)
|
|
1350
|
+
return plugin ? getPluginDisplayLabel(plugin) : entry.id
|
|
1351
|
+
})
|
|
1352
|
+
pluginSummaryList.text(summaryNames.join(', '))
|
|
1353
|
+
|
|
1354
|
+
entries.forEach(function (entry, index) {
|
|
1355
|
+
const plugin = getPluginMetadata(entry.id)
|
|
1356
|
+
const title = plugin
|
|
1357
|
+
? getPluginDisplayLabel(plugin) + ' v' + plugin.version
|
|
1358
|
+
: entry.id
|
|
1359
|
+
const sectionId = getPluginSectionId(entry, index)
|
|
1360
|
+
|
|
1361
|
+
renderPluginOverviewItem(title, sectionId)
|
|
1362
|
+
|
|
1363
|
+
const section = $('<details/>', {
|
|
1364
|
+
id: sectionId,
|
|
1365
|
+
class: 'nrchkb-section nrchkb-plugin-section',
|
|
1366
|
+
open: true
|
|
1367
|
+
}).appendTo(pluginSections)
|
|
1368
|
+
section.data('pluginEntry', $.extend(true, {}, entry))
|
|
1369
|
+
section.data('pluginInitialEntry', $.extend(true, {}, getInitialPluginEntry(entry, index) || {}))
|
|
1370
|
+
|
|
1371
|
+
const summary = $('<summary/>').appendTo(section)
|
|
1372
|
+
$('<i/>', {class: 'fa fa-plug'}).appendTo(summary)
|
|
1373
|
+
$('<span/>', {class: 'nrchkb-plugin-title'}).text(title).appendTo(summary)
|
|
1374
|
+
|
|
1375
|
+
const body = $('<div/>', {class: 'nrchkb-section-body'}).appendTo(section)
|
|
1376
|
+
const toolbar = $('<div/>', {class: 'nrchkb-plugin-section-toolbar'}).appendTo(body)
|
|
1377
|
+
$('<button/>', {
|
|
1378
|
+
type: 'button',
|
|
1379
|
+
class: 'red-ui-button red-ui-button-small nrchkb-plugin-remove',
|
|
1380
|
+
'aria-label': 'Remove plugin ' + title,
|
|
1381
|
+
title: 'Remove plugin'
|
|
1382
|
+
})
|
|
1383
|
+
.append($('<i/>', {class: 'fa fa-remove'}))
|
|
1384
|
+
.appendTo(toolbar)
|
|
1385
|
+
|
|
1386
|
+
renderPluginEditor(body, entry, plugin || {}, section)
|
|
1387
|
+
})
|
|
1388
|
+
|
|
1389
|
+
configNodeEntries.forEach(function (entry, index) {
|
|
1390
|
+
renderConfigNodePluginSection(entry, getPluginMetadata(entry.id), index)
|
|
1391
|
+
})
|
|
1392
|
+
}
|
|
1393
|
+
let pluginRenderTimer
|
|
1394
|
+
const schedulePluginSectionsRefresh = function () {
|
|
1395
|
+
clearTimeout(pluginRenderTimer)
|
|
1396
|
+
pluginRenderTimer = window.setTimeout(renderPluginSections, 250)
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
const pluginSlotNames = [
|
|
1400
|
+
'plugin1',
|
|
1401
|
+
'plugin2',
|
|
1402
|
+
'plugin3',
|
|
1403
|
+
'plugin4',
|
|
1404
|
+
'plugin5',
|
|
1405
|
+
'plugin6',
|
|
1406
|
+
'plugin7',
|
|
1407
|
+
'plugin8'
|
|
1408
|
+
]
|
|
1409
|
+
|
|
1410
|
+
const isOccupiedPluginSlotValue = function (value) {
|
|
1411
|
+
return typeof value === 'string' &&
|
|
1412
|
+
value.trim() !== '' &&
|
|
1413
|
+
value !== '_ADD_' &&
|
|
1414
|
+
value !== '_NONE_'
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
const getPluginSlotValue = function (slot) {
|
|
1418
|
+
const inputValue = $('#node-input-' + slot).val()
|
|
1419
|
+
const savedValue = node[slot]
|
|
1420
|
+
|
|
1421
|
+
if (isOccupiedPluginSlotValue(inputValue)) {
|
|
1422
|
+
return inputValue
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
if (isOccupiedPluginSlotValue(savedValue)) {
|
|
1426
|
+
return savedValue
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
return ''
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
const parsePluginInstanceConfig = function (value) {
|
|
1433
|
+
if (!value || typeof value !== 'string') {
|
|
1434
|
+
return {}
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
try {
|
|
1438
|
+
const parsed = JSON.parse(value)
|
|
1439
|
+
return parsed && typeof parsed === 'object' ? parsed : {}
|
|
1440
|
+
} catch (_) {
|
|
1441
|
+
return {}
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
const readPluginSlotEntries = function () {
|
|
1446
|
+
return pluginSlotNames.map(function (slot) {
|
|
1447
|
+
const configNodeId = getPluginSlotValue(slot)
|
|
1448
|
+
const configNode = configNodeId ? RED.nodes.node(configNodeId) : undefined
|
|
1449
|
+
const pluginId = configNode && configNode.pluginId
|
|
1450
|
+
|
|
1451
|
+
if (!configNodeId || !pluginId) {
|
|
1452
|
+
return undefined
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
return {
|
|
1456
|
+
id: pluginId,
|
|
1457
|
+
slot,
|
|
1458
|
+
configNodeId,
|
|
1459
|
+
config: parsePluginInstanceConfig(configNode.pluginConfig)
|
|
1460
|
+
}
|
|
1461
|
+
}).filter(function (entry) {
|
|
1462
|
+
return !!entry
|
|
1463
|
+
})
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
const renderPluginOverviewItem = function (title, sectionId, options) {
|
|
1467
|
+
const overviewItem = $('<div/>', {class: 'nrchkb-plugin-overview-item'}).appendTo(pluginOverview)
|
|
1468
|
+
const overviewText = $('<div/>').appendTo(overviewItem)
|
|
1469
|
+
$('<div/>', {class: 'nrchkb-plugin-overview-name'}).text(title).appendTo(overviewText)
|
|
1470
|
+
const overviewActions = $('<div/>', {class: 'nrchkb-plugin-overview-actions'}).appendTo(overviewItem)
|
|
1471
|
+
$('<button/>', {
|
|
1472
|
+
type: 'button',
|
|
1473
|
+
class: 'red-ui-button red-ui-button-small nrchkb-plugin-overview-edit',
|
|
1474
|
+
'aria-label': 'Edit plugin settings for ' + title,
|
|
1475
|
+
title: 'Edit plugin settings',
|
|
1476
|
+
'data-target': sectionId,
|
|
1477
|
+
'data-slot': options && options.slot ? options.slot : '',
|
|
1478
|
+
'data-config-id': options && options.configNodeId ? options.configNodeId : ''
|
|
1479
|
+
})
|
|
1480
|
+
.append($('<i/>', {class: 'fa fa-pencil'}))
|
|
1481
|
+
.append(' Edit')
|
|
1482
|
+
.appendTo(overviewActions)
|
|
1483
|
+
$('<button/>', {
|
|
1484
|
+
type: 'button',
|
|
1485
|
+
class: 'red-ui-button red-ui-button-small nrchkb-plugin-overview-remove',
|
|
1486
|
+
'aria-label': 'Remove plugin ' + title,
|
|
1487
|
+
title: 'Remove plugin',
|
|
1488
|
+
'data-target': sectionId,
|
|
1489
|
+
'data-slot': options && options.slot ? options.slot : ''
|
|
1490
|
+
})
|
|
1491
|
+
.append($('<i/>', {class: 'fa fa-remove'}))
|
|
1492
|
+
.append(' Remove')
|
|
1493
|
+
.appendTo(overviewActions)
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
const renderConfigNodePluginSection = function (entry, plugin, index) {
|
|
1497
|
+
const title = plugin
|
|
1498
|
+
? getPluginDisplayLabel(plugin) + ' v' + plugin.version
|
|
1499
|
+
: entry.id
|
|
1500
|
+
const sectionId = 'node-input-plugin-config-section-' + entry.slot + '-' + index
|
|
1501
|
+
|
|
1502
|
+
renderPluginOverviewItem(title, sectionId, entry)
|
|
1503
|
+
|
|
1504
|
+
const section = $('<details/>', {
|
|
1505
|
+
id: sectionId,
|
|
1506
|
+
class: 'nrchkb-section nrchkb-plugin-section',
|
|
1507
|
+
open: true,
|
|
1508
|
+
'data-plugin-slot': entry.slot
|
|
1509
|
+
}).appendTo(pluginSections)
|
|
1510
|
+
|
|
1511
|
+
const summary = $('<summary/>').appendTo(section)
|
|
1512
|
+
$('<i/>', {class: 'fa fa-plug'}).appendTo(summary)
|
|
1513
|
+
$('<span/>', {class: 'nrchkb-plugin-title'}).text(title).appendTo(summary)
|
|
1514
|
+
|
|
1515
|
+
const body = $('<div/>', {class: 'nrchkb-section-body'}).appendTo(section)
|
|
1516
|
+
const toolbar = $('<div/>', {class: 'nrchkb-plugin-section-toolbar'}).appendTo(body)
|
|
1517
|
+
$('<button/>', {
|
|
1518
|
+
type: 'button',
|
|
1519
|
+
class: 'red-ui-button red-ui-button-small nrchkb-plugin-remove',
|
|
1520
|
+
'aria-label': 'Remove plugin ' + title,
|
|
1521
|
+
title: 'Remove plugin',
|
|
1522
|
+
'data-slot': entry.slot
|
|
1523
|
+
})
|
|
1524
|
+
.append($('<i/>', {class: 'fa fa-remove'}))
|
|
1525
|
+
.appendTo(toolbar)
|
|
1526
|
+
|
|
1527
|
+
if (plugin) {
|
|
1528
|
+
body.append(createPluginAbout(plugin))
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
const getFirstEmptyPluginSlot = function () {
|
|
1533
|
+
return pluginSlotNames.find(function (slot) {
|
|
1534
|
+
return !getPluginSlotValue(slot)
|
|
1535
|
+
})
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
const addConfigNodePlugin = function (plugin) {
|
|
1539
|
+
const slot = getFirstEmptyPluginSlot()
|
|
1540
|
+
|
|
1541
|
+
if (!slot) {
|
|
1542
|
+
RED.notify('This service already has the maximum number of plugin config nodes.', 'warning')
|
|
1543
|
+
return
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
if (!RED.editor || !RED.editor.editConfig) {
|
|
1547
|
+
RED.notify('This plugin does not provide a config node editor.', 'warning')
|
|
1548
|
+
return
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
window.NRCHKBPluginInstanceDraft = {
|
|
1552
|
+
pluginId: plugin.id,
|
|
1553
|
+
pluginConfig: getPluginDefaultConfig(plugin)
|
|
1554
|
+
}
|
|
1555
|
+
RED.editor.editConfig(slot, 'homekit-plugin-instance', '_ADD_', 'node-input', node)
|
|
1556
|
+
pluginSetupComplete = true
|
|
1557
|
+
schedulePluginSectionsRefresh()
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
const addPluginEntry = function (entry) {
|
|
1561
|
+
const plugin = getPluginMetadata(entry.id)
|
|
1562
|
+
|
|
1563
|
+
if (!plugin) {
|
|
1564
|
+
RED.notify('Plugin metadata is not available.', 'warning')
|
|
1565
|
+
return
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
if (!isPluginCompatibleWithSelectedService(plugin)) {
|
|
1569
|
+
RED.notify('Plugin is not compatible with this service.', 'warning')
|
|
1570
|
+
return
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
if (!allowsAnotherPluginInstance(plugin, entry.id)) {
|
|
1574
|
+
RED.notify('This plugin can only be added once to this node.', 'warning')
|
|
1575
|
+
return
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
if (plugin.attachment === 'config-node') {
|
|
1579
|
+
addConfigNodePlugin(plugin)
|
|
1580
|
+
return
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
node.plugins = readPluginsFromList().concat([entry])
|
|
1584
|
+
pluginSetupComplete = true
|
|
1585
|
+
renderPluginSections()
|
|
1586
|
+
syncPluginsInput()
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
const setPluginEntries = function (plugins) {
|
|
1590
|
+
node.plugins = normalizePlugins(plugins)
|
|
1591
|
+
renderPluginSections()
|
|
1592
|
+
syncPluginsInput()
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
const openAddPluginDialog = function () {
|
|
1596
|
+
const dialog = $('<div/>', {class: 'nrchkb-plugin-picker'})
|
|
1597
|
+
|
|
1598
|
+
availablePlugins.forEach(function (plugin) {
|
|
1599
|
+
const compatible = isPluginCompatibleWithSelectedService(plugin)
|
|
1600
|
+
const duplicateAllowed = allowsAnotherPluginInstance(plugin, plugin.id)
|
|
1601
|
+
const disabled = !compatible || !duplicateAllowed
|
|
1602
|
+
const reason = !compatible
|
|
1603
|
+
? 'Not compatible with ' + (selectServiceName.val() || 'this service')
|
|
1604
|
+
: !duplicateAllowed
|
|
1605
|
+
? 'Already added'
|
|
1606
|
+
: renderCompatibilityText(plugin)
|
|
1607
|
+
const item = $('<button/>', {
|
|
1608
|
+
class: 'nrchkb-plugin-picker-item',
|
|
1609
|
+
'aria-label': getPluginDisplayLabel(plugin) + '. ' + reason,
|
|
1610
|
+
disabled,
|
|
1611
|
+
type: 'button'
|
|
1612
|
+
}).appendTo(dialog)
|
|
1613
|
+
|
|
1614
|
+
$('<div/>', {class: 'nrchkb-plugin-picker-title'})
|
|
1615
|
+
.append(
|
|
1616
|
+
$('<span/>').text(getPluginDisplayLabel(plugin)),
|
|
1617
|
+
$('<span/>').text('v' + plugin.version)
|
|
1618
|
+
)
|
|
1619
|
+
.appendTo(item)
|
|
1620
|
+
$('<div/>', {class: 'nrchkb-plugin-picker-description'})
|
|
1621
|
+
.text(plugin.description)
|
|
1622
|
+
.appendTo(item)
|
|
1623
|
+
$('<div/>', {class: 'nrchkb-plugin-picker-meta'})
|
|
1624
|
+
.text(reason + ' · ' + renderMultipleInstancesText(plugin))
|
|
1625
|
+
.appendTo(item)
|
|
1626
|
+
|
|
1627
|
+
if (!disabled) {
|
|
1628
|
+
const addSelectedPlugin = function () {
|
|
1629
|
+
addPluginEntry({
|
|
1630
|
+
id: plugin.id,
|
|
1631
|
+
automatic: false,
|
|
1632
|
+
modified: true,
|
|
1633
|
+
config: getPluginDefaultConfig(plugin)
|
|
1634
|
+
})
|
|
1635
|
+
dialog.dialog('close')
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
item.on('click', addSelectedPlugin)
|
|
1639
|
+
}
|
|
1640
|
+
})
|
|
1641
|
+
|
|
1642
|
+
if (!availablePlugins.length) {
|
|
1643
|
+
$('<div/>', {class: 'nrchkb-plugin-empty'})
|
|
1644
|
+
.text('No plugins are registered.')
|
|
1645
|
+
.appendTo(dialog)
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
dialog.dialog({
|
|
1649
|
+
modal: true,
|
|
1650
|
+
resizable: false,
|
|
1651
|
+
width: Math.min(560, $(window).width() - 80),
|
|
1652
|
+
title: 'Add plugin',
|
|
1653
|
+
close: function () {
|
|
1654
|
+
dialog.dialog('destroy').remove()
|
|
1655
|
+
},
|
|
1656
|
+
buttons: {
|
|
1657
|
+
Cancel: function () {
|
|
1658
|
+
$(this).dialog('close')
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
})
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
pluginSections.on('click', '.nrchkb-plugin-remove', function (event) {
|
|
1665
|
+
event.preventDefault()
|
|
1666
|
+
event.stopPropagation()
|
|
1667
|
+
const section = $(this).closest('.nrchkb-plugin-section')
|
|
1668
|
+
const slot = $(this).attr('data-slot') || section.attr('data-plugin-slot')
|
|
1669
|
+
|
|
1670
|
+
if (slot) {
|
|
1671
|
+
$('#node-input-' + slot).val('_ADD_').trigger('change')
|
|
1672
|
+
node[slot] = ''
|
|
1673
|
+
pluginSetupComplete = true
|
|
1674
|
+
renderPluginSections()
|
|
1675
|
+
return
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
section.remove()
|
|
1679
|
+
node.plugins = readPluginsFromList()
|
|
1680
|
+
pluginSetupComplete = true
|
|
1681
|
+
renderPluginSections()
|
|
1682
|
+
syncPluginsInput()
|
|
1683
|
+
})
|
|
1684
|
+
|
|
1685
|
+
pluginSections.on('change keyup', '[data-plugin-config-path]', function (event) {
|
|
1686
|
+
const section = $(this).closest('.nrchkb-plugin-section')
|
|
1687
|
+
const entry = section.data('pluginEntry') || {}
|
|
1688
|
+
const plugin = getPluginMetadata(entry.id)
|
|
1689
|
+
const changedInput = $(event.target)
|
|
1690
|
+
const changedPath = changedInput.attr('data-plugin-config-path') || ''
|
|
1691
|
+
const changedField = changedInput.data('pluginField') || {}
|
|
1692
|
+
|
|
1693
|
+
if (changedField.type === 'dynamic-select') {
|
|
1694
|
+
changedInput.data('pluginDynamicValue', changedInput.val() || '')
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
entry.modified = true
|
|
1698
|
+
entry.config = readPluginConfig(section, entry, plugin || {})
|
|
1699
|
+
section.data('pluginEntry', entry)
|
|
1700
|
+
section.find('[data-plugin-dynamic-select="true"]').each(function () {
|
|
1701
|
+
const dynamicInput = $(this)
|
|
1702
|
+
const dynamicField = dynamicInput.data('pluginField') || {}
|
|
1703
|
+
if (dynamicField.optionsDependsOn === changedPath) {
|
|
1704
|
+
dynamicInput.data(
|
|
1705
|
+
'pluginDynamicValue',
|
|
1706
|
+
dynamicInput.val() || dynamicInput.data('pluginDynamicValue') || ''
|
|
1707
|
+
)
|
|
1708
|
+
loadDynamicPluginSelect(section, dynamicInput)
|
|
1709
|
+
}
|
|
1710
|
+
})
|
|
1711
|
+
|
|
1712
|
+
syncPluginsInput()
|
|
1713
|
+
})
|
|
1714
|
+
|
|
1715
|
+
pluginOverview.on('click', '.nrchkb-plugin-overview-edit', function (event) {
|
|
1716
|
+
event.preventDefault()
|
|
1717
|
+
event.stopPropagation()
|
|
1718
|
+
|
|
1719
|
+
const slot = $(this).attr('data-slot')
|
|
1720
|
+
if (slot) {
|
|
1721
|
+
const configNodeId = $(this).attr('data-config-id') || getPluginSlotValue(slot)
|
|
1722
|
+
if (RED.editor && RED.editor.editConfig) {
|
|
1723
|
+
RED.editor.editConfig(slot, 'homekit-plugin-instance', configNodeId || '_ADD_', 'node-input', node)
|
|
1724
|
+
schedulePluginSectionsRefresh()
|
|
1725
|
+
}
|
|
1726
|
+
return
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
const targetId = $(this).attr('data-target')
|
|
1730
|
+
const section = targetId ? $('#' + targetId) : $()
|
|
1731
|
+
|
|
1732
|
+
if (!section.length) {
|
|
1733
|
+
return
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
section.prop('open', true)
|
|
1737
|
+
|
|
1738
|
+
scrollEditorSectionIntoView(section[0])
|
|
1739
|
+
})
|
|
1740
|
+
|
|
1741
|
+
pluginOverview.on('click', '.nrchkb-plugin-overview-remove', function (event) {
|
|
1742
|
+
event.preventDefault()
|
|
1743
|
+
event.stopPropagation()
|
|
1744
|
+
|
|
1745
|
+
const slot = $(this).attr('data-slot')
|
|
1746
|
+
if (slot) {
|
|
1747
|
+
$('#node-input-' + slot).val('_ADD_').trigger('change')
|
|
1748
|
+
node[slot] = ''
|
|
1749
|
+
pluginSetupComplete = true
|
|
1750
|
+
renderPluginSections()
|
|
1751
|
+
return
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
const targetId = $(this).attr('data-target')
|
|
1755
|
+
const section = targetId ? $('#' + targetId) : $()
|
|
1756
|
+
|
|
1757
|
+
if (section.length) {
|
|
1758
|
+
section.remove()
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
node.plugins = readPluginsFromList()
|
|
1762
|
+
pluginSetupComplete = true
|
|
1763
|
+
renderPluginSections()
|
|
1764
|
+
syncPluginsInput()
|
|
1765
|
+
})
|
|
1766
|
+
|
|
1767
|
+
$('#node-input-add-plugin').on('click', function (event) {
|
|
1768
|
+
event.preventDefault()
|
|
1769
|
+
event.stopPropagation()
|
|
1770
|
+
openAddPluginDialog()
|
|
1771
|
+
})
|
|
1772
|
+
|
|
1773
|
+
pluginSlotNames.forEach(function (slot) {
|
|
1774
|
+
$('#node-input-' + slot).on('change', function () {
|
|
1775
|
+
node[slot] = getPluginSlotValue(slot)
|
|
1776
|
+
renderPluginSections()
|
|
1777
|
+
})
|
|
1778
|
+
})
|
|
1779
|
+
|
|
1780
|
+
const pluginsDetailsElement = document.getElementById('plugins-configuration')
|
|
1781
|
+
const pluginsSummaryElement = pluginsDetailsElement && pluginsDetailsElement.querySelector('summary')
|
|
1782
|
+
const updatePluginsExpandedState = function () {
|
|
1783
|
+
if (pluginsSummaryElement) {
|
|
1784
|
+
pluginsSummaryElement.setAttribute('aria-expanded', pluginsDetailsElement.open ? 'true' : 'false')
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
if (pluginsSummaryElement) {
|
|
1789
|
+
pluginsSummaryElement.setAttribute('aria-controls', 'node-input-plugin-overview')
|
|
1790
|
+
updatePluginsExpandedState()
|
|
1791
|
+
}
|
|
1792
|
+
if (pluginsDetailsElement) {
|
|
1793
|
+
pluginsDetailsElement.addEventListener('toggle', updatePluginsExpandedState)
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
setPluginEntries(node.plugins)
|
|
1797
|
+
|
|
1798
|
+
$.getJSON('nrchkb/plugins', function (plugins) {
|
|
1799
|
+
availablePlugins = plugins.slice().sort(function (left, right) {
|
|
1800
|
+
return getPluginDisplayLabel(left).localeCompare(getPluginDisplayLabel(right), undefined, {
|
|
1801
|
+
numeric: true,
|
|
1802
|
+
sensitivity: 'base'
|
|
1803
|
+
})
|
|
1804
|
+
})
|
|
1805
|
+
if (selectServiceName.val() === 'Camera') {
|
|
1806
|
+
ensureCameraPluginEntry()
|
|
1807
|
+
}
|
|
1808
|
+
renderPluginSections()
|
|
1809
|
+
syncPluginsInput()
|
|
1810
|
+
})
|
|
1811
|
+
|
|
1812
|
+
Object.keys(serviceTypes).sort().forEach(function (key) {
|
|
1813
|
+
if (serviceTypes[key].nrchkbHiddenInService2) {
|
|
1814
|
+
return
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
const serviceOption = $('<option></option>')
|
|
1818
|
+
|
|
1819
|
+
serviceOption
|
|
1820
|
+
.val(key)
|
|
1821
|
+
.text(key)
|
|
1822
|
+
|
|
1823
|
+
if (serviceTypes[key].hasOwnProperty('nrchkbDisabledText')) {
|
|
1824
|
+
serviceOption.text(serviceTypes[key].nrchkbDisabledText)
|
|
1825
|
+
serviceOption.attr('disabled', 'disabled');
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
selectServiceName.append(serviceOption)
|
|
1829
|
+
})
|
|
1830
|
+
|
|
1831
|
+
let adaptiveLightningConfiguration = $('#adaptive-lightning-configuration')
|
|
1832
|
+
const selectOutputMode = $('#node-input-outputMode')
|
|
1833
|
+
|
|
1834
|
+
if (!node.outputMode) {
|
|
1835
|
+
selectOutputMode.val('events')
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
const updateOutputs = function () {
|
|
1839
|
+
const outputMode = selectOutputMode.val() || 'events'
|
|
1840
|
+
const serviceName = selectServiceName.val()
|
|
1841
|
+
const isCamera = serviceName === 'Camera'
|
|
1842
|
+
|
|
1843
|
+
if (outputMode === 'legacy') {
|
|
1844
|
+
node.outputs = isCamera ? 3 : 2
|
|
1845
|
+
} else {
|
|
1846
|
+
node.outputs = isCamera ? 2 : 1
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
selectServiceName
|
|
1851
|
+
.find('option')
|
|
1852
|
+
.filter(function () {
|
|
1853
|
+
return $(this).val() === node.serviceName
|
|
1854
|
+
})
|
|
1855
|
+
.attr('selected', true)
|
|
1856
|
+
|
|
1857
|
+
selectServiceName
|
|
1858
|
+
.change(function () {
|
|
1859
|
+
if (this.value === 'Camera') {
|
|
1860
|
+
ensureCameraPluginEntry()
|
|
1861
|
+
} else {
|
|
1862
|
+
setPluginEntries(readPluginsFromList().filter(function (entry) {
|
|
1863
|
+
return entry.id !== cameraPluginId || entry.automatic === false || entry.modified
|
|
1864
|
+
}))
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
syncPluginsInput()
|
|
1868
|
+
updateOutputs()
|
|
1869
|
+
})
|
|
1870
|
+
.change()
|
|
1871
|
+
|
|
1872
|
+
selectOutputMode.change(updateOutputs).change()
|
|
1873
|
+
|
|
1874
|
+
selectServiceName
|
|
1875
|
+
.change(function () {
|
|
1876
|
+
if (this.value === 'Lightbulb') {
|
|
1877
|
+
adaptiveLightningConfiguration.prop('open', true)
|
|
1878
|
+
showEditorSection(adaptiveLightningConfiguration)
|
|
1879
|
+
} else {
|
|
1880
|
+
hideEditorSection(adaptiveLightningConfiguration)
|
|
1881
|
+
}
|
|
1882
|
+
})
|
|
1883
|
+
.change()
|
|
1884
|
+
|
|
1885
|
+
$('#node-input-characteristicProperties').typedInput({
|
|
1886
|
+
type: 'json',
|
|
1887
|
+
types: ['json'],
|
|
1888
|
+
})
|
|
1889
|
+
|
|
1890
|
+
const selectParentService = $('#node-input-parentService')
|
|
1891
|
+
|
|
1892
|
+
const candidateNodes = [
|
|
1893
|
+
...RED.nodes.filterNodes({
|
|
1894
|
+
type: 'homekit-service',
|
|
1895
|
+
}),
|
|
1896
|
+
...RED.nodes.filterNodes({
|
|
1897
|
+
type: 'homekit-service2',
|
|
1898
|
+
})
|
|
1899
|
+
]
|
|
1900
|
+
|
|
1901
|
+
const parentServiceOptions = []
|
|
1902
|
+
|
|
1903
|
+
candidateNodes.forEach(function (n) {
|
|
1904
|
+
if (!n.name || n.name.length < 1 || !n.isParent) {
|
|
1905
|
+
return
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
if (n.id === node.id) {
|
|
1909
|
+
return
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
if (inSubflow) {
|
|
1913
|
+
if (n.z !== node.z) {
|
|
1914
|
+
return
|
|
1915
|
+
}
|
|
1916
|
+
} else {
|
|
1917
|
+
if (!!RED.nodes.subflow(n.z)) {
|
|
1918
|
+
return
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
let sublabel
|
|
1923
|
+
let tab = RED.nodes.workspace(n.z)
|
|
1924
|
+
if (tab) {
|
|
1925
|
+
sublabel = tab.label || tab.id
|
|
1926
|
+
} else {
|
|
1927
|
+
tab = RED.nodes.subflow(n.z)
|
|
687
1928
|
sublabel = 'subflow : ' + tab.name
|
|
688
1929
|
}
|
|
689
1930
|
|
|
@@ -691,14 +1932,17 @@ if (nrchkbExperimental) {
|
|
|
691
1932
|
const text = n.name + ' (' + sublabel + ')'
|
|
692
1933
|
const hostType = n.hostType
|
|
693
1934
|
|
|
694
|
-
|
|
695
|
-
|
|
1935
|
+
parentServiceOptions.push({
|
|
1936
|
+
text,
|
|
1937
|
+
element: $('<option></option>')
|
|
696
1938
|
.val(value)
|
|
697
1939
|
.text(text)
|
|
698
|
-
.attr("hostType", hostType)
|
|
699
|
-
)
|
|
1940
|
+
.attr("hostType", hostType)
|
|
1941
|
+
})
|
|
700
1942
|
})
|
|
701
1943
|
|
|
1944
|
+
sortSelectOptionsByLabel(selectParentService, parentServiceOptions)
|
|
1945
|
+
|
|
702
1946
|
selectParentService
|
|
703
1947
|
.find('option')
|
|
704
1948
|
.filter(function () {
|
|
@@ -766,13 +2010,49 @@ if (nrchkbExperimental) {
|
|
|
766
2010
|
const selectedParentService = $('#node-input-parentService option:selected')
|
|
767
2011
|
node.parentService = selectedParentService.val()
|
|
768
2012
|
node.hostType = selectedParentService.attr("hostType")
|
|
2013
|
+
node.bridge = ''
|
|
2014
|
+
node.accessoryId = ''
|
|
2015
|
+
$('#node-input-bridge').val('')
|
|
2016
|
+
$('#node-input-accessoryId').val('')
|
|
2017
|
+
} else if (node.hostType === '0') {
|
|
2018
|
+
node.accessoryId = ''
|
|
2019
|
+
node.parentService = ''
|
|
2020
|
+
$('#node-input-accessoryId').val('')
|
|
2021
|
+
$('#node-input-parentService').val('')
|
|
2022
|
+
} else if (node.hostType === '1') {
|
|
2023
|
+
node.bridge = ''
|
|
2024
|
+
node.parentService = ''
|
|
2025
|
+
$('#node-input-bridge').val('')
|
|
2026
|
+
$('#node-input-parentService').val('')
|
|
769
2027
|
}
|
|
770
2028
|
|
|
771
2029
|
node.serviceName = $(
|
|
772
2030
|
'#node-input-serviceName option:selected',
|
|
773
2031
|
).val()
|
|
2032
|
+
node.outputMode = $('#node-input-outputMode').val() || 'events'
|
|
2033
|
+
try {
|
|
2034
|
+
const plugins = JSON.parse($('#node-input-plugins').val() || '[]')
|
|
2035
|
+
node.plugins = Array.isArray(plugins) ? plugins : []
|
|
2036
|
+
} catch (_) {
|
|
2037
|
+
node.plugins = []
|
|
2038
|
+
}
|
|
2039
|
+
node.plugin1 = $('#node-input-plugin1').val() || ''
|
|
2040
|
+
node.plugin2 = $('#node-input-plugin2').val() || ''
|
|
2041
|
+
node.plugin3 = $('#node-input-plugin3').val() || ''
|
|
2042
|
+
node.plugin4 = $('#node-input-plugin4').val() || ''
|
|
2043
|
+
node.plugin5 = $('#node-input-plugin5').val() || ''
|
|
2044
|
+
node.plugin6 = $('#node-input-plugin6').val() || ''
|
|
2045
|
+
node.plugin7 = $('#node-input-plugin7').val() || ''
|
|
2046
|
+
node.plugin8 = $('#node-input-plugin8').val() || ''
|
|
2047
|
+
node.pluginSetupComplete = true
|
|
2048
|
+
node.outputs =
|
|
2049
|
+
node.outputMode === 'legacy'
|
|
2050
|
+
? node.serviceName === 'Camera'
|
|
2051
|
+
? 3
|
|
2052
|
+
: 2
|
|
2053
|
+
: node.serviceName === 'Camera'
|
|
2054
|
+
? 2
|
|
2055
|
+
: 1
|
|
774
2056
|
},
|
|
775
2057
|
})
|
|
776
|
-
}
|
|
777
2058
|
</script>
|
|
778
|
-
|