loupedeck-commander 1.2.7 → 1.2.9

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,4 +1,4 @@
1
- import { readJSONFile, writeJSONFile } from './utils.mjs'
1
+ import { readJSONFile, writeJSONFile,syncParams } from './utils.mjs'
2
2
 
3
3
  export class ApplicationConfig {
4
4
  application = 'undefined'
@@ -42,6 +42,7 @@ class Profile {
42
42
  knobs = {}
43
43
  buttons = {}
44
44
  parameters = {}
45
+ default = {}
45
46
  #file = ''
46
47
  loaded = false
47
48
  #error = true
@@ -57,6 +58,7 @@ class Profile {
57
58
  this.buttons = new ButtonConfig().buttons
58
59
  this.knobs = new KnobsConfig().knobs
59
60
  this.parameters = new ParametersConfig().parameters
61
+ this.default = new DefaultConfig().default
60
62
 
61
63
  this.loadFromFile(this.#file)
62
64
  if (this.#error) { this.saveToFile(`profile-${this.name}-sav.json`) }
@@ -83,15 +85,16 @@ class Profile {
83
85
  }
84
86
  this.profile = config.profile
85
87
  this.description = config.description
88
+ // Load Parameters.parameters = config.parameters
89
+ this.parameters = new ParametersConfig(config.parameters).parameters
90
+ this.default = new DefaultConfig(config.default).default
86
91
 
87
92
  // Load the Configurations for Touch-Displays
88
- this.touch = new TouchConfig(config.touch)
93
+ this.touch = new TouchConfig(config.touch,this.default)
89
94
  // Load the Configurations for Button-Areas
90
95
 
91
96
  this.buttons = new ButtonConfig(config.buttons,this.#profileCount,this.#index).buttons
92
97
  this.knobs = new KnobsConfig(config.knobs).knobs
93
- // Load Parameters.parameters = config.parameters
94
- this.parameters = new ParametersConfig(config.parameters).parameters
95
98
 
96
99
  this.#error = false
97
100
  this.loaded = true
@@ -132,11 +135,11 @@ class TouchConfig {
132
135
  } // RIGHT Display Config - Available in CT & LIVE
133
136
  //knob = {} // KNOB Display Config - Available only in CT
134
137
 
135
- constructor (data) {
136
- this.loadFromJSON(data)
138
+ constructor (data,defaultState) {
139
+ this.loadFromJSON(data,defaultState)
137
140
  }
138
141
 
139
- loadFromJSON (data) {
142
+ loadFromJSON (data,defaultState) {
140
143
  if (!data)
141
144
  return
142
145
  if (!data.center)
@@ -150,7 +153,21 @@ class TouchConfig {
150
153
  this.center = data.center
151
154
  this.left = data.left
152
155
  this.right = data.right
153
- //this.knob = data.knob
156
+
157
+ /**
158
+ * Sychronize the states of the buttons with the default state - add missing options from default state
159
+ */
160
+ var buttons = Object.keys(this.center)
161
+ for (var buttonID=0;buttonID<buttons.length;buttonID++){
162
+ if (!this.center[buttonID])
163
+ continue
164
+ var states = Object.keys(this.center[buttonID].states)
165
+ for (var stateID=0;stateID<states.length;stateID++){
166
+ var stateKey = states[stateID]
167
+ var stateOld = this.center[buttonID].states[stateKey]
168
+ this.center[buttonID].states[stateKey] = syncParams(stateOld,defaultState)
169
+ }
170
+ }
154
171
  }
155
172
  }
156
173
 
@@ -221,8 +238,10 @@ class ParametersConfig {
221
238
  parameters = {
222
239
  "hostname": "127.0.0.1",
223
240
  "endpointurl": "opc.tcp://{hostname}:4840",
224
- "nodeid" : "ns=0;s=nodeID"
225
- } // KNOB Config - Available only in CT
241
+ "nodeid" : "ns=0;s=nodeID",
242
+ "min" : 0,
243
+ "max" : 100
244
+ }
226
245
 
227
246
  constructor (data) {
228
247
  this.loadFromJSON(data)
@@ -234,3 +253,20 @@ class ParametersConfig {
234
253
  this.parameters = data
235
254
  }
236
255
  }
256
+
257
+ class DefaultConfig {
258
+ default = {
259
+ "filter": "released",
260
+ "color": "#111111"
261
+ }
262
+
263
+ constructor (data) {
264
+ this.loadFromJSON(data)
265
+ }
266
+
267
+ loadFromJSON (data) {
268
+ if (!data)
269
+ return
270
+ this.default = data
271
+ }
272
+ }
@@ -166,6 +166,8 @@ export class BaseLoupeDeckHandler {
166
166
  await this.updateScreens()
167
167
 
168
168
  await this.buttons.draw(this.device)
169
+ // Initialize the Interfaces
170
+ await InitializeInterfaces(profile)
169
171
 
170
172
  }
171
173
 
@@ -186,8 +188,9 @@ export class BaseLoupeDeckHandler {
186
188
 
187
189
  await this.activateProfile(0)
188
190
 
189
- var profile = this.getCurrentProfile()
190
- await InitializeInterfaces(profile,this.buttons.setState)
191
+ // move into activate profile function to call init with every profile change
192
+ // var profile = this.getCurrentProfile()
193
+ //await InitializeInterfaces(profile)
191
194
 
192
195
  const self = this
193
196
 
@@ -333,6 +336,7 @@ export class BaseLoupeDeckHandler {
333
336
  this.screenUpdate["right"] = true
334
337
 
335
338
  ok = await this.screens.center.changed(buttonID,nodeid,val)
339
+ ok = await this.knobs.changed(buttonID,nodeid,val)
336
340
 
337
341
  await this.updateScreens()
338
342
  return ok
@@ -5,7 +5,7 @@ import * as shellif from '../interfaces/shellif.mjs'
5
5
  import * as httpif from '../interfaces/httpif.mjs'
6
6
  import * as opcuaif from '../interfaces/opcuaif.mjs'
7
7
  import format from 'string-template'
8
- import { calcDelta } from './utils.mjs'
8
+ import { calcDelta, invertColor } from './utils.mjs'
9
9
  import { EventEmitter } from 'node:events'
10
10
 
11
11
 
@@ -13,17 +13,18 @@ export var opcuainterface = undefined
13
13
  var httpinterface = undefined
14
14
  var shellinterface = undefined
15
15
  export var profileEmitter = undefined
16
- export async function InitializeInterfaces(appConfig,callbackFunction){
16
+ export async function InitializeInterfaces(appConfig){
17
17
  if (opcuainterface === undefined ){
18
18
  opcuainterface = new opcuaif.OPCUAIf()
19
- opcuainterface.init(appConfig.parameters,appConfig,callbackFunction)
20
19
  }
20
+ // the opcua interface needs the profile to register nodes with subscriptions:
21
+ opcuainterface.init(appConfig.parameters,appConfig)
21
22
  if (httpinterface === undefined)
22
23
  httpinterface = new httpif.HTTPif()
23
24
  if (shellinterface === undefined)
24
25
  shellinterface = new shellif.SHELLif()
25
-
26
- profileEmitter = new EventEmitter()
26
+ if (profileEmitter === undefined)
27
+ profileEmitter = new EventEmitter()
27
28
  }
28
29
 
29
30
  export async function StopInterfaces(){
@@ -81,7 +82,7 @@ export class ButtonField {
81
82
  const keys = Object.keys(data)
82
83
  for (let i = 0; i < keys.length; i++) {
83
84
  const key = keys[i]
84
- const tb = new Button(`${this.#type}-${key}`, width / columns, height / rows, data[key],key,this.#profile.parameters)
85
+ const tb = new Button(`${this.#type}-${key}`, width / columns, height / rows, data[key],key,this.#profile)
85
86
  this.#buttons[key] = tb
86
87
  }
87
88
 
@@ -191,6 +192,8 @@ export class ButtonField {
191
192
 
192
193
  export class Button {
193
194
  #profile
195
+ #params
196
+ #data
194
197
  width = 0
195
198
  height = 0
196
199
 
@@ -229,7 +232,7 @@ export class Button {
229
232
  minPressed = 25
230
233
  key = -1
231
234
 
232
- constructor (id, width, height, data,key,params) {
235
+ constructor (id, width, height, data,key,profile) {
233
236
  this.id = id
234
237
  this.key = key
235
238
  this.width = width
@@ -237,8 +240,10 @@ export class Button {
237
240
  this.#index = 0
238
241
 
239
242
  if (data && data.states) {
240
- this.group = data.group
243
+ this.#data = data
241
244
 
245
+ this.group = data.group
246
+
242
247
  this.#states = data.states
243
248
  this.#keys = Object.keys(this.#states)
244
249
  if (data.type) {
@@ -253,12 +258,23 @@ export class Button {
253
258
  this.text = data.text
254
259
  }
255
260
  }
261
+ if (profile === undefined){
262
+ this.#profile = {}
263
+ this.#params = {}
264
+ }else{
265
+ this.#profile = profile
266
+ this.#params = profile.parameters
267
+ if (profile.parameters.min !== undefined)
268
+ this.#min = profile.parameters.min
269
+ if (profile.parameters.max !== undefined)
270
+ this.#max = profile.parameters.max
271
+ }
256
272
  if (this.#states === undefined) {
257
273
  this.#states = {}
258
274
  this.#keys = []
259
275
  }
260
276
  if (data.nodeid){
261
- this.#nodeid = format(data.nodeid, params)
277
+ this.#nodeid = format(data.nodeid, this.#params)
262
278
  }
263
279
  if (data.default) {
264
280
  this.#index = this.#keys.indexOf(data.default)
@@ -308,8 +324,10 @@ export class Button {
308
324
  }
309
325
  }
310
326
  if (this.text){
311
- const lastElem = this.getLastElement()
312
- ctx.fillStyle = lastElem.color
327
+ //const lastElem = this.getLastElement()
328
+ // Only change the text color, if it differnce from the currently set color
329
+
330
+ ctx.fillStyle = invertColor(elem.color)
313
331
  ctx.font = '20px Verdana'
314
332
  ctx.textBaseline = 'top';
315
333
  ctx.textAlign = 'left';
@@ -408,7 +426,7 @@ export class Button {
408
426
  if (!this.getCurrentElement()) { return false }
409
427
 
410
428
  this.#event = "rotated"
411
- this.#value = calcDelta(this.#value, delta, this.#max)
429
+ this.#value = calcDelta(this.#value, delta, this.#min, this.#max)
412
430
  return this.runCommand()
413
431
  }
414
432
 
@@ -429,20 +447,21 @@ export class Button {
429
447
 
430
448
  // check if the nodeid is the same and the value is one of the states
431
449
  let state = this.#states[key]
432
- if (!state.value)
450
+ if (state.value === undefined)
433
451
  continue
434
452
 
435
453
  const params = {
436
454
  id: buttonID,
437
455
  key: buttonID,
456
+ state : key,
438
457
  ...state
439
458
  }
440
459
  let val1 = format(state.value,params)
441
- if (val1 === val){
460
+ if (val1 === val.toString()){
442
461
  this.#index = i;
443
462
  break;
444
463
  }
445
- break;
464
+ //break;
446
465
  }
447
466
  }
448
467
 
@@ -507,7 +526,7 @@ export class Button {
507
526
  y: (this.#y %100)
508
527
  }
509
528
 
510
- if (!params.value)
529
+ if (params.value === undefined)
511
530
  params.value = this.#value
512
531
 
513
532
 
@@ -529,6 +548,12 @@ export class Button {
529
548
  if ('opcua' in elem) {
530
549
  if (opcuainterface){
531
550
  res = await opcuainterface.call(elem.opcua, params)
551
+
552
+ if (this.#data.statenodeid){
553
+ let stateParams = params
554
+ params.value = params.state
555
+ res = await opcuainterface.call(this.#data.statenodeid, params)
556
+ }
532
557
  }else{
533
558
  console.warn("opcuainterface not started")
534
559
  }
package/common/utils.mjs CHANGED
@@ -22,9 +22,64 @@ export function writeJSONFile (fileName, jsonObj) {
22
22
  writeFileSync(fileName, data)
23
23
  }
24
24
 
25
- export function calcDelta (data, delta, max = 100) {
25
+ /**
26
+ * Calculate the delta of a value within a range between min and max with overflow
27
+ * @param {*} data
28
+ * @param {*} delta
29
+ * @param {*} min
30
+ * @param {*} max
31
+ * @returns
32
+ */
33
+ export function calcDelta (data, delta, min = 0, max = 100) {
26
34
  data = data + delta
27
- if (data > max) { data = max }
28
- if (data < 0) { data = 0 }
35
+ if (data > max) { data = min }
36
+ else if (data < min) { data = max }
29
37
  return data
30
38
  }
39
+
40
+ /**
41
+ * Invert a color in hex format (#RRGGBB) to its inverted color
42
+ * @param {*} colorAsHex
43
+ * @returns
44
+ */
45
+ export function invertColor(colorAsHex){
46
+ let rgb = colorToRGB(colorAsHex)
47
+ for (var i = 0; i < rgb.length; i++) {
48
+ rgb[i] = (i === 3 ? 1 : 255) - rgb[i];
49
+ }
50
+ let invertedColor = `#${rgb[0].toString(16)}${rgb[1].toString(16)}${rgb[2].toString(16)})`
51
+ //let invertedColor = `#${rgb[0].toString(16).padStart(2, '0')}${rgb[1].toString(16).padStart(2, '0')}${rgb[2].toString(16).padStart(2, '0')})`
52
+ //console.log("invert", colorAsHex,"=>",invertedColor)
53
+ return invertedColor
54
+ }
55
+
56
+ /**
57
+ * Convert a color in hex format (#RRGGBB) to RGB array
58
+ * @param {*} colorAsHex
59
+ * @returns
60
+ */
61
+ export function colorToRGB(colorAsHex){
62
+ const r = parseInt(colorAsHex.slice(1, 3), 16)
63
+ const g = parseInt(colorAsHex.slice(3, 5), 16)
64
+ const b = parseInt(colorAsHex.slice(5, 7), 16)
65
+ return [r,g,b]
66
+ }
67
+
68
+ /**
69
+ * Synchronize the parameters of a node with the default node
70
+ * @param {*} node
71
+ * @param {*} defaultnode
72
+ * @returns node
73
+ */
74
+ export function syncParams(node, defaultnode){
75
+ if (node === undefined || defaultnode === undefined)
76
+ return node
77
+ let keys = Object.keys(defaultnode)
78
+ for (var i=0;i<keys.length;i++){
79
+ let key = keys[i]
80
+ if (!(key in node))
81
+ node[key] = defaultnode[key]
82
+ }
83
+ return node
84
+ }
85
+
package/config.json CHANGED
@@ -4,10 +4,6 @@
4
4
  {
5
5
  "name": "profile-1",
6
6
  "file": "profile-1.json"
7
- },
8
- {
9
- "name": "profile-2",
10
- "file": "profile-2.json"
11
7
  }
12
8
  ]
13
9
  }
@@ -33,22 +33,22 @@ export class OPCUAIf extends BaseIf {
33
33
  #sub
34
34
  #connected
35
35
  #endpointurl
36
+ #publishingInterval
36
37
  monitoreditems
37
38
  types
38
39
  buttons
39
- #callback
40
40
  constructor() {
41
41
  super()
42
42
 
43
43
  this.LogInfo(`OPCUAIf Constructed`);
44
- }
44
+ }
45
45
 
46
- async stop(){
46
+ async stop() {
47
47
  if (!this.#client)
48
48
  return
49
-
49
+
50
50
  this.LogInfo(`OPCUAIf Stopping\n`)
51
- await this.#client.closeSession(this.#session,true)
51
+ await this.#client.closeSession(this.#session, true)
52
52
  await this.#client.disconnect()
53
53
  this.#connected = false
54
54
  this.#client = null
@@ -61,35 +61,47 @@ export class OPCUAIf extends BaseIf {
61
61
  * @param {*} config
62
62
  * @param {*} callbackFunction
63
63
  */
64
- async init( options = {},config = {},callbackFunction){
64
+ async init(options = {}, config = {}) {
65
65
  var res = this.Check(options)
66
- if (res<0){
66
+ if (res < 0) {
67
67
  this.LogError(`OPCUAIf: Missing essential options in dictionary => Quitting $res $options\n`)
68
68
  }
69
- try{
70
- this.#endpointurl = this.formatString(options.endpointurl,options)
71
- this.#callback = callbackFunction
69
+ try {
70
+ this.#endpointurl = this.formatString(options.endpointurl, options)
71
+ if (options.publishingInterval)
72
+ this.#publishingInterval = options.publishingInterval
73
+ else{
74
+ this.#publishingInterval = 250;
75
+ this.LogInfo(`OPCUAIf init using default publishingInterval: ${this.#publishingInterval}ms\n`);
76
+ }
72
77
  this.monitoreditems = {}
73
78
  this.types = {}
74
79
  this.buttons = {}
75
80
  this.LogInfo(`OPCUAIf init ${this.#endpointurl}\n`);
76
-
81
+
77
82
  await this.Connect(this.#endpointurl);
78
83
 
79
84
  let fields = [config.touch.center, config.knobs, config.buttons]
80
85
  const fieldKeys = Object.keys(fields)
81
86
  for (let f = 0; f < fieldKeys.length; f++) {
82
- let field=fields[f]
87
+ let field = fields[f]
83
88
  const keys = Object.keys(field)
84
89
  for (let i = 0; i < keys.length; i++) {
85
90
  const key = keys[i]
86
91
  const elem = field[key]
87
- if (elem.nodeid){
88
- let format = this.formatString(elem.nodeid,options)
92
+ // groupnode
93
+ if (elem.nodeid) {
94
+ let format = this.formatString(elem.nodeid, options)
89
95
  let monitoredItemId = await this.Subscribe(format)
90
96
  this.buttons[monitoredItemId] = i
91
97
  }
92
- await this.monitorStates(elem,options)
98
+ // statenode
99
+ if (elem.statenodeid) {
100
+ let format = this.formatString(elem.statenodeid, options)
101
+ let monitoredItemId = await this.Subscribe(format)
102
+ this.buttons[monitoredItemId] = i
103
+ }
104
+ await this.monitorStates(elem, options)
93
105
  }
94
106
  }
95
107
  } catch (error) {
@@ -102,17 +114,17 @@ export class OPCUAIf extends BaseIf {
102
114
  * @param {*} elem : Elem Object
103
115
  * @param {*} options
104
116
  */
105
- async monitorStates(elem,options){
117
+ async monitorStates(elem, options) {
106
118
  const stateKeys = Object.keys(elem.states)
107
119
  for (let i = 0; i < stateKeys.length; i++) {
108
120
  const key2 = stateKeys[i]
109
121
  const state = elem.states[key2]
110
- if (state.opcua){
111
- let format = this.formatString(state.opcua,options)
122
+ if (state.opcua) {
123
+ let format = this.formatString(state.opcua, options)
112
124
  let monitoredItemId = await this.Subscribe(format)
113
125
  this.buttons[monitoredItemId] = i
114
126
  }
115
- }
127
+ }
116
128
  }
117
129
 
118
130
  /**
@@ -125,17 +137,17 @@ export class OPCUAIf extends BaseIf {
125
137
  * - DataType.String
126
138
  * @returns the converted value.
127
139
  */
128
- convert(value,type){
129
- switch(type){
140
+ convert(value, type) {
141
+ switch (type) {
130
142
  case DataType.Int16:
131
143
  case DataType.Int32:
132
- if (typeof value == "number"){
144
+ if (typeof value == "number") {
133
145
  if (Number.isInteger(value))
134
146
  return value
135
- else
147
+ else
136
148
  return Math.trunc(value)
137
149
  }
138
- return parseInt(value,10)
150
+ return parseInt(value, 10)
139
151
  break
140
152
  case DataType.Float:
141
153
  if (typeof value == "number")
@@ -146,15 +158,24 @@ export class OPCUAIf extends BaseIf {
146
158
  if (typeof value == "number")
147
159
  return value.toString();
148
160
  return value
161
+ case DataType.Boolean:
162
+ if (typeof value == "number" && value === 1)
163
+ return true
164
+ if (typeof value == "string") {
165
+ if (["true", "on"].includes(value)) {
166
+ return true
167
+ }
168
+ }
169
+ return false
149
170
  default:
150
171
  return value
151
172
  }
152
173
  }
153
174
 
154
- async call (opcuaNode, options = {}) {
175
+ async call(opcuaNode, options = {}) {
155
176
  var res = this.Check(options)
156
- if (res<0){
157
- // this.LogError(`OPCUAIf call: Missing essential options in dictionary => Quitting $res\n`)
177
+ if (res < 0) {
178
+ // this.LogError(`OPCUAIf call: Missing essential options in dictionary => Quitting $res\n`)
158
179
  return false
159
180
  }
160
181
 
@@ -165,34 +186,41 @@ export class OPCUAIf extends BaseIf {
165
186
  if (typeof value == "string")
166
187
  value = super.formatString(options.value, options)
167
188
 
168
- var convertedValue = this.convert(value,type)
189
+ var convertedValue = this.convert(value, type)
169
190
  this.LogInfo(`OPCUAIf: write ${nodeId} => ${value}\n`)
170
- await this.Write(nodeId,convertedValue,type)
191
+ await this.Write(nodeId, convertedValue, type)
171
192
 
172
193
  var NewState = "waiting"
173
194
  return NewState
174
195
  }
175
196
 
176
197
  Check(options) {
177
- var res= super.Check(options)
178
- if (res <0){
179
- this.LogError(`OPCUAIf: mandatory parameter missing\n`)
198
+ var res = super.Check(options)
199
+ if (res < 0) {
200
+ this.LogError(`OPCUAIf: mandatory parameter missing\n`)
180
201
  return res
181
202
  }
182
- if (!"endpointurl" in options){
183
- this.LogError(`OPCUAIf: mandatory parameter endpointurl missing\n`)
184
- return -11}
185
- if (!"nodeid" in options){
186
- this.LogError(`OPCUAIf: mandatory parameter nodeid missing\n`)
187
- return -12}
188
- if (!"value" in options){
189
- this.LogError(`OPCUAIf: mandatory parameter value missing\n`)
190
- return -13}
203
+ if (!"endpointurl" in options) {
204
+ this.LogError(`OPCUAIf: mandatory parameter endpointurl missing\n`)
205
+ return -11
206
+ }
207
+ if (!"publishingInterval" in options) {
208
+ this.LogError(`OPCUAIf: mandatory parameter publishingInterval missing\n`)
209
+ return -11
210
+ }
211
+ if (!"nodeid" in options) {
212
+ this.LogError(`OPCUAIf: mandatory parameter nodeid missing\n`)
213
+ return -12
214
+ }
215
+ if (!"value" in options) {
216
+ this.LogError(`OPCUAIf: mandatory parameter value missing\n`)
217
+ return -13
218
+ }
191
219
  return 0
192
220
  }
193
221
 
194
222
  async Disconnect() {
195
- if (this.#client){
223
+ if (this.#client) {
196
224
  this.LogInfo(`OPCUAIf: Disconnect\n`);
197
225
  await this.#client.Disconnect()
198
226
  this.LogInfo(`OPCUAIf: Disconnected\n`);
@@ -202,7 +230,7 @@ export class OPCUAIf extends BaseIf {
202
230
  let self = this
203
231
  this.#client = OPCUAClient.create({
204
232
  applicationName: "NodeOPCUA-Client",
205
-
233
+
206
234
  endpointMustExist: false,
207
235
  // keepSessionAlive: true,
208
236
  requestedSessionTimeout: 60 * 1000,
@@ -213,44 +241,44 @@ export class OPCUAIf extends BaseIf {
213
241
  maxDelay: 5000,
214
242
  initialDelay: 2500
215
243
  },
216
-
244
+
217
245
  defaultSecureTokenLifetime: 20000,
218
246
  tokenRenewalInterval: 1000
219
247
  });
220
-
248
+
221
249
  this.#client.on("backoff", (retry, delay) => {
222
- if((retry%10) == 0)
250
+ if ((retry % 10) == 0)
223
251
  self.LogInfo(`OPCUAIf Try Reconnection ${retry} next attempt in ${delay}ms ${self.#endpointurl}\n`);
224
252
  });
225
-
253
+
226
254
  this.#client.on("connection_lost", () => {
227
255
  self.LogInfo(`OPCUAIf: Connection lost\n`);
228
256
  });
229
-
257
+
230
258
  this.#client.on("connection_reestablished", () => {
231
259
  self.LogInfo(`OPCUAIf: Connection re-established\n`);
232
260
  });
233
-
261
+
234
262
  this.#client.on("connection_failed", () => {
235
263
  self.LogInfo(`OPCUAIf: Connection failed\n`);
236
264
  });
237
265
  this.#client.on("start_reconnection", () => {
238
266
  self.LogInfo(`OPCUAIf: Starting reconnection\n`);
239
267
  });
240
-
268
+
241
269
  this.#client.on("after_reconnection", (err) => {
242
270
  self.LogInfo(`OPCUAIf: After Reconnection event => ${err}\n`);
243
271
  });
244
272
  this.#client.on("security_token_renewed", () => {
245
273
  self.LogDebug(`OPCUAIf: security_token_renewed\n`);
246
274
  })
247
- this.#client.on("lifetime_75", (token) => {})
248
-
275
+ this.#client.on("lifetime_75", (token) => { })
276
+
249
277
  this.LogInfo(`OPCUAIf: connecting client to ${url}\n`);//, this.#session.toString());
250
278
  await this.#client.connect(url);
251
-
279
+
252
280
  this.#session = await this.#client.createSession();
253
-
281
+
254
282
  this.#session.on("session_closed", (statusCode) => {
255
283
  self.LogInfo(`OPCUAIf: Session has been closed\n`);
256
284
  })
@@ -263,13 +291,14 @@ export class OPCUAIf extends BaseIf {
263
291
  this.#session.on("keepalive_failure", () => {
264
292
  self.LogInfo(`OPCUAIf: KeepAlive failure\n`);
265
293
  });
266
-
294
+
295
+ // create subscription with a custom publishing interval:
267
296
  this.#sub = await this.#session.createSubscription2({
268
297
  maxNotificationsPerPublish: 9000,
269
298
  publishingEnabled: true,
270
299
  requestedLifetimeCount: 10,
271
300
  requestedMaxKeepAliveCount: 10,
272
- requestedPublishingInterval: 1000
301
+ requestedPublishingInterval: this.#publishingInterval
273
302
  });
274
303
 
275
304
  this.LogInfo(`OPCUAIf: session created\n`);//, this.#session.toString());
@@ -278,7 +307,7 @@ export class OPCUAIf extends BaseIf {
278
307
  this.#connected = true
279
308
  this.#endpointurl = url
280
309
  }
281
-
310
+
282
311
  async Subscribe(nodeID) {
283
312
  // install monitored item
284
313
 
@@ -286,7 +315,7 @@ export class OPCUAIf extends BaseIf {
286
315
  for (let i = 0; i < keys.length; i++) {
287
316
  const key = keys[i]
288
317
  const elem = this.monitoreditems[key]
289
- if (elem == nodeID){
318
+ if (elem == nodeID) {
290
319
  // already registered => return itemid
291
320
  return key
292
321
  }
@@ -301,14 +330,14 @@ export class OPCUAIf extends BaseIf {
301
330
  discardOldest: true,
302
331
  queueSize: 10
303
332
  };
304
-
305
- if (!this.#sub){
333
+
334
+ if (!this.#sub) {
306
335
  this.LogError(`OPCUAIf: not register monitored items $itemToMonitor\n`);
307
336
  return
308
337
  }
309
338
  const monitoredItem = await this.#sub.monitor(itemToMonitor, monitoringParameters, TimestampsToReturn.Both);
310
339
  this.monitoreditems[monitoredItem.monitoredItemId] = nodeID
311
- var self=this
340
+ var self = this
312
341
  monitoredItem.on("changed", function (dataValue) {
313
342
  var nodeId = self.monitoreditems[this.monitoredItemId]
314
343
  var buttonID = self.buttons[this.monitoredItemId]
@@ -316,55 +345,59 @@ export class OPCUAIf extends BaseIf {
316
345
  self.types[nodeId] = dataValue.value.dataType
317
346
  // publish the value to subscribers:
318
347
  self.LogInfo(`OPCUAIf: monitored item changed: ${nodeId} => ${dataValue.value.value}\n`);
319
- self.emit('monitored item changed',buttonID,nodeId, dataValue.value.value)
348
+ self.emit('monitored item changed', buttonID, nodeId, dataValue.value.value)
320
349
  });
321
350
 
322
351
  return monitoredItem.monitoredItemId;
323
352
  }
324
-
353
+
325
354
  async Read(nodeID) {
326
355
  const nodeToRead = {
327
356
  nodeId: nodeID,
328
357
  attributeId: AttributeIds.Value
329
358
  };
330
- if (!this.#connected){
359
+ if (!this.#connected) {
331
360
  this.LogError(`OPCUAIf: not connected, cannot read ${nodeID}\n`);
332
361
  return
333
362
  }
334
363
  const dataValue2 = await this.#session.read(nodeToRead, 0);
335
- this.LogError("OPCUAIf: read nodeID ",nodeID, dataValue2.toString(),"\n");
364
+ this.LogError("OPCUAIf: read nodeID ", nodeID, dataValue2.toString(), "\n");
336
365
  return dataValue2
337
366
  }
338
367
 
339
- async Write(nodeID,value,datatype=DataType.String) {
368
+ async Write(nodeID, value, datatype = DataType.String) {
340
369
  let self = this
341
- if (!this.#connected){
342
- self.LogError("OPCUAIf: not connected, cannot write",nodeID, value,"\n");
370
+ if (!this.#connected) {
371
+ self.LogError("OPCUAIf: not connected, cannot write", nodeID, value, "\n");
343
372
  return
344
373
  }
345
374
  var nodesToWrite = [{
346
375
  nodeId: nodeID,
347
376
  attributeId: AttributeIds.Value,
348
377
  indexRange: null,
349
- value: {
350
- value: {
378
+ value: {
379
+ value: {
351
380
  dataType: datatype,
352
381
  value: value
353
382
  }
354
- }
383
+ }
355
384
  }];
356
- await this.#session.write(nodesToWrite, function(err,statusCodes) {
357
- if (!err) {
358
- if (statusCodes && statusCodes[0].value != 0){
359
- self.LogInfo(`OPCUAIf: error with Node: "${nodeID}", status ${statusCodes[0]}\n`);
360
- }else{
361
- self.LogInfo(`OPCUAIf: wrote ${nodeID} => ${value}\n`);
385
+ try {
386
+ await this.#session.write(nodesToWrite, function (err, statusCodes) {
387
+ if (!err) {
388
+ if (statusCodes && statusCodes[0].value != 0) {
389
+ self.LogInfo(`OPCUAIf: error with Node: "${nodeID}", status ${statusCodes[0]}\n`);
390
+ } else {
391
+ self.LogInfo(`OPCUAIf: wrote ${nodeID} => ${value}\n`);
392
+ }
393
+ } else {
394
+ self.LogError("OPCUAIf: write NOT ok", nodeID, value, "\n");
395
+ self.LogError(err)
362
396
  }
363
- }else{
364
- self.LogError("OPCUAIf: write NOT ok",nodeID, value,"\n");
365
- self.LogError(err)
366
- }
367
- });
397
+ });
398
+ } catch (err) {
399
+ self.LogError("OPCUAIf: write NOT ok", nodeID, value, "\n");
400
+ self.LogError(err)
401
+ }
368
402
  }
369
403
  }
370
-
@@ -6,23 +6,23 @@ import { BaseIf } from './baseif.mjs'
6
6
  * Our Special-Handler just used the Default - and adds Vibration after triggers through Button-Releases
7
7
  */
8
8
  export class SHELLif extends BaseIf {
9
- async call (cmd, options = {}) {
9
+ async call(cmd, options = {}) {
10
10
  var res = this.Check(options)
11
- if (res<0){
12
- return false
11
+ if (res < 0) {
12
+ return false
13
13
  }
14
14
  let formattedCmd = super.call(cmd, options)
15
15
  return await this.sh(formattedCmd)
16
16
  }
17
17
 
18
- async stop(){
18
+ async stop() {
19
19
  this.LogInfo("SHELLif: Stopping")
20
20
  }
21
21
 
22
22
  Check(options) {
23
- var res= super.Check(options)
24
- if (res <0){
25
- this.LogError(`SHELLif: mandatory parameter missing\n`)
23
+ var res = super.Check(options)
24
+ if (res < 0) {
25
+ this.LogError(`SHELLif: mandatory parameter missing\n`)
26
26
  return res
27
27
  }
28
28
  }
@@ -32,22 +32,27 @@ export class SHELLif extends BaseIf {
32
32
  * @param {*} cmd
33
33
  * @returns
34
34
  */
35
- async sh (cmd) {
36
- let self = this;
35
+ async sh(cmd) {
36
+ let self = this;
37
37
  this.LogDebug(`ShellIf: runCmd: ${cmd}\n`)
38
-
38
+
39
39
  return new Promise(function (resolve, reject) {
40
- exec(cmd, (err, stdout, stderr) => {
41
- if (stdout.length>0)
42
- self.LogInfo(`SHELLif Out: ${stdout}`)
43
- if (stderr.length>0)
44
- self.LogError(`SHELLif Err: ${stderr}`)
45
- if (err) {
46
- reject(err)
47
- } else {
48
- resolve({ stdout, stderr })
49
- }
50
- })
40
+ try {
41
+ exec(cmd, (err, stdout, stderr) => {
42
+ if (stdout.length > 0)
43
+ self.LogInfo(`SHELLif Out: ${stdout}`)
44
+ if (stderr.length > 0)
45
+ self.LogError(`SHELLif Err: ${stderr}`)
46
+ if (err) {
47
+ resolve(err)
48
+ } else {
49
+ resolve({ stdout, stderr })
50
+ }
51
+ })
52
+ } catch (e) {
53
+ console.error(`Error in script: ${e}`)
54
+ resolve()
55
+ }
51
56
  })
52
57
  }
53
58
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loupedeck-commander",
3
- "version": "1.2.7",
3
+ "version": "1.2.9",
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": {
package/profile-1.json CHANGED
@@ -229,7 +229,7 @@
229
229
  "states": {
230
230
  "on": {
231
231
  "cmd": "echo \"{id} {state}\"",
232
- "opcua": "ns=2;s=Is{simnbr}.Audio.in.VolumeAccustic"
232
+ "opcua": "ns=1;s={simnbr}.Function1"
233
233
  }
234
234
  },
235
235
  "group": ""
@@ -263,6 +263,7 @@
263
263
  "hostname": "127.0.0.1",
264
264
  "simnbr": "1",
265
265
  "endpointurl": "opc.tcp://{hostname}:4840",
266
+ "publishingInterval" : 200,
266
267
  "nodeid" : "ns=0;s=nodeID",
267
268
  "verbose": true
268
269
  }
package/numbers/README.md DELETED
@@ -1,5 +0,0 @@
1
- # Numbers Icons
2
-
3
- Downloaded from:
4
-
5
- - https://www.freepik.com/author/moniruldislam/icons/generic-color-lineal-color_10172?query=Number#from_element=families
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,373 +0,0 @@
1
- {
2
- "name": "profile-1",
3
- "profile": "example",
4
- "description": "",
5
- "touch": {
6
- "center": {
7
- "0": {
8
- "states": {
9
- "off": {
10
- "color": "#000099",
11
- "image": "icons/home.png",
12
- "cmd": "echo \"{id} {state}\""
13
- },
14
- "on": {
15
- "color": "#00ff00",
16
- "image": "icons/home.png",
17
- "cmd": "echo \"{id} {state}\""
18
- }
19
- },
20
- "group": ""
21
- },
22
- "1": {
23
- "states": {
24
- "off": {
25
- "color": "#000099",
26
- "image": "icons/home.png",
27
- "cmd": "echo \"{id} {state}\""
28
- },
29
- "on": {
30
- "color": "#00ff00",
31
- "image": "icons/home.png",
32
- "cmd": "echo \"{id} {state}\""
33
- }
34
- },
35
- "group": ""
36
- },
37
- "2": {
38
- "states": {
39
- "off": {
40
- "color": "#000099",
41
- "image": "icons/home.png",
42
- "cmd": "echo \"{id} {state}\""
43
- },
44
- "on": {
45
- "color": "#00ff00",
46
- "image": "icons/home.png",
47
- "cmd": "echo \"{id} {state}\""
48
- }
49
- },
50
- "group": ""
51
- },
52
- "3": {
53
- "states": {
54
- "off": {
55
- "color": "#000099",
56
- "image": "icons/home.png",
57
- "cmd": "echo \"{id} {state}\""
58
- },
59
- "on": {
60
- "color": "#00ff00",
61
- "image": "icons/home.png",
62
- "cmd": "echo \"{id} {state}\""
63
- }
64
- },
65
- "group": ""
66
- },
67
- "4": {
68
- "states": {
69
- "off": {
70
- "color": "#000099",
71
- "image": "icons/home.png",
72
- "cmd": "echo \"{id} {state}\""
73
- },
74
- "on": {
75
- "color": "#00ff00",
76
- "image": "icons/home.png",
77
- "cmd": "echo \"{id} {state}\""
78
- }
79
- },
80
- "group": ""
81
- },
82
- "5": {
83
- "states": {
84
- "off": {
85
- "color": "#000099",
86
- "image": "icons/home.png",
87
- "cmd": "echo \"{id} {state}\""
88
- },
89
- "on": {
90
- "color": "#00ff00",
91
- "image": "icons/home.png",
92
- "cmd": "echo \"{id} {state}\""
93
- }
94
- },
95
- "group": ""
96
- },
97
- "6": {
98
- "states": {
99
- "off": {
100
- "color": "#000099",
101
- "image": "icons/home.png",
102
- "cmd": "echo \"{id} {state}\""
103
- },
104
- "on": {
105
- "color": "#00ff00",
106
- "image": "icons/home.png",
107
- "cmd": "echo \"{id} {state}\""
108
- }
109
- },
110
- "group": ""
111
- },
112
- "7": {
113
- "states": {
114
- "off": {
115
- "color": "#000099",
116
- "image": "icons/home.png",
117
- "cmd": "echo \"{id} {state}\""
118
- },
119
- "on": {
120
- "color": "#00ff00",
121
- "image": "icons/home.png",
122
- "cmd": "echo \"{id} {state}\""
123
- }
124
- },
125
- "group": ""
126
- },
127
- "8": {
128
- "states": {
129
- "off": {
130
- "color": "#000099",
131
- "image": "icons/home.png",
132
- "cmd": "echo \"{id} {state}\""
133
- },
134
- "on": {
135
- "color": "#00ff00",
136
- "image": "icons/home.png",
137
- "cmd": "echo \"{id} {state}\""
138
- }
139
- },
140
- "group": ""
141
- },
142
- "9": {
143
- "states": {
144
- "off": {
145
- "color": "#000099",
146
- "image": "icons/home.png",
147
- "cmd": "echo \"{id} {state}\""
148
- },
149
- "on": {
150
- "color": "#00ff00",
151
- "image": "icons/home.png",
152
- "cmd": "echo \"{id} {state}\""
153
- }
154
- },
155
- "group": ""
156
- },
157
- "10": {
158
- "states": {
159
- "off": {
160
- "color": "#000099",
161
- "image": "icons/home.png",
162
- "cmd": "echo \"{id} {state}\""
163
- },
164
- "on": {
165
- "color": "#00ff00",
166
- "image": "icons/home.png",
167
- "cmd": "echo \"{id} {state}\""
168
- }
169
- },
170
- "group": ""
171
- },
172
- "11": {
173
- "states": {
174
- "off": {
175
- "color": "#000099",
176
- "image": "icons/home.png",
177
- "cmd": "echo \"{id} {state}\""
178
- },
179
- "on": {
180
- "color": "#00ff00",
181
- "image": "icons/home.png",
182
- "cmd": "echo \"{id} {state}\""
183
- }
184
- },
185
- "group": ""
186
- }
187
- },
188
- "left": {
189
- "0": {
190
- "states": {
191
- "on": {
192
- "color": "#000000",
193
- "cmd": "echo \"{id} {state}\""
194
- }
195
- },
196
- "group": ""
197
- }
198
- },
199
- "right": {
200
- "0": {
201
- "states": {
202
- "on": {
203
- "color": "#000000",
204
- "cmd": "echo \"{id} {state}\""
205
- }
206
- },
207
- "group": ""
208
- }
209
- }
210
- },
211
- "knobs": {
212
- "knobTL": {
213
- "states": {
214
- "on": {
215
- "cmd": "echo \"{id} {state}\""
216
- }
217
- },
218
- "group": ""
219
- },
220
- "knobCL": {
221
- "states": {
222
- "on": {
223
- "cmd": "echo \"{id} {state}\""
224
- }
225
- },
226
- "group": ""
227
- },
228
- "knobBL": {
229
- "states": {
230
- "on": {
231
- "cmd": "echo \"{id} {state}\""
232
- }
233
- },
234
- "group": ""
235
- },
236
- "knobTR": {
237
- "states": {
238
- "on": {
239
- "cmd": "echo \"{id} {state}\""
240
- }
241
- },
242
- "group": ""
243
- },
244
- "knobCR": {
245
- "states": {
246
- "on": {
247
- "cmd": "echo \"{id} {state}\""
248
- }
249
- },
250
- "group": ""
251
- },
252
- "knobBR": {
253
- "states": {
254
- "on": {
255
- "cmd": "echo \"{id} {state}\""
256
- }
257
- },
258
- "group": ""
259
- }
260
- },
261
- "buttons": {
262
- "0": {
263
- "states": {
264
- "off": {
265
- "color": "#000000",
266
- "cmd": "echo \"{id} {state}\""
267
- },
268
- "on": {
269
- "color": "#550000",
270
- "cmd": "echo \"{id} {state}\""
271
- }
272
- },
273
- "group": ""
274
- },
275
- "1": {
276
- "states": {
277
- "off": {
278
- "color": "#000000",
279
- "cmd": "echo \"{id} {state}\""
280
- },
281
- "on": {
282
- "color": "#ff0000",
283
- "cmd": "echo \"{id} {state}\""
284
- }
285
- },
286
- "group": ""
287
- },
288
- "2": {
289
- "states": {
290
- "off": {
291
- "color": "#000000",
292
- "cmd": "echo \"{id} {state}\""
293
- },
294
- "on": {
295
- "color": "#005500",
296
- "cmd": "echo \"{id} {state}\""
297
- }
298
- },
299
- "group": ""
300
- },
301
- "3": {
302
- "states": {
303
- "off": {
304
- "color": "#000000",
305
- "cmd": "echo \"{id} {state}\""
306
- },
307
- "on": {
308
- "color": "#00ff00",
309
- "cmd": "echo \"{id} {state}\""
310
- }
311
- },
312
- "group": ""
313
- },
314
- "4": {
315
- "states": {
316
- "off": {
317
- "color": "#000000",
318
- "cmd": "echo \"{id} {state}\""
319
- },
320
- "on": {
321
- "color": "#000055",
322
- "cmd": "echo \"{id} {state}\""
323
- }
324
- },
325
- "group": ""
326
- },
327
- "5": {
328
- "states": {
329
- "off": {
330
- "color": "#000000",
331
- "cmd": "echo \"{id} {state}\""
332
- },
333
- "on": {
334
- "color": "#0000ff",
335
- "cmd": "echo \"{id} {state}\""
336
- }
337
- },
338
- "group": ""
339
- },
340
- "6": {
341
- "states": {
342
- "off": {
343
- "color": "#000000",
344
- "cmd": "echo \"{id} {state}\""
345
- },
346
- "on": {
347
- "color": "#555500",
348
- "cmd": "echo \"{id} {state}\""
349
- }
350
- },
351
- "group": ""
352
- },
353
- "7": {
354
- "states": {
355
- "off": {
356
- "color": "#000000",
357
- "cmd": "echo \"{id} {state}\""
358
- },
359
- "on": {
360
- "color": "#ffff00",
361
- "cmd": "echo \"{id} {state}\""
362
- }
363
- },
364
- "group": ""
365
- }
366
- },
367
- "parameters": {
368
- "hostname": "127.0.0.1",
369
- "endpointurl": "opc.tcp://{hostname}:4840",
370
- "nodeid": "ns=0;s=nodeID"
371
- },
372
- "loaded": false
373
- }