loupedeck-commander 1.2.0 → 1.2.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.
package/README.md CHANGED
@@ -53,15 +53,26 @@ import { BaseLoupeDeckHandler } from 'loupedeck-commander'
53
53
 
54
54
  const handler = new BaseLoupeDeckHandler('config.json')
55
55
 
56
- const stopHandler = () => {
57
- console.log('Receiving SIGINT => Stopping processes.')
58
- handler.stop()
56
+ /**
57
+ * Stop the handlers when a signal like SIGINT or SIGTERM arrive
58
+ * @param {*} signal
59
+ */
60
+ const stopHandler = async(signal) => {
61
+ console.log(`Receiving ${signal} => Stopping processes.`)
62
+ await handler.stop()
59
63
  }
60
64
 
61
- // Initiating a process
62
- process.on('SIGINT', stopHandler)
65
+ // Initiating the signal handlers:
66
+ // see https://www.tutorialspoint.com/unix/unix-signals-traps.htm
67
+ process.on('SIGINT', async (signal) => { stopHandler(signal) })
68
+ process.on('SIGTERM', async (signal) => { stopHandler(signal) })
63
69
 
70
+ // Start it
64
71
  await handler.start()
72
+
73
+ // Take care of running background processes and stop them accordingly:
74
+ await new Promise((resolve) => process.once("SIGINT", resolve));
75
+
65
76
  ```
66
77
 
67
78
  Run the script using the following command:
@@ -12,13 +12,14 @@ export class BaseLoupeDeckHandler {
12
12
  screenUpdate = {}
13
13
  stopping = false
14
14
 
15
- //touchButtons = undefined
16
-
17
15
  constructor (config) {
18
16
  console.log(`INIT with config ${config}`)
19
17
  this.loadConfig(config)
20
18
  }
21
19
 
20
+ /**
21
+ * event handler - start
22
+ */
22
23
  async start () {
23
24
  console.info('Start')
24
25
  while (!this.device && !this.stopping) {
@@ -61,11 +62,17 @@ export class BaseLoupeDeckHandler {
61
62
  console.info(`✅ Registered callbacks`)
62
63
  }
63
64
 
65
+ /**
66
+ * event handler - loupedeck device disconnected
67
+ */
64
68
  async disconnectDevice (event) {
65
- console.info("Device Disconnected",event)
69
+ console.info("Device Disconnected")
66
70
  this.device=undefined
67
71
  }
68
72
 
73
+ /**
74
+ * stop handler - close the dipslay-connection and also stop all interface handlers
75
+ */
69
76
  async stop () {
70
77
  console.info('Stopping Handler')
71
78
 
@@ -74,39 +81,41 @@ export class BaseLoupeDeckHandler {
74
81
  this.stopping = true
75
82
  if (this.device){
76
83
  this.device.vibrate(HAPTIC.DESCEND_MED)
77
- await new Promise(resolve => setTimeout(resolve, 500))
84
+ await new Promise(resolve => setTimeout(resolve, 250))
78
85
  }
79
86
  if (this.device){
80
87
  this.device.setBrightness(0)
81
- await new Promise(resolve => setTimeout(resolve, 500))
88
+ await new Promise(resolve => setTimeout(resolve, 250))
82
89
  }
83
90
  if (this.device){
84
91
  this.device.reconnectInterval = 0
85
92
  await this.device.close()
86
- await new Promise(resolve => setTimeout(resolve, 3000))
87
- console.error(`Device Closed`)
93
+ await new Promise(resolve => setTimeout(resolve, 2000))
94
+ console.info(`Device Closed`)
88
95
  }
89
96
 
90
97
  console.info(`Stopping interfaces`)
91
-
92
98
  await StopInterfaces()
93
- await new Promise(resolve => setTimeout(resolve, 1000))
94
-
95
- this.device = null
96
- } catch (e) {
97
- console.error(`${e}. Catched error in 3 seconds...`)
98
- }
99
+ await new Promise(resolve => setTimeout(resolve, 500))
100
+ } catch (e) {}
99
101
 
100
102
  process.exit()
101
103
 
102
104
  }
103
105
 
106
+ /**
107
+ * load Application config file from JSON
108
+ * @param filename
109
+ */
104
110
  loadConfig (fileName) {
105
111
  console.info(`Loading Config File ${fileName}`)
106
112
  this.appConfig = new ApplicationConfig()
107
113
  this.appConfig.loadFromFile(fileName)
108
114
  }
109
115
 
116
+ /**
117
+ * activate the profile with the givven ID
118
+ */
110
119
  async activateProfile (id) {
111
120
  // todo Profile-change implementation
112
121
  var oldProfile = this.currentProfile
@@ -151,11 +160,18 @@ export class BaseLoupeDeckHandler {
151
160
  await this.buttons.draw(this.device)
152
161
  }
153
162
 
163
+ /**
164
+ * get the dictionary iwth the current profile settings
165
+ */
154
166
  getCurrentProfile () {
155
167
  return this.appConfig.profiles[this.currentProfile]
156
168
  }
157
169
 
158
- // Events:
170
+ /**
171
+ * Triggered, when LoupeDeck Device is connected
172
+ * - Initialize the profile from config-file and
173
+ * - start the interface handlers accordingly
174
+ */
159
175
  async onConnected (address) {
160
176
  console.info(`✅ Connected to ${this.device.type}, ${address}`)
161
177
 
@@ -166,14 +182,18 @@ export class BaseLoupeDeckHandler {
166
182
 
167
183
  const self = this
168
184
 
185
+ // Register callback on monitored item change:
169
186
  opcuainterface.myEmitter.on("monitored item changed",(buttonID,nodeid,val) => { self.buttonStateChanged(buttonID,nodeid,val) })
170
187
 
171
188
  this.device.setBrightness(1)
172
189
  this.device.vibrate(HAPTIC.ASCEND_MED)
173
190
 
174
- console.info('Done initializing')
191
+ console.info('Done initializing')
175
192
  }
176
193
 
194
+ /**
195
+ * Fore an update of all screens
196
+ */
177
197
  async updateScreens () {
178
198
  const keys = Object.keys(this.screenUpdate)
179
199
  for (let i = 0; i < keys.length; i++) {
@@ -183,6 +203,9 @@ export class BaseLoupeDeckHandler {
183
203
  this.screenUpdate = {}
184
204
  }
185
205
 
206
+ /**
207
+ * Button Down Handler - triggered through Event Button Down - connected to LoupeDeck Event
208
+ */
186
209
  async onButtonDown (event) {
187
210
  let ok = false
188
211
  const id = event.id
@@ -191,6 +214,9 @@ export class BaseLoupeDeckHandler {
191
214
  return ok
192
215
  }
193
216
 
217
+ /**
218
+ * Button Up Handler - triggered through Event Button Up - connected to LoupeDeck Event
219
+ */
194
220
  async onButtonUp (event) {
195
221
  let ok = false
196
222
  const id = event.id
@@ -203,12 +229,18 @@ export class BaseLoupeDeckHandler {
203
229
  return ok
204
230
  }
205
231
 
232
+ /**
233
+ * Button Rotate Handler - triggered through Event Rotate available with knob-Buttons - connected to LoupeDeck Event
234
+ */
206
235
  async onRotate (event) {
207
236
  const id = event.id
208
237
  const delta = event.delta
209
238
  return await this.buttons.rotated(id, delta)
210
239
  }
211
240
 
241
+ /**
242
+ * TouchStart Handler - triggered through Event touch available on all touchbuttons on the loupedeck display - connected to LoupeDeck Event
243
+ */
212
244
  async onTouchStart (event) {
213
245
  let ok = false
214
246
  const changedTouches = event.changedTouches
@@ -225,6 +257,9 @@ export class BaseLoupeDeckHandler {
225
257
  return ok
226
258
  }
227
259
 
260
+ /**
261
+ * TouchMove Handler - triggered through Event touch available on all touchbuttons on the loupedeck display - connected to LoupeDeck Event
262
+ */
228
263
  async onTouchMove (event) {
229
264
  let ok = false
230
265
  const changedTouches = event.changedTouches
@@ -241,6 +276,9 @@ export class BaseLoupeDeckHandler {
241
276
  return ok
242
277
  }
243
278
 
279
+ /**
280
+ * TouchEnd Handler - triggered through Event touch available on all touchbuttons on the loupedeck display - connected to LoupeDeck Event
281
+ */
244
282
  async onTouchEnd (event) {
245
283
  let ok = false
246
284
  const changedTouches = event.changedTouches
@@ -258,6 +296,9 @@ export class BaseLoupeDeckHandler {
258
296
  return ok
259
297
  }
260
298
 
299
+ /**
300
+ * Handler for StateChanged through OPC/UA Interface - only connected with touch-buttons on center field (yet)
301
+ */
261
302
  async buttonStateChanged(buttonID,nodeid,val) {
262
303
  let ok = false
263
304
  this.screenUpdate["center"] = true
@@ -1,4 +1,5 @@
1
1
  import { loadImage } from 'canvas'
2
+ //import { loadImage } from "https://deno.land/x/canvas/mod.ts";
2
3
 
3
4
  import * as shellif from '../interfaces/shellif.mjs'
4
5
  import * as httpif from '../interfaces/httpif.mjs'
@@ -202,6 +203,7 @@ export class Button {
202
203
  #nodeid = ""
203
204
 
204
205
  #index = 0
206
+ #event
205
207
  #keys
206
208
  #states
207
209
 
@@ -346,7 +348,7 @@ export class Button {
346
348
  this.timeStampPressed = Date.now()
347
349
 
348
350
  this.#index++
349
- this.updateState(this.#index)
351
+ this.updateState(this.#index,"pressed")
350
352
  return true
351
353
  }
352
354
 
@@ -375,13 +377,14 @@ export class Button {
375
377
  break
376
378
  }
377
379
 
378
- this.updateState(this.#index)
380
+ this.updateState(this.#index,"released")
379
381
 
380
382
  return true // this.runCommand()
381
383
  }
382
384
 
383
- updateState(index){
385
+ updateState(index,eventType){
384
386
  this.#index = index
387
+ this.#event = eventType
385
388
  // Update the State according to the correctly pressed state
386
389
  if (this.#index < 0) { this.#index = this.#keys.length - 1 }
387
390
  this.#index %= this.#keys.length
@@ -393,6 +396,7 @@ export class Button {
393
396
  async rotated (delta) {
394
397
  if (!this.getCurrentElement()) { return false }
395
398
 
399
+ this.#event = "rotated"
396
400
  this.#value = calcDelta(this.#value, delta, this.#max)
397
401
  return this.runCommand()
398
402
  }
@@ -458,21 +462,27 @@ export class Button {
458
462
 
459
463
  async runCommand () {
460
464
  const elem = this.getCurrentElement()
465
+ // Only continue, if we have an element, that contains some kind of command:
461
466
  if (!elem || (!elem.cmd && !elem.http && !elem.opcua)) {
462
467
  return
463
468
  }
469
+ // Filter for Event Type:
470
+ if (elem.filter && elem.filter != this.#event){
471
+ return
472
+ }
464
473
  // Call an action - include dynamic parameters
465
474
  // and also all attributes of elem + global config
466
475
  const params = {
476
+ text: this.getCurrentText(),
477
+ ...this.#config.parameters,
478
+ ...elem,
467
479
  id: this.id,
468
480
  key: this.key,
481
+ event: this.#event,
469
482
  state: this.#keys[this.#index],
470
483
  min: this.#min,
471
484
  max: this.#max,
472
- value: this.#value,
473
- text: this.getCurrentText(),
474
- ...this.#config.parameters,
475
- ...elem
485
+ value: this.#value
476
486
  }
477
487
 
478
488
  let res = ''
@@ -22,7 +22,11 @@ export class BaseIf {
22
22
  }
23
23
 
24
24
  formatString (cmd, options = {}) {
25
- return format(cmd, options)
25
+ let f =""
26
+ try{
27
+ f = format(cmd, options)
28
+ }catch(e){}
29
+ return f
26
30
  }
27
31
 
28
32
  Check(options) {
@@ -44,14 +48,21 @@ export class BaseIf {
44
48
  }
45
49
 
46
50
  LogError(...args){
47
- console.error(args)
51
+ let str = new String(args)
52
+ process.stderr.write(str.toString())
53
+ }
54
+
55
+ LogDebug(...args){
56
+ if (this.options && this.options.verbose){
57
+ let str = new String(args)
58
+ process.stdout.write(str.toString())
59
+ }
48
60
  }
49
61
 
50
62
  LogInfo(...args){
51
- if (this.options && this.options.verbose)
52
- console.log(args)
63
+ let str = new String(args)
64
+ process.stdout.write(str.toString())
53
65
  }
54
-
55
66
  }
56
67
 
57
68
 
@@ -12,15 +12,15 @@ export class HTTPif extends BaseIf {
12
12
  try {
13
13
  myURL = new url.URL(url1)
14
14
  await this.get(myURL, options)
15
- } catch (exception) {
16
- console.error('error with URL', exception)
15
+ } catch (e) {
16
+ this.LogError(`HTTPif: error with URL: ${e.message}\n`)
17
17
  return false
18
18
  }
19
19
  return true
20
20
  }
21
21
 
22
22
  async stop(){
23
- console.log("Stopping HTTPif")
23
+ this.LogInfo("HTTPif: Stopping\n")
24
24
 
25
25
  }
26
26
 
@@ -30,10 +30,6 @@ export class HTTPif extends BaseIf {
30
30
  return res
31
31
  if (!options.hostname)
32
32
  return -21
33
- /*if (!options.port)
34
- return -22
35
- if (!options.pathname)
36
- return -23*/
37
33
  return 0
38
34
  }
39
35
 
@@ -53,8 +49,7 @@ export class HTTPif extends BaseIf {
53
49
  }
54
50
  }
55
51
 
56
- if (this.options.verbose)
57
- console.log('HTTPIf call URL:', myURL, getOptions)
52
+ this.LogInfo(`HTTPIf: call URL ${myURL} ${getOptions}\n`)
58
53
 
59
54
 
60
55
  const prom = new Promise((resolve, reject) => {
@@ -76,7 +71,7 @@ export class HTTPif extends BaseIf {
76
71
  })
77
72
 
78
73
  req.on('error', (e) => {
79
- console.warn(`ignore other errors like ERRNOTCONNECTED: ${e.message}`)
74
+ this.LogError(`HTTPif: ignore other errors like ERRNOTCONNECTED: ${e.message}\n`)
80
75
  return false
81
76
  });
82
77
  }).catch(function (error) { // (*)
@@ -42,32 +42,32 @@ export class OPCUAIf extends BaseIf {
42
42
  super()
43
43
  this.myEmitter = new EventEmitter();
44
44
 
45
- console.log("OPCUAIf Constructed");
45
+ this.LogInfo(`OPCUAIf Constructed`);
46
46
  }
47
47
 
48
48
  async stop(){
49
49
  if (!this.#client)
50
50
  return
51
51
 
52
- console.log("Stopping OPC/UA")
52
+ this.LogInfo(`OPCUAIf Stopping`)
53
53
  await this.#client.closeSession(this.#session,true)
54
54
  await this.#client.disconnect()
55
55
  this.#connected = false
56
56
  this.#client = null
57
- console.log("Stopped OPC/UA")
57
+ this.LogInfo(`OPCUAIf Stopped\n`)
58
58
  }
59
59
 
60
60
  async init( options = {},config = {},callbackFunction){
61
61
  var res = this.Check(options)
62
62
  if (res<0){
63
- console.error("OpcuaIf init: Missing essential options in dictionary => Quitting\n",res,options)
63
+ this.LogError(`OPCUAIf: Missing essential options in dictionary => Quitting $res $options\n`)
64
64
  }
65
65
  try{
66
66
  this.#endpointurl = options.endpointurl
67
67
  this.#callback = callbackFunction
68
68
  this.monitoreditems = {}
69
69
  this.buttons = {}
70
- console.log("OPCUAIf init",this.#endpointurl);
70
+ this.LogInfo(`OPCUAIf init ${this.#endpointurl}\n`);
71
71
 
72
72
  await this.Connect(this.#endpointurl);
73
73
 
@@ -84,21 +84,21 @@ export class OPCUAIf extends BaseIf {
84
84
 
85
85
  }
86
86
  } catch (error) {
87
- console.log(' Error', error)
87
+ this.LogError(`OPCUAIf: Error $error\n`)
88
88
  }
89
89
  }
90
90
 
91
91
  async call (opcuaNode, options = {}) {
92
92
  var res = this.Check(options)
93
93
  if (res<0){
94
- console.error("OpcuaIf call: Missing essential options in dictionary => Quitting\n",res,options)
94
+ this.LogError(`OPCUAIf call: Missing essential options in dictionary => Quitting $res\n`)
95
95
  return false
96
96
  }
97
97
 
98
98
  var nodeId = super.formatString(opcuaNode, options)
99
99
  var value = super.formatString(options.value, options)
100
100
 
101
- console.log("OPCUAIf:write", nodeId, value)
101
+ this.LogInfo(`OPCUAIf: write ${nodeId} => ${value}\n`)
102
102
  await this.Write(nodeId,value)
103
103
 
104
104
  var NewState = "waiting"
@@ -120,9 +120,9 @@ export class OPCUAIf extends BaseIf {
120
120
 
121
121
  async Disconnect() {
122
122
  if (this.#client){
123
- console.log("Disconnect !");
123
+ this.LogInfo(`OPCUAIf: Disconnect\n`);
124
124
  await this.#client.Disconnect()
125
- console.log("Done !");
125
+ this.LogInfo(`OPCUAIf: Disconnected\n`);
126
126
  }
127
127
  }
128
128
  async Connect(url) {
@@ -137,8 +137,8 @@ export class OPCUAIf extends BaseIf {
137
137
  securityPolicy: SecurityPolicy.None,
138
138
  connectionStrategy: {
139
139
  maxRetry: -1,
140
- maxDelay: 2500,
141
- initialDelay: 100
140
+ maxDelay: 5000,
141
+ initialDelay: 2500
142
142
  },
143
143
 
144
144
  defaultSecureTokenLifetime: 20000,
@@ -146,52 +146,49 @@ export class OPCUAIf extends BaseIf {
146
146
  });
147
147
 
148
148
  this.#client.on("backoff", (retry, delay) => {
149
- console.log("Backoff ", retry, " next attempt in ", delay, "ms", self.#endpointurl);
149
+ if((retry%10) == 0)
150
+ this.LogInfo(`OPCUAIf Try Reconnection ${retry} next attempt in ${delay}ms ${self.#endpointurl}\n`);
150
151
  });
151
152
 
152
153
  this.#client.on("connection_lost", () => {
153
- console.log("Connection lost");
154
+ this.LogInfo(`OPCUAIf: Connection lost\n`);
154
155
  });
155
156
 
156
157
  this.#client.on("connection_reestablished", () => {
157
- console.log("Connection re-established");
158
+ this.LogInfo(`OPCUAIf: Connection re-established\n`);
158
159
  });
