loupedeck-commander 1.2.1 → 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.
- package/README.md +131 -131
- package/VERSION.md +32 -32
- package/common/ApplicationConfig.mjs +84 -84
- package/common/BaseLoupeDeckHandler.mjs +313 -313
- package/common/cmd-executer.mjs +16 -16
- package/common/index.mjs +5 -5
- package/common/touchbuttons.mjs +521 -513
- package/common/utils.mjs +29 -29
- package/config.json +8 -8
- package/eslint.config.mjs +8 -8
- package/example/ExampleDeviceHandler.mjs +44 -44
- package/example/example.mjs +21 -21
- package/index.mjs +3 -3
- package/interfaces/baseif.mjs +68 -68
- package/interfaces/httpif.mjs +81 -81
- package/interfaces/opcuaif.mjs +282 -282
- package/interfaces/shellif.mjs +47 -47
- package/package.json +29 -29
- package/profile-1.json +280 -275
- package/test.mjs +22 -22
package/common/touchbuttons.mjs
CHANGED
|
@@ -1,513 +1,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
|
-
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
getCurrentElement () {
|
|
335
|
-
const key = this.#keys[this.#index]
|
|
336
|
-
return this.#states[key]
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
this.#
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
this.
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
this
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if (
|
|
398
|
-
|
|
399
|
-
this
|
|
400
|
-
|
|
401
|
-
return this.runCommand()
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
async
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
if (
|
|
450
|
-
this.#
|
|
451
|
-
this.#
|
|
452
|
-
} else if (
|
|
453
|
-
this.#
|
|
454
|
-
this.#
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
this.#
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
//
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
+
}
|