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