159
160
 
160
161
  this.#client.on("connection_failed", () => {
161
- console.log("Connection failed");
162
+ this.LogInfo(`OPCUAIf: Connection failed\n`);
162
163
  });
163
164
  this.#client.on("start_reconnection", () => {
164
- console.log("Starting reconnection");
165
+ this.LogInfo(`OPCUAIf: Starting reconnection\n`);
165
166
  });
166
167
 
167
168
  this.#client.on("after_reconnection", (err) => {
168
- console.log("After Reconnection event =>", err);
169
+ this.LogInfo(`OPCUAIf: After Reconnection event => ${err}\n`);
169
170
  });
170
171
  this.#client.on("security_token_renewed", () => {
171
- //console.log("security_token_renewed =>");
172
- //console.log(this.#client.toString());
173
- })
174
- this.#client.on("lifetime_75", (token) => {
175
- //// /*ChannelSecurityToken*/
176
- //console.log("lifetime_75 =>", token.toString());
172
+ this.LogInfo(`OPCUAIf: security_token_renewed\n`);
177
173
  })
174
+ this.#client.on("lifetime_75", (token) => {})
178
175
 
179
- console.log("connecting client to url",url);//, this.#session.toString());
176
+ this.LogInfo(`OPCUAIf: connecting client to ${url}\n`);//, this.#session.toString());
180
177
  await this.#client.connect(url);
