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 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
- - LoupeDeck devices after `version 0.2.x` use a serial interface instead of WebSocket.
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
- - [config.json](https://gitlab.com/keckxde/loupedeck-commander/-/blob/main/config.json)
42
- - [profile-1.json](https://gitlab.com/keckxde/loupedeck-commander/-/blob/main/profile-1.json)
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
- - Main/Center Touch-Area: 90px x 90px per Button
89
- - Left/Right Touch-Area: 90px x 270px per Button
90
- - LoupeDeck CT Knob Touch-Area: 240px x 240px Button
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 pkg from 'loupedeck'
1
+ import {discover, HAPTIC,LoupedeckDevice } from 'loupedeck'
2
2
  import { ApplicationConfig } from './ApplicationConfig.mjs'
3
- import { TouchButtonField, PhysicalButtonField } 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(res => setTimeout(res, 3000))
41
+ await new Promise(resolve => setTimeout(resolve, 3000))
42
+ }
43
+
44
+ if (this.stopping){
45
+ return
30
46
  }
31
47
  }
32
- console.info(`✅ Connected to ${this.device.type}`)
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
- // return await this.device.connect()
61
+ console.info(`✅ Registered callbacks`)
45
62
  }
46
63
 
47
- stop () {
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
- this.device.close()
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 TouchButtonField('center', this.device.rows, this.device.columns, dCenter.width, dCenter.height, profile.touch.center)
80
- this.screens.left = new TouchButtonField('left', 1, 1, dLeft.width, dLeft.height, profile.touch.left)
81
- this.screens.right = new TouchButtonField('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 TouchButtonField('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 PhysicalButtonField('bottom', 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
- await this.activateProfile(0)
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
- const ok = false
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
- console.info('TouchMove : ', screen, id, x, y)
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
  }