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.
- package/README.md +31 -8
- package/VERSION.md +33 -0
- package/common/ApplicationConfig.mjs +3 -0
- package/common/BaseLoupeDeckHandler.mjs +111 -28
- package/common/touchbuttons.mjs +304 -274
- package/config-test.json +9 -0
- package/eslint.config.mjs +9 -0
- package/{ExampleDeviceHandler.mjs → example/ExampleDeviceHandler.mjs} +3 -4
- package/example/example.mjs +21 -0
- package/icons/bulb_filled.png +0 -0
- 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 +60 -96
- package/profile-2.json +164 -0
- package/test.mjs +23 -0
- package/example.mjs +0 -13
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
The loupedeck-commander is helping you using your Loupedeck device more easily, and connect it with custom commands you define with script files.
|
|
6
6
|
|
|
7
7
|
Features:
|
|
8
|
+
|
|
8
9
|
- Reference image files for every state of your button in the touch-display fields
|
|
9
10
|
- Connect commands to your state transitions
|
|
10
11
|
- Tested with following LoupeDeck devices:
|
|
@@ -12,14 +13,17 @@ Features:
|
|
|
12
13
|
- [LoupeDeck CT](https://loupedeck.com/products/loupedeck-ct/)
|
|
13
14
|
|
|
14
15
|
Runs on:
|
|
16
|
+
|
|
15
17
|
- Linux (Tested with Ubuntu 22.04 and Debian 11/12 on x64 & arm32/arm64)
|
|
16
18
|
- Windows (Tested with Windows 10/11 on x64)
|
|
17
19
|
|
|
18
|
-
Small footprint
|
|
20
|
+
Small footprint
|
|
21
|
+
|
|
19
22
|
- Raspberry PI Zero is suitable to run this
|
|
20
23
|
|
|
21
24
|
Please take care about the following:
|
|
22
|
-
|
|
25
|
+
|
|
26
|
+
- LoupeDeck devices after `version 0.2.x` use a serial interface instead of WebSocket.
|
|
23
27
|
When using this library please upgrade your firmware using the [LoupeDeck Software](https://loupedeck.com/downloads/)
|
|
24
28
|
Tested with Firmware version [`version 0.2.5`](https://support.loupedeck.com/f-a-q-support#firmware-connectivity-issues)
|
|
25
29
|
|
|
@@ -27,7 +31,7 @@ Please take care about the following:
|
|
|
27
31
|
|
|
28
32
|
Do the following steps:
|
|
29
33
|
|
|
30
|
-
```
|
|
34
|
+
```bash
|
|
31
35
|
# init your new node package
|
|
32
36
|
npm init
|
|
33
37
|
|
|
@@ -38,10 +42,11 @@ npm install -s loupedeck-commander
|
|
|
38
42
|
Create a new configuration file with at least one profile or copy from here,
|
|
39
43
|
and replace the image references with your own icons (90x90px size):
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
- [config.json](https://gitlab.com/keckxde/loupedeck-commander/-/blob/main/config.json)
|
|
46
|
+
- [profile-1.json](https://gitlab.com/keckxde/loupedeck-commander/-/blob/main/profile-1.json)
|
|
43
47
|
|
|
44
48
|
Create a `index.mjs` file to open up your loupedeck connection:
|
|
49
|
+
|
|
45
50
|
```javascript
|
|
46
51
|
# save as index.mjs
|
|
47
52
|
import { BaseLoupeDeckHandler } from 'loupedeck-commander'
|
|
@@ -67,8 +72,26 @@ node index.mjs
|
|
|
67
72
|
|
|
68
73
|
If you end up with errors related to canvas module - please install the dependencies as mentioned below, and run `npm install -s` again
|
|
69
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
|
+
|
|
70
79
|
## Hints
|
|
71
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
|
+
|
|
72
95
|
### CANVAS Module - additional effort needed
|
|
73
96
|
|
|
74
97
|
The library is using [canvas](https://www.npmjs.com/package/canvas) to load images and render graphical content on the LoupeDeck devices.
|
|
@@ -85,9 +108,9 @@ sudo apt-get install build-essential libcairo2-dev libpango1.0-dev libjpeg-dev l
|
|
|
85
108
|
|
|
86
109
|
The buttons images shown on the different buttons should be prepared with specific resolutions:
|
|
87
110
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
111
|
+
- Main/Center Touch-Area: 90px x 90px per Button
|
|
112
|
+
- Left/Right Touch-Area: 90px x 270px per Button
|
|
113
|
+
- LoupeDeck CT Knob Touch-Area: 240px x 240px Button
|
|
91
114
|
|
|
92
115
|
if you already have suitable icons with the right aspect ratio, you could resize them with imagemagick/mogrify:
|
|
93
116
|
|
package/VERSION.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Versions
|
|
2
|
+
|
|
3
|
+
## 1.0.2
|
|
4
|
+
|
|
5
|
+
1. Updated button rendering strategy:
|
|
6
|
+
- Render Background Color - per Button State
|
|
7
|
+
- Render Image on Top (if available), works well with transparent images
|
|
8
|
+
|
|
9
|
+
2. Updated configuration strategy:
|
|
10
|
+
- Keep all state related attributes per button-state also in config:
|
|
11
|
+
example:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
"states": {
|
|
15
|
+
"off": {
|
|
16
|
+
"color": "#aaaaaa",
|
|
17
|
+
"image": "icons/bulb.png",
|
|
18
|
+
"cmd": "echo \"{id} {state}\""
|
|
19
|
+
},
|
|
20
|
+
"on": {
|
|
21
|
+
"color": "#11ff11",
|
|
22
|
+
"image": "icons/bulb.png",
|
|
23
|
+
"cmd": "echo \"{id} {state}\""
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 1.0.1
|
|
28
|
+
|
|
29
|
+
bugfixes and documentation
|
|
30
|
+
|
|
31
|
+
## 1.0.0
|
|
32
|
+
|
|
33
|
+
first public release
|
|
@@ -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 {
|
|
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
|
|
80
|
-
this.screens.left = new
|
|
81
|
-
this.screens.right = new
|
|
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
|
|
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
|
|
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
|
|
|
@@ -155,7 +226,7 @@ export class BaseLoupeDeckHandler {
|
|
|
155
226
|
}
|
|
156
227
|
|
|
157
228
|
async onTouchMove (event) {
|
|
158
|
-
|
|
229
|
+
let ok = false
|
|
159
230
|
const changedTouches = event.changedTouches
|
|
160
231
|
for (let i = 0; i < changedTouches.length; i++) {
|
|
161
232
|
const x = changedTouches[i].x
|
|
@@ -165,7 +236,7 @@ export class BaseLoupeDeckHandler {
|
|
|
165
236
|
if (id === undefined) {
|
|
166
237
|
id = 0 // for left/right
|
|
167
238
|
}
|
|
168
|
-
|
|
239
|
+
ok = await this.screens[screen].touchmove(id, x, y)
|
|
169
240
|
}
|
|
170
241
|
return ok
|
|
171
242
|
}
|
|
@@ -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
|
}
|