loupedeck-commander 1.4.2 → 1.4.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.
package/VERSION.md CHANGED
@@ -1,33 +1,33 @@
1
- # Versions
2
-
3
- ## 1.0.2
4
-
5
- 1. Updated button rendering strategy:
6
- - Render Background Color - per Button State
7
- - Render Image on Top (if available), works well with transparent images
8
-
9
- 2. Updated configuration strategy:
10
- - Keep all state related attributes per button-state also in config:
11
- example:
12
-
13
- ```json
14
- "states": {
15
- "off": {
16
- "color": "#aaaaaa",
17
- "image": "icons/bulb.png",
18
- "cmd": "echo \"{id} {state}\""
19
- },
20
- "on": {
21
- "color": "#11ff11",
22
- "image": "icons/bulb.png",
23
- "cmd": "echo \"{id} {state}\""
24
- }
25
- ```
26
-
27
- ## 1.0.1
28
-
29
- bugfixes and documentation
30
-
31
- ## 1.0.0
32
-
1
+ # Versions
2
+
3
+ ## 1.0.2
4
+
5
+ 1. Updated button rendering strategy:
6
+ - Render Background Color - per Button State
7
+ - Render Image on Top (if available), works well with transparent images
8
+
9
+ 2. Updated configuration strategy:
10
+ - Keep all state related attributes per button-state also in config:
11
+ example:
12
+
13
+ ```json
14
+ "states": {
15
+ "off": {
16
+ "color": "#aaaaaa",
17
+ "image": "icons/bulb.png",
18
+ "cmd": "echo \"{id} {state}\""
19
+ },
20
+ "on": {
21
+ "color": "#11ff11",
22
+ "image": "icons/bulb.png",
23
+ "cmd": "echo \"{id} {state}\""
24
+ }
25
+ ```
26
+
27
+ ## 1.0.1
28
+
29
+ bugfixes and documentation
30
+
31
+ ## 1.0.0
32
+
33
33
  first public release
@@ -14,6 +14,7 @@ export class BaseLoupeDeckHandler {
14
14
  buttons = {}
15
15
  knobs = {}
16
16
  screenUpdate = {}
17
+ counter = 0
17
18
  stopping = false
18
19
  IntervalID = undefined
19
20
 
@@ -197,7 +198,7 @@ export class BaseLoupeDeckHandler {
197
198
  await this.buttons.load()
198
199
  }
199
200
 
200
- //await this.updateScreens()
201
+ await this.updateScreens()
201
202
 
202
203
  await this.buttons.draw(this.device)
203
204
  // Initialize the Interfaces
@@ -247,11 +248,11 @@ export class BaseLoupeDeckHandler {
247
248
  //await this.updateScreens()
248
249
 
249
250
  this.IntervalID = setInterval(() => {
250
- // runs every 500ms seconds
251
+ // runs every 50ms seconds
251
252
  if (this.device.displays.center)
252
253
  this.screenUpdate["center"] = true
253
254
  self.updateScreens()
254
- }, 500);
255
+ }, 50);
255
256
 
256
257
 
257
258
  console.info('✅ Done initializing')
