node-red-contrib-room-tools 1.9.4 → 1.9.5

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.
@@ -1,5 +1,5 @@
1
1
  const { inspectTemplate } = require('./panel-template-instrumentation')
2
- const { compatibleTarget, controlTypeForAdapter, targetMeta, bindingModes } = require('./control-type-model')
2
+ const { compatibleTarget, controlTypeForAdapter, targetMeta, bindingModes, switchMapping, stateReverse, isMeterTarget } = require('./control-type-model')
3
3
  const { meterRoute } = require('./meter-route')
4
4
  const { xySeriesRoute } = require('./xy-series-route')
5
5
 
@@ -58,6 +58,16 @@ function visualRoutes (config) {
58
58
  if (!bindingModes(entry.adapter).includes(entry.mode) || !entry.type || !entry.label) {
59
59
  throw new Error(`Invalid visual binding settings: ${id}`)
60
60
  }
61
+ if (entry.adapter === 'meter' && (entry.type !== 'meter' || entry.mode !== 'read' ||
62
+ !entry.readPath || entry.writePath || entry.writeMeta || !entry.readMeta || !isMeterTarget(entry.readMeta))) {
63
+ throw Error(`Invalid readonly meter binding: ${id}`)
64
+ }
65
+ if (entry.adapter === 'range' && [entry.writeMeta, entry.readMeta].some(meta => meta && isMeterTarget(meta))) {
66
+ throw Error(`Numeric controls cannot bind to readonly meter targets: ${id}`)
67
+ }
68
+ for (const meta of [entry.readMeta, entry.writeMeta]) {
69
+ if (meta?.availability) require('./device-availability').availabilityRoute(meta.availability)
70
+ }
61
71
  if (entry.readMeta?.meter) {
62
72
  meterRoute(entry.readMeta.meter)
63
73
  if (entry.adapter !== 'meter' || entry.mode !== 'read' || entry.readMeta.access !== 'ro' ||
@@ -124,6 +134,7 @@ function validatedVisualBindings ({ format, entries, devices, targetsByDevice })
124
134
  }
125
135
  const target = write || read
126
136
  const type = controlTypeForAdapter(raw.adapter, target)
137
+ const reverse = raw.adapter === 'switch' ? stateReverse(raw, write, read) : undefined
127
138
  if (read?.meter && (!Number.isFinite(read.min) || !Number.isFinite(read.max) || read.min >= read.max)) {
128
139
  throw Error(`Meter needs a finite device range: ${raw.id}`)
129
140
  }
@@ -131,7 +142,7 @@ function validatedVisualBindings ({ format, entries, devices, targetsByDevice })
131
142
  const identity = JSON.stringify([raw.device, read?.path, write?.path])
132
143
  const modelKey = candidate.repeat ? `${candidate.model}#${raw.instanceKey}` : candidate.model
133
144
  const previous = models.get(modelKey)
134
- const realtime = Boolean(read?.meter || read?.xySeries)
145
+ const realtime = raw.adapter === 'meter' || raw.adapter === 'xy-series'
135
146
  if (previous && (realtime || previous.realtime) && previous.identity !== identity) {
136
147
  throw Error(`Realtime model "${candidate.model}" is already used by another binding; use a separate Vue model for each independent telemetry target`)
137
148
  }
@@ -140,6 +151,7 @@ function validatedVisualBindings ({ format, entries, devices, targetsByDevice })
140
151
  result[raw.id] = {
141
152
  locator: raw.locator, adapter: raw.adapter, type, label: raw.label.trim(), mode: raw.mode,
142
153
  ...(candidate.button ? { buttonBehavior: behavior } : {}),
154
+ ...(raw.adapter === 'switch' ? { stateReverse: reverse } : {}),
143
155
  ...(candidate.model ? { model: candidate.model } : {}),
144
156
  ...(candidate.repeat ? { repeat: candidate.repeat, instanceKey: raw.instanceKey } : {}),
145
157
  ...(candidate.component ? { component: candidate.component } : {}),
@@ -147,8 +159,10 @@ function validatedVisualBindings ({ format, entries, devices, targetsByDevice })
147
159
  ? { uiRange: { min: candidate.min, max: candidate.max, step: candidate.step } } : {}),
148
160
  device: raw.device,
149
161
  writePath: write?.path || '', readPath: read?.path || '',
150
- ...(write ? { writeMeta: targetMeta(write, raw.onebased) } : {}),
151
- ...(read ? { readMeta: targetMeta(read, raw.onebased) } : {})
162
+ ...(write ? { writeMeta: targetMeta(write, raw.onebased,
163
+ raw.adapter === 'switch' ? switchMapping(write, undefined, reverse) : undefined) } : {}),
164
+ ...(read ? { readMeta: targetMeta(read, raw.onebased,
165
+ raw.adapter === 'switch' ? switchMapping(read, undefined, reverse) : undefined) } : {})
152
166
  }
153
167
  }
154
168
  return result
@@ -85,8 +85,14 @@ function createRoomXYSeriesReceiver (layout) {
85
85
  (valid && points.some(point => !Array.isArray(point) || point.length !== 2 ||
86
86
  !finite(point[0]) || !finite(point[1])))) return false
87
87
  }
88
+ if (frame.sources !== undefined && (!Array.isArray(frame.sources) || frame.sources.length > 256 ||
89
+ frame.sources.some(source => typeof source?.device !== 'string' || source.device.length > 128 ||
90
+ typeof source.streamId !== 'string' || source.streamId.length > 128 ||
91
+ typeof source.epoch !== 'string' || source.epoch.length > 128 ||
92
+ !Number.isSafeInteger(source.seq) || source.seq < 0))) return false
88
93
  latest = { values: frame.values.map(points => points.map(point => [point[0], point[1]])),
89
- valid: frame.valid.slice(), seq: frame.seq }
94
+ valid: frame.valid.slice(), seq: frame.seq,
95
+ ...(frame.sources ? { sources: frame.sources.map(({ device, streamId, epoch, seq }) => ({ device, streamId, epoch, seq })) } : {}) }
90
96
  pending = true
91
97
  return true
92
98
  },