181
178
 
182
179
  this.#session = await this.#client.createSession();
183
180
 
184
181
  this.#session.on("session_closed", (statusCode) => {
185
- console.log(" Session has been closed with statusCode = ", statusCode.toString());
182
+ this.LogInfo(`OPCUAIf: Session has been closed\n`);
186
183
  })
187
184
  this.#session.on("session_restored", () => {
188
- console.log(" Session has been restored");
185
+ this.LogInfo(`OPCUAIf: Session has been restored\n`);
189
186
  });
190
187
  this.#session.on("keepalive", (lastKnownServerState) => {
191
- console.log("KeepAlive lastKnownServerState", lastKnownServerState);
188
+ this.LogInfo(`OPCUAIf: KeepAlive lastKnownServerState ${lastKnownServerState}\n`);
192
189
  });
193
190
  this.#session.on("keepalive_failure", () => {
194
- console.log("KeepAlive failure");
191
+ this.LogInfo(`OPCUAIf: KeepAlive failure\n`);
195
192
  });
196
193
 
197
194
  this.#sub = await this.#session.createSubscription2({
@@ -202,9 +199,9 @@ export class OPCUAIf extends BaseIf {
202
199
  requestedPublishingInterval: 1000
203
200
  });
204
201
 
205
- this.LogInfo("session created\n");//, this.#session.toString());
206
- this.LogInfo("client\n", this.#client.toString());
207
- this.LogInfo("subscription\n",this.#sub.toString());
202
+ this.LogInfo(`OPCUAIf: session created\n`);//, this.#session.toString());
203
+ this.LogInfo(`OPCUAIf: client\n`);
204
+ this.LogInfo(`OPCUAIf: subscription\n`);
208
205
  this.#connected = true
209
206
  this.#endpointurl = url
210
207
  }
@@ -222,17 +219,16 @@ export class OPCUAIf extends BaseIf {
222
219
  };
223
220
 
224
221
  if (!this.#sub){
225
- this.LogError(" not register monitored items",itemToMonitor);
222
+ this.LogError(`OPCUAIf: not register monitored items $itemToMonitor\n`);
226
223
  return
227
224
  }
228
225
  const monitoredItem = await this.#sub.monitor(itemToMonitor, monitoringParameters, TimestampsToReturn.Both);
229
226
  this.monitoreditems[monitoredItem.monitoredItemId] = nodeID
230
227
  var self=this
231
- //monitoredItem.on("changed", this.MonitoredItemUpdate)
232
228
  monitoredItem.on("changed", function (dataValue) {
233
229
  var nodeId = self.monitoreditems[this.monitoredItemId]
234
230
  var buttonID = self.buttons[this.monitoredItemId]
235
- //console.log("monitored item changed: ", this.monitoredItemId,nodeId, dataValue.value.value);
231
+ this.LogDebug("OPCUAIf: monitored item changed: ", this.monitoredItemId,nodeId, dataValue.value.value,"\n");
236
232
  self.myEmitter.emit('monitored item changed',buttonID,nodeId, dataValue.value.value)
237
233
  });
238
234
 
@@ -245,18 +241,18 @@ export class OPCUAIf extends BaseIf {
245
241
  attributeId: AttributeIds.Value
246
242
  };
247
243
  if (!this.#connected){
248
- this.LogError(" not connected, cannot read",nodeID);
244
+ this.LogError(`OPCUAIf: not connected, cannot read ${nodeID}\n`);
249
245
  return
250
246
  }
251
247
  const dataValue2 = await this.#session.read(nodeToRead, 0);
252
- this.LogError("read: nodeID ",nodeID, dataValue2.toString());
248
+ this.LogError("OPCUAIf: read nodeID ",nodeID, dataValue2.toString(),"\n");
253
249
  return dataValue2
254
250
  }
