node-red-contrib-room-tools 1.8.16 → 1.9.1
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/CONTROL_PANEL_AI_GUIDE.md +250 -109
- package/README.md +3 -1
- package/control-catalog.js +5 -2
- package/control-panel.js +24 -4
- package/control-policy.js +8 -0
- package/control-type-contract.json +1 -0
- package/control-type-model.js +8 -3
- package/managed-native-bridge.js +2 -1
- package/native-binding-plan.js +61 -7
- package/npm-shrinkwrap.json +2 -2
- package/package.json +8 -2
- package/panel-binding-editor.js +193 -34
- package/panel-meter.js +2 -1
- package/panel-template-instrumentation.js +144 -33
- package/panel-visual-bindings.js +33 -9
- package/panel-xy-series-client.js +84 -0
- package/panel-xy-series.js +186 -0
- package/xy-series-frame.js +98 -0
- package/xy-series-route.js +11 -0
package/control-panel.js
CHANGED
|
@@ -11,6 +11,7 @@ const { applyVisualBindings, editorConfig, getSnapshot } = require('./panel-visu
|
|
|
11
11
|
const { createGateway } = require('./gateway.cjs')
|
|
12
12
|
const { managedBridgeRevision } = require('./managed-bridge-revision')
|
|
13
13
|
const { createMeterRoom, createPanelMeter } = require('./panel-meter')
|
|
14
|
+
const { createXYSeriesRoom, createPanelXYSeries } = require('./panel-xy-series')
|
|
14
15
|
|
|
15
16
|
let statestore
|
|
16
17
|
const authHandoffKey = crypto.randomBytes(32)
|
|
@@ -71,6 +72,7 @@ module.exports = function (RED) {
|
|
|
71
72
|
const activePanels = new Map()
|
|
72
73
|
const activeEditors = new Map()
|
|
73
74
|
const meterRooms = new Map()
|
|
75
|
+
const xySeriesRooms = new Map()
|
|
74
76
|
RED.httpAdmin.get('/ui-control-panel/editor-sync.js', RED.auth.needsPermission('nodes.read'), (_req, res) => {
|
|
75
77
|
res.setHeader('Cache-Control', 'no-store')
|
|
76
78
|
res.sendFile(path.join(__dirname, 'panel-flow-editor-sync.js'))
|
|
@@ -159,6 +161,16 @@ module.exports = function (RED) {
|
|
|
159
161
|
} catch (error) { node.error(error) }
|
|
160
162
|
}
|
|
161
163
|
|
|
164
|
+
let xySeries
|
|
165
|
+
if ([...routes.values()].some(route => route.binding.readMeta?.xySeries) && /^[a-zA-Z0-9_-]+$/.test(boundRoom || '')) {
|
|
166
|
+
try {
|
|
167
|
+
let hub = xySeriesRooms.get(boundRoom)
|
|
168
|
+
if (!hub) { hub = createXYSeriesRoom({ room: boundRoom }); xySeriesRooms.set(boundRoom, hub) }
|
|
169
|
+
xySeries = createPanelXYSeries({ panelId: node.id, room: boundRoom, bindings: visualBindings, hub,
|
|
170
|
+
onSubscribe: () => { if (bridgeReady) node.send({ kind: 'read-all', room: boundRoom }) } })
|
|
171
|
+
} catch (error) { node.error(error) }
|
|
172
|
+
}
|
|
173
|
+
|
|
162
174
|
let gateway
|
|
163
175
|
let gatewayAuthKey
|
|
164
176
|
if (visualBinding && /^[a-zA-Z0-9_-]+$/.test(boundRoom || '')) {
|
|
@@ -173,7 +185,8 @@ module.exports = function (RED) {
|
|
|
173
185
|
secureCookie: config.secureCookie === true,
|
|
174
186
|
restoredSessions: takeSessions(gatewayHandoffs, node.id, gatewayAuthKey),
|
|
175
187
|
onAuthenticatedRequest: (req, res, identity) => meter?.ticket(req, res, identity,
|
|
176
|
-
() => gateway?.sessionActive(identity.token)) ||
|
|
188
|
+
() => gateway?.sessionActive(identity.token)) || xySeries?.ticket(req, res, identity,
|
|
189
|
+
() => gateway?.sessionActive(identity.token)) || false,
|
|
177
190
|
onBoundCommand: (message, identity) => {
|
|
178
191
|
if (!bridgeReady) return false
|
|
179
192
|
node.send({ ...resolveBoundCommand(routes, message), room: identity.room, user: identity.user })
|
|
@@ -203,7 +216,8 @@ module.exports = function (RED) {
|
|
|
203
216
|
secureCookie: config.secureCookie === true,
|
|
204
217
|
restoredSessions: takeSessions(editorHandoffs, node.id, editorAuthKey),
|
|
205
218
|
onAuthenticatedRequest: (req, res, identity) => meter?.ticket(req, res, identity,
|
|
206
|
-
() => editor?.sessionActive(identity.token)) ||
|
|
219
|
+
() => editor?.sessionActive(identity.token)) || xySeries?.ticket(req, res, identity,
|
|
220
|
+
() => editor?.sessionActive(identity.token)) || false,
|
|
207
221
|
getConfig: async req => editorConfig(await getSnapshot(req.socket.localPort, fetch,
|
|
208
222
|
RED.settings.httpAdminRoot ?? '/'), node.id),
|
|
209
223
|
getDevices: async () => {
|
|
@@ -230,7 +244,7 @@ module.exports = function (RED) {
|
|
|
230
244
|
}
|
|
231
245
|
|
|
232
246
|
const panelConfig = () => (boundMode || visualBinding) ? {
|
|
233
|
-
endpoint, room: boundRoom, meter: Boolean(meter),
|
|
247
|
+
endpoint, room: boundRoom, meter: Boolean(meter), xySeries: Boolean(xySeries),
|
|
234
248
|
...(visualBinding ? { visual: {
|
|
235
249
|
panelId: node.id,
|
|
236
250
|
editorEndpoint: `/control-panel-edit/panels/${node.id}`,
|
|
@@ -241,7 +255,7 @@ module.exports = function (RED) {
|
|
|
241
255
|
|
|
242
256
|
const events = {
|
|
243
257
|
onAction: !boundMode,
|
|
244
|
-
...(meter ? { onSocket: meter
|
|
258
|
+
...((meter || xySeries) ? { onSocket: { ...(meter?.hooks || {}), ...(xySeries?.hooks || {}) } } : {}),
|
|
245
259
|
beforeSend (msg) {
|
|
246
260
|
if (msg?._controlPanel?.kind === 'command') throw new Error('Bound commands require authenticated HTTP')
|
|
247
261
|
if (['device-meter-frame', 'device-meter-manifest'].includes(msg?.kind)) {
|
|
@@ -250,6 +264,10 @@ module.exports = function (RED) {
|
|
|
250
264
|
// should be emitted. `false` is property-safe and suppresses emit.
|
|
251
265
|
return false
|
|
252
266
|
}
|
|
267
|
+
if (['device-xy-series-frame', 'device-xy-series-manifest'].includes(msg?.kind)) {
|
|
268
|
+
if (bridgeReady) xySeries?.accept(msg)
|
|
269
|
+
return false
|
|
270
|
+
}
|
|
253
271
|
if (boundMode) {
|
|
254
272
|
if (msg?.kind === 'device-feedback' && gateway) {
|
|
255
273
|
gateway.boundFeedback(resolveBoundFeedback(routes, msg))
|
|
@@ -325,7 +343,9 @@ module.exports = function (RED) {
|
|
|
325
343
|
gateway?.close()
|
|
326
344
|
editor?.close()
|
|
327
345
|
meter?.close()
|
|
346
|
+
xySeries?.close()
|
|
328
347
|
if (meterRooms.get(boundRoom)?.stats().panels === 0) meterRooms.delete(boundRoom)
|
|
348
|
+
if (xySeriesRooms.get(boundRoom)?.stats().panels === 0) xySeriesRooms.delete(boundRoom)
|
|
329
349
|
if (activePanels.get(node.id) === gateway) activePanels.delete(node.id)
|
|
330
350
|
if (activeEditors.get(node.id) === editor) activeEditors.delete(node.id)
|
|
331
351
|
})
|
package/control-policy.js
CHANGED
|
@@ -5,6 +5,8 @@ const { fetchWithTimeout } = require('./panel-http')
|
|
|
5
5
|
const { createTargetMatcher } = require('./control-type-model')
|
|
6
6
|
const { createRoomMeterReceiver, createMeterRenderer } = require('./meter-frame')
|
|
7
7
|
const { createPanelMeterClient } = require('./panel-meter-client')
|
|
8
|
+
const { createRoomXYSeriesReceiver, createXYSeriesRenderer } = require('./xy-series-frame')
|
|
9
|
+
const { createPanelXYSeriesClient } = require('./panel-xy-series-client')
|
|
8
10
|
|
|
9
11
|
const CONTROL_POLICY_MARKER = '/* ui-control-panel-control-api-v1 */'
|
|
10
12
|
|
|
@@ -164,6 +166,11 @@ function buildDashboardPolicyOptionsSource (controls = [], boundPanel = null) {
|
|
|
164
166
|
__createMeterRenderer: ${createMeterRenderer.toString()},
|
|
165
167
|
__createPanelMeterClient: ${createPanelMeterClient.toString()},`
|
|
166
168
|
: ''
|
|
169
|
+
const xySeriesMethods = boundPanel?.xySeries
|
|
170
|
+
? `__createXYSeriesReceiver: ${createRoomXYSeriesReceiver.toString()},
|
|
171
|
+
__createXYSeriesRenderer: ${createXYSeriesRenderer.toString()},
|
|
172
|
+
__createPanelXYSeriesClient: ${createPanelXYSeriesClient.toString()},`
|
|
173
|
+
: ''
|
|
167
174
|
const visualMethods = boundPanel?.visual
|
|
168
175
|
? `__createTargetMatcher: ${createTargetMatcher.toString()},
|
|
169
176
|
__initializeVisualBinding: ${initializeVisualBinding.toString()},
|
|
@@ -188,6 +195,7 @@ export default {
|
|
|
188
195
|
},
|
|
189
196
|
methods: {
|
|
190
197
|
${meterMethods}
|
|
198
|
+
${xySeriesMethods}
|
|
191
199
|
${visualMethods}
|
|
192
200
|
__panelFetch: ${fetchWithTimeout.toString()},
|
|
193
201
|
__boundPanelConfig () {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
"level": { "family": "number", "delivery": "continuous" },
|
|
6
6
|
"volume": { "family": "number", "delivery": "continuous" },
|
|
7
7
|
"meter": { "family": "number", "delivery": "realtime", "access": "ro" },
|
|
8
|
+
"xy-series": { "family": "xy-series", "delivery": "realtime", "access": "ro" },
|
|
8
9
|
"dimmer": { "family": "number", "delivery": "continuous" },
|
|
9
10
|
"position": { "family": "number", "delivery": "continuous" },
|
|
10
11
|
"toggle": { "family": "boolean", "delivery": "discrete" },
|
package/control-type-model.js
CHANGED
|
@@ -6,10 +6,11 @@ const { meterRoute } = require('./meter-route')
|
|
|
6
6
|
// Used by both server validation and the injected editor; keep it self-contained.
|
|
7
7
|
function createTargetMatcher (typeContract) {
|
|
8
8
|
const families = {
|
|
9
|
-
range: ['number'], switch: ['boolean'], meter: ['number'],
|
|
9
|
+
range: ['number'], switch: ['boolean'], meter: ['number'], 'xy-series': ['xy-series'],
|
|
10
10
|
'text-display': ['text', 'number', 'enum', 'boolean'], 'text-input': ['text'], select: ['enum']
|
|
11
11
|
}
|
|
12
12
|
function family (target) {
|
|
13
|
+
if (target.xySeries) return 'xy-series'
|
|
13
14
|
if (target.controlSchemaVersion !== typeContract.schemaVersion) return null
|
|
14
15
|
const definition = typeContract.types[String(target.controlType || '').toLowerCase()]
|
|
15
16
|
if (!definition) return null
|
|
@@ -24,9 +25,11 @@ function createTargetMatcher (typeContract) {
|
|
|
24
25
|
}
|
|
25
26
|
function compatible (adapter, target, direction, candidate = {}) {
|
|
26
27
|
if (adapter === 'meter' && !target.meter) return false
|
|
28
|
+
if (adapter === 'xy-series' && !target.xySeries) return false
|
|
27
29
|
if (target.meter && (adapter !== 'meter' || direction !== 'read' || target.access !== 'ro')) return false
|
|
30
|
+
if (target.xySeries && (adapter !== 'xy-series' || direction !== 'read' || target.access !== 'ro')) return false
|
|
28
31
|
if (target.kind !== 'param' || !families[adapter]?.includes(family(target))) return false
|
|
29
|
-
if (direction === 'write' && ['meter', 'text-display'].includes(adapter)) return false
|
|
32
|
+
if (direction === 'write' && ['meter', 'xy-series', 'text-display'].includes(adapter)) return false
|
|
30
33
|
if (!['read', 'write'].includes(direction)) return false
|
|
31
34
|
if (!(direction === 'write' ? ['wo', 'rw'] : ['ro', 'rw']).includes(target.access)) return false
|
|
32
35
|
if (candidate.unit && candidate.unit !== target.unit) return false
|
|
@@ -42,6 +45,7 @@ function controlTypeForAdapter (adapter, target) {
|
|
|
42
45
|
throw new Error('Target is incompatible with this control')
|
|
43
46
|
}
|
|
44
47
|
if (adapter === 'meter' && target.meter) return 'meter'
|
|
48
|
+
if (adapter === 'xy-series' && target.xySeries) return 'xy-series'
|
|
45
49
|
const family = targetFamily(target)
|
|
46
50
|
if (family === 'number') return 'level'
|
|
47
51
|
if (family === 'boolean') return 'mute'
|
|
@@ -60,7 +64,8 @@ function targetMeta (target, onebased) {
|
|
|
60
64
|
min: target.min, max: target.max, enums: target.enums,
|
|
61
65
|
...(target.booleanEncoding ? { booleanEncoding: target.booleanEncoding } : {}),
|
|
62
66
|
controlType: target.controlType, controlSchemaVersion: target.controlSchemaVersion,
|
|
63
|
-
...(target.meter ? { meter: meterRoute(target.meter), unit: target.unit || '' } : {})
|
|
67
|
+
...(target.meter ? { meter: meterRoute(target.meter), unit: target.unit || '' } : {}),
|
|
68
|
+
...(target.xySeries ? { xySeries: require('./xy-series-route').xySeriesRoute(target.xySeries) } : {})
|
|
64
69
|
}
|
|
65
70
|
}
|
|
66
71
|
|
package/managed-native-bridge.js
CHANGED
|
@@ -25,7 +25,8 @@ function panelTargets (panel) {
|
|
|
25
25
|
device: binding.device, path, kind: meta.kind, type: meta.type,
|
|
26
26
|
access: meta.access, onebased: meta.onebased,
|
|
27
27
|
...(meta.booleanEncoding ? { booleanEncoding: meta.booleanEncoding } : {}),
|
|
28
|
-
...(meta.meter ? { meter: meta.meter } : {})
|
|
28
|
+
...(meta.meter ? { meter: meta.meter } : {}),
|
|
29
|
+
...(meta.xySeries ? { xySeries: meta.xySeries } : {})
|
|
29
30
|
}
|
|
30
31
|
const key = JSON.stringify([target.device, target.path])
|
|
31
32
|
const previous = targets.get(key)
|
package/native-binding-plan.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { configuredRoutes } = require('./control-bound-protocol')
|
|
2
2
|
const { meterRoute } = require('./meter-route')
|
|
3
|
+
const { xySeriesRoute } = require('./xy-series-route')
|
|
3
4
|
|
|
4
5
|
const supportedTypes = {
|
|
5
6
|
boolean: { eventType: 'Boolean', eventOptions: { boolParamFilter: 'Any' } },
|
|
@@ -30,6 +31,7 @@ function compileNativeBindingPlan ({ panel, targets, scopeId }) {
|
|
|
30
31
|
const readTargets = new Map()
|
|
31
32
|
const writeTargets = new Map()
|
|
32
33
|
const meterStreams = new Map()
|
|
34
|
+
const xySeriesStreams = new Map()
|
|
33
35
|
const commandRules = []
|
|
34
36
|
const commandWires = []
|
|
35
37
|
|
|
@@ -37,7 +39,7 @@ function compileNativeBindingPlan ({ panel, targets, scopeId }) {
|
|
|
37
39
|
const target = targetIndex.get(JSON.stringify([device, path]))
|
|
38
40
|
if (!target) throw new Error(`Missing target descriptor: ${device} ${path}`)
|
|
39
41
|
if (target.kind !== 'param') throw new Error(`Only parameter targets are supported: ${path}`)
|
|
40
|
-
if (!Object.hasOwn(supportedTypes, target.type)) {
|
|
42
|
+
if (!target.xySeries && !Object.hasOwn(supportedTypes, target.type)) {
|
|
41
43
|
throw new Error(`Unsupported native parameter type: ${path} (${target.type})`)
|
|
42
44
|
}
|
|
43
45
|
if (['mute', 'power'].includes(type) && target.type !== 'boolean' &&
|
|
@@ -56,7 +58,10 @@ function compileNativeBindingPlan ({ panel, targets, scopeId }) {
|
|
|
56
58
|
if (type === 'meter' && (!target.meter || !['integer', 'float'].includes(target.type) || target.access !== 'ro')) {
|
|
57
59
|
throw new Error(`Meter control must bind to a readonly Meter target: ${path}`)
|
|
58
60
|
}
|
|
59
|
-
if (
|
|
61
|
+
if (type === 'xy-series' && (!target.xySeries || target.access !== 'ro')) {
|
|
62
|
+
throw new Error(`XY-series control must bind to a readonly XY-series target: ${path}`)
|
|
63
|
+
}
|
|
64
|
+
if (!['mute', 'power', 'level', 'dimmer', 'position', 'meter', 'xy-series', 'text', 'source', 'preset'].includes(type)) {
|
|
60
65
|
throw new Error(`Control type requires a separate command adapter: ${type}`)
|
|
61
66
|
}
|
|
62
67
|
if (!['ro', 'wo', 'rw'].includes(target.access) || !target.access.includes(access)) {
|
|
@@ -134,6 +139,49 @@ function compileNativeBindingPlan ({ panel, targets, scopeId }) {
|
|
|
134
139
|
}
|
|
135
140
|
continue
|
|
136
141
|
}
|
|
142
|
+
if (target.xySeries) {
|
|
143
|
+
const series = xySeriesRoute(target.xySeries)
|
|
144
|
+
if (control.mode !== 'read' || control.adapter !== 'xy-series') {
|
|
145
|
+
throw Error(`XY-series is read-only: ${id}`)
|
|
146
|
+
}
|
|
147
|
+
const key = JSON.stringify([target.device, series.streamId])
|
|
148
|
+
const previous = xySeriesStreams.get(key)
|
|
149
|
+
if (previous) {
|
|
150
|
+
if (JSON.stringify(previous.series) !== JSON.stringify(series) || previous.onebased !== target.onebased) {
|
|
151
|
+
throw Error(`Conflicting xy-series stream: ${series.streamId}`)
|
|
152
|
+
}
|
|
153
|
+
continue
|
|
154
|
+
}
|
|
155
|
+
const index = xySeriesStreams.size + 1
|
|
156
|
+
const statusId = `${scopeId}-xy-series-read-${index}`
|
|
157
|
+
const tagIds = []
|
|
158
|
+
xySeriesStreams.set(key, { series, onebased: target.onebased, statusId, tagIds })
|
|
159
|
+
for (const [role, path] of [['manifest', series.manifestPath], ['frame', series.eventPath]]) {
|
|
160
|
+
const eventId = `${scopeId}-xy-series-${role}-${index}`
|
|
161
|
+
const tagId = `${eventId}-tag`
|
|
162
|
+
tagIds.push(tagId)
|
|
163
|
+
const y = 540 + (index - 1) * 280 + (role === 'frame' ? 140 : 0)
|
|
164
|
+
if (role === 'manifest') nodes.push({
|
|
165
|
+
id: statusId, type: 'Status', z: subflowId, name: `Read xy-series manifest ${target.device}`,
|
|
166
|
+
x: 440, y: y - 60, mojo: { version: '1.0', device: [target.device],
|
|
167
|
+
parameter: path, onebased: target.onebased }, wires: [[tagId]]
|
|
168
|
+
})
|
|
169
|
+
nodes.push({ id: eventId, type: 'Event', z: subflowId,
|
|
170
|
+
name: `Watch xy-series ${role} ${target.device} ${series.streamId}`, x: 440, y,
|
|
171
|
+
mojo: { version: '1.0', device: [target.device], event: path,
|
|
172
|
+
eventKind: role === 'frame' ? 'Event' : 'Parameter', paramType: 'String',
|
|
173
|
+
onebased: target.onebased }, wires: [[tagId]] })
|
|
174
|
+
nodes.push({ id: tagId, type: 'change', z: subflowId,
|
|
175
|
+
name: `Identify xy-series ${role} ${target.device}`, x: 1000, y,
|
|
176
|
+
rules: [
|
|
177
|
+
{ t: 'set', p: 'kind', pt: 'msg', to: 'device-xy-series-' + role, tot: 'str' },
|
|
178
|
+
{ t: 'set', p: 'device', pt: 'msg', to: target.device, tot: 'str' },
|
|
179
|
+
{ t: 'set', p: 'path', pt: 'msg', to: path, tot: 'str' },
|
|
180
|
+
{ t: 'set', p: 'streamId', pt: 'msg', to: series.streamId, tot: 'str' }
|
|
181
|
+
], wires: [[]] })
|
|
182
|
+
}
|
|
183
|
+
continue
|
|
184
|
+
}
|
|
137
185
|
const key = JSON.stringify([target.device, target.path])
|
|
138
186
|
if (readTargets.has(key)) continue
|
|
139
187
|
const index = readTargets.size + 1
|
|
@@ -175,7 +223,11 @@ function compileNativeBindingPlan ({ panel, targets, scopeId }) {
|
|
|
175
223
|
|
|
176
224
|
const commandSwitchId = `${scopeId}-commands`
|
|
177
225
|
// Meter lanes follow ordinary feedback lanes without overlapping their labels.
|
|
178
|
-
for (const node of nodes)
|
|
226
|
+
for (const node of nodes) {
|
|
227
|
+
if (node.id.startsWith(`${scopeId}-meter-`) || node.id.startsWith(`${scopeId}-xy-series-`)) {
|
|
228
|
+
node.y += readTargets.size * 170
|
|
229
|
+
}
|
|
230
|
+
}
|
|
179
231
|
const routerId = `${scopeId}-router`
|
|
180
232
|
if (commandRules.length) {
|
|
181
233
|
nodes.push({
|
|
@@ -196,17 +248,18 @@ function compileNativeBindingPlan ({ panel, targets, scopeId }) {
|
|
|
196
248
|
],
|
|
197
249
|
checkall: 'false', repair: false, outputs: 2,
|
|
198
250
|
wires: [commandRules.length ? [commandSwitchId] : [],
|
|
199
|
-
[...readTargets.values(), ...meterStreams.values()].map(target => target.statusId)]
|
|
251
|
+
[...readTargets.values(), ...meterStreams.values(), ...xySeriesStreams.values()].map(target => target.statusId)]
|
|
200
252
|
})
|
|
201
253
|
const subflow = {
|
|
202
254
|
id: subflowId, type: 'subflow', name: `Binding bridge ${scopeId}`,
|
|
203
255
|
info: 'Generated panel binding bridge. Requires a Controller node and authenticated command source.',
|
|
204
256
|
category: 'panel binding',
|
|
205
257
|
in: [{ x: 170, y: 150, wires: [{ id: routerId }] }],
|
|
206
|
-
out: readTargets.size || meterStreams.size
|
|
258
|
+
out: readTargets.size || meterStreams.size || xySeriesStreams.size
|
|
207
259
|
? [{ x: meterStreams.size ? 1600 : 1290, y: Math.round((390 + (readTargets.size + meterStreams.size + 1) * 85) / 10) * 10,
|
|
208
260
|
wires: [...readTargets.values()].map(target => ({ id: target.tagId, port: 0 }))
|
|
209
|
-
.concat([...meterStreams.values()].flatMap(target => target.tagIds.map(id => ({ id, port: 0 }))))
|
|
261
|
+
.concat([...meterStreams.values()].flatMap(target => target.tagIds.map(id => ({ id, port: 0 }))))
|
|
262
|
+
.concat([...xySeriesStreams.values()].flatMap(target => target.tagIds.map(id => ({ id, port: 0 })))) }]
|
|
210
263
|
: [],
|
|
211
264
|
env: [], meta: {}, color: '#96bca7'
|
|
212
265
|
}
|
|
@@ -215,8 +268,9 @@ function compileNativeBindingPlan ({ panel, targets, scopeId }) {
|
|
|
215
268
|
manifest: {
|
|
216
269
|
subflowId,
|
|
217
270
|
controls: routes.size,
|
|
218
|
-
nativeReads: readTargets.size + meterStreams.size,
|
|
271
|
+
nativeReads: readTargets.size + meterStreams.size + xySeriesStreams.size,
|
|
219
272
|
nativeMeterStreams: meterStreams.size,
|
|
273
|
+
nativeXYSeriesStreams: xySeriesStreams.size,
|
|
220
274
|
nativeWrites: writeTargets.size,
|
|
221
275
|
requiresController: true,
|
|
222
276
|
initialReadMessage: { kind: 'read-all' }
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-room-tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"lockfileVersion": 2,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "node-red-contrib-room-tools",
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.9.1",
|
|
10
10
|
"license": "UNLICENSED",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@vue/compiler-sfc": "3.5.42",
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-room-tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"description": "Authenticated visual control panel binding for Node-RED and MUSE Automator",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
|
-
"engines": {
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=18.16.0"
|
|
8
|
+
},
|
|
7
9
|
"keywords": [
|
|
8
10
|
"node-red"
|
|
9
11
|
],
|
|
@@ -25,6 +27,10 @@
|
|
|
25
27
|
"meter-frame.js",
|
|
26
28
|
"panel-meter.js",
|
|
27
29
|
"panel-meter-client.js",
|
|
30
|
+
"xy-series-route.js",
|
|
31
|
+
"xy-series-frame.js",
|
|
32
|
+
"panel-xy-series.js",
|
|
33
|
+
"panel-xy-series-client.js",
|
|
28
34
|
"panel-template-instrumentation.js",
|
|
29
35
|
"panel-visual-bindings.js",
|
|
30
36
|
"panel-visual-apply.js",
|