loupedeck-commander 1.2.12 → 1.3.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/README.md +3 -3
- package/common/ApplicationConfig.mjs +31 -24
- package/common/BaseLoupeDeckHandler.mjs +30 -11
- package/common/ButtonField.mjs +159 -0
- package/common/button.mjs +499 -0
- package/common/utils.mjs +28 -7
- package/config.yaml +6 -0
- package/interfaces/baseif.mjs +1 -1
- package/interfaces/interfaces.mjs +34 -0
- package/interfaces/opcuaif.mjs +67 -45
- package/package.json +3 -2
- package/profile-1.yaml +185 -0
- package/profile-2.yaml +210 -0
- package/test.mjs +15 -1
- package/common/touchbuttons.mjs +0 -597
- package/config.json +0 -13
- package/profile-1.json +0 -272
- package/profile-2.json +0 -290
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
|
|
2
|
+
import { loadImage } from 'canvas'
|
|
3
|
+
//import { loadImage } from "https://deno.land/x/canvas/mod.ts";
|
|
4
|
+
import { calcDelta, invertColor } from './utils.mjs'
|
|
5
|
+
import format from 'string-template'
|
|
6
|
+
|
|
7
|
+
import { shellinterface, httpinterface, opcuainterface, profileEmitter } from '../interfaces/interfaces.mjs'
|
|
8
|
+
|
|
9
|
+
export const ButtonType = {
|
|
10
|
+
NONE: '',
|
|
11
|
+
TOGGLE: 'TOGGLE',
|
|
12
|
+
PUSH: 'PUSH'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export class Button {
|
|
18
|
+
#profile
|
|
19
|
+
#params
|
|
20
|
+
//#data
|
|
21
|
+
#index = 0
|
|
22
|
+
#enforcedIndex = -1
|
|
23
|
+
#event
|
|
24
|
+
#keys
|
|
25
|
+
#states
|
|
26
|
+
// Timestamp when button was pressed
|
|
27
|
+
timeStampPressed
|
|
28
|
+
// Timestamp when button was released
|
|
29
|
+
timeStampReleased
|
|
30
|
+
// Time actually hold the button in ms
|
|
31
|
+
timeHold
|
|
32
|
+
#counter = 0
|
|
33
|
+
|
|
34
|
+
constructor(id, width, height, data = {}, key, profile = {}) {
|
|
35
|
+
this.#states = {}
|
|
36
|
+
this.#keys = []
|
|
37
|
+
|
|
38
|
+
this.id = id
|
|
39
|
+
this.#index = 0
|
|
40
|
+
this.#enforcedIndex = -1
|
|
41
|
+
|
|
42
|
+
this.#profile = profile
|
|
43
|
+
this.#params = {
|
|
44
|
+
"key": key,
|
|
45
|
+
"state": '',
|
|
46
|
+
"width": width,
|
|
47
|
+
"height": height,
|
|
48
|
+
"min": 0,
|
|
49
|
+
"max": 100,
|
|
50
|
+
"x": 0,
|
|
51
|
+
"y": 0,
|
|
52
|
+
"color": '#000000',
|
|
53
|
+
"text": '',
|
|
54
|
+
"textColor": '#ffffff',
|
|
55
|
+
"textAlign": 'center',
|
|
56
|
+
"textBaseline": 'top',
|
|
57
|
+
"font": '16px Arial',
|
|
58
|
+
"value": 50,
|
|
59
|
+
"minPressed": 25,
|
|
60
|
+
"moveLeft": false,
|
|
61
|
+
"moveRight": false,
|
|
62
|
+
"moveUp": false,
|
|
63
|
+
"moveDown": false,
|
|
64
|
+
"nodeid": '',
|
|
65
|
+
"type": ButtonType.TOGGLE,
|
|
66
|
+
"group": key,
|
|
67
|
+
"filter": "",
|
|
68
|
+
"blink": false,
|
|
69
|
+
"vibrate": false,
|
|
70
|
+
"profile": "",
|
|
71
|
+
"brightness": undefined,
|
|
72
|
+
"default": "0"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (profile.parameters) {
|
|
76
|
+
let params = Object.keys(profile.parameters)
|
|
77
|
+
for (let i = 0; i < params.length; i++) {
|
|
78
|
+
const k = params[i]
|
|
79
|
+
this.#params[k] = profile.parameters[k]
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// enforce key, so it's not oveerwritten somewhere else
|
|
83
|
+
this.#params.key = key
|
|
84
|
+
|
|
85
|
+
// New approach: Use all data from data.params:
|
|
86
|
+
if (data.params) {
|
|
87
|
+
this.#params = {
|
|
88
|
+
...this.#params,
|
|
89
|
+
...data.params
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Format the params:
|
|
93
|
+
this.#params.group = format(this.#params.group, this.getParams({}))
|
|
94
|
+
this.#params.nodeid = format(this.#params.nodeid, this.getParams({}))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (data.states) {
|
|
98
|
+
this.#states = data.states
|
|
99
|
+
this.#keys = Object.keys(this.#states)
|
|
100
|
+
|
|
101
|
+
var defaultIndex = this.#keys.indexOf(this.#params.default)
|
|
102
|
+
if (defaultIndex >= 0) {
|
|
103
|
+
this.#index = defaultIndex
|
|
104
|
+
} else {
|
|
105
|
+
this.#params.default = this.#keys[0]
|
|
106
|
+
// console.info(` button ${id} default set to ${this.#params.default}, available states`, this.#keys)
|
|
107
|
+
}
|
|
108
|
+
this.#params.state = this.#keys[this.#index]
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Set the default value for the button:
|
|
112
|
+
|
|
113
|
+
// Legacy approach: Use data directly:
|
|
114
|
+
if (data.minPressed) {
|
|
115
|
+
console.warn("Legacy approach: minPressed is deprecated, use params.minPressed instead")
|
|
116
|
+
this.#params.minPressed = data.minPressed
|
|
117
|
+
}
|
|
118
|
+
if (data.text) {
|
|
119
|
+
console.warn("Legacy approach: text is deprecated, use params.text instead")
|
|
120
|
+
this.#params.text = data.text
|
|
121
|
+
}
|
|
122
|
+
if (data.nodeid) {
|
|
123
|
+
this.#params.nodeid = format(data.nodeid, this.getParams({}))
|
|
124
|
+
console.warn("Legacy approach: nodeid is deprecated, use params.nodeid instead", this.#params.nodeid)
|
|
125
|
+
}
|
|
126
|
+
if (data.type) {
|
|
127
|
+
console.warn("Legacy approach: type is deprecated, use params.type instead")
|
|
128
|
+
this.#params.type = data.type
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (data.default) {
|
|
132
|
+
console.warn("Legacy approach: default is deprecated, use params.default instead")
|
|
133
|
+
this.#index = this.#keys.indexOf(data.default)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (data.group) {
|
|
137
|
+
console.warn("Legacy approach: group is deprecated, use params.group instead")
|
|
138
|
+
this.#params.group = format(data.group, this.getParams({}))
|
|
139
|
+
}
|
|
140
|
+
// End Legacy approach
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
setState(index = 0) {
|
|
144
|
+
this.#index = index
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Draw a physical button (color in RGBA format) on a device
|
|
149
|
+
* @param {*} device
|
|
150
|
+
* @param {*} id - id of the physical button
|
|
151
|
+
* @returns
|
|
152
|
+
*/
|
|
153
|
+
async drawPhysical(device, id) {
|
|
154
|
+
const elem = this.getCurrentElement()
|
|
155
|
+
if (!elem || !elem.color) { return }
|
|
156
|
+
|
|
157
|
+
const r = parseInt(elem.color.slice(1, 3), 16)
|
|
158
|
+
const g = parseInt(elem.color.slice(3, 5), 16)
|
|
159
|
+
const b = parseInt(elem.color.slice(5, 7), 16)
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
var idx = parseInt(id, 10);
|
|
163
|
+
|
|
164
|
+
const val = {
|
|
165
|
+
id: idx,
|
|
166
|
+
color: `rgba(${r}, ${g}, ${b})`
|
|
167
|
+
}
|
|
168
|
+
// console.log(' Set Button Color',id, val.id,elem.color, val.color)
|
|
169
|
+
device.setButtonColor(val)
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error(' Error', error)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Draw the Touch-Screen Elementat the given row and column
|
|
177
|
+
* @param {*} row
|
|
178
|
+
* @param {*} column
|
|
179
|
+
* @param {*} ctx
|
|
180
|
+
*/
|
|
181
|
+
async draw(row, column, ctx) {
|
|
182
|
+
// Calculate the x/y position based on the row and column and the width/height properties of the button
|
|
183
|
+
const x = column * this.#params.width
|
|
184
|
+
const y = row * this.#params.height
|
|
185
|
+
|
|
186
|
+
// Modifications in draw:
|
|
187
|
+
// - Support color and textColor settings also in params section which
|
|
188
|
+
// can be overwriten by button state property. This way color and textColor
|
|
189
|
+
// can be defined overall for all states without repeating them in every state.
|
|
190
|
+
// - Same for blink property.
|
|
191
|
+
|
|
192
|
+
let params = this.getParams(this.getCurrentElement())
|
|
193
|
+
|
|
194
|
+
let color = params.color
|
|
195
|
+
let textColor = params.textColor
|
|
196
|
+
|
|
197
|
+
if (params.blink && this.#counter % 2 == 0) {
|
|
198
|
+
// If blink is set, we switch textcolor and background color:
|
|
199
|
+
color = params.textColor
|
|
200
|
+
textColor = params.color
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Apply the color as fillStyle:
|
|
204
|
+
ctx.fillStyle = color
|
|
205
|
+
// Draw the background of the button::
|
|
206
|
+
ctx.fillRect(x, y, params.width, params.height)
|
|
207
|
+
|
|
208
|
+
// if we have an icon/image draw it on top of the button
|
|
209
|
+
if (params.imgBuffer) {
|
|
210
|
+
ctx.drawImage(params.imgBuffer, x, y, params.width, params.height)
|
|
211
|
+
}
|
|
212
|
+
// if we have a text defined, draw it on top:
|
|
213
|
+
if (params.text != undefined && params.text != '') {
|
|
214
|
+
ctx.fillStyle = textColor
|
|
215
|
+
ctx.font = params.font
|
|
216
|
+
ctx.textBaseline = params.textBaseline;
|
|
217
|
+
ctx.textAlign = params.textAlign;
|
|
218
|
+
let dynamicText = format(params.text, params)
|
|
219
|
+
let tx = x
|
|
220
|
+
let ty = y
|
|
221
|
+
// Handle Text Horizontal Alignment
|
|
222
|
+
switch (params.textAlign) {
|
|
223
|
+
case "center":
|
|
224
|
+
tx += this.#params.width / 2;
|
|
225
|
+
break;
|
|
226
|
+
case "left":
|
|
227
|
+
tx += 6;
|
|
228
|
+
break;
|
|
229
|
+
case "right":
|
|
230
|
+
tx += this.#params.width - 6;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
// Handle Text Vertical Alignment
|
|
234
|
+
switch (params.textBaseline) {
|
|
235
|
+
case "middle":
|
|
236
|
+
ty += this.#params.height / 2;
|
|
237
|
+
break;
|
|
238
|
+
case "top":
|
|
239
|
+
ty += 6;
|
|
240
|
+
ctx.textBaseline = "top";
|
|
241
|
+
break;
|
|
242
|
+
case "bottom":
|
|
243
|
+
ty += this.#params.height - 6;
|
|
244
|
+
ctx.textBaseline = "bottom";
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
ctx.fillText(dynamicText, x + 6, y + 6)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
this.#counter = this.#counter + 1
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async load(globalConfig) {
|
|
254
|
+
this.#profile = globalConfig
|
|
255
|
+
for (let i = 0; i < this.#keys.length; i++) {
|
|
256
|
+
const key = this.#keys[i]
|
|
257
|
+
const elem = this.#states[key]
|
|
258
|
+
const file = elem.image
|
|
259
|
+
if (file !== undefined && file !== '') {
|
|
260
|
+
try {
|
|
261
|
+
this.#states[key].imgBuffer = await loadImage(file)
|
|
262
|
+
} catch (e) {
|
|
263
|
+
console.error('No such image', file)
|
|
264
|
+
return false
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
getCurrentElement() {
|
|
271
|
+
const state = this.#keys[this.#index]
|
|
272
|
+
return this.#states[state]
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Triggered when either a physical button, touchscreen button or Knob Button was pressed
|
|
277
|
+
* Updates the timeStampPressed and timeHold, and checks if the button was hold long enough (minPressed attribute)
|
|
278
|
+
* If the button type was not TOGGLE, it will decrease the index to the previous state.
|
|
279
|
+
* @returns
|
|
280
|
+
*/
|
|
281
|
+
pressed() {
|
|
282
|
+
this.timeStampPressed = Date.now()
|
|
283
|
+
this.updateState(this.#index, "pressed", true)
|
|
284
|
+
this.#index++
|
|
285
|
+
|
|
286
|
+
if (this.#enforcedIndex >= 0) {
|
|
287
|
+
console.log("Enforced Index", this.#enforcedIndex)
|
|
288
|
+
this.#index = this.#enforcedIndex
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Update the State according to the correctly pressed state
|
|
292
|
+
if (this.#index < 0) { this.#index = this.#keys.length - 1 }
|
|
293
|
+
this.#index %= this.#keys.length
|
|
294
|
+
|
|
295
|
+
return true
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Triggered when either a physical button, touchscreen button or Knob Button was released
|
|
300
|
+
* Updates the timeStampReleased and timeHold, and checks if the button was hold long enough (minPressed attribute)
|
|
301
|
+
* If the button type was not TOGGLE, it will decrease the index to the previous state.
|
|
302
|
+
* @returns
|
|
303
|
+
*/
|
|
304
|
+
released() {
|
|
305
|
+
let elem = this.getCurrentElement()
|
|
306
|
+
if (!elem) { return false }
|
|
307
|
+
this.timeStampReleased = Date.now()
|
|
308
|
+
this.timeHold = this.timeStampReleased - this.timeStampPressed
|
|
309
|
+
|
|
310
|
+
let bExecute = true
|
|
311
|
+
if (this.timeHold < this.#params.minPressed) {
|
|
312
|
+
// Update the State according to the not correct pressed state
|
|
313
|
+
console.log('Did not hold minimum time of ', this.#params.minPressed, 'only', this.timeHold)
|
|
314
|
+
bExecute = false
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Update the State according to the correctly pressed state
|
|
318
|
+
switch (this.#params.type) {
|
|
319
|
+
case ButtonType.TOGGLE:
|
|
320
|
+
// do nothing
|
|
321
|
+
break
|
|
322
|
+
default:
|
|
323
|
+
this.#index--
|
|
324
|
+
break
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (this.#enforcedIndex >= 0 && this.#enforcedIndex != this.#index) {
|
|
328
|
+
// If we have an enforced index, we set it to the enforced index:
|
|
329
|
+
console.log("Enforcing Index", this.#enforcedIndex, "instead of", this.#index)
|
|
330
|
+
this.#index = this.#enforcedIndex
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Update the State according to the correctly pressed state
|
|
334
|
+
if (this.#index < 0) { this.#index = this.#keys.length - 1 }
|
|
335
|
+
this.#index %= this.#keys.length
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
this.updateState(this.#index, "released", bExecute)
|
|
339
|
+
|
|
340
|
+
return true
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
updateState(index, eventType, bExecute) {
|
|
345
|
+
this.#index = index
|
|
346
|
+
this.#event = eventType
|
|
347
|
+
if (bExecute)
|
|
348
|
+
this.runCommand()
|
|
349
|
+
return true
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Triggered when one of the physical KNOBS is rotated
|
|
354
|
+
* Calculates the new value based on the delta, and store it in the params.value
|
|
355
|
+
* @param {*} delta
|
|
356
|
+
* @returns
|
|
357
|
+
*/
|
|
358
|
+
async rotated(delta) {
|
|
359
|
+
if (!this.getCurrentElement()) { return false }
|
|
360
|
+
|
|
361
|
+
this.#event = "rotated"
|
|
362
|
+
this.#params.value = calcDelta(this.#params.value, delta, this.#params.min, this.#params.max)
|
|
363
|
+
return this.runCommand()
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* This function is called when the value of a button is changed from outside (e.g. coming from an opcua subscription)
|
|
368
|
+
* @param {*} buttonID: the ID of the button that caused the change
|
|
369
|
+
* @param {*} nodeid: the nodeid that changed
|
|
370
|
+
* @param {*} val : the current value of the given nodeid
|
|
371
|
+
* @returns
|
|
372
|
+
*/
|
|
373
|
+
async changed(buttonID, nodeid, val) {
|
|
374
|
+
// Only handle updates within the same group identified by nodeid
|
|
375
|
+
if (nodeid !== this.#params.nodeid) {
|
|
376
|
+
return
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
this.#index = 0;
|
|
380
|
+
this.#enforcedIndex = -1;
|
|
381
|
+
for (let i = 0; i < this.#keys.length; i++) {
|
|
382
|
+
let stateStr = this.#keys[i].toString()
|
|
383
|
+
let valStr = val.toString()
|
|
384
|
+
// check if the state-name is same as the value we get from outside:
|
|
385
|
+
if (valStr == stateStr) {
|
|
386
|
+
if (i !== this.#index) {
|
|
387
|
+
console.info("Changed index", this.id, nodeid, val, i)
|
|
388
|
+
this.#index = i;
|
|
389
|
+
console.info("enforce State index", this.id, nodeid, val, i)
|
|
390
|
+
this.#enforcedIndex = this.#index;
|
|
391
|
+
}
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Handle a touchmove event on a touchscreen button
|
|
399
|
+
* This function calculates the new value based on the x and y coordinates of the touchmove event.
|
|
400
|
+
* It updates the moveLeft, moveRight, moveUp and moveDown parameters based on the x and y coordinates.
|
|
401
|
+
* It also calculates the delta for the value based on the y coordinate and updates the value parameter.
|
|
402
|
+
* @param {*} x
|
|
403
|
+
* @param {*} y
|
|
404
|
+
* @returns
|
|
405
|
+
*/
|
|
406
|
+
async touchmove(x, y) {
|
|
407
|
+
|
|
408
|
+
let delta = 0
|
|
409
|
+
if (x > this.#params.x) {
|
|
410
|
+
this.#params.moveRight = true
|
|
411
|
+
this.#params.moveLeft = false
|
|
412
|
+
} else if (x < this.#params.x) {
|
|
413
|
+
this.#params.moveRight = false
|
|
414
|
+
this.#params.moveLeft = true
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (y > this.#params.y) {
|
|
418
|
+
this.#params.moveDown = true
|
|
419
|
+
this.#params.moveUp = false
|
|
420
|
+
delta = -1
|
|
421
|
+
} else if (y < this.#params.y) {
|
|
422
|
+
this.#params.moveDown = false
|
|
423
|
+
this.#params.moveUp = true
|
|
424
|
+
delta = 1
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
this.#params.x = (x % 100)
|
|
428
|
+
this.#params.y = (y % 100)
|
|
429
|
+
|
|
430
|
+
// Calculate delta for value no touchmove up/down
|
|
431
|
+
this.#params.value = calcDelta(this.#params.value, delta, this.#params.min, this.#params.max)
|
|
432
|
+
|
|
433
|
+
// console.log(`d: ${this.#params.moveDown} r: ${this.#params.moveRight} `)
|
|
434
|
+
return false
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
getParams(elem) {
|
|
438
|
+
// Call an action - include dynamic parameters
|
|
439
|
+
// and also all attributes of elem + global config
|
|
440
|
+
const params = {
|
|
441
|
+
...this.#profile.parameters,
|
|
442
|
+
...this.#params,
|
|
443
|
+
...elem,
|
|
444
|
+
id: this.id,
|
|
445
|
+
//key: this.key,
|
|
446
|
+
event: this.#event,
|
|
447
|
+
pressed: this.#event == "pressed",
|
|
448
|
+
released: this.#event == "released",
|
|
449
|
+
rotated: this.#event == "rotated",
|
|
450
|
+
}
|
|
451
|
+
return params
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Run a command based on the current element's parameters.
|
|
456
|
+
* This function checks if the current element has a command, http request or opcua request defined.
|
|
457
|
+
* If so, it will execute the command using the respective interface (shell, http or opcua).
|
|
458
|
+
* It will also emit profile and brightness changes if defined in the parameters.
|
|
459
|
+
* @returns
|
|
460
|
+
*/
|
|
461
|
+
async runCommand() {
|
|
462
|
+
//const elem = this.getCurrentElement()
|
|
463
|
+
let params = this.getParams(this.getCurrentElement())
|
|
464
|
+
|
|
465
|
+
// Only continue, if we have an element:
|
|
466
|
+
if (!params) {
|
|
467
|
+
return
|
|
468
|
+
}
|
|
469
|
+
// Filter for Event Type:
|
|
470
|
+
if (params.filter && params.filter != this.#event) {
|
|
471
|
+
return
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (params.profile !== undefined && params.profile !== '') {
|
|
475
|
+
profileEmitter.emit("profileChanged", params.profile)
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (params.brightness !== undefined) {
|
|
479
|
+
profileEmitter.emit("brightnessChanged", params.brightness)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (params.vibrate !== undefined) {
|
|
483
|
+
profileEmitter.emit("vibrate", params.vibrate)
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
let res = ''
|
|
487
|
+
if ('cmd' in params && shellinterface) {
|
|
488
|
+
res = await shellinterface.call(params.cmd, params)
|
|
489
|
+
}
|
|
490
|
+
if ('http' in params && httpinterface) {
|
|
491
|
+
res = await httpinterface.call(params.http, params)
|
|
492
|
+
}
|
|
493
|
+
if ('opcua' in params && opcuainterface) {
|
|
494
|
+
res = await opcuainterface.call(params.opcua, params)
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return res
|
|
498
|
+
}
|
|
499
|
+
}
|
package/common/utils.mjs
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import YAML from 'yaml'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
* Read a JSON
|
|
5
|
+
* Read a Config file in either JSON or YAML Fileformat
|
|
6
|
+
* @param {string} fileName - The name of the file to read
|
|
7
|
+
* @returns {object} - The parsed object from the file
|
|
5
8
|
*/
|
|
6
|
-
export function
|
|
7
|
-
let
|
|
9
|
+
export function readConfigFile (fileName) {
|
|
10
|
+
let obj = undefined
|
|
8
11
|
try {
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
//console.log("Reading File:", fileName)
|
|
13
|
+
let data = readFileSync(fileName, 'utf8')
|
|
14
|
+
// If the file is a YAML file, parse it
|
|
15
|
+
if (fileName.endsWith('.yaml') || fileName.endsWith('.yml')) {
|
|
16
|
+
obj = YAML.parse(data)
|
|
17
|
+
} else if (fileName.endsWith('.json')) {
|
|
18
|
+
// If the file is a JSON file, parse it
|
|
19
|
+
obj = JSON.parse(data)
|
|
20
|
+
// automatically convert all json to yaml
|
|
21
|
+
writeYAMLFile(fileName.replace(".json",".yaml"), obj)
|
|
22
|
+
}
|
|
11
23
|
} catch (error) {
|
|
12
|
-
console.info(`Error reading File: ${fileName}
|
|
13
|
-
return data
|
|
24
|
+
console.info(`Error reading File: ${fileName}`, error)
|
|
14
25
|
}
|
|
26
|
+
return obj
|
|
15
27
|
}
|
|
16
28
|
|
|
17
29
|
/**
|
|
@@ -22,6 +34,15 @@ export function writeJSONFile (fileName, jsonObj) {
|
|
|
22
34
|
writeFileSync(fileName, data)
|
|
23
35
|
}
|
|
24
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Write a JSON File
|
|
39
|
+
*/
|
|
40
|
+
export function writeYAMLFile (fileName, jsonObj) {
|
|
41
|
+
const data = YAML.stringify(jsonObj)
|
|
42
|
+
writeFileSync(fileName, data)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
25
46
|
/**
|
|
26
47
|
* Calculate the delta of a value within a range between min and max with overflow
|
|
27
48
|
* @param {*} data
|
package/config.yaml
ADDED
package/interfaces/baseif.mjs
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as shellif from '../interfaces/shellif.mjs'
|
|
2
|
+
import * as httpif from '../interfaces/httpif.mjs'
|
|
3
|
+
import * as opcuaif from '../interfaces/opcuaif.mjs'
|
|
4
|
+
import { EventEmitter } from 'node:events'
|
|
5
|
+
|
|
6
|
+
export var opcuainterface = undefined
|
|
7
|
+
export var httpinterface = undefined
|
|
8
|
+
export var shellinterface = undefined
|
|
9
|
+
export var profileEmitter = undefined
|
|
10
|
+
|
|
11
|
+
export async function InitializeInterfaces(appConfig){
|
|
12
|
+
if (opcuainterface === undefined ){
|
|
13
|
+
opcuainterface = new opcuaif.OPCUAIf()
|
|
14
|
+
}
|
|
15
|
+
// the opcua interface needs the profile to register nodes with subscriptions:
|
|
16
|
+
opcuainterface.init(appConfig.parameters,appConfig)
|
|
17
|
+
if (httpinterface === undefined)
|
|
18
|
+
httpinterface = new httpif.HTTPif()
|
|
19
|
+
if (shellinterface === undefined)
|
|
20
|
+
shellinterface = new shellif.SHELLif()
|
|
21
|
+
if (profileEmitter === undefined)
|
|
22
|
+
profileEmitter = new EventEmitter()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function StopInterfaces(){
|
|
26
|
+
if (opcuainterface !== undefined )
|
|
27
|
+
await opcuainterface.stop()
|
|
28
|
+
if (httpinterface !== undefined)
|
|
29
|
+
await httpinterface.stop()
|
|
30
|
+
if (shellinterface !== undefined)
|
|
31
|
+
await shellinterface.stop()
|
|
32
|
+
|
|
33
|
+
}
|
|
34
|
+
|