255
251
 
256
252
  async Write(nodeID,value,datatype=DataType.String) {
257
253
  let self = this
258
254
  if (!this.#connected){
259
- self.LogError(" not connected, cannot write",nodeID, value);
255
+ self.LogError("OPCUAIf: not connected, cannot write",nodeID, value,"\n");
260
256
  return
261
257
  }
262
258
  var nodesToWrite = [{
@@ -273,12 +269,12 @@ export class OPCUAIf extends BaseIf {
273
269
  await this.#session.write(nodesToWrite, function(err,statusCodes) {
274
270
  if (!err) {
275
271
  if (statusCodes && statusCodes[0].value != 0){
276
- self.LogInfo("status", statusCodes);
272
+ self.LogInfo(`OPCUAIf: status $statusCodes\n`);
277
273
  }else{
278
- self.LogInfo(" write ok",nodeID, value);
274
+ self.LogInfo(`OPCUAIf: wrote $nodeID => $value\n`);
279
275
  }
280
276
  }else{
281
- self.LogError(" write NOT ok",nodeID, value);
277
+ self.LogError("OPCUAIf: write NOT ok",nodeID, value,"\n");
282
278
  self.LogError(err)
283
279
  }
284
280
  });
@@ -1,4 +1,5 @@
1
1
  import { exec } from 'child_process'
2
+ //import { exec } from 'node:child_process'
2
3
  import { BaseIf } from './baseif.mjs'
3
4
 
4
5
  /**
@@ -11,7 +12,7 @@ export class SHELLif extends BaseIf {
11
12
  }
12
13
 
13
14
  async stop(){
14
- console.log("Stopping SHELLif")
15
+ this.LogInfo("SHELLif: Stopping")
15
16
  }
16
17
 
17
18
  Check(options) {
@@ -26,15 +27,15 @@ export class SHELLif extends BaseIf {
26
27
  * @returns
27
28
  */
28
29
  async sh (cmd) {
29
- if (this.options.verbose)
30
- console.log('ShellIf call', cmd)
31
-
30
+ let self = this;
31
+ this.LogDebug(`ShellIf: runCmd: ${cmd}\n`)
32
+
32
33
  return new Promise(function (resolve, reject) {
33
34
  exec(cmd, (err, stdout, stderr) => {
34
35
  if (stdout.length>0)
35
- console.log(`SHELLif Out: ${stdout}`)
36
+ self.LogInfo(`SHELLif Out: ${stdout}`)
36
37
  if (stderr.length>0)
37
- console.log(`SHELLif Err: ${stderr}`)
38
+ self.LogError(`SHELLif Err: ${stderr}`)
38
39
  if (err) {
39
40
  reject(err)
40
41
  } else {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "loupedeck-commander",
3
- "version": "1.2.0",
4
- "description": "A system to ease working with LoupeDeck devices using CMD-line interfaces",
3
+ "version": "1.2.1",
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": {
7
7
  "test": "node test.mjs",
package/profile-1.json CHANGED
@@ -6,9 +6,9 @@
6
6
  "password": "httppasswd",
7
7
  "verbose" : false,
8
8
  "endpointurl": "opc.tcp://localhost:4840",
9
- "nodeid" : "ns=4;s=Is{simnbr}.Kvm.in.Source",
10
- "value" : "kvm-transmitter-1",
11
- "simnbr": "1"
9
+ "nodeid" : "ns=0;s=OpcUaNode-{customarg}",
10
+ "value" : "value",
11
+ "customarg": "1"
12
12
  },
13
13
  "description": "",
14
14
  "config": {},
@@ -29,25 +29,22 @@
29
29
  "cmd": "echo \"{id} {state}\""
30
30
  }
31
31
  },
32
- "group": "kvm1"
32
+ "group": "group1"
33
33
  },
34
34
  "1": {
35
35
  "states": {
36
36
  "off": {
37
37
  "color": "#aaaaaa",
38
- "image": "icons/bulb.png",
39
- "http": "http://{user}:{password}@{hostname}:7778/control/connections"
38
+ "image": "icons/bulb.png"
40
39
  },
41
40
  "on": {
42
41
  "color": "#11ff11",
43
42
  "image": "icons/bulb.png",
44
- "http": "http://{hostname}:7778/control/connections",
45
- "opcua": "write",
46
- "nodeid": "ns=4;s=Is{simnbr}.Kvm.in.Source",
43
+ "http": "http://{user}:{password}@{hostname}:7778/control/connections",
47
44
  "value": "{key}"
48
45
  }
49
46
  },
50
- "group": "kvm1"
47
+ "group": "group1"
51
48
  },
52
49
  "2": {
53
50
  "states": {
@@ -58,33 +55,222 @@
58
55
  "on": {
59
56
  "color": "#11ff11",
60
57
  "image": "icons/mountain.png",
61
- "opcua": "write",
62
- "nodeid": "ns=4;s=Is{simnbr}.Kvm.in.Source",
58
+ "opcua": "ns=0;s=Node{simnbr}",
63
59
  "value": "{key}"
64
60
  }
65
61
  },
66
- "group": "kvm1"
62
+ "group": "group1"
63
+ },
64
+ "8": {
65
+ "states": {
66
+ "off": {
67
+ "color": "#aaaaaa",
68
+ "cmd": "echo \"{id} {state} {event} {value}\"",
69
+ "value": "custom-value-{key}" },
70
+ "on": {
71
+ "color": "#11ff11",
72
+ "cmd": "echo \"{id} {state} {event} {value}\"",
73
+ "value": "custom-value-{key}"
74
+ }
75
+ },
76
+ "group": "group2"
77
+ },
78
+ "9": {
79
+ "states": {
80
+ "off": {
81
+ "color": "#bbbbbb",
82
+ "cmd": "echo \"{id} {state} {event} {value}\"",
83
+ "value": "custom-value-{key}" },
84
+ "on": {
85
+ "color": "#33ff33",
86
+ "cmd": "echo \"{id} {state} {event} {value}\"",
87
+ "value": "custom-value-{key}"
88
+ }
89
+ },
90
+ "group": "group2"
67
91
  }
68
- },
69
- "knob": {}
70
- },
71
- "knobs": {
72
- "left": {},
73
- "right": {}
92
+
93
+
94
+ }
74
95
  },
75
96
  "buttons": {
97
+ "knobTL": {
98
+ "states": {
99
+ "off": {
100
+ "cmd": "echo \"{id} {state} Val: {value} Event: {event}\""
101
+ },
102
+ "on": {
103
+ "cmd": "echo \"{id} {state} Val: {value} Event: {event}\""
104
+ }
105
+ }
106
+ },
107
+ "knobCL": {
108
+ "states": {
109
+ "off": {
110
+ "cmd": "echo \"{id} {state} Val: {value} Event: {event}\""
111
+ },
112
+ "on": {
113
+ "cmd": "echo \"{id} {state} Val: {value} Event: {event}\""
114
+ }
115
+ }
116
+ },
117
+ "knobBL": {
118
+ "states": {
119
+ "off": {
120
+ "cmd": "echo \"{id} {state} Val: {value} Event: {event}\""
121
+ },
122
+ "on": {
123
+ "cmd": "echo \"{id} {state} Val: {value} Event: {event}\""
124
+ }
125
+ }
126
+ },
127
+ "knobTR": {
128
+ "states": {
129
+ "off": {
130
+ "cmd": "echo \"{id} {state} Val: {value} Event: {event}\""
131
+ },
132
+ "on": {
133
+ "cmd": "echo \"{id} {state} Val: {value} Event: {event}\""
134
+ }
135
+ }
136
+ },
137
+ "knobCR": {
138
+ "states": {
139
+ "off": {
140
+ "cmd": "echo \"{id} {state} Val: {value} Event: {event}\""
141
+ },
142
+ "on": {
143
+ "cmd": "echo \"{id} {state} Val: {value} Event: {event}\""
144
+ }
145
+ }
146
+ },
147
+ "knobBR": {
148
+ "states": {
149
+ "off": {
150
+ "cmd": "echo \"{id} {state} Val: {value} Event: {event}\""
151
+ },
152
+ "on": {
153
+ "cmd": "echo \"{id} {state} Val: {value} Event: {event}\""
154
+ }
155
+ }
156
+ },
157
+ "0": {
158
+ "states": {
159
+ "off": {
160
+ "color": "#aaaaaa",
161
+ "cmd": "echo \"{id} {state} {event}\""
162
+ },
163
+ "med": {
164
+ "color": "ff11ff",
165
+ "cmd": "echo \"{id} {state} {event}\""
166
+ },
167
+ "on": {
168
+ "color": "#11ff11",
169
+ "cmd": "echo \"{id} {state} {event}\""
170
+ }
171
+ },
172
+ "group": "group1"
173
+ },
76
174
  "1": {
77
175
  "states": {
78
176
  "off": {
79
177
  "color": "#aaaaaa",
80
- "cmd": "echo \"{id} {state}\""
178
+ "cmd": "echo \"{id} {state} {event}\""
81
179
  },
180
+ "med": {
181
+ "color": "ff11ff",
182
+ "cmd": "echo \"{id} {state} {event}\""
183
+ },
184
+ "on": {
185
+ "color": "#11ff11",
186
+ "cmd": "echo \"{id} {state} {event}\""
187
+ }
188
+ },
189
+ "group": "group1"
190
+ },
191
+ "2": {
192
+ "states": {
193
+ "off": {
194
+ "color": "#aaaaaa",
195
+ "cmd": "echo \"{id} {key} {state} {event}\"",
196
+ "filter": "released" },
197
+ "on": {
198
+ "color": "#11ff11",
199
+ "cmd": "echo \"{id} {key} {state} {event}\"",
200
+ "filter": "released"
201
+ }
202
+ },
203
+ "group": "group1"
204
+ },
205
+ "3": {
206
+ "states": {
207
+ "off": {
208
+ "color": "#aaaaaa",
209
+ "cmd": "echo \"{id} {key} {state} {event}\"",
210
+ "filter": "released" },
211
+ "on": {
212
+ "color": "#11ff11",
213
+ "cmd": "echo \"{id} {key} {state} {event}\"",
214
+ "filter": "released"
215
+ }
216
+ },
217
+ "group": "group1"
218
+ },
219
+ "4": {
220
+ "states": {
221
+ "off": {
222
+ "color": "#aaaaaa",
223
+ "cmd": "echo \"{id} {key} {state} {event}\"",
224
+ "filter": "released" },
225
+ "on": {
226
+ "color": "#11ff11",
227
+ "cmd": "echo \"{id} {key} {state} {event}\"",
228
+ "filter": "released"
229
+ }
230
+ },
231
+ "group": "group1"
232
+ },
233
+ "5": {
234
+ "states": {
235
+ "off": {
236
+ "color": "#aaaaaa",
237
+ "cmd": "echo \"{id} {key} {state} {event}\"",
238
+ "filter": "released" },
239
+ "on": {
240
+ "color": "#11ff11",
241
+ "cmd": "echo \"{id} {key} {state} {event}\"",
242
+ "filter": "released"
243
+ }
244
+ },
245
+ "group": "group1"
246
+ },
247
+ "6": {
248
+ "states": {
249
+ "off": {
250
+ "color": "#aaaaaa",
251
+ "cmd": "echo \"{id} {key} {state} {event}\"",
252
+ "filter": "released" },
253
+ "on": {
254
+ "color": "#11ff11",
255
+ "cmd": "echo \"{id} {key} {state} {event}\"",
256
+ "filter": "released"
257
+ }
258
+ },
259
+ "group": "group1"
260
+ },
261
+ "7": {
262
+ "states": {
263
+ "off": {
264
+ "color": "#aaaaaa",
265
+ "cmd": "echo \"{id} {key} {state} {event}\"",
266
+ "filter": "released" },
82
267
  "on": {
83
268
  "color": "#11ff11",
84
- "cmd": "echo \"{id} {state}\""
269
+ "cmd": "echo \"{id} {key} {state} {event}\"",
270
+ "filter": "released"
85
271
  }
86
272
  },
87
- "group": "kvm1"
273
+ "group": "group1"
88
274
  }
89
275
  }
90
276
  }
