loupedeck-commander 1.2.12 → 1.3.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
@@ -42,8 +42,8 @@ npm install -s loupedeck-commander
42
42
  Create a new configuration file with at least one profile or copy from here,
43
43
  and replace the image references with your own icons (90x90px size):
44
44
 
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)
45
+ - [config.yaml](https://gitlab.com/keckxde/loupedeck-commander/-/blob/main/config.yaml)
46
+ - [profile-1.yaml](https://gitlab.com/keckxde/loupedeck-commander/-/blob/main/profile-1.yaml)
47
47
 
48
48
  Create a `index.mjs` file to open up your loupedeck connection:
49
49
 
@@ -51,7 +51,7 @@ Create a `index.mjs` file to open up your loupedeck connection:
51
51
  # save as index.mjs
52
52
  import { BaseLoupeDeckHandler } from 'loupedeck-commander'
53
53
 
54
- const handler = new BaseLoupeDeckHandler('config.json')
54
+ const handler = new BaseLoupeDeckHandler('config.yaml')
55
55
 
56
56
  /**
57
57
  * Stop the handlers when a signal like SIGINT or SIGTERM arrive
@@ -1,4 +1,4 @@
1
- import { readJSONFile, writeJSONFile,syncParams } from './utils.mjs'
1
+ import { readConfigFile as readConfigFile, writeJSONFile,syncParams, writeYAMLFile } from './utils.mjs'
2
2
 
3
3
  export class ApplicationConfig {
4
4
  application = 'undefined'
@@ -7,7 +7,8 @@ export class ApplicationConfig {
7
7
 
8
8
  loadFromFile (fileName) {
9
9
  console.info(`ApplicationConfig: Loading Config File ${fileName}`)
10
- const config = readJSONFile(fileName)
10
+ const config = readConfigFile(fileName)
11
+
11
12
 
12
13
  if (!config){
13
14
  console.info(`ApplicationConfig: Could not read/parse Config File ${fileName} - breaking here`)
@@ -66,7 +67,7 @@ class Profile {
66
67
 
67
68
  loadFromFile (fileName) {
68
69
  console.info(`ProfileConfig: Loading Profile File ${fileName}, Index ${this.#index}`)
69
- const config = readJSONFile(fileName)
70
+ const config = readConfigFile(fileName)
70
71
  if (config === undefined) {
71
72
  console.warn(`ProfileConfig: Cannot parse/load Profile File ${fileName}`)
72
73
  return false
@@ -105,7 +106,13 @@ class Profile {
105
106
  fileName = fileName.toLowerCase()
106
107
 
107
108
  console.info(`ProfileConfig: Save To Config File ${fileName}`)
108
- writeJSONFile(fileName, this)
109
+ if(fileName.endsWith('.json')) {
110
+ writeJSONFile(fileName, this)
111
+ writeYAMLFile(fileName.replace('.json','.yaml'), this)
112
+ }else if(fileName.endsWith('.yaml')) {
113
+ writeYAMLFile(fileName, this)
114
+ writeJSONFile(fileName.replace('.yaml','.json'), this)
115
+ }
109
116
  }
110
117
  }
111
118
 
@@ -114,18 +121,18 @@ class Profile {
114
121
  */
115
122
  class TouchConfig {
116
123
  center = {
117
- "0" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } }, group : "" },
118
- "1" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } }, group : "" },
119
- "2" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } }, group : "" },
120
- "3" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } }, group : "" },
121
- "4" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } }, group : "" },
122
- "5" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } }, group : "" },
123
- "6" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } }, group : "" },
124
- "7" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } }, group : "" },
125
- "8" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } }, group : "" },
126
- "9" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } }, group : "" },
127
- "10" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } }, group : "" },
128
- "11" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } }, group : "" },
124
+ "0" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } } },
125
+ "1" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } } },
126
+ "2" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } } },
127
+ "3" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } } },
128
+ "4" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } } },
129
+ "5" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } } },
130
+ "6" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } } },
131
+ "7" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } } },
132
+ "8" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } } },
133
+ "9" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } } },
134
+ "10" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } } },
135
+ "11" : { "states" : { "off" : { "color": "#000099", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" }, "on" : { "color": "#00ff00", "image": "icons/home.png","cmd": "echo \"{id} {state}\"" } } },
129
136
  } // CENTER Display Config - Available in CT & LIVE
130
137
  left = {
131
138
  "0" : { "states" : { "on" : { "color": "#000000", "cmd": "echo \"{id} {state}\"" } }, group : "" },
@@ -177,14 +184,14 @@ class TouchConfig {
177
184
  class ButtonConfig {
178
185
  buttons = {}
179
186
  defaultButtons = {
180
- "0" : {"states" : { "off" : { "filter": "pressed", "color": "#000000", "cmd": "echo \"{id} {state}\"" }, "on" : { "filter": "pressed","color": "#ff0000", "cmd": "echo \"{id} {state}\"" } }, group : "onoff" },
181
- "1" : {"states" : { "off" : { "filter": "pressed", "color": "#000055", "cmd": "echo \"{id} {state}\"" }, "on" : { "filter": "pressed", "profile": 0, "color": "#00ff00", "cmd": "echo \"{id} {state}\"" } }, group : "profiles" },
182
- "2" : {"states" : { "off" : { "filter": "pressed", "color": "#000055", "cmd": "echo \"{id} {state}\"" }, "on" : { "filter": "pressed", "profile": 1, "color": "#00ff00", "cmd": "echo \"{id} {state}\"" } }, group : "profiles" },
183
- "3" : {"states" : { "off" : { "filter": "pressed", "color": "#000055", "cmd": "echo \"{id} {state}\"" }, "on" : { "filter": "pressed", "profile": 2, "color": "#00ff00", "cmd": "echo \"{id} {state}\"" } }, group : "profiles" },
184
- "4" : {"states" : { "off" : { "filter": "pressed", "color": "#000055", "cmd": "echo \"{id} {state}\"" }, "on" : { "filter": "pressed", "profile": 3, "color": "#00ff00", "cmd": "echo \"{id} {state}\"" } }, group : "profiles" },
185
- "5" : {"states" : { "off" : { "filter": "pressed", "color": "#000055", "cmd": "echo \"{id} {state}\"" }, "on" : { "filter": "pressed", "profile": 4, "color": "#00ff00", "cmd": "echo \"{id} {state}\"" } }, group : "profiles" },
186
- "6" : {"states" : { "off" : { "filter": "pressed", "color": "#000055", "cmd": "echo \"{id} {state}\"" }, "on" : { "filter": "pressed", "profile": 5, "color": "#00ff00", "cmd": "echo \"{id} {state}\"" } }, group : "profiles" },
187
- "7" : {"states" : { "off" : { "filter": "pressed", "color": "#000055", "cmd": "echo \"{id} {state}\"" }, "on" : { "filter": "pressed", "profile": 6, "color": "#00ff00", "cmd": "echo \"{id} {state}\"" } }, group : "profiles" },
187
+ "0" : {"states" : { "off" : { "filter": "pressed", "color": "#005500", "brightness" : 0 }, "half" : { "filter": "pressed", "brightness" : 0.5, "vibrate": 0x58, "color": "#00aa00" } , "on" : { "filter": "pressed", "brightness" : 1, "color": "#00ff00" } }, params: { group : "brightness"} },
188
+ "1" : {"states" : { "off" : { "filter": "pressed", "color": "#000055", }, "on" : { "filter": "pressed", "profile": 0, "color": "#00ff00" } }, params: { group : "profiles"} },
189
+ "2" : {"states" : { "off" : { "filter": "pressed", "color": "#000055", }, "on" : { "filter": "pressed", "profile": 1, "color": "#00ff00" } }, params: { group : "profiles"} },
190
+ "3" : {"states" : { "off" : { "filter": "pressed", "color": "#000055", }, "on" : { "filter": "pressed", "profile": 2, "color": "#00ff00" } }, params: { group : "profiles"} },
191
+ "4" : {"states" : { "off" : { "filter": "pressed", "color": "#000055", }, "on" : { "filter": "pressed", "profile": 3, "color": "#00ff00" } }, params: { group : "profiles"} },
192
+ "5" : {"states" : { "off" : { "filter": "pressed", "color": "#000055", }, "on" : { "filter": "pressed", "profile": 4, "color": "#00ff00" } }, params: { group : "profiles"} },
193
+ "6" : {"states" : { "off" : { "filter": "pressed", "color": "#000055", }, "on" : { "filter": "pressed", "profile": 5, "color": "#00ff00" } }, params: { group : "profiles"} },
194
+ "7" : {"states" : { "off" : { "filter": "pressed", "color": "#000055", }, "on" : { "filter": "pressed", "profile": 6, "color": "#00ff00" } }, params: { group : "profiles"} }
188
195
  }
189
196
 
190
197
  constructor (data,profileCount,index) {
@@ -1,12 +1,15 @@
1
1
  import {discover, HAPTIC,LoupedeckDevice } from 'loupedeck'
2
2
  import { ApplicationConfig } from './ApplicationConfig.mjs'
3
- import { ButtonField, StopInterfaces, InitializeInterfaces,opcuainterface,profileEmitter} from './touchbuttons.mjs'
3
+ import { ButtonField } from './ButtonField.mjs'
4
+
5
+ import { InitializeInterfaces, StopInterfaces, opcuainterface, profileEmitter } from '../interfaces/interfaces.mjs'
4
6
 
5
7
  export class BaseLoupeDeckHandler {
6
8
  device = undefined
7
9
  appConfig = undefined
8
10
  profileConfigs = []
9
11
  currentProfile = 0
12
+ brightness = 1
10
13
  screens = {}
11
14
  buttons = {}
12
15
  knobs = {}
@@ -92,10 +95,8 @@ export class BaseLoupeDeckHandler {
92
95
  await new Promise(resolve => setTimeout(resolve, 250))
93
96
  }
94
97
  */
95
- if (this.device){
96
- this.device.setBrightness(0)
98
+ this.changeBrightness(0)
97
99
  await new Promise(resolve => setTimeout(resolve, 250))
98
- }
99
100
  if (this.device){
100
101
  this.device.reconnectInterval = 0
101
102
  await this.device.close()
@@ -125,6 +126,25 @@ export class BaseLoupeDeckHandler {
125
126
  return this.appConfig.loadFromFile(fileName)
126
127
  }
127
128
 
129
+ vibrate (pattern) {
130
+ console.info(`Vibrate with pattern ${pattern}`)
131
+ if (this.device) {
132
+ this.device.vibrate(pattern)
133
+ } else {
134
+ console.warn("No Device connected, cannot vibrate")
135
+ }
136
+ }
137
+
138
+ changeBrightness (brightness) {
139
+ console.info(`Change Brightness to ${brightness}`)
140
+ this.brightness = brightness
141
+ if (this.device) {
142
+ this.device.setBrightness(brightness)
143
+ } else {
144
+ console.warn("No Device connected, cannot change brightness")
145
+ }
146
+ }
147
+
128
148
  /**
129
149
  * activate the profile with the givven ID
130
150
  */
@@ -196,20 +216,19 @@ export class BaseLoupeDeckHandler {
196
216
 
197
217
  await this.activateProfile(0)
198
218
 
199
- // move into activate profile function to call init with every profile change
200
- // var profile = this.getCurrentProfile()
201
- //await InitializeInterfaces(profile)
202
-
203
219
  const self = this
204
220
 
205
221
  // Register callback on monitored item change:
206
222
  opcuainterface.on("monitored item changed",(buttonID,nodeid,val) => { self.buttonStateChanged(buttonID,nodeid,val) })
207
223
  profileEmitter.on("profileChanged",(profileID) => { self.activateProfile(profileID) })
224
+ profileEmitter.on("brightnessChanged",(brightness) => { self.changeBrightness(brightness) })
225
+ profileEmitter.on("vibrate",(pattern) => { self.vibrate(pattern) })
226
+
208
227
 
209
228
 
210
- this.device.setBrightness(1)
211
- this.device.vibrate(HAPTIC.ASCEND_MED)
212
-
229
+ this.changeBrightness(1)
230
+ this.vibrate(HAPTIC.ASCEND_MED)
231
+
213
232
  // Fore update of all screens
214
233
  console.info('✅ Force Screen update')
215
234
  this.screenUpdate["center"] = true
@@ -0,0 +1,159 @@
1
+ import {Button} from './button.mjs'
2
+
3
+ export class ButtonField {
4
+ #buttons = {}
5
+ #screen
6
+ width = 0
7
+ height = 0
8
+ #keys = []
9
+ #type
10
+ #name
11
+ #profile
12
+
13
+ constructor (name, rows, columns, width, height, data, profile) {
14
+ console.info(`ButtonField ${name.padEnd(10, ' ')} Buttons: ${rows} x ${columns} , Pixels ${width} x ${height}`)
15
+ this.#name = name
16
+ this.width = width
17
+ this.height = height
18
+ // this.#rows = rows
19
+ // this.#columns = columns
20
+ this.#screen = this.width > 0 && this.height > 0
21
+ this.#type = 'button'
22
+ this.#profile = profile
23
+ if (this.#screen) { this.#type = 'touch' }
24
+
25
+ const keys = Object.keys(data)
26
+ for (let i = 0; i < keys.length; i++) {
27
+ const key = keys[i]
28
+ const tb = new Button(`${this.#type}-${key}`, width / columns, height / rows, data[key],key,this.#profile)
29
+ this.#buttons[key] = tb
30
+ }
31
+
32
+ this.#keys = keys
33
+ }
34
+
35
+
36
+ async draw (device) {
37
+ if (!this.#screen) {
38
+ // physical buttons:
39
+ for (let i = 0; i < this.#keys.length; i++) {
40
+ const key = this.#keys[i]
41
+ this.#buttons[key].drawPhysical(device, key)
42
+ }
43
+ } else {
44
+ // screen:
45
+ device.drawScreen(this.#name, ctx => {
46
+ ctx.globalCompositeOperation = 'source-atop'
47
+ for (let i = 0; i < this.#keys.length; i++) {
48
+ const key = this.#keys[i]
49
+ const iValue = parseInt(key, 10)
50
+ const row = Math.floor(iValue / device.columns)
51
+ const column = iValue % device.columns
52
+
53
+ this.#buttons[key].draw(row, column, ctx)
54
+ }
55
+ })
56
+ }
57
+ }
58
+
59
+ setState (id, val) {
60
+ this.#buttons[id].setState(val)
61
+ }
62
+
63
+ /*setIntState (id, val) {
64
+ this.#buttons[id].setIntState(val)
65
+ }*/
66
+
67
+ async load () {
68
+ for (let i = 0; i < this.#keys.length; i++) {
69
+ const key = this.#keys[i]
70
+ if (isNaN(key)) {
71
+ await this.#buttons[key].load(this.#profile)
72
+ } else {
73
+ const iVal = parseInt(key, 10)
74
+ await this.#buttons[iVal].load(this.#profile)
75
+ }
76
+ }
77
+ }
78
+
79
+ async pressed (id) {
80
+ this.checkAndCreateButton(id)
81
+ const result = await this.#buttons[id].pressed()
82
+ if (!result) {
83
+ console.info(`pressed ${this.#type} ${id}`)
84
+ }
85
+ return result
86
+ }
87
+
88
+ /**
89
+ * Button released event handler
90
+ * @param {*} id
91
+ * @returns
92
+ */
93
+ async released (id) {
94
+ const result = await this.#buttons[id].released()
95
+ if (result) {
96
+ // disable all other buttons of the group, if this one had been activated:
97
+ for (let i = 0; i < this.#keys.length; i++) {
98
+ let key = this.#keys[i]
99
+ if (!isNaN(key)) { key = parseInt(key, 10) }
100
+ if (id === key) { continue }
101
+ if (this.#buttons[key].group === this.#buttons[id].group) {
102
+ this.#buttons[key].setState(0)
103
+ }
104
+ }
105
+ }
106
+ if (!result) {
107
+ console.info(`released ${this.#type} ${id}`)
108
+ }
109
+ return result
110
+ }
111
+
112
+ /**
113
+ * Button changed event handler (after opcua value change)
114
+ * @param {*} buttonID
115
+ * @param {*} nodeid
116
+ * @param {*} val
117
+ */
118
+ async changed(buttonID,nodeid,val){
119
+ for (let i = 0; i < this.#keys.length; i++) {
120
+ let bID = this.#keys[i]
121
+ await this.#buttons[bID].changed(i,nodeid,val)
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Handle a rotation event for a knob
127
+ * @param {*} id : Knob ID (e.g. 'knobTL, 'knobTR', 'knobBL', 'knobBR')
128
+ * @param {*} delta
129
+ * @returns
130
+ */
131
+ async rotated (id, delta) {
132
+ this.checkAndCreateButton(id)
133
+ const result = await this.#buttons[id].rotated(delta)
134
+ if (!result) { console.info(`rotated ${this.#type} ${id} ${delta}`) }
135
+ return result
136
+ }
137
+
138
+ /**
139
+ * Handle a touchmove event for a touchbutton (available in left, center and right button fields)
140
+ * @param {*} id
141
+ * @param {*} x
142
+ * @param {*} y
143
+ * @returns
144
+ */
145
+ async touchmove (id, x, y) {
146
+ this.checkAndCreateButton(id)
147
+ const result = await this.#buttons[id].touchmove(x, y)
148
+ if (!result) { console.info(`touchmove ${id} ${x} ${y}`) }
149
+ return result
150
+ }
151
+
152
+ checkAndCreateButton (id) {
153
+ if (!(id in this.#buttons)) {
154
+ const tb = new Button(id, 1, 1, id)
155
+ this.#buttons[id] = tb
156
+ }
157
+ }
158
+ }
159
+