node-red-contrib-room-tools 1.9.0 → 1.9.3
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 +172 -29
- package/README.md +8 -0
- package/control-catalog.js +5 -2
- package/control-panel.js +37 -5
- package/control-policy.js +13 -1
- package/control-type-contract.json +1 -0
- package/control-type-model.js +8 -3
- package/gateway.cjs +10 -4
- package/managed-native-bridge.js +2 -1
- package/native-binding-plan.js +61 -7
- package/npm-shrinkwrap.json +2 -2
- package/package.json +8 -1
- package/panel-binding-editor.js +140 -9
- package/panel-button-press.js +46 -0
- package/panel-momentary-client.js +91 -0
- package/panel-momentary.js +93 -0
- package/panel-template-instrumentation.js +21 -1
- package/panel-visual-bindings.js +26 -2
- 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
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
function finite (value) {
|
|
2
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function createXYSeriesReceiver (manifest) {
|
|
6
|
+
if (manifest?.kind !== 'xy-series-manifest' || typeof manifest.streamId !== 'string' ||
|
|
7
|
+
!manifest.streamId.trim() || manifest.streamId.length > 255 ||
|
|
8
|
+
typeof manifest.epoch !== 'string' || !manifest.epoch || manifest.epoch.length > 128 ||
|
|
9
|
+
!Number.isInteger(manifest.layoutVersion) || manifest.layoutVersion < 1 ||
|
|
10
|
+
manifest.layoutVersion > 2147483647 || manifest.clock !== 'monotonic-ms' ||
|
|
11
|
+
!finite(manifest.publishHz) || manifest.publishHz < 1 || manifest.publishHz > 60 ||
|
|
12
|
+
!Number.isSafeInteger(manifest.maxPoints) || manifest.maxPoints < 1 || manifest.maxPoints > 8192 ||
|
|
13
|
+
typeof manifest.xUnit !== 'string' || manifest.xUnit.length > 32 ||
|
|
14
|
+
typeof manifest.yUnit !== 'string' || manifest.yUnit.length > 32) {
|
|
15
|
+
throw Error('Invalid xy-series manifest')
|
|
16
|
+
}
|
|
17
|
+
const schema = Object.freeze({ ...manifest })
|
|
18
|
+
let latest = null, pending = false, closed = false
|
|
19
|
+
return {
|
|
20
|
+
manifest: schema,
|
|
21
|
+
accept (frame) {
|
|
22
|
+
if (closed || frame?.kind !== 'xy-series-frame' || frame.streamId !== schema.streamId ||
|
|
23
|
+
frame.epoch !== schema.epoch || frame.layoutVersion !== schema.layoutVersion ||
|
|
24
|
+
!Number.isSafeInteger(frame.seq) || frame.seq < 1 || (latest && frame.seq <= latest.seq) ||
|
|
25
|
+
!finite(frame.sampleTime) || frame.sampleTime < 0 || !Array.isArray(frame.points) ||
|
|
26
|
+
frame.points.length > schema.maxPoints || frame.points.some(point =>
|
|
27
|
+
!Array.isArray(point) || point.length !== 2 || !finite(point[0]) || !finite(point[1]))) return false
|
|
28
|
+
latest = Object.freeze({ kind: 'xy-series-frame', streamId: schema.streamId,
|
|
29
|
+
epoch: schema.epoch, seq: frame.seq, layoutVersion: schema.layoutVersion,
|
|
30
|
+
sampleTime: frame.sampleTime,
|
|
31
|
+
points: Object.freeze(frame.points.map(point => Object.freeze([point[0], point[1]]))) })
|
|
32
|
+
pending = true
|
|
33
|
+
return true
|
|
34
|
+
},
|
|
35
|
+
snapshot () { return latest },
|
|
36
|
+
take () { if (!pending) return null; pending = false; return latest },
|
|
37
|
+
close () { closed = true; pending = false; latest = null }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function createXYSeriesRenderer ({ receiver, requestFrame, cancelFrame, apply }) {
|
|
42
|
+
let handle = null, closed = false
|
|
43
|
+
return {
|
|
44
|
+
receive (frame) {
|
|
45
|
+
if (closed || !receiver.accept(frame)) return false
|
|
46
|
+
if (handle === null) {
|
|
47
|
+
handle = requestFrame(() => {
|
|
48
|
+
handle = null
|
|
49
|
+
const current = receiver.take()
|
|
50
|
+
if (!closed && current) apply(current)
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
return true
|
|
54
|
+
},
|
|
55
|
+
close () {
|
|
56
|
+
closed = true
|
|
57
|
+
if (handle !== null) cancelFrame(handle)
|
|
58
|
+
handle = null
|
|
59
|
+
receiver.close()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function createRoomXYSeriesReceiver (layout) {
|
|
65
|
+
const finite = value => typeof value === 'number' && Number.isFinite(value)
|
|
66
|
+
if (layout?.kind !== 'room-xy-series-layout' || typeof layout.panelId !== 'string' ||
|
|
67
|
+
typeof layout.room !== 'string' || typeof layout.epoch !== 'string' || !layout.epoch ||
|
|
68
|
+
!Array.isArray(layout.ids) || !layout.ids.length || layout.ids.length > 256 ||
|
|
69
|
+
layout.ids.some(id => typeof id !== 'string' || !id) || new Set(layout.ids).size !== layout.ids.length) {
|
|
70
|
+
throw Error('Invalid Room XY-series layout')
|
|
71
|
+
}
|
|
72
|
+
let latest = null, pending = false, closed = false
|
|
73
|
+
const count = layout.ids.length
|
|
74
|
+
return {
|
|
75
|
+
accept (frame) {
|
|
76
|
+
if (closed || frame?.kind !== 'room-xy-series-frame' || frame.panelId !== layout.panelId ||
|
|
77
|
+
frame.room !== layout.room || frame.epoch !== layout.epoch ||
|
|
78
|
+
!Number.isSafeInteger(frame.seq) || frame.seq < 1 || (latest && frame.seq <= latest.seq) ||
|
|
79
|
+
!Array.isArray(frame.values) || frame.values.length !== count ||
|
|
80
|
+
!Array.isArray(frame.valid) || frame.valid.length !== count) return false
|
|
81
|
+
for (let index = 0; index < count; index++) {
|
|
82
|
+
const points = frame.values[index], valid = frame.valid[index]
|
|
83
|
+
if (typeof valid !== 'boolean' || !Array.isArray(points) || points.length > 8192 ||
|
|
84
|
+
(!valid && points.length !== 0) ||
|
|
85
|
+
(valid && points.some(point => !Array.isArray(point) || point.length !== 2 ||
|
|
86
|
+
!finite(point[0]) || !finite(point[1])))) return false
|
|
87
|
+
}
|
|
88
|
+
latest = { values: frame.values.map(points => points.map(point => point.slice())),
|
|
89
|
+
valid: frame.valid.slice(), seq: frame.seq }
|
|
90
|
+
pending = true
|
|
91
|
+
return true
|
|
92
|
+
},
|
|
93
|
+
take () { if (!pending) return null; pending = false; return latest },
|
|
94
|
+
close () { closed = true; latest = null; pending = false }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = { createXYSeriesReceiver, createXYSeriesRenderer, createRoomXYSeriesReceiver }
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
function xySeriesRoute (value) {
|
|
2
|
+
if (!value || typeof value !== 'object' ||
|
|
3
|
+
typeof value.streamId !== 'string' || !value.streamId.trim() || value.streamId.length > 255 ||
|
|
4
|
+
![value.eventPath, value.manifestPath].every(path => typeof path === 'string' &&
|
|
5
|
+
/^\/\/[^/]/.test(path) && path.length <= 1026) || value.eventPath === value.manifestPath) {
|
|
6
|
+
throw Error('Invalid xy-series delivery metadata')
|
|
7
|
+
}
|
|
8
|
+
return { streamId: value.streamId, eventPath: value.eventPath, manifestPath: value.manifestPath }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = { xySeriesRoute }
|