package/test.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { BaseLoupeDeckHandler } from './common/BaseLoupeDeckHandler.mjs'
2
2
  //import { BaseLoupeDeckHandler } from 'loupedeck-commander'
3
3
 
4
- const handler = new BaseLoupeDeckHandler('config-test.json')
4
+ const handler = new BaseLoupeDeckHandler('config.json')
5
5
 
6
6
  /**
7
7
  * Stop the handlers when a signal like SIGINT or SIGTERM arrive
package/config-test.json DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "application": "TEST",
3
- "profiles": [
4
- {
5
- "name": "profile1",
6
- "file": "profile-2.json"
7
- }
8
- ]
9
- }
@@ -1,31 +0,0 @@
1
- import * as opcuaif from '../interfaces/opcuaif.mjs'
2
-
3
-
4
- var opcuainterface = new opcuaif.OPCUAIf()
5
- import {
6
- DataType
7
- } from "node-opcua";
8
-
9
- var call = {
10
-
11
- }
12
-
13
- var option = {
14
- "id" : "touch-0",
15
- "key" : "1",
16
- "state" : "off",
17
- "min": "0",
18
- "max": "0",
19
- "color": "0",
20
- "image": "0",
21
- "endpointurl": "opc.tcp://localhost:4840",
22
- "nodeid": "ns=4;s=Is{key}.Kvm.in.Source",
23
- "value": "{key}"
24
- }
25
-
26
- await opcuainterface.call(call,option)
27
- await opcuainterface.Subscribe("ns=4;s=Is1.Kvm.out.Source")
28
- await opcuainterface.Subscribe("ns=4;s=Is1.Kvm.out.Turret")
29
- await opcuainterface.Write("ns=4;s=Is1.Kvm.out.Turret",1,DataType.Int16)
30
- await opcuainterface.Write("ns=4;s=Is1.Kvm.out.Source","hello",DataType.String)
31
- //await opcuainterface.Write("ns=4;s=Is1.Kvm.state",2,DataType.Int32)
@@ -1,95 +0,0 @@
1
- import {
2
- OPCUAClient,
3
- BrowseDirection,
4
- AttributeIds,
5
- NodeClassMask,
6
- makeBrowsePath,
7
- resolveNodeId,
8
- TimestampsToReturn,
9
- coerceInt32,
10
- coerceByteString
11
- } from "node-opcua";
12
- import { BaseIf } from './baseif.mjs'
13
-
14
- var endpointUrl = "opc.tcp://localhost:4840";
15
- const subscriptionParameters = {
16
- maxNotificationsPerPublish: 1000,
17
- publishingEnabled: true,
18
- requestedLifetimeCount: 100,
19
- requestedMaxKeepAliveCount: 10,
20
- requestedPublishingInterval: 1000
21
- };
22
- var client
23
- var GlobalSession
24
- var GlobalSub
25
-
26
-
27
- async function Connect() {
28
- client = OPCUAClient.create({
29
- endpointMustExist: false
30
- });
31
- client.on("backoff", (retry, delay) =>
32
- console.log("still trying to connect to ", endpointUrl, ": retry =", retry, "next attempt in ", delay / 1000, "seconds")
33
- );
34
-
35
-
36
- //await client.connect(endpointUrl)
37
- var self=this
38
- await client.withSubscriptionAsync(endpointUrl, subscriptionParameters, async (session, subscription) => {
39
- GlobalSession = session
40
- GlobalSub = subscription
41
- console.log("Session initialized")
42
-
43
- // wait until CTRL+C is pressed
44
- console.log("CTRL+C to stop");
45
- await new Promise((resolve) => process.once("SIGINT", resolve));
46
- } )
47
- }
48
-
49
- async function Subscribe(nodeID) {
50
- // install monitored item
51
-
52
- nodeID = "ns=4;s=Is1.Kvm.in.Source"
53
- const itemToMonitor = {
54
- nodeId: resolveNodeId(nodeID),
55
- attributeId: AttributeIds.Value
56
- };
57
- const monitoringParameters = {
58
- samplingInterval: 100,
59
- discardOldest: true,
60
- queueSize: 10
61
- };
62
-
63
- const monitoredItem = await GlobalSub.monitor(itemToMonitor, monitoringParameters, TimestampsToReturn.Both);
64
- monitoredItem.on("changed", function (dataValue) {
65
- //console.log("monitored item changed: ", coerceInt32(dataValue.value.value), "bytes");
66
- console.log("monitored item changed: ", nodeID, dataValue.value.value);
67
- });
68
- }
69
-
70
- async function Read(nodeID) {
71
- if client
72
- const maxAge = 0;
73
- const nodeToRead = {
74
- nodeId: "ns=4;s=Is1.Kvm.in.Receiver",
75
- attributeId: AttributeIds.Value
76
- };
77
-
78
- const dataValue2 = await GlobalSession.read(nodeToRead, maxAge);
79
- console.log(" nodeID ",nodeID, dataValue2.toString());
80
- }
81
-
82
- Connect();
83
- //await Read("ns=4;s=Is1.Kvm.in.Receiver")
84
- await Subscribe("ns=4;s=Is1.Kvm.in.Receiver")
85
- await new Promise((resolve) => process.once("SIGINT", resolve));
86
-
87
-
88
- /**
89
- * Our Special-Handler just used the Default - and adds Vibration after triggers through Button-Releases
90
- */
91
- export class OPCUAIf extends BaseIf {
92
- async call (callString, options = {}) {
93
- }
94
- }
95
-
package/profile-2.json DELETED
@@ -1,164 +0,0 @@
1
- {
2
- "profile": "sample",
3
- "parameters": {
4
- "hostname": "localhost",
5
- "user": "httpuser",
6
- "password": "httppasswd",
7
- "verbose" : false,
8
- "endpointurl": "opc.tcp://localhost:4840",
9
- "nodeid" : "ns=4;s=Is{simnbr}.Kvm.in.Source",
10
- "value" : "kvm-transmitter-1",
11
- "simnbr": "1"
12
- },
13
- "description": "",
14
- "config": {},
15
- "touch": {
16
- "left": {},
17
- "right": {},
18
- "center": {
19
- "0": {
20
- "states": {
21
- "off": {
22
- "color": "#aaaaaa",
23
- "image": "icons/home.png"
24
- },
25
- "on": {
26
- "color": "#11ff11",
27
- "image": "icons/home.png",
28
- "opcua": "ns=4;s=Is{simnbr}.Kvm.in.Source",
29
- "value": "kvm-transmitter{key}-1"
30
- }
31
- },
32
- "nodeid": "ns=4;s=Is{simnbr}.Kvm.out.Source",
33
- "group": "kvm1"
34
- },
35
- "1": {
36
- "states": {
37
- "off": {
38
- "color": "#aaaaaa",
39
- "image": "icons/bulb.png"
40
- },
41
- "on": {
42
- "color": "#11ff11",
43
- "image": "icons/bulb.png",
44
- "opcua": "ns=4;s=Is{simnbr}.Kvm.in.Source",
45
- "value": "kvm-transmitter{key}-1"
46
- }
47
- },
48
- "nodeid": "ns=4;s=Is{simnbr}.Kvm.out.Source",
49
- "group": "kvm1"
50
- },
51
- "2": {
52
- "states": {
53
- "off": {
54
- "color": "#aaaaaa",
55
- "image": "icons/mountain.png"
56
- },
57
- "on": {
58
- "color": "#11ff11",
59
- "image": "icons/mountain.png",
60
- "opcua": "ns=4;s=Is{simnbr}.Kvm.in.Source",
61
- "value": "kvm-transmitter{key}-1"
62
- }
63
- },
64
- "nodeid": "ns=4;s=Is{simnbr}.Kvm.out.Source",
65
- "group": "kvm1"
66
- },
67
- "3": {
68
- "states": {
69
- "off": {
70
- "color": "#aaaaaa",
71
- "image": "icons/mountain.png"
72
- },
73
- "on": {
74
- "color": "#11ff11",
75
- "image": "icons/mountain.png",
76
- "opcua": "ns=4;s=Is{simnbr}.Kvm.in.Source",
77
- "value": "kvm-transmitter{key}-1"
78
- }
79
- },
80
- "nodeid": "ns=4;s=Is{simnbr}.Kvm.out.Source",
81
- "group": "kvm1"
82
- },
83
- "4": {
84
- "states": {
85
- "off": {
86
- "color": "#aaaaaa",
87
- "image": "icons/mountain.png"
88
- },
89
- "on": {
90
- "color": "#11ff11",
91
- "image": "icons/mountain.png",
92
- "opcua": "ns=4;s=Is{simnbr}.Kvm.in.Source",
93
- "value": "kvm-transmitter{key}-1"
94
- }
95
- },
96
- "nodeid": "ns=4;s=Is{simnbr}.Kvm.out.Source",
97
- "group": "kvm1"
98
- }
99
- },
100
- "knob": {}
101
- },
102
- "knobs": {
103
- "left": {},
104
- "right": {}
105
- },
106
- "buttons": {
107
- "1": {
108
- "states": {
109
- "off": {
110
- "color": "#aaaaaa"
111
- },
112
- "on": {
113
- "color": "#11ff11",
114
- "http": "http://{hostname}:7778/control/connections",
115
- "opcua": "ns=4;s=Is{simnbr}.Kvm.out.Source",
116
- "value": "kvm-transmitter{key}-1"
117
- }
118
- },
119
- "group": "kvm1"
120
- },
121
- "2": {
122
- "states": {
123
- "off": {
124
- "color": "#aaaaaa"
125
- },
126
- "on": {
127
- "color": "#11ff11",
128
- "http": "http://{hostname}:7778/control/connections",
129
- "opcua": "ns=4;s=Is{simnbr}.Kvm.out.Source",
130
- "value": "kvm-transmitter{key}-1"
131
- }
132
- },
133
- "group": "kvm1"
134
- },
135
- "3": {
136
- "states": {
137
- "off": {
138
- "color": "#aaaaaa"
139
- },
140
- "on": {
141
- "color": "#11ff11",
142
- "http": "http://{hostname}:7778/control/connections",
143
- "opcua": "ns=4;s=Is{simnbr}.Kvm.out.Source",
144
- "value": "kvm-transmitter{key}-1"
145
- }
146
- },
147
- "group": "kvm1"
148
- },
149
- "4": {
150
- "states": {
151
- "off": {
152
- "color": "#aaaaaa"
153
- },
154
- "on": {
155
- "color": "#11ff11",
156
- "http": "http://{hostname}:7778/control/connections",
157
- "opcua": "ns=4;s=Is{simnbr}.Kvm.out.Source",
158
- "value": "kvm-transmitter{key}-1"
159
- }
160
- },
161
- "group": "kvm1"
162
- }
163
- }
164
- }