loupedeck-commander 1.2.0 → 1.2.2

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