loupedeck-commander 1.2.2 → 1.2.4

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,521 +1,534 @@
1
- import { loadImage } from 'canvas'
2
- //import { loadImage } from "https://deno.land/x/canvas/mod.ts";
3
-
4
- import * as shellif from '../interfaces/shellif.mjs'
5
- import * as httpif from '../interfaces/httpif.mjs'
6
- import * as opcuaif from '../interfaces/opcuaif.mjs'
7
- import format from 'string-template'
8
- import { calcDelta } from './utils.mjs'
9
-
10
- export var opcuainterface = undefined
11
- var httpinterface = undefined
12
- var shellinterface = undefined
13
-
14
- export async function InitializeInterfaces(appConfig,callbackFunction){
15
- if (opcuainterface === undefined ){
16
- opcuainterface = new opcuaif.OPCUAIf()
17
- opcuainterface.init(appConfig.parameters,appConfig,callbackFunction)
18
- }
19
- if (httpinterface === undefined)
20
- httpinterface = new httpif.HTTPif()
21
- if (shellinterface === undefined)
22
- shellinterface = new shellif.SHELLif()
23
-
24
- }
25
-
26
- export async function StopInterfaces(){
27
- if (opcuainterface !== undefined )
28
- await opcuainterface.stop()
29
- if (httpinterface !== undefined)
30
- await httpinterface.stop()
31
- if (shellinterface !== undefined)
32
- await shellinterface.stop()
33
-
34
- }
35
-
36
- export const ButtonIndex = {
37
- BUTN_0: 0,
38
- BUTN_1: 1,
39
- BUTN_2: 2,
40
- BUTN_3: 3,
41
- BUTN_4: 3,
42
- BUTN_5: 5,
43
- BUTN_6: 6,
44
- BUTN_7: 7,
45
- home: 'home'
46
- }
47
-
48
- const ButtonType = {
49
- NONE: '',
50
- TOGGLE: 'TOGGLE',
51
- PUSH: 'PUSH'
52
- }
53
-
54
- export class ButtonField {
55
- #buttons = {}
56
- #screen
57
- width = 0
58
- height = 0
59
- #rows = 0
60
- #columns = 0
61
- #keys = []
62
- #type
63
- #name
64
- #config
65
-
66
- constructor (name, rows, columns, width, height, data, config) {
67
- console.info(`ButtonField ${name.padEnd(10, ' ')} Buttons: ${rows} x ${columns} , Pixels ${width} x ${height}`)
68
- this.#name = name
69
- this.width = width
70
- this.height = height
71
- this.#rows = rows
72
- this.#columns = columns
73
- this.#screen = this.width > 0 && this.height > 0
74
- this.#type = 'button'
75
- if (this.#screen) { this.#type = 'touch' }
76
-
77
- const keys = Object.keys(data)
78
- for (let i = 0; i < keys.length; i++) {
79
- const key = keys[i]
80
- const tb = new Button(`${this.#type}-${key}`, width / columns, height / rows, data[key],key,config.parameters)
81
- this.#buttons[key] = tb
82
- }
83
-
84
- this.#keys = keys
85
- this.#config = config
86
- }
87
-
88
- //setProfileConfig (config) {
89
- // this.#config = config
90
- // }
91
-
92
- async draw (device) {
93
- if (!this.#screen) {
94
- // physical buttons:
95
- for (let i = 0; i < this.#keys.length; i++) {
96
- const key = this.#keys[i]
97
- this.#buttons[key].drawPhysical(device, key)
98
- }
99
- } else {
100
- // screen:
101
- device.drawScreen(this.#name, ctx => {
102
- ctx.globalCompositeOperation = 'source-atop'
103
- for (let i = 0; i < this.#keys.length; i++) {
104
- const key = this.#keys[i]
105
- const iValue = parseInt(key, 10)
106
- const row = Math.floor(iValue / device.columns)
107
- const column = iValue % device.columns
108
-
109
- this.#buttons[key].draw(row, column, ctx)
110
- }
111
- })
112
- }
113
- }
114
-
115
- setState (id, val) {
116
- this.#buttons[id].setState(val)
117
- }
118
-
119
- setIntState (id, val) {
120
- this.#buttons[id].setIntState(val)
121
- }
122
-
123
- async load () {
124
- for (let i = 0; i < this.#keys.length; i++) {
125
- const key = this.#keys[i]
126
- if (isNaN(key)) {
127
- await this.#buttons[key].load(this.#config)
128
- } else {
129
- const iVal = parseInt(key, 10)
130
- await this.#buttons[iVal].load(this.#config)
131
- }
132
- }
133
- }
134
-
135
- async pressed (id) {
136
- this.checkAndCreateButton(id)
137
- const result = await this.#buttons[id].pressed()
138
- if (!result) {
139
- console.info(`pressed ${this.#type} ${id}`)
140
- }
141
- return result
142
- }
143
-
144
- async released (id) {
145
- const result = await this.#buttons[id].released()
146
- if (result) {
147
- // disable all other buttons of the group, if this one had been activated:
148
- for (let i = 0; i < this.#keys.length; i++) {
149
- let key = this.#keys[i]
150
- if (!isNaN(key)) { key = parseInt(key, 10) }
151
- if (id === key) { continue }
152
- if (this.#buttons[key].group === this.#buttons[id].group) {
153
- this.#buttons[key].setState(0)
154
- }
155
- }
156
- }
157
- if (!result) {
158
- console.info(`released ${this.#type} ${id}`)
159
- }
160
- return result
161
- }
162
-
163
- async changed(buttonID,nodeid,val){
164
- for (let i = 0; i < this.#keys.length; i++) {
165
- let bID = this.#keys[i]
166
- const result = await this.#buttons[bID].changed(i,nodeid,val)
167
- }
168
- }
169
-
170
- async rotated (id, delta) {
171
- this.checkAndCreateButton(id)
172
- const result = await this.#buttons[id].rotated(delta)
173
- if (!result) { console.info(`rotated ${this.#type} ${id} ${delta}`) }
174
- return result
175
- }
176
-
177
- async touchmove (id, x, y) {
178
- this.checkAndCreateButton(id)
179
- const result = await this.#buttons[id].touchmove(x, y)
180
- if (!result) { console.info(`touchmove ${id} ${x} ${y}`) }
181
- return result
182
- }
183
-
184
- checkAndCreateButton (id) {
185
- if (!(id in this.#buttons)) {
186
- const tb = new Button(id, 1, 1, id)
187
- this.#buttons[id] = tb
188
- }
189
- }
190
- }
191
-
192
- export class Button {
193
- #config
194
- width = 0
195
- height = 0
196
-
197
- #type = ButtonType.TOGGLE
198
-
199
- #min = 0
200
- #max = 100
201
- #value = 50
202
- #name = undefined
203
- #nodeid = ""
204
-
205
- #index = 0
206
- #event
207
- #keys
208
- #states
209
-
210
- group = ''
211
-
212
- text = ''
213
- font = '16px Arial'
214
-
215
- #x
216
- #y
217
- #moveLeft
218
- #moveRight
219
- #moveUp
220
- #moveDown
221
-
222
- // Timestamp when button was pressed
223
- timeStampPressed
224
- // Timestamp when button was released
225
- timeStampReleased
226
- // Time actually hold the button in ms
227
- timeHold
228
- // Minimum ammount of time in ms to press a button:
229
- minPressed = 25
230
- key = -1
231
-
232
- constructor (id, width, height, data,key,params) {
233
- this.id = id
234
- this.key = key
235
- this.width = width
236
- this.height = height
237
- this.#index = 0
238
-
239
- if (data && data.states) {
240
- this.group = data.group
241
-
242
- this.#states = data.states
243
- this.#keys = Object.keys(this.#states)
244
- if (data.type) {
245
- this.#type = data.type.toUpperCase()
246
- }
247
-
248
- if (data.minPressed) {
249
- this.minPressed = data.minPressed
250
- }
251
-
252
- if (data.text) {
253
- this.text = data.text
254
- }
255
- }
256
- if (this.#states === undefined) {
257
- this.#states = {}
258
- this.#keys = []
259
- }
260
- if (data.nodeid){
261
- this.#nodeid = format(data.nodeid, params)
262
- }
263
-
264
- }
265
-
266
- setState (index = 0) {
267
- this.#index = index
268
- }
269
-
270
- async drawPhysical (device, id) {
271
- const elem = this.getCurrentElement()
272
- if (!elem || !elem.color) { return }
273
-
274
- const r = parseInt(elem.color.slice(1, 3), 16)
275
- const g = parseInt(elem.color.slice(3, 5), 16)
276
- const b = parseInt(elem.color.slice(5, 7), 16)
277
-
278
- try {
279
- var idx = parseInt(id, 10);
280
-
281
- const val = {
282
- id:idx,
283
- color: `rgba(${r}, ${g}, ${b})`
284
- }
285
- //console.log(' Set Button Color',val.id, val.color)
286
- device.setButtonColor(val)
287
- } catch (error) {
288
- console.error(' Error', error)
289
- }
290
- }
291
-
292
- async draw (row, column, ctx) {
293
- const x = column * this.width
294
- const y = row * this.height
295
-
296
- const elem = this.getCurrentElement()
297
-
298
- if (elem) {
299
- if (elem.color) {
300
- ctx.fillStyle = elem.color
301
- ctx.fillRect(x, y, this.width, this.height)
302
- }
303
- if (elem.imgBuffer) {
304
- ctx.drawImage(elem.imgBuffer, x, y, this.width, this.height)
305
- }
306
- }
307
- if (this.text){
308
- const lastElem = this.getLastElement()
309
- ctx.fillStyle = lastElem.color
310
- ctx.font = '20px Verdana'
311
- ctx.textBaseline = 'top';
312
- ctx.textAlign = 'left';
313
- ctx.fillText(this.text,x+6,y+6)
314
- }
315
- }
316
-
317
- async load (globalConfig) {
318
- this.#config = globalConfig
319
- for (let i = 0; i < this.#keys.length; i++) {
320
- const key = this.#keys[i]
321
- const elem = this.#states[key]
322
- const file = elem.image
323
- if (file !== undefined && file !== '') {
324
- try {
325
- this.#states[key].imgBuffer = await loadImage(file)
326
- } catch (e) {
327
- console.error('No such image', file)
328
- return false
329
- }
330
- }
331
- }
332
- }
333
-
334
- getCurrentElement () {
335
- const key = this.#keys[this.#index]
336
- return this.#states[key]
337
- }
338
-
339
- getLastElement () {
340
- let i=this.#index-1
341
- if (i < 0) { i = this.#keys.length - 1 }
342
-
343
- const key = this.#keys[i]
344
- return this.#states[key]
345
- }
346
-
347
- getCurrentText () {
348
- return this.text
349
- }
350
-
351
- setIntState (val) {
352
- this.#index = val
353
- }
354
-
355
- pressed () {
356
- this.timeStampPressed = Date.now()
357
-
358
- this.#index++
359
- this.updateState(this.#index,"pressed")
360
- return true
361
- }
362
-
363
- released () {
364
- if (!this.getCurrentElement()) { return false }
365
- this.timeStampReleased = Date.now()
366
- this.timeHold = this.timeStampReleased - this.timeStampPressed
367
-
368
- if (this.timeHold < this.minPressed) {
369
- // Update the State according to the not correct pressed state
370
- console.log('Did not hold minimum time of ', this.minPressed, 'only', this.timeHold)
371
- this.#index--
372
- if (this.#index < 0) { this.#index = this.#keys.length - 1 }
373
- return false
374
- }
375
-
376
- // Update the State according to the correctly pressed state
377
- switch (this.#type) {
378
- case ButtonType.TOGGLE:
379
- // do nothing
380
- break
381
- default:
382
- this.#index--
383
- if (this.#index < 0) { this.#index = this.#keys.length - 1 }
384
-
385
- break
386
- }
387
-
388
- this.updateState(this.#index,"released")
389
-
390
- return true // this.runCommand()
391
- }
392
-
393
- updateState(index,eventType){
394
- this.#index = index
395
- this.#event = eventType
396
- // Update the State according to the correctly pressed state
397
- if (this.#index < 0) { this.#index = this.#keys.length - 1 }
398
- this.#index %= this.#keys.length
399
- this.runCommand()
400
- //console.log("TODO: expect newState", newState)
401
- return true // this.runCommand()
402
- }
403
-
404
- async rotated (delta) {
405
- if (!this.getCurrentElement()) { return false }
406
-
407
- this.#event = "rotated"
408
- this.#value = calcDelta(this.#value, delta, this.#max)
409
- return this.runCommand()
410
- }
411
-
412
- async changed(buttonID,nodeid,val){
413
- // Only handle updates within the same group identified by nodeid
414
- if (nodeid !== this.#nodeid){
415
- return
416
- }
417
-
418
- this.#index = 0;
419
- for (let i = 0; i < this.#keys.length; i++) {
420
- let key = this.#keys[i]
421
- // check if the state-name is same as the value we get from outside:
422
- if (val == key){
423
- this.#index = i;
424
- break;
425
- }
426
-
427
- // check if the nodeid is the same and the value is one of the states
428
- let state = this.#states[key]
429
- if (!state.value)
430
- continue
431
-
432
- const params = {
433
- id: buttonID,
434
- key: buttonID,
435
- ...state
436
- }
437
- let val1 = format(state.value,params)
438
- if (val1 === val){
439
- this.#index = i;
440
- break;
441
- }
442
- break;
443
- }
444
- }
445
-
446
- async touchmove (x, y) {
447
- // if (!this.getCurrentElement()) { return false }
448
-
449
- if (x > this.#x) {
450
- this.#moveRight = true
451
- this.#moveLeft = false
452
- } else if (x < this.#x) {
453
- this.#moveRight = false
454
- this.#moveLeft = true
455
- }
456
-
457
- if (y > this.#y) {
458
- this.#moveDown = true
459
- this.#moveUp = false
460
- } else if (y < this.#y) {
461
- this.#moveDown = false
462
- this.#moveUp = true
463
- }
464
-
465
- this.#x = x
466
- this.#y = y
467
- // console.log(`d: ${this.#moveDown} r: ${this.#moveRight} `)
468
- return false
469
- }
470
-
471
- async runCommand () {
472
- const elem = this.getCurrentElement()
473
- // Only continue, if we have an element, that contains some kind of command:
474
- if (!elem || (!elem.cmd && !elem.http && !elem.opcua)) {
475
- return
476
- }
477
- // Filter for Event Type:
478
- if (elem.filter && elem.filter != this.#event){
479
- return
480
- }
481
- // Call an action - include dynamic parameters
482
- // and also all attributes of elem + global config
483
- const params = {
484
- text: this.getCurrentText(),
485
- ...this.#config.parameters,
486
- ...elem,
487
- id: this.id,
488
- key: this.key,
489
- event: this.#event,
490
- state: this.#keys[this.#index],
491
- min: this.#min,
492
- max: this.#max,
493
- value: this.#value
494
- }
495
-
496
- let res = ''
497
- if ('cmd' in elem) {
498
- if (shellinterface){
499
- res = await shellinterface.call(elem.cmd, params)
500
- }else{
501
- console.warn("shellinterface not started")
502
- }
503
- }
504
- if ('http' in elem) {
505
- if (httpinterface){
506
- res = await httpinterface.call(elem.http, params)
507
- }else{
508
- console.warn("httpinterface not started")
509
- }
510
- }
511
- if ('opcua' in elem) {
512
- if (opcuainterface){
513
- res = await opcuainterface.call(elem.opcua, params)
514
- }else{
515
- console.warn("opcuainterface not started")
516
- }
517
- }
518
-
519
- return res
520
- }
521
- }
1
+ import { loadImage } from 'canvas'
2
+ //import { loadImage } from "https://deno.land/x/canvas/mod.ts";
3
+
4
+ import * as shellif from '../interfaces/shellif.mjs'
5
+ import * as httpif from '../interfaces/httpif.mjs'
6
+ import * as opcuaif from '../interfaces/opcuaif.mjs'
7
+ import format from 'string-template'
8
+ import { calcDelta } from './utils.mjs'
9
+ import { EventEmitter } from 'node:events'
10
+
11
+
12
+ export var opcuainterface = undefined
13
+ var httpinterface = undefined
14
+ var shellinterface = undefined
15
+ export var profileEmitter = undefined
16
+ export async function InitializeInterfaces(appConfig,callbackFunction){
17
+ if (opcuainterface === undefined ){
18
+ opcuainterface = new opcuaif.OPCUAIf()
19
+ opcuainterface.init(appConfig.parameters,appConfig,callbackFunction)
20
+ }
21
+ if (httpinterface === undefined)
22
+ httpinterface = new httpif.HTTPif()
23
+ if (shellinterface === undefined)
24
+ shellinterface = new shellif.SHELLif()
25
+
26
+ profileEmitter = new EventEmitter()
27
+ }
28
+
29
+ export async function StopInterfaces(){
30
+ if (opcuainterface !== undefined )
31
+ await opcuainterface.stop()
32
+ if (httpinterface !== undefined)
33
+ await httpinterface.stop()
34
+ if (shellinterface !== undefined)
35
+ await shellinterface.stop()
36
+
37
+ }
38
+
39
+ export const ButtonIndex = {
40
+ BUTN_0: 0,
41
+ BUTN_1: 1,
42
+ BUTN_2: 2,
43
+ BUTN_3: 3,
44
+ BUTN_4: 3,
45
+ BUTN_5: 5,
46
+ BUTN_6: 6,
47
+ BUTN_7: 7,
48
+ home: 'home'
49
+ }
50
+
51
+ const ButtonType = {
52
+ NONE: '',
53
+ TOGGLE: 'TOGGLE',
54
+ PUSH: 'PUSH'
55
+ }
56
+
57
+ export class ButtonField {
58
+ #buttons = {}
59
+ #screen
60
+ width = 0
61
+ height = 0
62
+ #rows = 0
63
+ #columns = 0
64
+ #keys = []
65
+ #type
66
+ #name
67
+ #config
68
+
69
+ constructor (name, rows, columns, width, height, data, config) {
70
+ console.info(`ButtonField ${name.padEnd(10, ' ')} Buttons: ${rows} x ${columns} , Pixels ${width} x ${height}`)
71
+ this.#name = name
72
+ this.width = width
73
+ this.height = height
74
+ this.#rows = rows
75
+ this.#columns = columns
76
+ this.#screen = this.width > 0 && this.height > 0
77
+ this.#type = 'button'
78
+ if (this.#screen) { this.#type = 'touch' }
79
+
80
+ const keys = Object.keys(data)
81
+ for (let i = 0; i < keys.length; i++) {
82
+ const key = keys[i]
83
+ const tb = new Button(`${this.#type}-${key}`, width / columns, height / rows, data[key],key,config.parameters)
84
+ this.#buttons[key] = tb
85
+ }
86
+
87
+ this.#keys = keys
88
+ this.#config = config
89
+ }
90
+
91
+
92
+ async draw (device) {
93
+ if (!this.#screen) {
94
+ // physical buttons:
95
+ for (let i = 0; i < this.#keys.length; i++) {
96
+ const key = this.#keys[i]
97
+ this.#buttons[key].drawPhysical(device, key)
98
+ }
99
+ } else {
100
+ // screen:
101
+ device.drawScreen(this.#name, ctx => {
102
+ ctx.globalCompositeOperation = 'source-atop'
103
+ for (let i = 0; i < this.#keys.length; i++) {
104
+ const key = this.#keys[i]
105
+ const iValue = parseInt(key, 10)
106
+ const row = Math.floor(iValue / device.columns)
107
+ const column = iValue % device.columns
108
+
109
+ this.#buttons[key].draw(row, column, ctx)
110
+ }
111
+ })
112
+ }
113
+ }
114
+
115
+ setState (id, val) {
116
+ this.#buttons[id].setState(val)
117
+ }
118
+
119
+ setIntState (id, val) {
120
+ this.#buttons[id].setIntState(val)
121
+ }
122
+
123
+ async load () {
124
+ for (let i = 0; i < this.#keys.length; i++) {
125
+ const key = this.#keys[i]
126
+ if (isNaN(key)) {
127
+ await this.#buttons[key].load(this.#config)
128
+ } else {
129
+ const iVal = parseInt(key, 10)
130
+ await this.#buttons[iVal].load(this.#config)
131
+ }
132
+ }
133
+ }
134
+
135
+ async pressed (id) {
136
+ this.checkAndCreateButton(id)
137
+ const result = await this.#buttons[id].pressed()
138
+ if (!result) {
139
+ console.info(`pressed ${this.#type} ${id}`)
140
+ }
141
+ return result
142
+ }
143
+
144
+ async released (id) {
145
+ const result = await this.#buttons[id].released()
146
+ if (result) {
147
+ // disable all other buttons of the group, if this one had been activated:
148
+ for (let i = 0; i < this.#keys.length; i++) {
149
+ let key = this.#keys[i]
150
+ if (!isNaN(key)) { key = parseInt(key, 10) }
151
+ if (id === key) { continue }
152
+ if (this.#buttons[key].group === this.#buttons[id].group) {
153
+ this.#buttons[key].setState(0)
154
+ }
155
+ }
156
+ }
157
+ if (!result) {
158
+ console.info(`released ${this.#type} ${id}`)
159
+ }
160
+ return result
161
+ }
162
+
163
+ async changed(buttonID,nodeid,val){
164
+ for (let i = 0; i < this.#keys.length; i++) {
165
+ let bID = this.#keys[i]
166
+ const result = await this.#buttons[bID].changed(i,nodeid,val)
167
+ }
168
+ }
169
+
170
+ async rotated (id, delta) {
171
+ this.checkAndCreateButton(id)
172
+ const result = await this.#buttons[id].rotated(delta)
173
+ if (!result) { console.info(`rotated ${this.#type} ${id} ${delta}`) }
174
+ return result
175
+ }
176
+
177
+ async touchmove (id, x, y) {
178
+ this.checkAndCreateButton(id)
179
+ const result = await this.#buttons[id].touchmove(x, y)
180
+ if (!result) { console.info(`touchmove ${id} ${x} ${y}`) }
181
+ return result
182
+ }
183
+
184
+ checkAndCreateButton (id) {
185
+ if (!(id in this.#buttons)) {
186
+ const tb = new Button(id, 1, 1, id)
187
+ this.#buttons[id] = tb
188
+ }
189
+ }
190
+ }
191
+
192
+ export class Button {
193
+ #config
194
+ width = 0
195
+ height = 0
196
+
197
+ #type = ButtonType.TOGGLE
198
+
199
+ #min = 0
200
+ #max = 100
201
+ #value = 50
202
+ #name = undefined
203
+ #nodeid = ""
204
+
205
+ #index = 0
206
+ #event
207
+ #keys
208
+ #states
209
+
210
+ group = ''
211
+
212
+ text = ''
213
+ font = '16px Arial'
214
+
215
+ #x
216
+ #y
217
+ #moveLeft
218
+ #moveRight
219
+ #moveUp
220
+ #moveDown
221
+
222
+ // Timestamp when button was pressed
223
+ timeStampPressed
224
+ // Timestamp when button was released
225
+ timeStampReleased
226
+ // Time actually hold the button in ms
227
+ timeHold
228
+ // Minimum ammount of time in ms to press a button:
229
+ minPressed = 25
230
+ key = -1
231
+
232
+ constructor (id, width, height, data,key,params) {
233
+ this.id = id
234
+ this.key = key
235
+ this.width = width
236
+ this.height = height
237
+ this.#index = 0
238
+
239
+ if (data && data.states) {
240
+ this.group = data.group
241
+
242
+ this.#states = data.states
243
+ this.#keys = Object.keys(this.#states)
244
+ if (data.type) {
245
+ this.#type = data.type.toUpperCase()
246
+ }
247
+
248
+ if (data.minPressed) {
249
+ this.minPressed = data.minPressed
250
+ }
251
+
252
+ if (data.text) {
253
+ this.text = data.text
254
+ }
255
+ }
256
+ if (this.#states === undefined) {
257
+ this.#states = {}
258
+ this.#keys = []
259
+ }
260
+ if (data.nodeid){
261
+ this.#nodeid = format(data.nodeid, params)
262
+ }
263
+
264
+ }
265
+
266
+ setState (index = 0) {
267
+ this.#index = index
268
+ }
269
+
270
+ async drawPhysical (device, id) {
271
+ const elem = this.getCurrentElement()
272
+ if (!elem || !elem.color) { return }
273
+
274
+ const r = parseInt(elem.color.slice(1, 3), 16)
275
+ const g = parseInt(elem.color.slice(3, 5), 16)
276
+ const b = parseInt(elem.color.slice(5, 7), 16)
277
+
278
+ try {
279
+ var idx = parseInt(id, 10);
280
+
281
+ const val = {
282
+ id:idx,
283
+ color: `rgba(${r}, ${g}, ${b})`
284
+ }
285
+ // console.log(' Set Button Color',val.id,elem.color, val.color)
286
+ device.setButtonColor(val)
287
+ } catch (error) {
288
+ console.error(' Error', error)
289
+ }
290
+ }
291
+
292
+ async draw (row, column, ctx) {
293
+ const x = column * this.width
294
+ const y = row * this.height
295
+
296
+ const elem = this.getCurrentElement()
297
+
298
+ if (elem) {
299
+ if (elem.color) {
300
+ ctx.fillStyle = elem.color
301
+ ctx.fillRect(x, y, this.width, this.height)
302
+ }
303
+ if (elem.imgBuffer) {
304
+ ctx.drawImage(elem.imgBuffer, x, y, this.width, this.height)
305
+ }
306
+ }
307
+ if (this.text){
308
+ const lastElem = this.getLastElement()
309
+ ctx.fillStyle = lastElem.color
310
+ ctx.font = '20px Verdana'
311
+ ctx.textBaseline = 'top';
312
+ ctx.textAlign = 'left';
313
+ ctx.fillText(this.text,x+6,y+6)
314
+ }
315
+ }
316
+
317
+ async load (globalConfig) {
318
+ this.#config = globalConfig
319
+ for (let i = 0; i < this.#keys.length; i++) {
320
+ const key = this.#keys[i]
321
+ const elem = this.#states[key]
322
+ const file = elem.image
323
+ if (file !== undefined && file !== '') {
324
+ try {
325
+ this.#states[key].imgBuffer = await loadImage(file)
326
+ } catch (e) {
327
+ console.error('No such image', file)
328
+ return false
329
+ }
330
+ }
331
+ }
332
+ }
333
+
334
+ getCurrentElement () {
335
+ const key = this.#keys[this.#index]
336
+ return this.#states[key]
337
+ }
338
+
339
+ getLastElement () {
340
+ let i=this.#index-1
341
+ if (i < 0) { i = this.#keys.length - 1 }
342
+
343
+ const key = this.#keys[i]
344
+ return this.#states[key]
345
+ }
346
+
347
+ getCurrentText () {
348
+ return this.text
349
+ }
350
+
351
+ setIntState (val) {
352
+ this.#index = val
353
+ }
354
+
355
+ pressed () {
356
+ this.timeStampPressed = Date.now()
357
+
358
+ this.#index++
359
+ this.updateState(this.#index,"pressed")
360
+ return true
361
+ }
362
+
363
+ released () {
364
+ if (!this.getCurrentElement()) { return false }
365
+ this.timeStampReleased = Date.now()
366
+ this.timeHold = this.timeStampReleased - this.timeStampPressed
367
+
368
+ if (this.timeHold < this.minPressed) {
369
+ // Update the State according to the not correct pressed state
370
+ console.log('Did not hold minimum time of ', this.minPressed, 'only', this.timeHold)
371
+ this.#index--
372
+ if (this.#index < 0) { this.#index = this.#keys.length - 1 }
373
+ return false
374
+ }
375
+
376
+ // Update the State according to the correctly pressed state
377
+ switch (this.#type) {
378
+ case ButtonType.TOGGLE:
379
+ // do nothing
380
+ break
381
+ default:
382
+ this.#index--
383
+ if (this.#index < 0) { this.#index = this.#keys.length - 1 }
384
+
385
+ break
386
+ }
387
+
388
+ this.updateState(this.#index,"released")
389
+
390
+ return true // this.runCommand()
391
+ }
392
+
393
+ updateState(index,eventType){
394
+ this.#index = index
395
+ this.#event = eventType
396
+ // Update the State according to the correctly pressed state
397
+ if (this.#index < 0) { this.#index = this.#keys.length - 1 }
398
+ this.#index %= this.#keys.length
399
+ this.runCommand()
400
+ //console.log("TODO: expect newState", newState)
401
+ return true // this.runCommand()
402
+ }
403
+
404
+ async rotated (delta) {
405
+ if (!this.getCurrentElement()) { return false }
406
+
407
+ this.#event = "rotated"
408
+ this.#value = calcDelta(this.#value, delta, this.#max)
409
+ return this.runCommand()
410
+ }
411
+
412
+ async changed(buttonID,nodeid,val){
413
+ // Only handle updates within the same group identified by nodeid
414
+ if (nodeid !== this.#nodeid){
415
+ return
416
+ }
417
+
418
+ this.#index = 0;
419
+ for (let i = 0; i < this.#keys.length; i++) {
420
+ let key = this.#keys[i]
421
+ // check if the state-name is same as the value we get from outside:
422
+ if (val == key){
423
+ this.#index = i;
424
+ break;
425
+ }
426
+
427
+ // check if the nodeid is the same and the value is one of the states
428
+ let state = this.#states[key]
429
+ if (!state.value)
430
+ continue
431
+
432
+ const params = {
433
+ id: buttonID,
434
+ key: buttonID,
435
+ ...state
436
+ }
437
+ let val1 = format(state.value,params)
438
+ if (val1 === val){
439
+ this.#index = i;
440
+ break;
441
+ }
442
+ break;
443
+ }
444
+ }
445
+
446
+ async touchmove (x, y) {
447
+ // if (!this.getCurrentElement()) { return false }
448
+
449
+ if (x > this.#x) {
450
+ this.#moveRight = true
451
+ this.#moveLeft = false
452
+ } else if (x < this.#x) {
453
+ this.#moveRight = false
454
+ this.#moveLeft = true
455
+ }
456
+
457
+ if (y > this.#y) {
458
+ this.#moveDown = true
459
+ this.#moveUp = false
460
+ } else if (y < this.#y) {
461
+ this.#moveDown = false
462
+ this.#moveUp = true
463
+ }
464
+
465
+ this.#x = x
466
+ this.#y = y
467
+ // console.log(`d: ${this.#moveDown} r: ${this.#moveRight} `)
468
+ return false
469
+ }
470
+
471
+ async runCommand () {
472
+ const elem = this.getCurrentElement()
473
+ // Only continue, if we have an element:
474
+ if (!elem) {
475
+ return
476
+ }
477
+ // Filter for Event Type:
478
+ if (elem.filter && elem.filter != this.#event){
479
+ return
480
+ }
481
+
482
+ if (elem.profile){
483
+ profileEmitter.emit("profileChanged", elem.profile)
484
+ }
485
+
486
+ // Only continue, if we have an element, that contains some kind of command:
487
+ if (!elem.cmd && !elem.http && !elem.opcua) {
488
+ return
489
+ }
490
+
491
+ // Call an action - include dynamic parameters
492
+ // and also all attributes of elem + global config
493
+ const params = {
494
+ text: this.getCurrentText(),
495
+ ...this.#config.parameters,
496
+ ...elem,
497
+ id: this.id,
498
+ key: this.key,
499
+ event: this.#event,
500
+ state: this.#keys[this.#index],
501
+ min: this.#min,
502
+ max: this.#max,
503
+ }
504
+
505
+ if (!params.value)
506
+ params.value = this.#value
507
+
508
+
509
+ let res = ''
510
+ if ('cmd' in elem) {
511
+ if (shellinterface){
512
+ res = await shellinterface.call(elem.cmd, params)
513
+ }else{
514
+ console.warn("shellinterface not started")
515
+ }
516
+ }
517
+ if ('http' in elem) {
518
+ if (httpinterface){
519
+ res = await httpinterface.call(elem.http, params)
520
+ }else{
521
+ console.warn("httpinterface not started")
522
+ }
523
+ }
524
+ if ('opcua' in elem) {
525
+ if (opcuainterface){
526
+ res = await opcuainterface.call(elem.opcua, params)
527
+ }else{
528
+ console.warn("opcuainterface not started")
529
+ }
530
+ }
531
+
532
+ return res
533
+ }
534
+ }