loupedeck-commander 1.0.2 → 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.
- package/README.md +18 -0
- package/common/ApplicationConfig.mjs +3 -0
- package/common/BaseLoupeDeckHandler.mjs +109 -26
- package/common/touchbuttons.mjs +150 -20
- package/config-test.json +9 -0
- package/eslint.config.mjs +9 -0
- package/example/ExampleDeviceHandler.mjs +2 -3
- package/example/example.mjs +13 -5
- package/interfaces/baseif.mjs +57 -0
- package/interfaces/httpif.mjs +86 -0
- package/interfaces/opcuaif.mjs +287 -0
- package/interfaces/opcuaif_test.mjs +31 -0
- package/interfaces/pubel.js +95 -0
- package/interfaces/shellif.mjs +46 -0
- package/package.json +9 -11
- package/profile-1.json +31 -4
- package/profile-2.json +164 -0
- package/test.mjs +23 -0
package/README.md
CHANGED
|
@@ -72,8 +72,26 @@ node index.mjs
|
|
|
72
72
|
|
|
73
73
|
If you end up with errors related to canvas module - please install the dependencies as mentioned below, and run `npm install -s` again
|
|
74
74
|
|
|
75
|
+
## Thanks
|
|
76
|
+
|
|
77
|
+
Big thanks go out to [foxxyz](https://github.com/foxxyz/loupedeck) and his team maintaining a great javascript loopdeck module
|
|
78
|
+
|
|
75
79
|
## Hints
|
|
76
80
|
|
|
81
|
+
### Cannot connect to Loupedeck device
|
|
82
|
+
|
|
83
|
+
You may need to add your user to the `dialout` group to allow access to the LoupeDeck device, you could do su using the `usermod` as shown below
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# ls -la /dev/ttyACM0
|
|
87
|
+
crw-rw---- 1 root dialout 166, 0 Nov 30 12:59 /dev/ttyACM0
|
|
88
|
+
|
|
89
|
+
# add the current user to the dialout group:
|
|
90
|
+
# sudo usermod -a -G dialout $USER
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
After modifying users group you need to logout and login again to activate this change
|
|
94
|
+
|
|
77
95
|
### CANVAS Module - additional effort needed
|
|
78
96
|
|
|
79
97
|
The library is using [canvas](https://www.npmjs.com/package/canvas) to load images and render graphical content on the LoupeDeck devices.
|
|
@@ -24,6 +24,7 @@ class Profile {
|
|
|
24
24
|
touch = {}
|
|
25
25
|
knobs = {}
|
|
26
26
|
buttons = {}
|
|
27
|
+
parameters = {}
|
|
27
28
|
#file = ''
|
|
28
29
|
#loaded = false
|
|
29
30
|
#error = false
|
|
@@ -48,6 +49,8 @@ class Profile {
|
|
|
48
49
|
this.touch = new TouchConfig(config.touch)
|
|
49
50
|
// Load the Configurations for Button-Areas
|
|
50
51
|
this.buttons = config.buttons
|
|
52
|
+
// Load Parameters
|
|
53
|
+
this.parameters = config.parameters
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {discover, HAPTIC,LoupedeckDevice } from 'loupedeck'
|
|
2
2
|
import { ApplicationConfig } from './ApplicationConfig.mjs'
|
|
3
|
-
import { ButtonField } from './touchbuttons.mjs'
|
|
4
|
-
const { discover, HAPTIC } = pkg
|
|
3
|
+
import { ButtonField, StopInterfaces, InitializeInterfaces,opcuainterface} from './touchbuttons.mjs'
|
|
5
4
|
|
|
6
5
|
export class BaseLoupeDeckHandler {
|
|
7
6
|
device = undefined
|
|
@@ -11,8 +10,9 @@ export class BaseLoupeDeckHandler {
|
|
|
11
10
|
screens = {}
|
|
12
11
|
buttons = {}
|
|
13
12
|
screenUpdate = {}
|
|
13
|
+
stopping = false
|
|
14
14
|
|
|
15
|
-
touchButtons = undefined
|
|
15
|
+
//touchButtons = undefined
|
|
16
16
|
|
|
17
17
|
constructor (config) {
|
|
18
18
|
console.log(`INIT with config ${config}`)
|
|
@@ -21,15 +21,31 @@ export class BaseLoupeDeckHandler {
|
|
|
21
21
|
|
|
22
22
|
async start () {
|
|
23
23
|
console.info('Start')
|
|
24
|
-
while (!this.device) {
|
|
24
|
+
while (!this.device && !this.stopping) {
|
|
25
25
|
try {
|
|
26
|
+
var list = await LoupedeckDevice.list()
|
|
27
|
+
if (list.length>0){
|
|
28
|
+
var element = list[0]
|
|
29
|
+
console.log("Connecting to device")
|
|
30
|
+
console.log("Path: ", element.path)
|
|
31
|
+
console.log("vendorId: ", element.vendorId, element.productId)
|
|
32
|
+
console.log("serialNumber: ", element.serialNumber)
|
|
33
|
+
}
|
|
34
|
+
|
|
26
35
|
this.device = await discover()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
27
39
|
} catch (e) {
|
|
28
40
|
console.error(`${e}. Reattempting in 3 seconds...`)
|
|
29
|
-
await new Promise(
|
|
41
|
+
await new Promise(resolve => setTimeout(resolve, 3000))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (this.stopping){
|
|
45
|
+
return
|
|
30
46
|
}
|
|
31
47
|
}
|
|
32
|
-
console.info(`✅
|
|
48
|
+
console.info(`✅ Discovered Device ${this.device.type}`)
|
|
33
49
|
|
|
34
50
|
const self = this
|
|
35
51
|
|
|
@@ -40,13 +56,49 @@ export class BaseLoupeDeckHandler {
|
|
|
40
56
|
this.device.on('touchstart', (event) => { self.onTouchStart(event) })
|
|
41
57
|
this.device.on('touchmove', (event) => { self.onTouchMove(event) })
|
|
42
58
|
this.device.on('touchend', (event) => { self.onTouchEnd(event) })
|
|
59
|
+
this.device.on('disconnect', (event) => { self.disconnectDevice(event) })
|
|
43
60
|
|
|
44
|
-
|
|
61
|
+
console.info(`✅ Registered callbacks`)
|
|
45
62
|
}
|
|
46
63
|
|
|
47
|
-
|
|
64
|
+
async disconnectDevice (event) {
|
|
65
|
+
console.info("Device Disconnected",event)
|
|
66
|
+
this.device=undefined
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async stop () {
|
|
48
70
|
console.info('Stopping Handler')
|
|
49
|
-
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
console.info(`Closing Device`)
|
|
74
|
+
this.stopping = true
|
|
75
|
+
if (this.device){
|
|
76
|
+
this.device.vibrate(HAPTIC.DESCEND_MED)
|
|
77
|
+
await new Promise(resolve => setTimeout(resolve, 500))
|
|
78
|
+
}
|
|
79
|
+
if (this.device){
|
|
80
|
+
this.device.setBrightness(0)
|
|
81
|
+
await new Promise(resolve => setTimeout(resolve, 500))
|
|
82
|
+
}
|
|
83
|
+
if (this.device){
|
|
84
|
+
this.device.reconnectInterval = 0
|
|
85
|
+
await this.device.close()
|
|
86
|
+
await new Promise(resolve => setTimeout(resolve, 3000))
|
|
87
|
+
console.error(`Device Closed`)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.info(`Stopping interfaces`)
|
|
91
|
+
|
|
92
|
+
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
|
+
|
|
100
|
+
process.exit()
|
|
101
|
+
|
|
50
102
|
}
|
|
51
103
|
|
|
52
104
|
loadConfig (fileName) {
|
|
@@ -57,34 +109,28 @@ export class BaseLoupeDeckHandler {
|
|
|
57
109
|
|
|
58
110
|
async activateProfile (id) {
|
|
59
111
|
// todo Profile-change implementation
|
|
112
|
+
var oldProfile = this.currentProfile
|
|
60
113
|
if (this.appConfig.profiles.length >= id) {
|
|
61
114
|
this.currentProfile = id
|
|
115
|
+
}else{
|
|
116
|
+
return
|
|
62
117
|
}
|
|
63
|
-
await this.buttons.draw(this.device)
|
|
64
|
-
}
|
|
65
118
|
|
|
66
|
-
getCurrentProfile () {
|
|
67
|
-
return this.appConfig.profiles[this.currentProfile]
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Events:
|
|
71
|
-
async onConnected (address) {
|
|
72
|
-
console.info('Connected: ', address)
|
|
73
119
|
const dLeft = this.device.displays.left
|
|
74
120
|
const dRight = this.device.displays.right
|
|
75
121
|
const dCenter = this.device.displays.center
|
|
76
122
|
const dKnob = this.device.displays.knob
|
|
77
|
-
|
|
123
|
+
|
|
78
124
|
const profile = this.getCurrentProfile()
|
|
79
|
-
this.screens.center = new ButtonField('center', this.device.rows, this.device.columns, dCenter.width, dCenter.height, profile.touch.center)
|
|
80
|
-
this.screens.left = new ButtonField('left', 1, 1, dLeft.width, dLeft.height, profile.touch.left)
|
|
81
|
-
this.screens.right = new ButtonField('right', 1, 1, dRight.width, dRight.height, profile.touch.right)
|
|
125
|
+
this.screens.center = new ButtonField('center', this.device.rows, this.device.columns, dCenter.width, dCenter.height, profile.touch.center,profile)
|
|
126
|
+
this.screens.left = new ButtonField('left', 1, 1, dLeft.width, dLeft.height, profile.touch.left,profile)
|
|
127
|
+
this.screens.right = new ButtonField('right', 1, 1, dRight.width, dRight.height, profile.touch.right,profile)
|
|
82
128
|
// knob Display is only available in the CT-version - not with live:
|
|
83
129
|
if (dKnob) {
|
|
84
|
-
this.screens.knob = new ButtonField('knob', 1, 1, dKnob.width, dKnob.height, profile.touch.knob)
|
|
130
|
+
this.screens.knob = new ButtonField('knob', 1, 1, dKnob.width, dKnob.height, profile.touch.knob,profile)
|
|
85
131
|
}
|
|
86
132
|
|
|
87
|
-
this.buttons = new ButtonField('buttons', 1, 1, 0, 0, profile.buttons)
|
|
133
|
+
this.buttons = new ButtonField('buttons', 1, 1, 0, 0, profile.buttons,profile)
|
|
88
134
|
|
|
89
135
|
await this.screens.center.load()
|
|
90
136
|
await this.screens.right.load()
|
|
@@ -98,8 +144,29 @@ export class BaseLoupeDeckHandler {
|
|
|
98
144
|
if (dKnob) {
|
|
99
145
|
await this.screens.knob.draw(this.device)
|
|
100
146
|
}
|
|
101
|
-
|
|
147
|
+
|
|
148
|
+
this.buttons.setState(this.currentProfile+1,1)
|
|
149
|
+
this.buttons.setState(oldProfile+1,1)
|
|
150
|
+
|
|
102
151
|
await this.buttons.draw(this.device)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
getCurrentProfile () {
|
|
155
|
+
return this.appConfig.profiles[this.currentProfile]
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Events:
|
|
159
|
+
async onConnected (address) {
|
|
160
|
+
console.info(`✅ Connected to ${this.device.type}, ${address}`)
|
|
161
|
+
|
|
162
|
+
await this.activateProfile(0)
|
|
163
|
+
|
|
164
|
+
var profile = this.getCurrentProfile()
|
|
165
|
+
await InitializeInterfaces(profile,this.buttons.setState)
|
|
166
|
+
|
|
167
|
+
const self = this
|
|
168
|
+
|
|
169
|
+
opcuainterface.myEmitter.on("monitored item changed",(buttonID,nodeid,val) => { self.buttonStateChanged(buttonID,nodeid,val) })
|
|
103
170
|
|
|
104
171
|
this.device.setBrightness(1)
|
|
105
172
|
this.device.vibrate(HAPTIC.ASCEND_MED)
|
|
@@ -129,6 +196,10 @@ export class BaseLoupeDeckHandler {
|
|
|
129
196
|
const id = event.id
|
|
130
197
|
ok = await this.buttons.released(id)
|
|
131
198
|
await this.buttons.draw(this.device)
|
|
199
|
+
|
|
200
|
+
/*if (this.currentProfile != id){
|
|
201
|
+
this.activateProfile(id-1)
|
|
202
|
+
}*/
|
|
132
203
|
return ok
|
|
133
204
|
}
|
|
134
205
|
|
|
@@ -186,4 +257,16 @@ export class BaseLoupeDeckHandler {
|
|
|
186
257
|
await this.updateScreens()
|
|
187
258
|
return ok
|
|
188
259
|
}
|
|
260
|
+
|
|
261
|
+
async buttonStateChanged(buttonID,nodeid,val) {
|
|
262
|
+
let ok = false
|
|
263
|
+
this.screenUpdate["center"] = true
|
|
264
|
+
|
|
265
|
+
ok = await this.screens.center.changed(buttonID,nodeid,val)
|
|
266
|
+
|
|
267
|
+
await this.updateScreens()
|
|
268
|
+
return ok
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
|
|
189
272
|
}
|
package/common/touchbuttons.mjs
CHANGED
|
@@ -1,8 +1,37 @@
|
|
|
1
1
|
import { loadImage } from 'canvas'
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
import * as shellif from '../interfaces/shellif.mjs'
|
|
4
|
+
import * as httpif from '../interfaces/httpif.mjs'
|
|
5
|
+
import * as opcuaif from '../interfaces/opcuaif.mjs'
|
|
3
6
|
import format from 'string-template'
|
|
4
7
|
import { calcDelta } from './utils.mjs'
|
|
5
8
|
|
|
9
|
+
export var opcuainterface = undefined
|
|
10
|
+
var httpinterface = undefined
|
|
11
|
+
var shellinterface = undefined
|
|
12
|
+
|
|
13
|
+
export async function InitializeInterfaces(appConfig,callbackFunction){
|
|
14
|
+
if (opcuainterface === undefined ){
|
|
15
|
+
opcuainterface = new opcuaif.OPCUAIf()
|
|
16
|
+
opcuainterface.init(appConfig.parameters,appConfig,callbackFunction)
|
|
17
|
+
}
|
|
18
|
+
if (httpinterface === undefined)
|
|
19
|
+
httpinterface = new httpif.HTTPif()
|
|
20
|
+
if (shellinterface === undefined)
|
|
21
|
+
shellinterface = new shellif.SHELLif()
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function StopInterfaces(){
|
|
26
|
+
if (opcuainterface !== undefined )
|
|
27
|
+
await opcuainterface.stop()
|
|
28
|
+
if (httpinterface !== undefined)
|
|
29
|
+
await httpinterface.stop()
|
|
30
|
+
if (shellinterface !== undefined)
|
|
31
|
+
await shellinterface.stop()
|
|
32
|
+
|
|
33
|
+
}
|
|
34
|
+
|
|
6
35
|
export const ButtonIndex = {
|
|
7
36
|
BUTN_0: 0,
|
|
8
37
|
BUTN_1: 1,
|
|
@@ -31,7 +60,9 @@ export class ButtonField {
|
|
|
31
60
|
#keys = []
|
|
32
61
|
#type
|
|
33
62
|
#name
|
|
34
|
-
|
|
63
|
+
#config
|
|
64
|
+
|
|
65
|
+
constructor (name, rows, columns, width, height, data, config) {
|
|
35
66
|
console.info(`ButtonField ${name.padEnd(10, ' ')} Buttons: ${rows} x ${columns} , Pixels ${width} x ${height}`)
|
|
36
67
|
this.#name = name
|
|
37
68
|
this.width = width
|
|
@@ -45,13 +76,18 @@ export class ButtonField {
|
|
|
45
76
|
const keys = Object.keys(data)
|
|
46
77
|
for (let i = 0; i < keys.length; i++) {
|
|
47
78
|
const key = keys[i]
|
|
48
|
-
const tb = new Button(`${this.#type}-${key}`, width / columns, height / rows, data[key])
|
|
79
|
+
const tb = new Button(`${this.#type}-${key}`, width / columns, height / rows, data[key],key,config.parameters)
|
|
49
80
|
this.#buttons[key] = tb
|
|
50
81
|
}
|
|
51
82
|
|
|
52
83
|
this.#keys = keys
|
|
84
|
+
this.#config = config
|
|
53
85
|
}
|
|
54
86
|
|
|
87
|
+
//setProfileConfig (config) {
|
|
88
|
+
// this.#config = config
|
|
89
|
+
// }
|
|
90
|
+
|
|
55
91
|
async draw (device) {
|
|
56
92
|
if (!this.#screen) {
|
|
57
93
|
// physical buttons:
|
|
@@ -75,6 +111,10 @@ export class ButtonField {
|
|
|
75
111
|
}
|
|
76
112
|
}
|
|
77
113
|
|
|
114
|
+
setState (id, val) {
|
|
115
|
+
this.#buttons[id].setState(val)
|
|
116
|
+
}
|
|
117
|
+
|
|
78
118
|
setIntState (id, val) {
|
|
79
119
|
this.#buttons[id].setIntState(val)
|
|
80
120
|
}
|
|
@@ -83,10 +123,10 @@ export class ButtonField {
|
|
|
83
123
|
for (let i = 0; i < this.#keys.length; i++) {
|
|
84
124
|
const key = this.#keys[i]
|
|
85
125
|
if (isNaN(key)) {
|
|
86
|
-
await this.#buttons[key].load()
|
|
126
|
+
await this.#buttons[key].load(this.#config)
|
|
87
127
|
} else {
|
|
88
128
|
const iVal = parseInt(key, 10)
|
|
89
|
-
await this.#buttons[iVal].load()
|
|
129
|
+
await this.#buttons[iVal].load(this.#config)
|
|
90
130
|
}
|
|
91
131
|
}
|
|
92
132
|
}
|
|
@@ -119,6 +159,13 @@ export class ButtonField {
|
|
|
119
159
|
return result
|
|
120
160
|
}
|
|
121
161
|
|
|
162
|
+
async changed(buttonID,nodeid,val){
|
|
163
|
+
for (let i = 0; i < this.#keys.length; i++) {
|
|
164
|
+
let bID = this.#keys[i]
|
|
165
|
+
const result = await this.#buttons[bID].changed(i,nodeid,val)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
122
169
|
async rotated (id, delta) {
|
|
123
170
|
this.checkAndCreateButton(id)
|
|
124
171
|
const result = await this.#buttons[id].rotated(delta)
|
|
@@ -142,6 +189,7 @@ export class ButtonField {
|
|
|
142
189
|
}
|
|
143
190
|
|
|
144
191
|
export class Button {
|
|
192
|
+
#config
|
|
145
193
|
width = 0
|
|
146
194
|
height = 0
|
|
147
195
|
|
|
@@ -151,6 +199,7 @@ export class Button {
|
|
|
151
199
|
#max = 100
|
|
152
200
|
#value = 50
|
|
153
201
|
#name = undefined
|
|
202
|
+
#nodeid = ""
|
|
154
203
|
|
|
155
204
|
#index = 0
|
|
156
205
|
#keys
|
|
@@ -176,9 +225,11 @@ export class Button {
|
|
|
176
225
|
timeHold
|
|
177
226
|
// Minimum ammount of time in ms to press a button:
|
|
178
227
|
minPressed = 25
|
|
228
|
+
key = -1
|
|
179
229
|
|
|
180
|
-
constructor (id, width, height, data) {
|
|
230
|
+
constructor (id, width, height, data,key,params) {
|
|
181
231
|
this.id = id
|
|
232
|
+
this.key = key
|
|
182
233
|
this.width = width
|
|
183
234
|
this.height = height
|
|
184
235
|
this.#index = 0
|
|
@@ -204,6 +255,10 @@ export class Button {
|
|
|
204
255
|
this.#states = {}
|
|
205
256
|
this.#keys = []
|
|
206
257
|
}
|
|
258
|
+
if (data.nodeid){
|
|
259
|
+
this.#nodeid = format(data.nodeid, params)
|
|
260
|
+
}
|
|
261
|
+
|
|
207
262
|
}
|
|
208
263
|
|
|
209
264
|
setState (index = 0) {
|
|
@@ -219,13 +274,16 @@ export class Button {
|
|
|
219
274
|
const b = parseInt(elem.color.slice(5, 7), 16)
|
|
220
275
|
|
|
221
276
|
try {
|
|
277
|
+
var idx = parseInt(id, 10);
|
|
278
|
+
|
|
222
279
|
const val = {
|
|
223
|
-
id,
|
|
280
|
+
id:idx,
|
|
224
281
|
color: `rgba(${r}, ${g}, ${b})`
|
|
225
282
|
}
|
|
283
|
+
//console.log(' Set Button Color',val.id, val.color)
|
|
226
284
|
device.setButtonColor(val)
|
|
227
285
|
} catch (error) {
|
|
228
|
-
|
|
286
|
+
console.error(' Error', error)
|
|
229
287
|
}
|
|
230
288
|
}
|
|
231
289
|
|
|
@@ -250,7 +308,8 @@ export class Button {
|
|
|
250
308
|
// ctx.fillText(this.text, x + 10, y - 10)
|
|
251
309
|
}
|
|
252
310
|
|
|
253
|
-
async load () {
|
|
311
|
+
async load (globalConfig) {
|
|
312
|
+
this.#config = globalConfig
|
|
254
313
|
for (let i = 0; i < this.#keys.length; i++) {
|
|
255
314
|
const key = this.#keys[i]
|
|
256
315
|
const elem = this.#states[key]
|
|
@@ -263,6 +322,10 @@ export class Button {
|
|
|
263
322
|
return false
|
|
264
323
|
}
|
|
265
324
|
}
|
|
325
|
+
//const uastate = elem.state
|
|
326
|
+
//if (uastate){
|
|
327
|
+
// await opcuainterface.Subscribe(uastate)
|
|
328
|
+
//}
|
|
266
329
|
}
|
|
267
330
|
}
|
|
268
331
|
|
|
@@ -283,7 +346,7 @@ export class Button {
|
|
|
283
346
|
this.timeStampPressed = Date.now()
|
|
284
347
|
|
|
285
348
|
this.#index++
|
|
286
|
-
this.#index
|
|
349
|
+
this.updateState(this.#index)
|
|
287
350
|
return true
|
|
288
351
|
}
|
|
289
352
|
|
|
@@ -311,7 +374,19 @@ export class Button {
|
|
|
311
374
|
|
|
312
375
|
break
|
|
313
376
|
}
|
|
377
|
+
|
|
378
|
+
this.updateState(this.#index)
|
|
379
|
+
|
|
380
|
+
return true // this.runCommand()
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
updateState(index){
|
|
384
|
+
this.#index = index
|
|
385
|
+
// Update the State according to the correctly pressed state
|
|
386
|
+
if (this.#index < 0) { this.#index = this.#keys.length - 1 }
|
|
387
|
+
this.#index %= this.#keys.length
|
|
314
388
|
this.runCommand()
|
|
389
|
+
//console.log("TODO: expect newState", newState)
|
|
315
390
|
return true // this.runCommand()
|
|
316
391
|
}
|
|
317
392
|
|
|
@@ -322,6 +397,40 @@ export class Button {
|
|
|
322
397
|
return this.runCommand()
|
|
323
398
|
}
|
|
324
399
|
|
|
400
|
+
async changed(buttonID,nodeid,val){
|
|
401
|
+
// Only handle updates within the same group identified by nodeid
|
|
402
|
+
if (nodeid !== this.#nodeid){
|
|
403
|
+
return
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
this.#index = 0;
|
|
407
|
+
for (let i = 0; i < this.#keys.length; i++) {
|
|
408
|
+
let key = this.#keys[i]
|
|
409
|
+
// check if the state-name is same as the value we get from outside:
|
|
410
|
+
if (val == key){
|
|
411
|
+
this.#index = i;
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// check if the nodeid is the same and the value is one of the states
|
|
416
|
+
let state = this.#states[key]
|
|
417
|
+
if (!state.value)
|
|
418
|
+
continue
|
|
419
|
+
|
|
420
|
+
const params = {
|
|
421
|
+
id: buttonID,
|
|
422
|
+
key: buttonID,
|
|
423
|
+
...state
|
|
424
|
+
}
|
|
425
|
+
let val1 = format(state.value,params)
|
|
426
|
+
if (val1 === val){
|
|
427
|
+
this.#index = i;
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
325
434
|
async touchmove (x, y) {
|
|
326
435
|
// if (!this.getCurrentElement()) { return false }
|
|
327
436
|
|
|
@@ -347,27 +456,48 @@ export class Button {
|
|
|
347
456
|
return false
|
|
348
457
|
}
|
|
349
458
|
|
|
350
|
-
runCommand () {
|
|
459
|
+
async runCommand () {
|
|
351
460
|
const elem = this.getCurrentElement()
|
|
352
|
-
if (!elem || !elem.cmd) {
|
|
461
|
+
if (!elem || (!elem.cmd && !elem.http && !elem.opcua)) {
|
|
353
462
|
return
|
|
354
463
|
}
|
|
355
|
-
// Call an action
|
|
464
|
+
// Call an action - include dynamic parameters
|
|
465
|
+
// and also all attributes of elem + global config
|
|
356
466
|
const params = {
|
|
357
467
|
id: this.id,
|
|
468
|
+
key: this.key,
|
|
358
469
|
state: this.#keys[this.#index],
|
|
359
470
|
min: this.#min,
|
|
360
471
|
max: this.#max,
|
|
361
472
|
value: this.#value,
|
|
362
|
-
text: this.getCurrentText()
|
|
473
|
+
text: this.getCurrentText(),
|
|
474
|
+
...this.#config.parameters,
|
|
475
|
+
...elem
|
|
363
476
|
}
|
|
364
|
-
/* const keys = Object.keys(this.#data.config)
|
|
365
|
-
keys.forEach(key => {
|
|
366
|
-
params[key] = this.#data.config[key]
|
|
367
|
-
}) */
|
|
368
477
|
|
|
369
|
-
|
|
478
|
+
let res = ''
|
|
479
|
+
if ('cmd' in elem) {
|
|
480
|
+
if (shellinterface){
|
|
481
|
+
res = await shellinterface.call(elem.cmd, params)
|
|
482
|
+
}else{
|
|
483
|
+
console.warn("shellinterface not started")
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if ('http' in elem) {
|
|
487
|
+
if (httpinterface){
|
|
488
|
+
res = await httpinterface.call(elem.http, params)
|
|
489
|
+
}else{
|
|
490
|
+
console.warn("httpinterface not started")
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if ('opcua' in elem) {
|
|
494
|
+
if (opcuainterface){
|
|
495
|
+
res = await opcuainterface.call(elem.opcua, params)
|
|
496
|
+
}else{
|
|
497
|
+
console.warn("opcuainterface not started")
|
|
498
|
+
}
|
|
499
|
+
}
|
|
370
500
|
|
|
371
|
-
return
|
|
501
|
+
return res
|
|
372
502
|
}
|
|
373
503
|
}
|
package/config-test.json
ADDED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { HAPTIC } from 'loupedeck'
|
|
2
2
|
import { BaseLoupeDeckHandler } from '../common/BaseLoupeDeckHandler.mjs'
|
|
3
|
-
const { HAPTIC } = pkg
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
|
-
|
|
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
|
/**
|
package/example/example.mjs
CHANGED
|
@@ -2,12 +2,20 @@ import { ExampleDeviceHandler } from './ExampleDeviceHandler.mjs'
|
|
|
2
2
|
|
|
3
3
|
const handler = new ExampleDeviceHandler('config.json')
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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()
|
|
8
13
|
}
|
|
9
14
|
|
|
10
|
-
// Initiating
|
|
11
|
-
|
|
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) })
|
|
12
19
|
|
|
20
|
+
// Initiating a process
|
|
13
21
|
await handler.start()
|
|
@@ -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
|
+
|