@@ -261,12 +262,20 @@ export class BaseLoupeDeckHandler {
261
262
  * Fore an update of all screens
262
263
  */
263
264
  async updateScreens () {
265
+ this.counter = this.counter + 1
264
266
  const keys = Object.keys(this.screenUpdate)
265
267
  for (let i = 0; i < keys.length; i++) {
266
268
  const screen = keys[i]
267
269
  //ok = await this.screens.center.updateAllButtons()
268
- if (this.screens[screen])
269
- await this.screens[screen].draw(this.device)
270
+ if (this.screens[screen]){
271
+ if (this.counter % 5 == 0) { // Only check for blink every 5 times, to reduce the load on the device and avoid flickering
272
+ await this.screens[screen].draw(this.device)
273
+ }else{
274
+ await this.screens[screen].updateAllButtons(this.device)
275
+ }
276
+
277
+ }
278
+
270
279
  }
271
280
  this.screenUpdate = {}
272
281
  }
@@ -1,4 +1,4 @@
1
- import {Button} from './button.mjs'
1
+ import { Button } from './button.mjs'
2
2
 
3
3
  export class ButtonField {
4
4
  #buttons = {}
@@ -11,8 +11,8 @@ export class ButtonField {
11
11
  #type
12
12
  #name
13
13
  #profile
14
-
15
- constructor (name, rows, columns, width, height, data, profile) {
14
+
15
+ constructor(name, rows, columns, width, height, data, profile) {
16
16
  console.info(`ButtonField ${name.padEnd(10, ' ')} Buttons: ${rows} x ${columns} , Pixels ${width} x ${height}`)
17
17
  this.#name = name
18
18
  this.width = width
@@ -27,15 +27,25 @@ export class ButtonField {
27
27
  const keys = Object.keys(data)
28
28
  for (let i = 0; i < keys.length; i++) {
29
29
  const key = keys[i]
30
- const tb = new Button(`${this.#type}-${key}`, width / columns, height / rows, data[key],key,this.#profile)
30
+ const tb = new Button(`${this.#type}-${key}`, width / columns, height / rows, data[key], key, this.#profile)
31
31
  this.#buttons[key] = tb
32
32
  }
33
33
 
34
34
  this.#keys = keys
35
35
  }
36
36
 
37
-
38
- async draw (device) {
37
+ async updateAllButtons(device) {
38
+ for (let i = 0; i < this.#keys.length; i++) {
39
+ const key = this.#keys[i]
40
+ const iValue = parseInt(key, 10)
41
+ const row = Math.floor(iValue / device.columns)
42
+ const column = iValue % device.columns
43
+
44
+ this.#buttons[key].draw(row, column)
45
+ }
46
+ }
47
+
48
+ async draw(device) {
39
49
  if (!this.#screen) {
40
50
  // physical buttons:
41
51
  for (let i = 0; i < this.#keys.length; i++) {
@@ -58,7 +68,7 @@ export class ButtonField {
58
68
  }
59
69
  }
60
70
 
61
- setState (id, val) {
71
+ setState(id, val) {
62
72
  this.#buttons[id].setState(val)
63
73
  }
64
74
 
@@ -66,7 +76,7 @@ export class ButtonField {
66
76
  this.#buttons[id].setIntState(val)
67
77
  }*/
68
78
 
69
- async load () {
79
+ async load() {
70
80
  for (let i = 0; i < this.#keys.length; i++) {
71
81
  const key = this.#keys[i]
72
82
  if (isNaN(key)) {
@@ -78,7 +88,7 @@ export class ButtonField {
78
88
  }
79
89
  }
80
90
 
81
- async pressed (id) {
91
+ async pressed(id) {
82
92
  this.checkAndCreateButton(id)
83
93
  const result = await this.#buttons[id].pressed()
84
94
  if (!result) {
@@ -92,7 +102,7 @@ export class ButtonField {
92
102
  * @param {*} id
93
103
  * @returns
94
104
  */
95
- async released (id) {
105
+ async released(id) {
96
106
  const result = await this.#buttons[id].released()
97
107
  if (result) {
98
108
  // disable all other buttons of the group, if this one had been activated:
@@ -103,7 +113,7 @@ export class ButtonField {
103
113
  if (id === key) { continue }
104
114
  let toggleGroup = this.#buttons[id].getGroup()
105
115
  // Ignore empty groups
106
- if (toggleGroup === undefined || toggleGroup == "" ) { continue }
116
+ if (toggleGroup === undefined || toggleGroup == "") { continue }
107
117
  // Switch all other elements of the same group off:
108
118
  if (this.#buttons[key].getGroup() === toggleGroup) {
109
119
  this.#buttons[key].setState(0)
@@ -122,13 +132,13 @@ export class ButtonField {
122
132
  * @param {*} nodeid
123
133
  * @param {*} val
124
134
  */
125
- async buttonStateChanged(changedButtonID,attribute,nodeid,val){
135
+ async buttonStateChanged(changedButtonID, attribute, nodeid, val) {
126
136
  // todo: filter for buttonID
127
137
  for (let i = 0; i < this.#keys.length; i++) {
128
138
  let buttonID = this.#keys[i]
129
139
  if (changedButtonID == buttonID)
130
- await this.#buttons[buttonID].buttonStateChanged(i,attribute,nodeid,val)
131
- }
140
+ await this.#buttons[buttonID].buttonStateChanged(i, attribute, nodeid, val)
141
+ }
132
142
  }
133
143
 
134
144
  /**
@@ -137,7 +147,7 @@ export class ButtonField {
137
147
  * @param {*} delta
138
148
  * @returns
139
149
  */
140
- async rotated (id, delta) {
150
+ async rotated(id, delta) {
141
151
  this.checkAndCreateButton(id)
142
152
  const result = await this.#buttons[id].rotated(delta)
143
153
  if (!result) { console.info(`rotated ${this.#type} ${id} ${delta}`) }
@@ -151,14 +161,15 @@ export class ButtonField {
151
161
  * @param {*} y
152
162
  * @returns
153
163
  */
154
- async touchmove (id, x, y) {
164
+ async touchmove(id, x, y) {
155
165
  this.checkAndCreateButton(id)
156
166
  const result = await this.#buttons[id].touchmove(x, y)
157
- if (!result) { console.info(`touchmove ${id} ${x} ${y}`) }
158
- return result
167
+ //if (!result) {
168
+ // console.info(`touchmove ${id} ${x} ${y}`) }
169
+ //return result
159
170
  }
160
171
 
161
- checkAndCreateButton (id) {
172
+ checkAndCreateButton(id) {
162
173
  if (!(id in this.#buttons)) {
163
174
  const tb = new Button(id, 1, 1, id)
164
175
  this.#buttons[id] = tb
package/common/button.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  import { loadImage } from 'canvas'
3
3
  //import { loadImage } from "https://deno.land/x/canvas/mod.ts";
4
- import { calcDelta, invertColor } from './utils.mjs'
4
+ import { calcDelta, darkerColor } from './utils.mjs'
5
5
  import format from 'string-template'
6
6
 
7
7
  import { shellinterface, httpinterface, opcuainterface, profileEmitter } from '../interfaces/interfaces.mjs'
@@ -21,9 +21,12 @@ export class Button {
21
21
  #index = 0
22
22
  #enforcedIndex = -1
23
23
  #enforcedBlink = false
24
+ inversed = false
24
25
  #event
25
26
  #keys
26
27
  #states
28
+ // PressActive
29
+ pressActive = false
27
30
  // Timestamp when button was pressed
28
31
  timeStampPressed
29
32
  // Timestamp when button was released
@@ -37,9 +40,9 @@ export class Button {
37
40
  this.#keys = []
38
41
 
39
42
  this.id = id
40
- this.#index = 0
41
- this.#enforcedIndex = -1
42
- this.#enforcedBlink = false
43
+ //this.#index = 0
44
+ //this.#enforcedIndex = -1
45
+ //this.#enforcedBlink = false
43
46
 
44
47
  // console.info(` Button ${id.padEnd(10, ' ')} Size: ${width} x ${height}`)
45
48
 
@@ -193,19 +196,35 @@ export class Button {
193
196
  async draw(row, column, ctx) {
194
197
  let params = this.getParams(this.getCurrentElement())
195
198
  let x = 0
196
- let y = 0
199
+ let y = 0
197
200
 
198
201
  // Adjust for non-square buttons:
199
202
  if (params.width > params.height) {
200
- x += (2+column) * (params.width - params.height) / 2
203
+ x += (2 + column) * (params.width - params.height) / 2
201
204
  params.width = params.height
202
205
  }
203
-
206
+
207
+ // If we have a filter for the event type, we check if the current event matches the filter. If not, we do not execute the command:
208
+ if (this.pressActive && params.filter == "released") {
209
+ this.timeHold = Date.now() - this.timeStampPressed
210
+ let overTime = this.timeHold - params.minPressed
211
+ if (overTime > 0 && overTime < 100) {
212
+ // Update the State according to the not correct pressed state
213
+ console.log('Minimum time of ', this.#params.minPressed, 'reached:', this.timeHold)
214
+ profileEmitter.emit("vibrate", 0x01) // Vibrate with a short pulse to indicate the button is pressed long enough
215
+ }
216
+ }
217
+ this.#counter = this.#counter + 1
218
+ // Only render the screen every 500ms (all 5 counter increments
219
+ if (!ctx) {
220
+ return
221
+ }
222
+
204
223
  // Calculate the x/y position based on the row and column and the width/height properties of the button
205
224
  x += column * params.width
206
- y += row * params.height
225
+ y += row * params.height
226
+
207
227
 
208
-
209
228
  // Modifications in draw:
210
229
  // - Support color and textColor settings also in params section which
211
230
  // can be overwriten by button state property. This way color and textColor
@@ -214,15 +233,24 @@ export class Button {
214
233
 
215
234
 
216
235
 
236
+ if (((params.blink === true) || this.#enforcedBlink)) {
237
+ this.inversed = !this.inversed
238
+ }
239
+
217
240
  let color = params.color
218
241
  let textColor = params.textColor
219
242
 
220
- if (((params.blink === true) || this.#enforcedBlink) && this.#counter % 2 == 0) {
221
- // If blink is set, we switch textcolor and background color:
243
+ if (this.inversed) { // If blink is set, we switch textcolor and background color:
222
244
  color = params.textColor
223
245
  textColor = params.color
224
246
  }
225
247
 
248
+ if (this.pressActive) {
249
+ // If the button is currently pressed, we apply a darken effect to the color:
250
+ color = darkerColor(color, 0.5)
251
+ }
252
+
253
+
226
254
  // Apply the color as fillStyle:
227
255
  ctx.fillStyle = color
228
256
  // Draw the background of the button::
@@ -269,8 +297,6 @@ export class Button {
269
297
  }
270
298
  ctx.fillText(dynamicText, tx, ty)
271
299
  }
272
-
273
- this.#counter = this.#counter + 1
274
300
  }
275
301
 
276
302
  async load(globalConfig) {
@@ -278,7 +304,11 @@ export class Button {
278
304
  for (let i = 0; i < this.#keys.length; i++) {
279
305
  const key = this.#keys[i]
280
306
  const elem = this.#states[key]
281
- const file = elem.image
307
+ let file = elem.image
308
+ if (file === undefined && this.#params.image) {
309
+ // If we do not have an image defined on state level, but we have an image defined on params level, we use the image from params:
310
+ file = this.#params.image
311
+ }
282
312
  if (file !== undefined && file !== '') {
283
313
  try {
284
314
  this.#states[key].imgBuffer = await loadImage(file)
@@ -302,11 +332,12 @@ export class Button {
302
332
  * @returns
303
333
  */
304
334
  pressed() {
335
+ this.pressActive = true
305
336
  this.timeStampPressed = Date.now()
306
337
  this.updateState(this.#index, "pressed", true)
307
338
  this.#index++
308
339
 
309
- if (this.#enforcedIndex >= 0) {
340
+ if (this.#enforcedIndex >= 0 && this.#index != this.#enforcedIndex) {
310
341
  console.log("Enforced Index", this.#enforcedIndex)
311
342
  this.#index = this.#enforcedIndex
312
343
  }
@@ -325,6 +356,8 @@ export class Button {
325
356
  * @returns
326
357
  */
327
358
  released() {
359
+ this.pressActive = false
360
+ this.inversed = false; // reset inversed state on release, so that blink starts from the defined color again on next press
328
361
  let elem = this.getCurrentElement()
329
362
  if (!elem) { return false }
330
363
  this.timeStampReleased = Date.now()
@@ -405,22 +438,24 @@ export class Button {
405
438
  // check if the state-name is same as the value we get from outside:
406
439
  if (valStr == stateStr) {
407
440
  //if (i !== this.#index) {
408
- //console.info("enforce State index", this.id, nodeid, val, i)
409
- this.#enforcedIndex = i;
441
+ //console.info("enforce State index", this.id, nodeid, val, i)
442
+ this.#enforcedIndex = i;
410
443
  //}
411
444
  break;
412
445
  }
413
446
  }
414
447
  //console.info("Changed index", this.id, nodeid, val, this.#enforcedIndex)
415
448
  this.#index = this.#enforcedIndex;
449
+ this.inversed = false
450
+ this.pressActive = false
416
451
  //console.info("enforce State index", this.id, nodeid, val, i)
417
452
  //this.#enforcedIndex = this.#index;
418
453
  }
419
-
454
+
420
455
 
421
456
  if (attribute == "blink") {
422
- console.info("Changed blink", buttonID, nodeid, val)
423
- this.#enforcedBlink = val;
457
+ console.info("Changed blink", buttonID, nodeid, val)
458
+ this.#enforcedBlink = val;
424
459
  }
425
460
  }
426
461
 
@@ -458,7 +493,7 @@ export class Button {
458
493
  this.#params.y = (y % 100)
459
494
 
460
495
  // Calculate delta for value no touchmove up/down
461
- this.#params.value = calcDelta(this.#params.value, delta, this.#params.min, this.#params.max)
496
+ this.#params.touchvalue = calcDelta(this.#params.touchvalue, delta, this.#params.min, this.#params.max)
462
497
 
463
498
  // console.log(`d: ${this.#params.moveDown} r: ${this.#params.moveRight} `)
464
499
  return false
File without changes
package/common/index.mjs CHANGED
File without changes
package/common/utils.mjs CHANGED
@@ -1,12 +1,13 @@
1
1
  import { readFileSync, writeFileSync } from 'node:fs'
2
2
  import YAML from 'yaml'
3
+ import JSON5 from 'json5'
3
4
 
4
5
  /**
5
6
  * Read a Config file in either JSON or YAML Fileformat
6
7
  * @param {string} fileName - The name of the file to read
7
8
  * @returns {object} - The parsed object from the file
8
9
  */
9
- export function readConfigFile (fileName) {
10
+ export function readConfigFile(fileName) {
10
11
  let obj = undefined
11
12
  try {
12
13
  //console.log("Reading File:", fileName)
@@ -14,14 +15,18 @@ export function readConfigFile (fileName) {
14
15
  // If the file is a YAML file, parse it
15
16
  if (fileName.endsWith('.yaml') || fileName.endsWith('.yml')) {
16
17
  obj = YAML.parse(data)
17
- } else if (fileName.endsWith('.json')) {
18
+ } else if (fileName.endsWith('.json') || fileName.endsWith('.json5')) {
18
19
  // If the file is a JSON file, parse it
19
- obj = JSON.parse(data)
20
+ obj = JSON5.parse(data)
20
21
  // automatically convert all json to yaml
21
- writeYAMLFile(fileName.replace(".json",".yaml"), obj)
22
+ writeYAMLFile(fileName.replace(".json", ".yaml"), obj)
23
+ }
24
+ } catch (err) {
25
+ if (err.code === 'ENOENT') {
26
+ console.error(`Error reading file: File ${fileName} not found.`);
27
+ } else {
28
+ console.error(`Unexpected error: ${err.message}`);
22
29
  }
23
- } catch (error) {
24
- console.info(`Error reading File: ${fileName}`, error)
25
30
  }
26
31
  return obj
27
32
  }
@@ -30,12 +35,12 @@ export function readConfigFile (fileName) {
30
35
  * Write a JSON File
31
36
  */
32
37
  export function writeJSONFile (fileName, jsonObj) {
33
- const data = JSON.stringify(jsonObj, null, 4)
38
+ const data = JSON5.stringify(jsonObj, null, 4)
34
39
  writeFileSync(fileName, data)
35
40
  }
36
41
 
37
42
  /**
38
- * Write a JSON File
43
+ * Write a YAML File
39
44
  */
40
45
  export function writeYAMLFile (fileName, jsonObj) {
41
46
  const data = YAML.stringify(jsonObj)
@@ -68,12 +73,25 @@ export function invertColor(colorAsHex){
68
73
  for (var i = 0; i < rgb.length; i++) {
69
74
  rgb[i] = (i === 3 ? 1 : 255) - rgb[i];
70
75
  }
71
- let invertedColor = `#${rgb[0].toString(16)}${rgb[1].toString(16)}${rgb[2].toString(16)})`
72
- //let invertedColor = `#${rgb[0].toString(16).padStart(2, '0')}${rgb[1].toString(16).padStart(2, '0')}${rgb[2].toString(16).padStart(2, '0')})`
76
+ let invertedColor = `#${ rgb[0].toString(16) }${ rgb[1].toString(16) }${ rgb[2].toString(16) })`
77
+ //let invertedColor = `#${ rgb[0].toString(16).padStart(2, '0') }${ rgb[1].toString(16).padStart(2, '0') }${ rgb[2].toString(16).padStart(2, '0') })`
73
78
  //console.log("invert", colorAsHex,"=>",invertedColor)
74
79
  return invertedColor
75
80
  }
76
81
 
82
+ /**
83
+ * Invert a color in hex format (#RRGGBB) to its inverted color
84
+ * @param {*} colorAsHex
85
+ * @returns
86
+ */
87
+ export function darkerColor(colorAsHex, factor = 0.5){
88
+ let rgb = colorToRGB(colorAsHex)
89
+ for (var i = 0; i < rgb.length; i++) {
90
+ rgb[i] = factor* rgb[i];
91
+ }
92
+ return `#${ rgb[0].toString(16) }${ rgb[1].toString(16) }${ rgb[2].toString(16) })`
93
+ }
94
+
77
95
  /**
78
96
  * Convert a color in hex format (#RRGGBB) to RGB array
79
97
  * @param {*} colorAsHex
package/eslint.config.mjs CHANGED
@@ -1,9 +1,9 @@
1
- import globals from "globals";
2
- import pluginJs from "@eslint/js";
3
-
4
-
5
- /** @type {import('eslint').Linter.Config[]} */
6
- export default [
7
- {languageOptions: { globals: globals.browser }},
8
- pluginJs.configs.recommended,
1
+ import globals from "globals";
2
+ import pluginJs from "@eslint/js";
3
+
4
+
5
+ /** @type {import('eslint').Linter.Config[]} */
6
+ export default [
7
+ {languageOptions: { globals: globals.browser }},
8
+ pluginJs.configs.recommended,
9
9
  ];
@@ -1,44 +1,44 @@
1
- import { HAPTIC } from 'loupedeck'
2
- import { BaseLoupeDeckHandler } from '../common/BaseLoupeDeckHandler.mjs'
3
-
4
- /**
5
- * Our Special-Handler just used the Default - and adds Vibration after triggers through Button-Releases
6
- */
7
- export class ExampleDeviceHandler extends BaseLoupeDeckHandler {
8
- /**
9
- * Handle different Vibration-Feedback on OK (true), and NOK (false)
10
- * @param {*} ok
11
- */
12
- async vibrateHandler (ok) {
13
- if (ok) { await this.device.vibrate(HAPTIC.REV_FASTEST) } else {
14
- this.device.vibrate(HAPTIC.RUMBLE2)
15
- }
16
- }
17
-
18
- /**
19
- * Handle Button-Up Events with Vibration Handler
20
- * @param {*} event
21
- */
22
- async onButtonUp (event) {
23
- const res = await super.onButtonUp(event)
24
- await this.vibrateHandler(res)
25
- }
26
-
27
- /**
28
- * Handle Knob-Rotation Events with Vibration Handler
29
- * @param {*} event
30
- */
31
- async onRotate (event) {
32
- const res = await super.onRotate(event)
33
- await this.vibrateHandler(res)
34
- }
35
-
36
- /**
37
- * Handle Touch-End Events with Vibration Handler
38
- * @param {*} event
39
- */
40
- async onTouchEnd (event) {
41
- const res = await super.onTouchEnd(event)
42
- await this.vibrateHandler(res)
43
- }
44
- }
1
+ import { HAPTIC } from 'loupedeck'
2
+ import { BaseLoupeDeckHandler } from '../common/BaseLoupeDeckHandler.mjs'
3
+
4
+ /**
5
+ * Our Special-Handler just used the Default - and adds Vibration after triggers through Button-Releases
6
+ */
7
+ export class ExampleDeviceHandler extends BaseLoupeDeckHandler {
8
+ /**
9
+ * Handle different Vibration-Feedback on OK (true), and NOK (false)
10
+ * @param {*} ok
11
+ */
12
+ async vibrateHandler (ok) {
13
+ if (ok) { await this.device.vibrate(HAPTIC.REV_FASTEST) } else {
14
+ this.device.vibrate(HAPTIC.RUMBLE2)
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Handle Button-Up Events with Vibration Handler
20
+ * @param {*} event
21
+ */
22
+ async onButtonUp (event) {
23
+ const res = await super.onButtonUp(event)
24
+ await this.vibrateHandler(res)
25
+ }
26
+
27
+ /**
28
+ * Handle Knob-Rotation Events with Vibration Handler
29
+ * @param {*} event
30
+ */
31
+ async onRotate (event) {
32
+ const res = await super.onRotate(event)
33
+ await this.vibrateHandler(res)
34
+ }
35
+
36
+ /**
37
+ * Handle Touch-End Events with Vibration Handler
38
+ * @param {*} event
39
+ */
40
+ async onTouchEnd (event) {
41
+ const res = await super.onTouchEnd(event)
42
+ await this.vibrateHandler(res)
43
+ }
44
+ }
@@ -1,21 +1,21 @@
1
- import { ExampleDeviceHandler } from './ExampleDeviceHandler.mjs'
2
-
3
- const handler = new ExampleDeviceHandler('config.json')
4
-
5
-
6
- /**
7
- * Stop the handlers when a signal like SIGINT or SIGTERM arrive
8
- * @param {*} signal
9
- */
10
- const stopHandler = async(signal) => {
11
- console.log(`Receiving ${signal} => Stopping processes.`)
12
- await handler.stop()
13
- }
14
-
15
- // Initiating the signal handlers:
16
- // see https://www.tutorialspoint.com/unix/unix-signals-traps.htm
17
- process.on('SIGINT', async (signal) => { stopHandler(signal) })
18
- process.on('SIGTERM', async (signal) => { stopHandler(signal) })
19
-
20
- // Initiating a process
21
- await handler.start()
1
+ import { ExampleDeviceHandler } from './ExampleDeviceHandler.mjs'
2
+
3
+ const handler = new ExampleDeviceHandler('config.json')
4
+
5
+
6
+ /**
7
+ * Stop the handlers when a signal like SIGINT or SIGTERM arrive
8
+ * @param {*} signal
9
+ */
10
+ const stopHandler = async(signal) => {
11
+ console.log(`Receiving ${signal} => Stopping processes.`)
12
+ await handler.stop()
13
+ }
14
+
15
+ // Initiating the signal handlers:
16
+ // see https://www.tutorialspoint.com/unix/unix-signals-traps.htm
17
+ process.on('SIGINT', async (signal) => { stopHandler(signal) })
18
+ process.on('SIGTERM', async (signal) => { stopHandler(signal) })
19
+
20
+ // Initiating a process
21
+ await handler.start()
package/index.mjs CHANGED
File without changes
File without changes
File without changes
File without changes
@@ -87,7 +87,7 @@ export class OPCUAIf extends BaseIf {
87
87
 
88
88
  let monitoredItemId = await this.Subscribe(nodeID, options, attribute)
89
89
  if (monitoredItemId) {
90
- console.log("Register nodeid", nodeID, monitoredItemId, buttonID, attribute,)
90
+ console.log(`Register nodeid: ${nodeID}, monitoredItemId: ${monitoredItemId}, buttonID: ${buttonID}, attribute: ${attribute}`)
91
91
  if (this.buttons[monitoredItemId] == undefined)
92
92
  this.buttons[monitoredItemId] = []
93
93
  this.buttons[monitoredItemId].push({
@@ -167,6 +167,8 @@ export class OPCUAIf extends BaseIf {
167
167
  return value.toString();
168
168
  return value
169
169
  case DataType.Boolean:
170
+ if (typeof value == "boolean" )
171
+ return value
170
172
  if (typeof value == "number" && value === 1)
171
173
  return true
172
174
  if (typeof value == "string") {
@@ -211,7 +213,7 @@ export class OPCUAIf extends BaseIf {
211
213
  value = super.formatString(options.value, options)
212
214
 
213
215
  var convertedValue = this.convert(value, type)
214
- this.LogInfo(`OPCUAIf: write ${nodeId} => ${value}\n`)
216
+ //this.LogInfo(`OPCUAIf: write ${nodeId} => ${value}\n`)
215
217
  await this.Write(nodeId, convertedValue, type)
216
218
 
217
219
  var NewState = "waiting"
@@ -280,7 +282,7 @@ export class OPCUAIf extends BaseIf {
280
282
  this.#client = OPCUAClient.create({
281
283
  applicationName: "NodeOPCUA-Client",
282
284
  endpointMustExist: true,
283
- // keepSessionAlive: true,
285
+ //keepSessionAlive: true,
284
286
  requestedSessionTimeout: 60 * 1000,
285
287
  securityMode: MessageSecurityMode.None,
286
288
  securityPolicy: SecurityPolicy.None,
@@ -289,7 +291,7 @@ export class OPCUAIf extends BaseIf {
289
291
  maxDelay: 5000,
290
292
  initialDelay: 2500
291
293
  },
292
-
294
+ keepPendingSessionsOnDisconnect: true,
293
295
  defaultSecureTokenLifetime: 20000,
294
296
  tokenRenewalInterval: 1000
295
297
  });
@@ -424,11 +426,13 @@ export class OPCUAIf extends BaseIf {
424
426
  self.types[nodeId] = dataValue.value.dataType
425
427
  // publish the value to subscribers:
426
428
  let buttons = self.buttons[this.monitoredItemId]
429
+ let list = []
427
430
  for (var i = 0; i < buttons.length; i++) {
428
431
  var buttonInfo = buttons[i]
429
- self.LogInfo(`OPCUAIf: monitored item changed: ${buttonInfo.buttonID} ${buttonInfo.attribute} ${nodeId} => ${dataValue.value.value}\n`);
432
+ list.push(`${buttonInfo.buttonID}:${buttonInfo.attribute}`)
430
433
  self.emit('monitored item changed', buttonInfo.buttonID, buttonInfo.attribute, nodeId, dataValue.value.value)
431
434
  }
435
+ self.LogInfo(`OPCUAIf: monitored item ${nodeId} changed value: ${dataValue.value.value} - push to buttons: ${list.join(', ')}\n`);
432
436
  });
433
437
 
434
438
  return monitoredItem.monitoredItemId;
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loupedeck-commander",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
4
4
  "description": "A system to ease working with LoupeDeck devices using CMD-line, OPC/UA or HTTP-client interfaces",
5
5
  "main": "index.mjs",
6
6
  "scripts": {
@@ -11,11 +11,12 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "canvas": "^3.2.1",
14
+ "json5": "^2.2.3",
14
15
  "loupedeck": "^7.0.3",
15
16
  "mkdirp": "^3.0.1",
16
- "node-opcua": "^2.160.0",
17
+ "node-opcua": "^2.169.0",
17
18
  "string-template": "^1.0.0",
18
- "yaml": "^2.8.2"
19
+ "yaml": "^2.8.3"
19
20
  },
20
21
  "author": "Thomas Schneider",
21
22
  "license": "Apache-2.0",
package/test_opcua.mjs ADDED
@@ -0,0 +1,11 @@
1
+ import { ApplicationConfig } from './common/ApplicationConfig.mjs'
2
+ import { InitializeInterfaces, StopInterfaces, opcuainterface, profileEmitter } from './interfaces/interfaces.mjs'
3
+
4
+ var config='config.yaml'
5
+
6
+ let appConfig = new ApplicationConfig()
7
+ appConfig.loadFromFile(config)
8
+
9
+ await InitializeInterfaces(appConfig)
10
+
11
+ await StopInterfaces()