loupedeck-commander 1.2.6 → 1.2.8
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/common/ApplicationConfig.mjs +81 -28
- package/common/BaseLoupeDeckHandler.mjs +15 -4
- package/common/touchbuttons.mjs +56 -28
- package/common/utils.mjs +58 -3
- package/config.json +4 -0
- package/interfaces/baseif.mjs +3 -1
- package/interfaces/opcuaif.mjs +45 -6
- package/package.json +5 -5
- package/profile-1.json +5 -110
- package/profile-2.json +290 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readJSONFile, writeJSONFile } from './utils.mjs'
|
|
1
|
+
import { readJSONFile, writeJSONFile,syncParams } from './utils.mjs'
|
|
2
2
|
|
|
3
3
|
export class ApplicationConfig {
|
|
4
4
|
application = 'undefined'
|
|
@@ -19,7 +19,7 @@ export class ApplicationConfig {
|
|
|
19
19
|
else{
|
|
20
20
|
this.application = config.application
|
|
21
21
|
for (let i = 0; i < config.profiles.length; i++) {
|
|
22
|
-
const profile = new Profile(config.profiles[i])
|
|
22
|
+
const profile = new Profile(config.profiles[i],config.profiles.length,i)
|
|
23
23
|
if (profile.loaded){
|
|
24
24
|
this.profiles.push(profile)
|
|
25
25
|
}else{
|
|
@@ -42,24 +42,30 @@ class Profile {
|
|
|
42
42
|
knobs = {}
|
|
43
43
|
buttons = {}
|
|
44
44
|
parameters = {}
|
|
45
|
+
default = {}
|
|
45
46
|
#file = ''
|
|
46
47
|
loaded = false
|
|
47
48
|
#error = true
|
|
49
|
+
#profileCount = 0
|
|
50
|
+
#index = 0
|
|
48
51
|
|
|
49
|
-
constructor (data) {
|
|
52
|
+
constructor (data,profileCount,index) {
|
|
50
53
|
this.name = data.name
|
|
51
54
|
this.#file = data.file
|
|
55
|
+
this.#profileCount = profileCount
|
|
56
|
+
this.#index = index
|
|
52
57
|
this.touch = new TouchConfig({})
|
|
53
58
|
this.buttons = new ButtonConfig().buttons
|
|
54
59
|
this.knobs = new KnobsConfig().knobs
|
|
55
60
|
this.parameters = new ParametersConfig().parameters
|
|
61
|
+
this.default = new DefaultConfig().default
|
|
56
62
|
|
|
57
63
|
this.loadFromFile(this.#file)
|
|
58
64
|
if (this.#error) { this.saveToFile(`profile-${this.name}-sav.json`) }
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
loadFromFile (fileName) {
|
|
62
|
-
console.info(`ProfileConfig: Loading Profile File ${fileName}`)
|
|
68
|
+
console.info(`ProfileConfig: Loading Profile File ${fileName}, Index ${this.#index}`)
|
|
63
69
|
const config = readJSONFile(fileName)
|
|
64
70
|
if (config === undefined) {
|
|
65
71
|
console.warn(`ProfileConfig: Cannot parse/load Profile File ${fileName}`)
|
|
@@ -70,24 +76,25 @@ class Profile {
|
|
|
70
76
|
}else if (!config.touch){
|
|
71
77
|
console.warn(`ProfileConfig: File ${fileName} is missing a touch attribute`)
|
|
72
78
|
return false
|
|
73
|
-
}else if (!config.buttons){
|
|
74
|
-
console.warn(`ProfileConfig: File ${fileName} is missing a buttons attribute`)
|
|
75
|
-
return false
|
|
76
79
|
}else if (!config.parameters){
|
|
77
80
|
console.warn(`ProfileConfig: File ${fileName} is missing a parameters attribute`)
|
|
78
81
|
return false
|
|
82
|
+
}else if (!config.buttons){
|
|
83
|
+
console.info(`ProfileConfig: File ${fileName} is missing a buttons attribute - will be used to switch profiles by default`)
|
|
84
|
+
// return false
|
|
79
85
|
}
|
|
80
86
|
this.profile = config.profile
|
|
81
87
|
this.description = config.description
|
|
88
|
+
// Load Parameters.parameters = config.parameters
|
|
89
|
+
this.parameters = new ParametersConfig(config.parameters).parameters
|
|
90
|
+
this.default = new DefaultConfig(config.default).default
|
|
82
91
|
|
|
83
92
|
// Load the Configurations for Touch-Displays
|
|
84
|
-
this.touch = new TouchConfig(config.touch)
|
|
93
|
+
this.touch = new TouchConfig(config.touch,this.default)
|
|
85
94
|
// Load the Configurations for Button-Areas
|
|
86
95
|
|
|
87
|
-
this.buttons = new ButtonConfig(config.buttons).buttons
|
|
96
|
+
this.buttons = new ButtonConfig(config.buttons,this.#profileCount,this.#index).buttons
|
|
88
97
|
this.knobs = new KnobsConfig(config.knobs).knobs
|
|
89
|
-
// Load Parameters.parameters = config.parameters
|
|
90
|
-
this.parameters = new ParametersConfig(config.parameters).parameters
|
|
91
98
|
|
|
92
99
|
this.#error = false
|
|
93
100
|
this.loaded = true
|
|
@@ -128,11 +135,11 @@ class TouchConfig {
|
|
|
128
135
|
} // RIGHT Display Config - Available in CT & LIVE
|
|
129
136
|
//knob = {} // KNOB Display Config - Available only in CT
|
|
130
137
|
|
|
131
|
-
constructor (data) {
|
|
132
|
-
this.loadFromJSON(data)
|
|
138
|
+
constructor (data,defaultState) {
|
|
139
|
+
this.loadFromJSON(data,defaultState)
|
|
133
140
|
}
|
|
134
141
|
|
|
135
|
-
loadFromJSON (data) {
|
|
142
|
+
loadFromJSON (data,defaultState) {
|
|
136
143
|
if (!data)
|
|
137
144
|
return
|
|
138
145
|
if (!data.center)
|
|
@@ -146,7 +153,21 @@ class TouchConfig {
|
|
|
146
153
|
this.center = data.center
|
|
147
154
|
this.left = data.left
|
|
148
155
|
this.right = data.right
|
|
149
|
-
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Sychronize the states of the buttons with the default state - add missing options from default state
|
|
159
|
+
*/
|
|
160
|
+
var buttons = Object.keys(this.center)
|
|
161
|
+
for (var buttonID=0;buttonID<buttons.length;buttonID++){
|
|
162
|
+
if (!this.center[buttonID])
|
|
163
|
+
continue
|
|
164
|
+
var states = Object.keys(this.center[buttonID].states)
|
|
165
|
+
for (var stateID=0;stateID<states.length;stateID++){
|
|
166
|
+
var stateKey = states[stateID]
|
|
167
|
+
var stateOld = this.center[buttonID].states[stateKey]
|
|
168
|
+
this.center[buttonID].states[stateKey] = syncParams(stateOld,defaultState)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
150
171
|
}
|
|
151
172
|
}
|
|
152
173
|
|
|
@@ -154,19 +175,32 @@ class TouchConfig {
|
|
|
154
175
|
* Class that handles the different configs for the touch displays of the device covering Loupedeck Live (without knob) and also CT (with knob)
|
|
155
176
|
*/
|
|
156
177
|
class ButtonConfig {
|
|
157
|
-
buttons = {
|
|
158
|
-
|
|
159
|
-
"
|
|
160
|
-
"
|
|
161
|
-
"
|
|
162
|
-
"
|
|
163
|
-
"
|
|
164
|
-
"
|
|
165
|
-
"
|
|
178
|
+
buttons = {}
|
|
179
|
+
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" },
|
|
166
188
|
}
|
|
167
189
|
|
|
168
|
-
constructor (data) {
|
|
169
|
-
|
|
190
|
+
constructor (data,profileCount,index) {
|
|
191
|
+
if (data)
|
|
192
|
+
return this.loadFromJSON(data)
|
|
193
|
+
// Default to profile switching buttons
|
|
194
|
+
var keys = Object.keys(this.defaultButtons)
|
|
195
|
+
for (let i = 0; i <= profileCount; i++) {
|
|
196
|
+
var key = keys[i]
|
|
197
|
+
this.buttons[key] = this.defaultButtons[key]
|
|
198
|
+
if (i==(index+1)){
|
|
199
|
+
this.buttons[key].default = "on"
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
}
|
|
203
|
+
|
|
170
204
|
}
|
|
171
205
|
|
|
172
206
|
loadFromJSON (data) {
|
|
@@ -204,8 +238,10 @@ class ParametersConfig {
|
|
|
204
238
|
parameters = {
|
|
205
239
|
"hostname": "127.0.0.1",
|
|
206
240
|
"endpointurl": "opc.tcp://{hostname}:4840",
|
|
207
|
-
"nodeid" : "ns=0;s=nodeID"
|
|
208
|
-
|
|
241
|
+
"nodeid" : "ns=0;s=nodeID",
|
|
242
|
+
"min" : 0,
|
|
243
|
+
"max" : 100
|
|
244
|
+
}
|
|
209
245
|
|
|
210
246
|
constructor (data) {
|
|
211
247
|
this.loadFromJSON(data)
|
|
@@ -217,3 +253,20 @@ class ParametersConfig {
|
|
|
217
253
|
this.parameters = data
|
|
218
254
|
}
|
|
219
255
|
}
|
|
256
|
+
|
|
257
|
+
class DefaultConfig {
|
|
258
|
+
default = {
|
|
259
|
+
"filter": "released",
|
|
260
|
+
"color": "#111111"
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
constructor (data) {
|
|
264
|
+
this.loadFromJSON(data)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
loadFromJSON (data) {
|
|
268
|
+
if (!data)
|
|
269
|
+
return
|
|
270
|
+
this.default = data
|
|
271
|
+
}
|
|
272
|
+
}
|
|
@@ -134,6 +134,13 @@ export class BaseLoupeDeckHandler {
|
|
|
134
134
|
const dLeft = this.device.displays.left
|
|
135
135
|
const dRight = this.device.displays.right
|
|
136
136
|
const dCenter = this.device.displays.center
|
|
137
|
+
|
|
138
|
+
this.screens.center = undefined
|
|
139
|
+
this.screens.left = undefined
|
|
140
|
+
this.screens.right = undefined
|
|
141
|
+
this.buttons = undefined
|
|
142
|
+
this.knobs = undefined
|
|
143
|
+
|
|
137
144
|
|
|
138
145
|
this.screens.center = new ButtonField('center', this.device.rows, this.device.columns, dCenter.width, dCenter.height, profile.touch.center,profile)
|
|
139
146
|
this.screens.left = new ButtonField('left', 1, 1, dLeft.width, dLeft.height, profile.touch.left,profile)
|
|
@@ -159,6 +166,8 @@ export class BaseLoupeDeckHandler {
|
|
|
159
166
|
await this.updateScreens()
|
|
160
167
|
|
|
161
168
|
await this.buttons.draw(this.device)
|
|
169
|
+
// Initialize the Interfaces
|
|
170
|
+
await InitializeInterfaces(profile)
|
|
162
171
|
|
|
163
172
|
}
|
|
164
173
|
|
|
@@ -179,8 +188,9 @@ export class BaseLoupeDeckHandler {
|
|
|
179
188
|
|
|
180
189
|
await this.activateProfile(0)
|
|
181
190
|
|
|
182
|
-
|
|
183
|
-
|
|
191
|
+
// move into activate profile function to call init with every profile change
|
|
192
|
+
// var profile = this.getCurrentProfile()
|
|
193
|
+
//await InitializeInterfaces(profile)
|
|
184
194
|
|
|
185
195
|
const self = this
|
|
186
196
|
|
|
@@ -222,7 +232,7 @@ export class BaseLoupeDeckHandler {
|
|
|
222
232
|
const id = event.id
|
|
223
233
|
if (Number.isInteger(id)){
|
|
224
234
|
ok = await this.buttons.pressed(id)
|
|
225
|
-
await this.buttons.draw(this.device)
|
|
235
|
+
//await this.buttons.draw(this.device)
|
|
226
236
|
}else{
|
|
227
237
|
ok = await this.knobs.pressed(id)
|
|
228
238
|
}
|
|
@@ -237,7 +247,7 @@ export class BaseLoupeDeckHandler {
|
|
|
237
247
|
const id = event.id
|
|
238
248
|
if (Number.isInteger(id) && this.buttons){
|
|
239
249
|
ok = await this.buttons.released(id)
|
|
240
|
-
await this.buttons.draw(this.device)
|
|
250
|
+
//await this.buttons.draw(this.device)
|
|
241
251
|
}else{
|
|
242
252
|
if(this.knobs)
|
|
243
253
|
ok = await this.knobs.released(id)
|
|
@@ -326,6 +336,7 @@ export class BaseLoupeDeckHandler {
|
|
|
326
336
|
this.screenUpdate["right"] = true
|
|
327
337
|
|
|
328
338
|
ok = await this.screens.center.changed(buttonID,nodeid,val)
|
|
339
|
+
ok = await this.knobs.changed(buttonID,nodeid,val)
|
|
329
340
|
|
|
330
341
|
await this.updateScreens()
|
|
331
342
|
return ok
|
package/common/touchbuttons.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import * as shellif from '../interfaces/shellif.mjs'
|
|
|
5
5
|
import * as httpif from '../interfaces/httpif.mjs'
|
|
6
6
|
import * as opcuaif from '../interfaces/opcuaif.mjs'
|
|
7
7
|
import format from 'string-template'
|
|
8
|
-
import { calcDelta } from './utils.mjs'
|
|
8
|
+
import { calcDelta, invertColor } from './utils.mjs'
|
|
9
9
|
import { EventEmitter } from 'node:events'
|
|
10
10
|
|
|
11
11
|
|
|
@@ -13,17 +13,18 @@ export var opcuainterface = undefined
|
|
|
13
13
|
var httpinterface = undefined
|
|
14
14
|
var shellinterface = undefined
|
|
15
15
|
export var profileEmitter = undefined
|
|
16
|
-
export async function InitializeInterfaces(appConfig
|
|
16
|
+
export async function InitializeInterfaces(appConfig){
|
|
17
17
|
if (opcuainterface === undefined ){
|
|
18
18
|
opcuainterface = new opcuaif.OPCUAIf()
|
|
19
|
-
opcuainterface.init(appConfig.parameters,appConfig,callbackFunction)
|
|
20
19
|
}
|
|
20
|
+
// the opcua interface needs the profile to register nodes with subscriptions:
|
|
21
|
+
opcuainterface.init(appConfig.parameters,appConfig)
|
|
21
22
|
if (httpinterface === undefined)
|
|
22
23
|
httpinterface = new httpif.HTTPif()
|
|
23
24
|
if (shellinterface === undefined)
|
|
24
25
|
shellinterface = new shellif.SHELLif()
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
if (profileEmitter === undefined)
|
|
27
|
+
profileEmitter = new EventEmitter()
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
export async function StopInterfaces(){
|
|
@@ -64,9 +65,9 @@ export class ButtonField {
|
|
|
64
65
|
#keys = []
|
|
65
66
|
#type
|
|
66
67
|
#name
|
|
67
|
-
#
|
|
68
|
+
#profile
|
|
68
69
|
|
|
69
|
-
constructor (name, rows, columns, width, height, data,
|
|
70
|
+
constructor (name, rows, columns, width, height, data, profile) {
|
|
70
71
|
console.info(`ButtonField ${name.padEnd(10, ' ')} Buttons: ${rows} x ${columns} , Pixels ${width} x ${height}`)
|
|
71
72
|
this.#name = name
|
|
72
73
|
this.width = width
|
|
@@ -75,17 +76,17 @@ export class ButtonField {
|
|
|
75
76
|
this.#columns = columns
|
|
76
77
|
this.#screen = this.width > 0 && this.height > 0
|
|
77
78
|
this.#type = 'button'
|
|
79
|
+
this.#profile = profile
|
|
78
80
|
if (this.#screen) { this.#type = 'touch' }
|
|
79
81
|
|
|
80
82
|
const keys = Object.keys(data)
|
|
81
83
|
for (let i = 0; i < keys.length; i++) {
|
|
82
84
|
const key = keys[i]
|
|
83
|
-
const tb = new Button(`${this.#type}-${key}`, width / columns, height / rows, data[key],key,
|
|
85
|
+
const tb = new Button(`${this.#type}-${key}`, width / columns, height / rows, data[key],key,this.#profile)
|
|
84
86
|
this.#buttons[key] = tb
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
this.#keys = keys
|
|
88
|
-
this.#config = config
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
|
|
@@ -124,10 +125,10 @@ export class ButtonField {
|
|
|
124
125
|
for (let i = 0; i < this.#keys.length; i++) {
|
|
125
126
|
const key = this.#keys[i]
|
|
126
127
|
if (isNaN(key)) {
|
|
127
|
-
await this.#buttons[key].load(this.#
|
|
128
|
+
await this.#buttons[key].load(this.#profile)
|
|
128
129
|
} else {
|
|
129
130
|
const iVal = parseInt(key, 10)
|
|
130
|
-
await this.#buttons[iVal].load(this.#
|
|
131
|
+
await this.#buttons[iVal].load(this.#profile)
|
|
131
132
|
}
|
|
132
133
|
}
|
|
133
134
|
}
|
|
@@ -190,7 +191,9 @@ export class ButtonField {
|
|
|
190
191
|
}
|
|
191
192
|
|
|
192
193
|
export class Button {
|
|
193
|
-
#
|
|
194
|
+
#profile
|
|
195
|
+
#params
|
|
196
|
+
#data
|
|
194
197
|
width = 0
|
|
195
198
|
height = 0
|
|
196
199
|
|
|
@@ -229,7 +232,7 @@ export class Button {
|
|
|
229
232
|
minPressed = 25
|
|
230
233
|
key = -1
|
|
231
234
|
|
|
232
|
-
constructor (id, width, height, data,key,
|
|
235
|
+
constructor (id, width, height, data,key,profile) {
|
|
233
236
|
this.id = id
|
|
234
237
|
this.key = key
|
|
235
238
|
this.width = width
|
|
@@ -237,8 +240,10 @@ export class Button {
|
|
|
237
240
|
this.#index = 0
|
|
238
241
|
|
|
239
242
|
if (data && data.states) {
|
|
240
|
-
this
|
|
243
|
+
this.#data = data
|
|
241
244
|
|
|
245
|
+
this.group = data.group
|
|
246
|
+
|
|
242
247
|
this.#states = data.states
|
|
243
248
|
this.#keys = Object.keys(this.#states)
|
|
244
249
|
if (data.type) {
|
|
@@ -253,14 +258,28 @@ export class Button {
|
|
|
253
258
|
this.text = data.text
|
|
254
259
|
}
|
|
255
260
|
}
|
|
261
|
+
if (profile === undefined){
|
|
262
|
+
this.#profile = {}
|
|
263
|
+
this.#params = {}
|
|
264
|
+
}else{
|
|
265
|
+
this.#profile = profile
|
|
266
|
+
this.#params = profile.parameters
|
|
267
|
+
if (profile.parameters.min !== undefined)
|
|
268
|
+
this.#min = profile.parameters.min
|
|
269
|
+
if (profile.parameters.max !== undefined)
|
|
270
|
+
this.#max = profile.parameters.max
|
|
271
|
+
}
|
|
256
272
|
if (this.#states === undefined) {
|
|
257
273
|
this.#states = {}
|
|
258
274
|
this.#keys = []
|
|
259
275
|
}
|
|
260
276
|
if (data.nodeid){
|
|
261
|
-
this.#nodeid = format(data.nodeid, params)
|
|
277
|
+
this.#nodeid = format(data.nodeid, this.#params)
|
|
262
278
|
}
|
|
263
|
-
|
|
279
|
+
if (data.default) {
|
|
280
|
+
this.#index = this.#keys.indexOf(data.default)
|
|
281
|
+
}
|
|
282
|
+
|
|
264
283
|
}
|
|
265
284
|
|
|
266
285
|
setState (index = 0) {
|
|
@@ -282,7 +301,7 @@ export class Button {
|
|
|
282
301
|
id:idx,
|
|
283
302
|
color: `rgba(${r}, ${g}, ${b})`
|
|
284
303
|
}
|
|
285
|
-
// console.log(' Set Button Color',val.id,elem.color, val.color)
|
|
304
|
+
// console.log(' Set Button Color',id, val.id,elem.color, val.color)
|
|
286
305
|
device.setButtonColor(val)
|
|
287
306
|
} catch (error) {
|
|
288
307
|
console.error(' Error', error)
|
|
@@ -305,8 +324,10 @@ export class Button {
|
|
|
305
324
|
}
|
|
306
325
|
}
|
|
307
326
|
if (this.text){
|
|
308
|
-
const lastElem = this.getLastElement()
|
|
309
|
-
|
|
327
|
+
//const lastElem = this.getLastElement()
|
|
328
|
+
// Only change the text color, if it differnce from the currently set color
|
|
329
|
+
|
|
330
|
+
ctx.fillStyle = invertColor(elem.color)
|
|
310
331
|
ctx.font = '20px Verdana'
|
|
311
332
|
ctx.textBaseline = 'top';
|
|
312
333
|
ctx.textAlign = 'left';
|
|
@@ -315,7 +336,7 @@ export class Button {
|
|
|
315
336
|
}
|
|
316
337
|
|
|
317
338
|
async load (globalConfig) {
|
|
318
|
-
this.#
|
|
339
|
+
this.#profile = globalConfig
|
|
319
340
|
for (let i = 0; i < this.#keys.length; i++) {
|
|
320
341
|
const key = this.#keys[i]
|
|
321
342
|
const elem = this.#states[key]
|
|
@@ -392,7 +413,7 @@ export class Button {
|
|
|
392
413
|
|
|
393
414
|
updateState(index,eventType){
|
|
394
415
|
this.#index = index
|
|
395
|
-
this.#event = eventType
|
|
416
|
+
this.#event = eventType
|
|
396
417
|
// Update the State according to the correctly pressed state
|
|
397
418
|
if (this.#index < 0) { this.#index = this.#keys.length - 1 }
|
|
398
419
|
this.#index %= this.#keys.length
|
|
@@ -405,7 +426,7 @@ export class Button {
|
|
|
405
426
|
if (!this.getCurrentElement()) { return false }
|
|
406
427
|
|
|
407
428
|
this.#event = "rotated"
|
|
408
|
-
this.#value = calcDelta(this.#value, delta, this.#max)
|
|
429
|
+
this.#value = calcDelta(this.#value, delta, this.#min, this.#max)
|
|
409
430
|
return this.runCommand()
|
|
410
431
|
}
|
|
411
432
|
|
|
@@ -426,20 +447,21 @@ export class Button {
|
|
|
426
447
|
|
|
427
448
|
// check if the nodeid is the same and the value is one of the states
|
|
428
449
|
let state = this.#states[key]
|
|
429
|
-
if (
|
|
450
|
+
if (state.value === undefined)
|
|
430
451
|
continue
|
|
431
452
|
|
|
432
453
|
const params = {
|
|
433
454
|
id: buttonID,
|
|
434
455
|
key: buttonID,
|
|
456
|
+
state : key,
|
|
435
457
|
...state
|
|
436
458
|
}
|
|
437
459
|
let val1 = format(state.value,params)
|
|
438
|
-
if (val1 === val){
|
|
460
|
+
if (val1 === val.toString()){
|
|
439
461
|
this.#index = i;
|
|
440
462
|
break;
|
|
441
463
|
}
|
|
442
|
-
break;
|
|
464
|
+
//break;
|
|
443
465
|
}
|
|
444
466
|
}
|
|
445
467
|
|
|
@@ -479,7 +501,7 @@ export class Button {
|
|
|
479
501
|
return
|
|
480
502
|
}
|
|
481
503
|
|
|
482
|
-
if (elem.profile){
|
|
504
|
+
if (elem.profile !== undefined) {
|
|
483
505
|
profileEmitter.emit("profileChanged", elem.profile)
|
|
484
506
|
}
|
|
485
507
|
|
|
@@ -492,7 +514,7 @@ export class Button {
|
|
|
492
514
|
// and also all attributes of elem + global config
|
|
493
515
|
const params = {
|
|
494
516
|
text: this.getCurrentText(),
|
|
495
|
-
...this.#
|
|
517
|
+
...this.#profile.parameters,
|
|
496
518
|
...elem,
|
|
497
519
|
id: this.id,
|
|
498
520
|
key: this.key,
|
|
@@ -504,7 +526,7 @@ export class Button {
|
|
|
504
526
|
y: (this.#y %100)
|
|
505
527
|
}
|
|
506
528
|
|
|
507
|
-
if (
|
|
529
|
+
if (params.value === undefined)
|
|
508
530
|
params.value = this.#value
|
|
509
531
|
|
|
510
532
|
|
|
@@ -526,6 +548,12 @@ export class Button {
|
|
|
526
548
|
if ('opcua' in elem) {
|
|
527
549
|
if (opcuainterface){
|
|
528
550
|
res = await opcuainterface.call(elem.opcua, params)
|
|
551
|
+
|
|
552
|
+
if (this.#data.statenodeid){
|
|
553
|
+
let stateParams = params
|
|
554
|
+
params.value = params.state
|
|
555
|
+
res = await opcuainterface.call(this.#data.statenodeid, params)
|
|
556
|
+
}
|
|
529
557
|
}else{
|
|
530
558
|
console.warn("opcuainterface not started")
|
|
531
559
|
}
|
package/common/utils.mjs
CHANGED
|
@@ -22,9 +22,64 @@ export function writeJSONFile (fileName, jsonObj) {
|
|
|
22
22
|
writeFileSync(fileName, data)
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Calculate the delta of a value within a range between min and max with overflow
|
|
27
|
+
* @param {*} data
|
|
28
|
+
* @param {*} delta
|
|
29
|
+
* @param {*} min
|
|
30
|
+
* @param {*} max
|
|
31
|
+
* @returns
|
|
32
|
+
*/
|
|
33
|
+
export function calcDelta (data, delta, min = 0, max = 100) {
|
|
26
34
|
data = data + delta
|
|
27
|
-
if (data > max) { data =
|
|
28
|
-
if (data <
|
|
35
|
+
if (data > max) { data = min }
|
|
36
|
+
else if (data < min) { data = max }
|
|
29
37
|
return data
|
|
30
38
|
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Invert a color in hex format (#RRGGBB) to its inverted color
|
|
42
|
+
* @param {*} colorAsHex
|
|
43
|
+
* @returns
|
|
44
|
+
*/
|
|
45
|
+
export function invertColor(colorAsHex){
|
|
46
|
+
let rgb = colorToRGB(colorAsHex)
|
|
47
|
+
for (var i = 0; i < rgb.length; i++) {
|
|
48
|
+
rgb[i] = (i === 3 ? 1 : 255) - rgb[i];
|
|
49
|
+
}
|
|
50
|
+
let invertedColor = `#${rgb[0].toString(16)}${rgb[1].toString(16)}${rgb[2].toString(16)})`
|
|
51
|
+
//let invertedColor = `#${rgb[0].toString(16).padStart(2, '0')}${rgb[1].toString(16).padStart(2, '0')}${rgb[2].toString(16).padStart(2, '0')})`
|
|
52
|
+
//console.log("invert", colorAsHex,"=>",invertedColor)
|
|
53
|
+
return invertedColor
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Convert a color in hex format (#RRGGBB) to RGB array
|
|
58
|
+
* @param {*} colorAsHex
|
|
59
|
+
* @returns
|
|
60
|
+
*/
|
|
61
|
+
export function colorToRGB(colorAsHex){
|
|
62
|
+
const r = parseInt(colorAsHex.slice(1, 3), 16)
|
|
63
|
+
const g = parseInt(colorAsHex.slice(3, 5), 16)
|
|
64
|
+
const b = parseInt(colorAsHex.slice(5, 7), 16)
|
|
65
|
+
return [r,g,b]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Synchronize the parameters of a node with the default node
|
|
70
|
+
* @param {*} node
|
|
71
|
+
* @param {*} defaultnode
|
|
72
|
+
* @returns node
|
|
73
|
+
*/
|
|
74
|
+
export function syncParams(node, defaultnode){
|
|
75
|
+
if (node === undefined || defaultnode === undefined)
|
|
76
|
+
return node
|
|
77
|
+
let keys = Object.keys(defaultnode)
|
|
78
|
+
for (var i=0;i<keys.length;i++){
|
|
79
|
+
let key = keys[i]
|
|
80
|
+
if (!(key in node))
|
|
81
|
+
node[key] = defaultnode[key]
|
|
82
|
+
}
|
|
83
|
+
return node
|
|
84
|
+
}
|
|
85
|
+
|
package/config.json
CHANGED
package/interfaces/baseif.mjs
CHANGED
|
@@ -14,7 +14,9 @@ export class BaseIf extends EventEmitter {
|
|
|
14
14
|
|
|
15
15
|
this.cmd = cmd
|
|
16
16
|
this.options = options
|
|
17
|
-
this.formattedCommand = this.formatString(cmd, options)
|
|
17
|
+
this.formattedCommand = this.formatString(cmd, options)
|
|
18
|
+
this.LogDebug("Formatted command: ", this.formattedCommand,"\n")
|
|
19
|
+
this.LogDebug("Options",JSON.stringify(options),"\n" )
|
|
18
20
|
return this.formattedCommand
|
|
19
21
|
}
|
|
20
22
|
|
package/interfaces/opcuaif.mjs
CHANGED
|
@@ -36,7 +36,6 @@ export class OPCUAIf extends BaseIf {
|
|
|
36
36
|
monitoreditems
|
|
37
37
|
types
|
|
38
38
|
buttons
|
|
39
|
-
#callback
|
|
40
39
|
constructor() {
|
|
41
40
|
super()
|
|
42
41
|
|
|
@@ -55,14 +54,19 @@ export class OPCUAIf extends BaseIf {
|
|
|
55
54
|
this.LogInfo(`OPCUAIf Stopped\n`)
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Initialize the OPCUA client and subscribe to monitored items.
|
|
59
|
+
* @param {*} options
|
|
60
|
+
* @param {*} config
|
|
61
|
+
* @param {*} callbackFunction
|
|
62
|
+
*/
|
|
63
|
+
async init( options = {},config = {}){
|
|
59
64
|
var res = this.Check(options)
|
|
60
65
|
if (res<0){
|
|
61
66
|
this.LogError(`OPCUAIf: Missing essential options in dictionary => Quitting $res $options\n`)
|
|
62
67
|
}
|
|
63
68
|
try{
|
|
64
69
|
this.#endpointurl = this.formatString(options.endpointurl,options)
|
|
65
|
-
this.#callback = callbackFunction
|
|
66
70
|
this.monitoreditems = {}
|
|
67
71
|
this.types = {}
|
|
68
72
|
this.buttons = {}
|
|
@@ -70,7 +74,7 @@ export class OPCUAIf extends BaseIf {
|
|
|
70
74
|
|
|
71
75
|
await this.Connect(this.#endpointurl);
|
|
72
76
|
|
|
73
|
-
let fields = [config.touch.center, config.knobs]
|
|
77
|
+
let fields = [config.touch.center, config.knobs, config.buttons]
|
|
74
78
|
const fieldKeys = Object.keys(fields)
|
|
75
79
|
for (let f = 0; f < fieldKeys.length; f++) {
|
|
76
80
|
let field=fields[f]
|
|
@@ -78,11 +82,18 @@ export class OPCUAIf extends BaseIf {
|
|
|
78
82
|
for (let i = 0; i < keys.length; i++) {
|
|
79
83
|
const key = keys[i]
|
|
80
84
|
const elem = field[key]
|
|
85
|
+
// groupnode
|
|
81
86
|
if (elem.nodeid){
|
|
82
87
|
let format = this.formatString(elem.nodeid,options)
|
|
83
88
|
let monitoredItemId = await this.Subscribe(format)
|
|
84
89
|
this.buttons[monitoredItemId] = i
|
|
85
90
|
}
|
|
91
|
+
// statenode
|
|
92
|
+
if (elem.statenodeid){
|
|
93
|
+
let format = this.formatString(elem.statenodeid,options)
|
|
94
|
+
let monitoredItemId = await this.Subscribe(format)
|
|
95
|
+
this.buttons[monitoredItemId] = i
|
|
96
|
+
}
|
|
86
97
|
await this.monitorStates(elem,options)
|
|
87
98
|
}
|
|
88
99
|
}
|
|
@@ -91,6 +102,11 @@ export class OPCUAIf extends BaseIf {
|
|
|
91
102
|
}
|
|
92
103
|
}
|
|
93
104
|
|
|
105
|
+
/**
|
|
106
|
+
*
|
|
107
|
+
* @param {*} elem : Elem Object
|
|
108
|
+
* @param {*} options
|
|
109
|
+
*/
|
|
94
110
|
async monitorStates(elem,options){
|
|
95
111
|
const stateKeys = Object.keys(elem.states)
|
|
96
112
|
for (let i = 0; i < stateKeys.length; i++) {
|
|
@@ -104,6 +120,16 @@ export class OPCUAIf extends BaseIf {
|
|
|
104
120
|
}
|
|
105
121
|
}
|
|
106
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Convert the given value to the specified type.
|
|
125
|
+
* @param {*} value : The value to convert.
|
|
126
|
+
* @param {*} type : The type to convert to. Can be one of the following:
|
|
127
|
+
* - DataType.Int16
|
|
128
|
+
* - DataType.Int32
|
|
129
|
+
* - DataType.Float
|
|
130
|
+
* - DataType.String
|
|
131
|
+
* @returns the converted value.
|
|
132
|
+
*/
|
|
107
133
|
convert(value,type){
|
|
108
134
|
switch(type){
|
|
109
135
|
case DataType.Int16:
|
|
@@ -121,6 +147,19 @@ export class OPCUAIf extends BaseIf {
|
|
|
121
147
|
return value
|
|
122
148
|
return parseFloat(value)
|
|
123
149
|
break;
|
|
150
|
+
case DataType.String:
|
|
151
|
+
if (typeof value == "number")
|
|
152
|
+
return value.toString();
|
|
153
|
+
return value
|
|
154
|
+
case DataType.Boolean:
|
|
155
|
+
if (typeof value == "number" && value === 1)
|
|
156
|
+
return true
|
|
157
|
+
if (typeof value == "string"){
|
|
158
|
+
if (["true","on"].includes(value)){
|
|
159
|
+
return true
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return false
|
|
124
163
|
default:
|
|
125
164
|
return value
|
|
126
165
|
}
|
|
@@ -135,13 +174,13 @@ export class OPCUAIf extends BaseIf {
|
|
|
135
174
|
|
|
136
175
|
var nodeId = super.formatString(opcuaNode, options)
|
|
137
176
|
|
|
138
|
-
var type = this.types[
|
|
177
|
+
var type = this.types[nodeId]
|
|
139
178
|
var value = options.value
|
|
140
179
|
if (typeof value == "string")
|
|
141
180
|
value = super.formatString(options.value, options)
|
|
142
181
|
|
|
143
182
|
var convertedValue = this.convert(value,type)
|
|
144
|
-
|
|
183
|
+
this.LogInfo(`OPCUAIf: write ${nodeId} => ${value}\n`)
|
|
145
184
|
await this.Write(nodeId,convertedValue,type)
|
|
146
185
|
|
|
147
186
|
var NewState = "waiting"
|
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loupedeck-commander",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.8",
|
|
4
4
|
"description": "A system to ease working with LoupeDeck devices using CMD-line, OPC/UA or HTTP-client interfaces",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "
|
|
7
|
+
"test": "node test.mjs",
|
|
8
8
|
"lint": "echo \"INFO: no linter specified\" && exit 0",
|
|
9
9
|
"format": "echo \"INFO: no format rule specified\" && exit 0",
|
|
10
10
|
"start": "node index.mjs"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"canvas": "^
|
|
14
|
-
"loupedeck": "^
|
|
13
|
+
"canvas": "^3.1.0",
|
|
14
|
+
"loupedeck": "^7.0.0",
|
|
15
15
|
"mkdirp": "^3.0.1",
|
|
16
|
-
"node-opcua": "^2.
|
|
16
|
+
"node-opcua": "^2.153.0",
|
|
17
17
|
"string-template": "^1.0.0"
|
|
18
18
|
},
|
|
19
19
|
"author": "Thomas Schneider",
|
package/profile-1.json
CHANGED
|
@@ -228,7 +228,8 @@
|
|
|
228
228
|
"knobBL": {
|
|
229
229
|
"states": {
|
|
230
230
|
"on": {
|
|
231
|
-
"cmd": "echo \"{id} {state}\""
|
|
231
|
+
"cmd": "echo \"{id} {state}\"",
|
|
232
|
+
"opcua": "ns=2;s=Is{simnbr}.Audio.in.VolumeAccustic"
|
|
232
233
|
}
|
|
233
234
|
},
|
|
234
235
|
"group": ""
|
|
@@ -258,117 +259,11 @@
|
|
|
258
259
|
"group": ""
|
|
259
260
|
}
|
|
260
261
|
},
|
|
261
|
-
"buttons": {
|
|
262
|
-
"0": {
|
|
263
|
-
"states": {
|
|
264
|
-
"off": {
|
|
265
|
-
"color": "#000000",
|
|
266
|
-
"cmd": "echo \"{id} {state}\""
|
|
267
|
-
},
|
|
268
|
-
"on": {
|
|
269
|
-
"color": "#550000",
|
|
270
|
-
"cmd": "echo \"{id} {state}\""
|
|
271
|
-
}
|
|
272
|
-
},
|
|
273
|
-
"group": ""
|
|
274
|
-
},
|
|
275
|
-
"1": {
|
|
276
|
-
"states": {
|
|
277
|
-
"off": {
|
|
278
|
-
"color": "#000000",
|
|
279
|
-
"cmd": "echo \"{id} {state}\""
|
|
280
|
-
},
|
|
281
|
-
"on": {
|
|
282
|
-
"color": "#ff0000",
|
|
283
|
-
"cmd": "echo \"{id} {state}\""
|
|
284
|
-
}
|
|
285
|
-
},
|
|
286
|
-
"group": ""
|
|
287
|
-
},
|
|
288
|
-
"2": {
|
|
289
|
-
"states": {
|
|
290
|
-
"off": {
|
|
291
|
-
"color": "#000000",
|
|
292
|
-
"cmd": "echo \"{id} {state}\""
|
|
293
|
-
},
|
|
294
|
-
"on": {
|
|
295
|
-
"color": "#005500",
|
|
296
|
-
"cmd": "echo \"{id} {state}\""
|
|
297
|
-
}
|
|
298
|
-
},
|
|
299
|
-
"group": ""
|
|
300
|
-
},
|
|
301
|
-
"3": {
|
|
302
|
-
"states": {
|
|
303
|
-
"off": {
|
|
304
|
-
"color": "#000000",
|
|
305
|
-
"cmd": "echo \"{id} {state}\""
|
|
306
|
-
},
|
|
307
|
-
"on": {
|
|
308
|
-
"color": "#00ff00",
|
|
309
|
-
"cmd": "echo \"{id} {state}\""
|
|
310
|
-
}
|
|
311
|
-
},
|
|
312
|
-
"group": ""
|
|
313
|
-
},
|
|
314
|
-
"4": {
|
|
315
|
-
"states": {
|
|
316
|
-
"off": {
|
|
317
|
-
"color": "#000000",
|
|
318
|
-
"cmd": "echo \"{id} {state}\""
|
|
319
|
-
},
|
|
320
|
-
"on": {
|
|
321
|
-
"color": "#000055",
|
|
322
|
-
"cmd": "echo \"{id} {state}\""
|
|
323
|
-
}
|
|
324
|
-
},
|
|
325
|
-
"group": ""
|
|
326
|
-
},
|
|
327
|
-
"5": {
|
|
328
|
-
"states": {
|
|
329
|
-
"off": {
|
|
330
|
-
"color": "#000000",
|
|
331
|
-
"cmd": "echo \"{id} {state}\""
|
|
332
|
-
},
|
|
333
|
-
"on": {
|
|
334
|
-
"color": "#0000ff",
|
|
335
|
-
"cmd": "echo \"{id} {state}\""
|
|
336
|
-
}
|
|
337
|
-
},
|
|
338
|
-
"group": ""
|
|
339
|
-
},
|
|
340
|
-
"6": {
|
|
341
|
-
"states": {
|
|
342
|
-
"off": {
|
|
343
|
-
"color": "#000000",
|
|
344
|
-
"cmd": "echo \"{id} {state}\""
|
|
345
|
-
},
|
|
346
|
-
"on": {
|
|
347
|
-
"color": "#555500",
|
|
348
|
-
"cmd": "echo \"{id} {state}\""
|
|
349
|
-
}
|
|
350
|
-
},
|
|
351
|
-
"group": ""
|
|
352
|
-
},
|
|
353
|
-
"7": {
|
|
354
|
-
"states": {
|
|
355
|
-
"off": {
|
|
356
|
-
"color": "#000000",
|
|
357
|
-
"cmd": "echo \"{id} {state}\""
|
|
358
|
-
},
|
|
359
|
-
"on": {
|
|
360
|
-
"color": "#ffff00",
|
|
361
|
-
"cmd": "echo \"{id} {state}\"",
|
|
362
|
-
"http": "http://{hostname}:7778/control/connections",
|
|
363
|
-
"opcua": "ns=4;s=Is{simnbr}.Kvm.out.Source"
|
|
364
|
-
}
|
|
365
|
-
},
|
|
366
|
-
"group": ""
|
|
367
|
-
}
|
|
368
|
-
},
|
|
369
262
|
"parameters": {
|
|
370
263
|
"hostname": "127.0.0.1",
|
|
264
|
+
"simnbr": "1",
|
|
371
265
|
"endpointurl": "opc.tcp://{hostname}:4840",
|
|
372
|
-
"nodeid" : "ns=0;s=nodeID"
|
|
266
|
+
"nodeid" : "ns=0;s=nodeID",
|
|
267
|
+
"verbose": true
|
|
373
268
|
}
|
|
374
269
|
}
|
package/profile-2.json
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "profile-1",
|
|
3
|
+
"profile": "example",
|
|
4
|
+
"description": "",
|
|
5
|
+
"touch": {
|
|
6
|
+
"center": {
|
|
7
|
+
"0": {
|
|
8
|
+
"default": "one",
|
|
9
|
+
"states": {
|
|
10
|
+
"off": {
|
|
11
|
+
"color": "#000099",
|
|
12
|
+
"cmd": "echo \"{id} {state}\""
|
|
13
|
+
},
|
|
14
|
+
"one": {
|
|
15
|
+
"color": "#00ff00",
|
|
16
|
+
"image": "numbers/one_9278045.png",
|
|
17
|
+
"cmd": "echo \"{id} {state}\""
|
|
18
|
+
},
|
|
19
|
+
"two": {
|
|
20
|
+
"color": "#00ff00",
|
|
21
|
+
"image": "numbers/two_9278103.png",
|
|
22
|
+
"cmd": "echo \"{id} {state}\""
|
|
23
|
+
},
|
|
24
|
+
"three": {
|
|
25
|
+
"color": "#00ff00",
|
|
26
|
+
"image": "numbers/three_9278150.png",
|
|
27
|
+
"cmd": "echo \"{id} {state}\""
|
|
28
|
+
},
|
|
29
|
+
"four": {
|
|
30
|
+
"color": "#00ff00",
|
|
31
|
+
"image": "numbers/four_9278183.png",
|
|
32
|
+
"cmd": "echo \"{id} {state}\""
|
|
33
|
+
},
|
|
34
|
+
"five": {
|
|
35
|
+
"color": "#00ff00",
|
|
36
|
+
"image": "numbers/five_9278222.png",
|
|
37
|
+
"cmd": "echo \"{id} {state}\""
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"1": {
|
|
42
|
+
"default": "two",
|
|
43
|
+
"states": {
|
|
44
|
+
"off": {
|
|
45
|
+
"color": "#000099",
|
|
46
|
+
"cmd": "echo \"{id} {state}\""
|
|
47
|
+
},
|
|
48
|
+
"one": {
|
|
49
|
+
"color": "#00ff00",
|
|
50
|
+
"image": "numbers/one_9278045.png",
|
|
51
|
+
"cmd": "echo \"{id} {state}\""
|
|
52
|
+
},
|
|
53
|
+
"two": {
|
|
54
|
+
"color": "#00ff00",
|
|
55
|
+
"image": "numbers/two_9278103.png",
|
|
56
|
+
"cmd": "echo \"{id} {state}\""
|
|
57
|
+
},
|
|
58
|
+
"three": {
|
|
59
|
+
"color": "#00ff00",
|
|
60
|
+
"image": "numbers/three_9278150.png",
|
|
61
|
+
"cmd": "echo \"{id} {state}\""
|
|
62
|
+
},
|
|
63
|
+
"four": {
|
|
64
|
+
"color": "#00ff00",
|
|
65
|
+
"image": "numbers/four_9278183.png",
|
|
66
|
+
"cmd": "echo \"{id} {state}\""
|
|
67
|
+
},
|
|
68
|
+
"five": {
|
|
69
|
+
"color": "#00ff00",
|
|
70
|
+
"image": "numbers/five_9278222.png",
|
|
71
|
+
"cmd": "echo \"{id} {state}\""
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"2": {
|
|
76
|
+
"default": "three",
|
|
77
|
+
"states": {
|
|
78
|
+
"off": {
|
|
79
|
+
"color": "#000099",
|
|
80
|
+
"cmd": "echo \"{id} {state}\""
|
|
81
|
+
},
|
|
82
|
+
"one": {
|
|
83
|
+
"color": "#00ff00",
|
|
84
|
+
"image": "numbers/one_9278045.png",
|
|
85
|
+
"cmd": "echo \"{id} {state}\""
|
|
86
|
+
},
|
|
87
|
+
"two": {
|
|
88
|
+
"color": "#00ff00",
|
|
89
|
+
"image": "numbers/two_9278103.png",
|
|
90
|
+
"cmd": "echo \"{id} {state}\""
|
|
91
|
+
},
|
|
92
|
+
"three": {
|
|
93
|
+
"color": "#00ff00",
|
|
94
|
+
"image": "numbers/three_9278150.png",
|
|
95
|
+
"cmd": "echo \"{id} {state}\""
|
|
96
|
+
},
|
|
97
|
+
"four": {
|
|
98
|
+
"color": "#00ff00",
|
|
99
|
+
"image": "numbers/four_9278183.png",
|
|
100
|
+
"cmd": "echo \"{id} {state}\""
|
|
101
|
+
},
|
|
102
|
+
"five": {
|
|
103
|
+
"color": "#00ff00",
|
|
104
|
+
"image": "numbers/five_9278222.png",
|
|
105
|
+
"cmd": "echo \"{id} {state}\""
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"3": {
|
|
110
|
+
"default": "four",
|
|
111
|
+
"states": {
|
|
112
|
+
"off": {
|
|
113
|
+
"color": "#000099",
|
|
114
|
+
"cmd": "echo \"{id} {state}\""
|
|
115
|
+
},
|
|
116
|
+
"one": {
|
|
117
|
+
"color": "#00ff00",
|
|
118
|
+
"image": "numbers/one_9278045.png",
|
|
119
|
+
"cmd": "echo \"{id} {state}\""
|
|
120
|
+
},
|
|
121
|
+
"two": {
|
|
122
|
+
"color": "#00ff00",
|
|
123
|
+
"image": "numbers/two_9278103.png",
|
|
124
|
+
"cmd": "echo \"{id} {state}\""
|
|
125
|
+
},
|
|
126
|
+
"three": {
|
|
127
|
+
"color": "#00ff00",
|
|
128
|
+
"image": "numbers/three_9278150.png",
|
|
129
|
+
"cmd": "echo \"{id} {state}\""
|
|
130
|
+
},
|
|
131
|
+
"four": {
|
|
132
|
+
"color": "#00ff00",
|
|
133
|
+
"image": "numbers/four_9278183.png",
|
|
134
|
+
"cmd": "echo \"{id} {state}\""
|
|
135
|
+
},
|
|
136
|
+
"five": {
|
|
137
|
+
"color": "#00ff00",
|
|
138
|
+
"image": "numbers/five_9278222.png",
|
|
139
|
+
"cmd": "echo \"{id} {state}\""
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
"4": {
|
|
144
|
+
"default": "five",
|
|
145
|
+
"states": {
|
|
146
|
+
"off": {
|
|
147
|
+
"color": "#000099",
|
|
148
|
+
"cmd": "echo \"{id} {state}\""
|
|
149
|
+
},
|
|
150
|
+
"one": {
|
|
151
|
+
"color": "#00ff00",
|
|
152
|
+
"image": "numbers/one_9278045.png",
|
|
153
|
+
"cmd": "echo \"{id} {state}\""
|
|
154
|
+
},
|
|
155
|
+
"two": {
|
|
156
|
+
"color": "#00ff00",
|
|
157
|
+
"image": "numbers/two_9278103.png",
|
|
158
|
+
"cmd": "echo \"{id} {state}\""
|
|
159
|
+
},
|
|
160
|
+
"three": {
|
|
161
|
+
"color": "#00ff00",
|
|
162
|
+
"image": "numbers/three_9278150.png",
|
|
163
|
+
"cmd": "echo \"{id} {state}\""
|
|
164
|
+
},
|
|
165
|
+
"four": {
|
|
166
|
+
"color": "#00ff00",
|
|
167
|
+
"image": "numbers/four_9278183.png",
|
|
168
|
+
"cmd": "echo \"{id} {state}\""
|
|
169
|
+
},
|
|
170
|
+
"five": {
|
|
171
|
+
"color": "#00ff00",
|
|
172
|
+
"image": "numbers/five_9278222.png",
|
|
173
|
+
"cmd": "echo \"{id} {state}\""
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
"5": {
|
|
178
|
+
"states": {
|
|
179
|
+
"off": {
|
|
180
|
+
"color": "#000099",
|
|
181
|
+
"cmd": "echo \"{id} {state}\""
|
|
182
|
+
},
|
|
183
|
+
"one": {
|
|
184
|
+
"color": "#00ff00",
|
|
185
|
+
"image": "numbers/one_9278045.png",
|
|
186
|
+
"cmd": "echo \"{id} {state}\""
|
|
187
|
+
},
|
|
188
|
+
"two": {
|
|
189
|
+
"color": "#00ff00",
|
|
190
|
+
"image": "numbers/two_9278103.png",
|
|
191
|
+
"cmd": "echo \"{id} {state}\""
|
|
192
|
+
},
|
|
193
|
+
"three": {
|
|
194
|
+
"color": "#00ff00",
|
|
195
|
+
"image": "numbers/three_9278150.png",
|
|
196
|
+
"cmd": "echo \"{id} {state}\""
|
|
197
|
+
},
|
|
198
|
+
"four": {
|
|
199
|
+
"color": "#00ff00",
|
|
200
|
+
"image": "numbers/four_9278183.png",
|
|
201
|
+
"cmd": "echo \"{id} {state}\""
|
|
202
|
+
},
|
|
203
|
+
"five": {
|
|
204
|
+
"color": "#00ff00",
|
|
205
|
+
"image": "numbers/five_9278222.png",
|
|
206
|
+
"cmd": "echo \"{id} {state}\""
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
"left": {
|
|
212
|
+
"0": {
|
|
213
|
+
"states": {
|
|
214
|
+
"on": {
|
|
215
|
+
"color": "#000000",
|
|
216
|
+
"cmd": "echo \"{id} {state}\""
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
"right": {
|
|
222
|
+
"0": {
|
|
223
|
+
"states": {
|
|
224
|
+
"on": {
|
|
225
|
+
"color": "#000000",
|
|
226
|
+
"cmd": "echo \"{id} {state}\""
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
"knobs": {
|
|
233
|
+
"knobTL": {
|
|
234
|
+
"states": {
|
|
235
|
+
"on": {
|
|
236
|
+
"cmd": "echo \"{id} {state}\""
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
"group": ""
|
|
240
|
+
},
|
|
241
|
+
"knobCL": {
|
|
242
|
+
"states": {
|
|
243
|
+
"on": {
|
|
244
|
+
"cmd": "echo \"{id} {state}\""
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
"group": ""
|
|
248
|
+
},
|
|
249
|
+
"knobBL": {
|
|
250
|
+
"states": {
|
|
251
|
+
"on": {
|
|
252
|
+
"cmd": "echo \"{id} {state}\"",
|
|
253
|
+
"opcua": "ns=2;s=Is{simnbr}.Audio.in.VolumeAccustic"
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
"group": ""
|
|
257
|
+
},
|
|
258
|
+
"knobTR": {
|
|
259
|
+
"states": {
|
|
260
|
+
"on": {
|
|
261
|
+
"cmd": "echo \"{id} {state}\""
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
"group": ""
|
|
265
|
+
},
|
|
266
|
+
"knobCR": {
|
|
267
|
+
"states": {
|
|
268
|
+
"on": {
|
|
269
|
+
"cmd": "echo \"{id} {state}\""
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
"group": ""
|
|
273
|
+
},
|
|
274
|
+
"knobBR": {
|
|
275
|
+
"states": {
|
|
276
|
+
"on": {
|
|
277
|
+
"cmd": "echo \"{id} {state}\""
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
"group": ""
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
"parameters": {
|
|
284
|
+
"hostname": "127.0.0.1",
|
|
285
|
+
"simnbr": "1",
|
|
286
|
+
"endpointurl": "opc.tcp://{hostname}:4840",
|
|
287
|
+
"nodeid" : "ns=0;s=nodeID",
|
|
288
|
+
"verbose": true
|
|
289
|
+
}
|
|
290
|
+
}
|