loupedeck-commander 1.0.1 → 1.2.0

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.
@@ -0,0 +1,9 @@
1
+ {
2
+ "application": "TEST",
3
+ "profiles": [
4
+ {
5
+ "name": "profile1",
6
+ "file": "profile-2.json"
7
+ }
8
+ ]
9
+ }
@@ -0,0 +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,
9
+ ];
@@ -1,9 +1,8 @@
1
- import pkg from 'loupedeck'
2
- import { BaseLoupeDeckHandler } from './common/BaseLoupeDeckHandler.mjs'
3
- const { HAPTIC } = pkg
1
+ import { HAPTIC } from 'loupedeck'
2
+ import { BaseLoupeDeckHandler } from '../common/BaseLoupeDeckHandler.mjs'
4
3
 
5
4
  /**
6
- * Our Special-Handler just used the Default - and adds Vibration after triggers through Button-Releases
5
+ * Our Special-Handler just used the Default - and adds Vibration after triggers through Button-Releases
7
6
  */
8
7
  export class ExampleDeviceHandler extends BaseLoupeDeckHandler {
9
8
  /**
@@ -0,0 +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()
Binary file
@@ -0,0 +1,57 @@
1
+ import format from 'string-template'
2
+
3
+ export class BaseIf {
4
+ formattedCommand
5
+ cmd
6
+ options
7
+ call (cmd, options = {}) {
8
+ var res = this.Check(options)
9
+ if (res < 0){
10
+ LogError("Missing essential options in dictionary => Quitting\n",res,options)
11
+ return false
12
+ }
13
+
14
+ this.cmd = cmd
15
+ this.options = options
16
+ this.formattedCommand = this.formatString(cmd, options)
17
+ return this.formattedCommand
18
+ }
19
+
20
+ async stop (){
21
+
22
+ }
23
+
24
+ formatString (cmd, options = {}) {
25
+ return format(cmd, options)
26
+ }
27
+
28
+ Check(options) {
29
+ if (!"id" in options)
30
+ return -1
31
+ if (!"key" in options)
32
+ return -2
33
+ if (!"state" in options)
34
+ return -3
35
+ if (!"min" in options)
36
+ return -4
37
+ if (!"max" in options)
38
+ return -5
39
+ if (!"color" in options)
40
+ return -6
41
+ if (!"image" in options)
42
+ return -7
43
+ return 0
44
+ }
45
+
46
+ LogError(...args){
47
+ console.error(args)
48
+ }
49
+
50
+ LogInfo(...args){
51
+ if (this.options && this.options.verbose)
52
+ console.log(args)
53
+ }
54
+
55
+ }
56
+
57
+
@@ -0,0 +1,86 @@
1
+ import * as http from 'node:http'
2
+ import url from 'node:url'
3
+ import { BaseIf } from './baseif.mjs'
4
+
5
+ /**
6
+ * Our Special-Handler just used the Default - and adds Vibration after triggers through Button-Releases
7
+ */
8
+ export class HTTPif extends BaseIf {
9
+ async call (url1, options = {}) {
10
+ url1 = super.call(url1, options)
11
+ let myURL
12
+ try {
13
+ myURL = new url.URL(url1)
14
+ await this.get(myURL, options)
15
+ } catch (exception) {
16
+ console.error('error with URL', exception)
17
+ return false
18
+ }
19
+ return true
20
+ }
21
+
22
+ async stop(){
23
+ console.log("Stopping HTTPif")
24
+
25
+ }
26
+
27
+ Check(options) {
28
+ var res= super.Check(options)
29
+ if (res <0)
30
+ return res
31
+ if (!options.hostname)
32
+ return -21
33
+ /*if (!options.port)
34
+ return -22
35
+ if (!options.pathname)
36
+ return -23*/
37
+ return 0
38
+ }
39
+
40
+ /**
41
+ * Handle a HTTP Get request with Basic Authentification
42
+ * @param {*} myURL Uri
43
+ */
44
+ async get (myURL) {
45
+ const auth = 'Basic ' + Buffer.from(myURL.username + ':' + myURL.password).toString('base64')
46
+ const getOptions = {
47
+ hostname: myURL.hostname,
48
+ port: myURL.port,
49
+ path: myURL.pathname,
50
+ agent: false, // Create a new agent just for this one request
51
+ headers: {
52
+ Authorization: auth
53
+ }
54
+ }
55
+
56
+ if (this.options.verbose)
57
+ console.log('HTTPIf call URL:', myURL, getOptions)
58
+
59
+
60
+ const prom = new Promise((resolve, reject) => {
61
+ const req = http.get(getOptions, (response) => {
62
+ const chunksOfData = []
63
+
64
+ response.on('data', (fragments) => {
65
+ chunksOfData.push(fragments)
66
+ })
67
+
68
+ response.on('end', () => {
69
+ const responseBody = Buffer.concat(chunksOfData)
70
+ resolve(responseBody.toString())
71
+ })
72
+
73
+ response.on('error', (error) => {
74
+ resolve("")
75
+ })
76
+ })
77
+
78
+ req.on('error', (e) => {
79
+ console.warn(`ignore other errors like ERRNOTCONNECTED: ${e.message}`)
80
+ return false
81
+ });
82
+ }).catch(function (error) { // (*)
83
+ return false
84
+ })
85
+ }
86
+ }
@@ -0,0 +1,287 @@
1
+ import {
2
+ OPCUAClient,
3
+ MessageSecurityMode,
4
+ SecurityPolicy,
5
+ BrowseDirection,
6
+ AttributeIds,
7
+ NodeClassMask,
8
+ makeBrowsePath,
9
+ resolveNodeId,
10
+ TimestampsToReturn,
11
+ coerceInt32,
12
+ coerceByteString,
13
+ DataType
14
+ } from "node-opcua";
15
+ import { EventEmitter } from 'node:events'
16
+ import { BaseIf } from './baseif.mjs'
17
+
18
+ const subscriptionParameters = {
19
+ maxNotificationsPerPublish: 1000,
20
+ publishingEnabled: true,
21
+ requestedLifetimeCount: 100,
22
+ requestedMaxKeepAliveCount: 10,
23
+ requestedPublishingInterval: 1000
24
+ };
25
+
26
+
27
+ /**
28
+ * Our Special-Handler just used the Default - and adds Vibration after triggers through Button-Releases
29
+ */
30
+ export class OPCUAIf extends BaseIf {
31
+
32
+ #client
33
+ #session
34
+ #sub
35
+ #connected
36
+ #endpointurl
37
+ monitoreditems
38
+ buttons
39
+ #callback
40
+ myEmitter
41
+ constructor() {
42
+ super()
43
+ this.myEmitter = new EventEmitter();
44
+
45
+ console.log("OPCUAIf Constructed");
46
+ }
47
+
48
+ async stop(){
49
+ if (!this.#client)
50
+ return
51
+
52
+ console.log("Stopping OPC/UA")
53
+ await this.#client.closeSession(this.#session,true)
54
+ await this.#client.disconnect()
55
+ this.#connected = false
56
+ this.#client = null
57
+ console.log("Stopped OPC/UA")
58
+ }
59
+
60
+ async init( options = {},config = {},callbackFunction){
61
+ var res = this.Check(options)
62
+ if (res<0){
63
+ console.error("OpcuaIf init: Missing essential options in dictionary => Quitting\n",res,options)
64
+ }
65
+ try{
66
+ this.#endpointurl = options.endpointurl
67
+ this.#callback = callbackFunction
68
+ this.monitoreditems = {}
69
+ this.buttons = {}
70
+ console.log("OPCUAIf init",this.#endpointurl);
71
+
72
+ await this.Connect(this.#endpointurl);
73
+
74
+ let field=config.touch.center
75
+ const keys = Object.keys(field)
76
+ for (let i = 0; i < keys.length; i++) {
77
+ const key = keys[i]
78
+ const elem = config.touch.center[key]
79
+ if (elem.nodeid){
80
+ let format = this.formatString(elem.nodeid,options)
81
+ let monitoredItemId = await this.Subscribe(format)
82
+ this.buttons[monitoredItemId] = i
83
+ }
84
+
85
+ }
86
+ } catch (error) {
87
+ console.log(' Error', error)
88
+ }
89
+ }
90
+
91
+ async call (opcuaNode, options = {}) {
92
+ var res = this.Check(options)
93
+ if (res<0){
94
+ console.error("OpcuaIf call: Missing essential options in dictionary => Quitting\n",res,options)
95
+ return false
96
+ }
97
+
98
+ var nodeId = super.formatString(opcuaNode, options)
99
+ var value = super.formatString(options.value, options)
100
+
101
+ console.log("OPCUAIf:write", nodeId, value)
102
+ await this.Write(nodeId,value)
103
+
104
+ var NewState = "waiting"
105
+ return NewState
106
+ }
107
+
108
+ Check(options) {
109
+ var res= super.Check(options)
110
+ if (res <0)
111
+ return res
112
+ if (!options.endpointurl)
113
+ return -11
114
+ if (!options.nodeid)
115
+ return -12
116
+ if (!options.value)
117
+ return -13
118
+ return 0
119
+ }
120
+
121
+ async Disconnect() {
122
+ if (this.#client){
123
+ console.log("Disconnect !");
124
+ await this.#client.Disconnect()
125
+ console.log("Done !");
126
+ }
127
+ }
128
+ async Connect(url) {
129
+ let self = this
130
+ this.#client = OPCUAClient.create({
131
+ applicationName: "NodeOPCUA-Client",
132
+
133
+ endpointMustExist: false,
134
+ // keepSessionAlive: true,
135
+ requestedSessionTimeout: 60 * 1000,
136
+ securityMode: MessageSecurityMode.None,
137
+ securityPolicy: SecurityPolicy.None,
138
+ connectionStrategy: {
139
+ maxRetry: -1,
140
+ maxDelay: 2500,
141
+ initialDelay: 100
142
+ },
143
+
144
+ defaultSecureTokenLifetime: 20000,
145
+ tokenRenewalInterval: 1000
146
+ });
147
+
148
+ this.#client.on("backoff", (retry, delay) => {
149
+ console.log("Backoff ", retry, " next attempt in ", delay, "ms", self.#endpointurl);
150
+ });
151
+
152
+ this.#client.on("connection_lost", () => {
153
+ console.log("Connection lost");
154
+ });
155
+
156
+ this.#client.on("connection_reestablished", () => {
157
+ console.log("Connection re-established");
158
+ });
159
+
160
+ this.#client.on("connection_failed", () => {
161
+ console.log("Connection failed");
162
+ });
163
+ this.#client.on("start_reconnection", () => {
164
+ console.log("Starting reconnection");
165
+ });
166
+
167
+ this.#client.on("after_reconnection", (err) => {
168
+ console.log("After Reconnection event =>", err);
169
+ });
170
+ 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());
177
+ })
178
+
179
+ console.log("connecting client to url",url);//, this.#session.toString());
180
+ await this.#client.connect(url);
181
+
182
+ this.#session = await this.#client.createSession();
183
+
184
+ this.#session.on("session_closed", (statusCode) => {
185
+ console.log(" Session has been closed with statusCode = ", statusCode.toString());
186
+ })
187
+ this.#session.on("session_restored", () => {
188
+ console.log(" Session has been restored");
189
+ });
190
+ this.#session.on("keepalive", (lastKnownServerState) => {
191
+ console.log("KeepAlive lastKnownServerState", lastKnownServerState);
192
+ });
193
+ this.#session.on("keepalive_failure", () => {
194
+ console.log("KeepAlive failure");
195
+ });
196
+
197
+ this.#sub = await this.#session.createSubscription2({
198
+ maxNotificationsPerPublish: 9000,
199
+ publishingEnabled: true,
200
+ requestedLifetimeCount: 10,
201
+ requestedMaxKeepAliveCount: 10,
202
+ requestedPublishingInterval: 1000
203
+ });
204
+
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());
208
+ this.#connected = true
209
+ this.#endpointurl = url
210
+ }
211
+
212
+ async Subscribe(nodeID) {
213
+ // install monitored item
214
+ const itemToMonitor = {
215
+ nodeId: resolveNodeId(nodeID),
216
+ attributeId: AttributeIds.Value
217
+ };
218
+ const monitoringParameters = {
219
+ samplingInterval: 100,
220
+ discardOldest: true,
221
+ queueSize: 10
222
+ };
223
+
224
+ if (!this.#sub){
225
+ this.LogError(" not register monitored items",itemToMonitor);
226
+ return
227
+ }
228
+ const monitoredItem = await this.#sub.monitor(itemToMonitor, monitoringParameters, TimestampsToReturn.Both);
229
+ this.monitoreditems[monitoredItem.monitoredItemId] = nodeID
230
+ var self=this
231
+ //monitoredItem.on("changed", this.MonitoredItemUpdate)
232
+ monitoredItem.on("changed", function (dataValue) {
233
+ var nodeId = self.monitoreditems[this.monitoredItemId]
234
+ var buttonID = self.buttons[this.monitoredItemId]
235
+ //console.log("monitored item changed: ", this.monitoredItemId,nodeId, dataValue.value.value);
236
+ self.myEmitter.emit('monitored item changed',buttonID,nodeId, dataValue.value.value)
237
+ });
238
+
239
+ return monitoredItem.monitoredItemId;
240
+ }
241
+
242
+ async Read(nodeID) {
243
+ const nodeToRead = {
244
+ nodeId: nodeID,
245
+ attributeId: AttributeIds.Value
246
+ };
247
+ if (!this.#connected){
248
+ this.LogError(" not connected, cannot read",nodeID);
249
+ return
250
+ }
251
+ const dataValue2 = await this.#session.read(nodeToRead, 0);
252
+ this.LogError("read: nodeID ",nodeID, dataValue2.toString());
253
+ return dataValue2
254
+ }
255
+
256
+ async Write(nodeID,value,datatype=DataType.String) {
257
+ let self = this
258
+ if (!this.#connected){
259
+ self.LogError(" not connected, cannot write",nodeID, value);
260
+ return
261
+ }
262
+ var nodesToWrite = [{
263
+ nodeId: nodeID,
264
+ attributeId: AttributeIds.Value,
265
+ indexRange: null,
266
+ value: {
267
+ value: {
268
+ dataType: datatype,
269
+ value: value
270
+ }
271
+ }
272
+ }];
273
+ await this.#session.write(nodesToWrite, function(err,statusCodes) {
274
+ if (!err) {
275
+ if (statusCodes && statusCodes[0].value != 0){
276
+ self.LogInfo("status", statusCodes);
277
+ }else{
278
+ self.LogInfo(" write ok",nodeID, value);
279
+ }
280
+ }else{
281
+ self.LogError(" write NOT ok",nodeID, value);
282
+ self.LogError(err)
283
+ }
284
+ });
285
+ }
286
+ }
287
+
@@ -0,0 +1,31 @@
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)
@@ -0,0 +1,95 @@
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
+
@@ -0,0 +1,46 @@
1
+ import { exec } from 'child_process'
2
+ import { BaseIf } from './baseif.mjs'
3
+
4
+ /**
5
+ * Our Special-Handler just used the Default - and adds Vibration after triggers through Button-Releases
6
+ */
7
+ export class SHELLif extends BaseIf {
8
+ async call (cmd, options = {}) {
9
+ cmd = super.call(cmd, options)
10
+ return await this.sh(cmd)
11
+ }
12
+
13
+ async stop(){
14
+ console.log("Stopping SHELLif")
15
+ }
16
+
17
+ Check(options) {
18
+ var res= super.Check(options)
19
+ if (res <0)
20
+ return res
21
+ }
22
+
23
+ /**
24
+ * Run a Shell command in ASYNC mode
25
+ * @param {*} cmd
26
+ * @returns
27
+ */
28
+ async sh (cmd) {
29
+ if (this.options.verbose)
30
+ console.log('ShellIf call', cmd)
31
+
32
+ return new Promise(function (resolve, reject) {
33
+ exec(cmd, (err, stdout, stderr) => {
34
+ if (stdout.length>0)
35
+ console.log(`SHELLif Out: ${stdout}`)
36
+ if (stderr.length>0)
37
+ console.log(`SHELLif Err: ${stderr}`)
38
+ if (err) {
39
+ reject(err)
40
+ } else {
41
+ resolve({ stdout, stderr })
42
+ }
43
+ })
44
+ })
45
+ }
46
+ }