loupedeck-commander 1.2.7 → 1.2.9
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 +46 -10
- package/common/BaseLoupeDeckHandler.mjs +6 -2
- package/common/touchbuttons.mjs +41 -16
- package/common/utils.mjs +58 -3
- package/config.json +0 -4
- package/interfaces/opcuaif.mjs +116 -83
- package/interfaces/shellif.mjs +26 -21
- package/package.json +1 -1
- package/profile-1.json +2 -1
- package/numbers/README.md +0 -5
- package/numbers/five_9278222.png +0 -0
- package/numbers/four_9278183.png +0 -0
- package/numbers/one_9278045.png +0 -0
- package/numbers/six_9278279.png +0 -0
- package/numbers/three_9278150.png +0 -0
- package/numbers/two_9278103.png +0 -0
- package/profile-profile-1-sav.json +0 -373
|
@@ -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'
|
|
@@ -42,6 +42,7 @@ class Profile {
|
|
|
42
42
|
knobs = {}
|
|
43
43
|
buttons = {}
|
|
44
44
|
parameters = {}
|
|
45
|
+
default = {}
|
|
45
46
|
#file = ''
|
|
46
47
|
loaded = false
|
|
47
48
|
#error = true
|
|
@@ -57,6 +58,7 @@ class Profile {
|
|
|
57
58
|
this.buttons = new ButtonConfig().buttons
|
|
58
59
|
this.knobs = new KnobsConfig().knobs
|
|
59
60
|
this.parameters = new ParametersConfig().parameters
|
|
61
|
+
this.default = new DefaultConfig().default
|
|
60
62
|
|
|
61
63
|
this.loadFromFile(this.#file)
|
|
62
64
|
if (this.#error) { this.saveToFile(`profile-${this.name}-sav.json`) }
|
|
@@ -83,15 +85,16 @@ class Profile {
|
|
|
83
85
|
}
|
|
84
86
|
this.profile = config.profile
|
|
85
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
|
|
86
91
|
|
|
87
92
|
// Load the Configurations for Touch-Displays
|
|
88
|
-
this.touch = new TouchConfig(config.touch)
|
|
93
|
+
this.touch = new TouchConfig(config.touch,this.default)
|
|
89
94
|
// Load the Configurations for Button-Areas
|
|
90
95
|
|
|
91
96
|
this.buttons = new ButtonConfig(config.buttons,this.#profileCount,this.#index).buttons
|
|
92
97
|
this.knobs = new KnobsConfig(config.knobs).knobs
|
|
93
|
-
// Load Parameters.parameters = config.parameters
|
|
94
|
-
this.parameters = new ParametersConfig(config.parameters).parameters
|
|
95
98
|
|
|
96
99
|
this.#error = false
|
|
97
100
|
this.loaded = true
|
|
@@ -132,11 +135,11 @@ class TouchConfig {
|
|
|
132
135
|
} // RIGHT Display Config - Available in CT & LIVE
|
|
133
136
|
//knob = {} // KNOB Display Config - Available only in CT
|
|
134
137
|
|
|
135
|
-
constructor (data) {
|
|
136
|
-
this.loadFromJSON(data)
|
|
138
|
+
constructor (data,defaultState) {
|
|
139
|
+
this.loadFromJSON(data,defaultState)
|
|
137
140
|
}
|
|
138
141
|
|
|
139
|
-
loadFromJSON (data) {
|
|
142
|
+
loadFromJSON (data,defaultState) {
|
|
140
143
|
if (!data)
|
|
141
144
|
return
|
|
142
145
|
if (!data.center)
|
|
@@ -150,7 +153,21 @@ class TouchConfig {
|
|
|
150
153
|
this.center = data.center
|
|
151
154
|
this.left = data.left
|
|
152
155
|
this.right = data.right
|
|
153
|
-
|
|
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
|
+
}
|
|
154
171
|
}
|
|
155
172
|
}
|
|
156
173
|
|
|
@@ -221,8 +238,10 @@ class ParametersConfig {
|
|
|
221
238
|
parameters = {
|
|
222
239
|
"hostname": "127.0.0.1",
|
|
223
240
|
"endpointurl": "opc.tcp://{hostname}:4840",
|
|
224
|
-
"nodeid" : "ns=0;s=nodeID"
|
|
225
|
-
|
|
241
|
+
"nodeid" : "ns=0;s=nodeID",
|
|
242
|
+
"min" : 0,
|
|
243
|
+
"max" : 100
|
|
244
|
+
}
|
|
226
245
|
|
|
227
246
|
constructor (data) {
|
|
228
247
|
this.loadFromJSON(data)
|
|
@@ -234,3 +253,20 @@ class ParametersConfig {
|
|
|
234
253
|
this.parameters = data
|
|
235
254
|
}
|
|
236
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
|
+
}
|
|
@@ -166,6 +166,8 @@ export class BaseLoupeDeckHandler {
|
|
|
166
166
|
await this.updateScreens()
|
|
167
167
|
|
|
168
168
|
await this.buttons.draw(this.device)
|
|
169
|
+
// Initialize the Interfaces
|
|
170
|
+
await InitializeInterfaces(profile)
|
|
169
171
|
|
|
170
172
|
}
|
|
171
173
|
|
|
@@ -186,8 +188,9 @@ export class BaseLoupeDeckHandler {
|
|
|
186
188
|
|
|
187
189
|
await this.activateProfile(0)
|
|
188
190
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
+
// move into activate profile function to call init with every profile change
|
|
192
|
+
// var profile = this.getCurrentProfile()
|
|
193
|
+
//await InitializeInterfaces(profile)
|
|
191
194
|
|
|
192
195
|
const self = this
|
|
193
196
|
|
|
@@ -333,6 +336,7 @@ export class BaseLoupeDeckHandler {
|
|
|
333
336
|
this.screenUpdate["right"] = true
|
|
334
337
|
|
|
335
338
|
ok = await this.screens.center.changed(buttonID,nodeid,val)
|
|
339
|
+
ok = await this.knobs.changed(buttonID,nodeid,val)
|
|
336
340
|
|
|
337
341
|
await this.updateScreens()
|
|
338
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(){
|
|
@@ -81,7 +82,7 @@ export class ButtonField {
|
|
|
81
82
|
const keys = Object.keys(data)
|
|
82
83
|
for (let i = 0; i < keys.length; i++) {
|
|
83
84
|
const key = keys[i]
|
|
84
|
-
const tb = new Button(`${this.#type}-${key}`, width / columns, height / rows, data[key],key,this.#profile
|
|
85
|
+
const tb = new Button(`${this.#type}-${key}`, width / columns, height / rows, data[key],key,this.#profile)
|
|
85
86
|
this.#buttons[key] = tb
|
|
86
87
|
}
|
|
87
88
|
|
|
@@ -191,6 +192,8 @@ export class ButtonField {
|
|
|
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,12 +258,23 @@ 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) {
|
|
264
280
|
this.#index = this.#keys.indexOf(data.default)
|
|
@@ -308,8 +324,10 @@ export class Button {
|
|
|
308
324
|
}
|
|
309
325
|
}
|
|
310
326
|
if (this.text){
|
|
311
|
-
const lastElem = this.getLastElement()
|
|
312
|
-
|
|
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)
|
|
313
331
|
ctx.font = '20px Verdana'
|
|
314
332
|
ctx.textBaseline = 'top';
|
|
315
333
|
ctx.textAlign = 'left';
|
|
@@ -408,7 +426,7 @@ export class Button {
|
|
|
408
426
|
if (!this.getCurrentElement()) { return false }
|
|
409
427
|
|
|
410
428
|
this.#event = "rotated"
|
|
411
|
-
this.#value = calcDelta(this.#value, delta, this.#max)
|
|
429
|
+
this.#value = calcDelta(this.#value, delta, this.#min, this.#max)
|
|
412
430
|
return this.runCommand()
|
|
413
431
|
}
|
|
414
432
|
|
|
@@ -429,20 +447,21 @@ export class Button {
|
|
|
429
447
|
|
|
430
448
|
// check if the nodeid is the same and the value is one of the states
|
|
431
449
|
let state = this.#states[key]
|
|
432
|
-
if (
|
|
450
|
+
if (state.value === undefined)
|
|
433
451
|
continue
|
|
434
452
|
|
|
435
453
|
const params = {
|
|
436
454
|
id: buttonID,
|
|
437
455
|
key: buttonID,
|
|
456
|
+
state : key,
|
|
438
457
|
...state
|
|
439
458
|
}
|
|
440
459
|
let val1 = format(state.value,params)
|
|
441
|
-
if (val1 === val){
|
|
460
|
+
if (val1 === val.toString()){
|
|
442
461
|
this.#index = i;
|
|
443
462
|
break;
|
|
444
463
|
}
|
|
445
|
-
break;
|
|
464
|
+
//break;
|
|
446
465
|
}
|
|
447
466
|
}
|
|
448
467
|
|
|
@@ -507,7 +526,7 @@ export class Button {
|
|
|
507
526
|
y: (this.#y %100)
|
|
508
527
|
}
|
|
509
528
|
|
|
510
|
-
if (
|
|
529
|
+
if (params.value === undefined)
|
|
511
530
|
params.value = this.#value
|
|
512
531
|
|
|
513
532
|
|
|
@@ -529,6 +548,12 @@ export class Button {
|
|
|
529
548
|
if ('opcua' in elem) {
|
|
530
549
|
if (opcuainterface){
|
|
531
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
|
+
}
|
|
532
557
|
}else{
|
|
533
558
|
console.warn("opcuainterface not started")
|
|
534
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/opcuaif.mjs
CHANGED
|
@@ -33,22 +33,22 @@ export class OPCUAIf extends BaseIf {
|
|
|
33
33
|
#sub
|
|
34
34
|
#connected
|
|
35
35
|
#endpointurl
|
|
36
|
+
#publishingInterval
|
|
36
37
|
monitoreditems
|
|
37
38
|
types
|
|
38
39
|
buttons
|
|
39
|
-
#callback
|
|
40
40
|
constructor() {
|
|
41
41
|
super()
|
|
42
42
|
|
|
43
43
|
this.LogInfo(`OPCUAIf Constructed`);
|
|
44
|
-
|
|
44
|
+
}
|
|
45
45
|
|
|
46
|
-
async stop(){
|
|
46
|
+
async stop() {
|
|
47
47
|
if (!this.#client)
|
|
48
48
|
return
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
this.LogInfo(`OPCUAIf Stopping\n`)
|
|
51
|
-
await this.#client.closeSession(this.#session,true)
|
|
51
|
+
await this.#client.closeSession(this.#session, true)
|
|
52
52
|
await this.#client.disconnect()
|
|
53
53
|
this.#connected = false
|
|
54
54
|
this.#client = null
|
|
@@ -61,35 +61,47 @@ export class OPCUAIf extends BaseIf {
|
|
|
61
61
|
* @param {*} config
|
|
62
62
|
* @param {*} callbackFunction
|
|
63
63
|
*/
|
|
64
|
-
async init(
|
|
64
|
+
async init(options = {}, config = {}) {
|
|
65
65
|
var res = this.Check(options)
|
|
66
|
-
if (res<0){
|
|
66
|
+
if (res < 0) {
|
|
67
67
|
this.LogError(`OPCUAIf: Missing essential options in dictionary => Quitting $res $options\n`)
|
|
68
68
|
}
|
|
69
|
-
try{
|
|
70
|
-
this.#endpointurl = this.formatString(options.endpointurl,options)
|
|
71
|
-
|
|
69
|
+
try {
|
|
70
|
+
this.#endpointurl = this.formatString(options.endpointurl, options)
|
|
71
|
+
if (options.publishingInterval)
|
|
72
|
+
this.#publishingInterval = options.publishingInterval
|
|
73
|
+
else{
|
|
74
|
+
this.#publishingInterval = 250;
|
|
75
|
+
this.LogInfo(`OPCUAIf init using default publishingInterval: ${this.#publishingInterval}ms\n`);
|
|
76
|
+
}
|
|
72
77
|
this.monitoreditems = {}
|
|
73
78
|
this.types = {}
|
|
74
79
|
this.buttons = {}
|
|
75
80
|
this.LogInfo(`OPCUAIf init ${this.#endpointurl}\n`);
|
|
76
|
-
|
|
81
|
+
|
|
77
82
|
await this.Connect(this.#endpointurl);
|
|
78
83
|
|
|
79
84
|
let fields = [config.touch.center, config.knobs, config.buttons]
|
|
80
85
|
const fieldKeys = Object.keys(fields)
|
|
81
86
|
for (let f = 0; f < fieldKeys.length; f++) {
|
|
82
|
-
let field=fields[f]
|
|
87
|
+
let field = fields[f]
|
|
83
88
|
const keys = Object.keys(field)
|
|
84
89
|
for (let i = 0; i < keys.length; i++) {
|
|
85
90
|
const key = keys[i]
|
|
86
91
|
const elem = field[key]
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
// groupnode
|
|
93
|
+
if (elem.nodeid) {
|
|
94
|
+
let format = this.formatString(elem.nodeid, options)
|
|
89
95
|
let monitoredItemId = await this.Subscribe(format)
|
|
90
96
|
this.buttons[monitoredItemId] = i
|
|
91
97
|
}
|
|
92
|
-
|
|
98
|
+
// statenode
|
|
99
|
+
if (elem.statenodeid) {
|
|
100
|
+
let format = this.formatString(elem.statenodeid, options)
|
|
101
|
+
let monitoredItemId = await this.Subscribe(format)
|
|
102
|
+
this.buttons[monitoredItemId] = i
|
|
103
|
+
}
|
|
104
|
+
await this.monitorStates(elem, options)
|
|
93
105
|
}
|
|
94
106
|
}
|
|
95
107
|
} catch (error) {
|
|
@@ -102,17 +114,17 @@ export class OPCUAIf extends BaseIf {
|
|
|
102
114
|
* @param {*} elem : Elem Object
|
|
103
115
|
* @param {*} options
|
|
104
116
|
*/
|
|
105
|
-
async monitorStates(elem,options){
|
|
117
|
+
async monitorStates(elem, options) {
|
|
106
118
|
const stateKeys = Object.keys(elem.states)
|
|
107
119
|
for (let i = 0; i < stateKeys.length; i++) {
|
|
108
120
|
const key2 = stateKeys[i]
|
|
109
121
|
const state = elem.states[key2]
|
|
110
|
-
if (state.opcua){
|
|
111
|
-
let format = this.formatString(state.opcua,options)
|
|
122
|
+
if (state.opcua) {
|
|
123
|
+
let format = this.formatString(state.opcua, options)
|
|
112
124
|
let monitoredItemId = await this.Subscribe(format)
|
|
113
125
|
this.buttons[monitoredItemId] = i
|
|
114
126
|
}
|
|
115
|
-
}
|
|
127
|
+
}
|
|
116
128
|
}
|
|
117
129
|
|
|
118
130
|
/**
|
|
@@ -125,17 +137,17 @@ export class OPCUAIf extends BaseIf {
|
|
|
125
137
|
* - DataType.String
|
|
126
138
|
* @returns the converted value.
|
|
127
139
|
*/
|
|
128
|
-
convert(value,type){
|
|
129
|
-
switch(type){
|
|
140
|
+
convert(value, type) {
|
|
141
|
+
switch (type) {
|
|
130
142
|
case DataType.Int16:
|
|
131
143
|
case DataType.Int32:
|
|
132
|
-
if (typeof value == "number"){
|
|
144
|
+
if (typeof value == "number") {
|
|
133
145
|
if (Number.isInteger(value))
|
|
134
146
|
return value
|
|
135
|
-
else
|
|
147
|
+
else
|
|
136
148
|
return Math.trunc(value)
|
|
137
149
|
}
|
|
138
|
-
return parseInt(value,10)
|
|
150
|
+
return parseInt(value, 10)
|
|
139
151
|
break
|
|
140
152
|
case DataType.Float:
|
|
141
153
|
if (typeof value == "number")
|
|
@@ -146,15 +158,24 @@ export class OPCUAIf extends BaseIf {
|
|
|
146
158
|
if (typeof value == "number")
|
|
147
159
|
return value.toString();
|
|
148
160
|
return value
|
|
161
|
+
case DataType.Boolean:
|
|
162
|
+
if (typeof value == "number" && value === 1)
|
|
163
|
+
return true
|
|
164
|
+
if (typeof value == "string") {
|
|
165
|
+
if (["true", "on"].includes(value)) {
|
|
166
|
+
return true
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return false
|
|
149
170
|
default:
|
|
150
171
|
return value
|
|
151
172
|
}
|
|
152
173
|
}
|
|
153
174
|
|
|
154
|
-
async call
|
|
175
|
+
async call(opcuaNode, options = {}) {
|
|
155
176
|
var res = this.Check(options)
|
|
156
|
-
if (res<0){
|
|
157
|
-
// this.LogError(`OPCUAIf call: Missing essential options in dictionary => Quitting $res\n`)
|
|
177
|
+
if (res < 0) {
|
|
178
|
+
// this.LogError(`OPCUAIf call: Missing essential options in dictionary => Quitting $res\n`)
|
|
158
179
|
return false
|
|
159
180
|
}
|
|
160
181
|
|
|
@@ -165,34 +186,41 @@ export class OPCUAIf extends BaseIf {
|
|
|
165
186
|
if (typeof value == "string")
|
|
166
187
|
value = super.formatString(options.value, options)
|
|
167
188
|
|
|
168
|
-
var convertedValue = this.convert(value,type)
|
|
189
|
+
var convertedValue = this.convert(value, type)
|
|
169
190
|
this.LogInfo(`OPCUAIf: write ${nodeId} => ${value}\n`)
|
|
170
|
-
await this.Write(nodeId,convertedValue,type)
|
|
191
|
+
await this.Write(nodeId, convertedValue, type)
|
|
171
192
|
|
|
172
193
|
var NewState = "waiting"
|
|
173
194
|
return NewState
|
|
174
195
|
}
|
|
175
196
|
|
|
176
197
|
Check(options) {
|
|
177
|
-
var res= super.Check(options)
|
|
178
|
-
if (res <0){
|
|
179
|
-
this.LogError(`OPCUAIf: mandatory parameter missing\n`)
|
|
198
|
+
var res = super.Check(options)
|
|
199
|
+
if (res < 0) {
|
|
200
|
+
this.LogError(`OPCUAIf: mandatory parameter missing\n`)
|
|
180
201
|
return res
|
|
181
202
|
}
|
|
182
|
-
if (!"endpointurl" in options){
|
|
183
|
-
this.LogError(`OPCUAIf: mandatory parameter endpointurl missing\n`)
|
|
184
|
-
return -11
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
203
|
+
if (!"endpointurl" in options) {
|
|
204
|
+
this.LogError(`OPCUAIf: mandatory parameter endpointurl missing\n`)
|
|
205
|
+
return -11
|
|
206
|
+
}
|
|
207
|
+
if (!"publishingInterval" in options) {
|
|
208
|
+
this.LogError(`OPCUAIf: mandatory parameter publishingInterval missing\n`)
|
|
209
|
+
return -11
|
|
210
|
+
}
|
|
211
|
+
if (!"nodeid" in options) {
|
|
212
|
+
this.LogError(`OPCUAIf: mandatory parameter nodeid missing\n`)
|
|
213
|
+
return -12
|
|
214
|
+
}
|
|
215
|
+
if (!"value" in options) {
|
|
216
|
+
this.LogError(`OPCUAIf: mandatory parameter value missing\n`)
|
|
217
|
+
return -13
|
|
218
|
+
}
|
|
191
219
|
return 0
|
|
192
220
|
}
|
|
193
221
|
|
|
194
222
|
async Disconnect() {
|
|
195
|
-
if (this.#client){
|
|
223
|
+
if (this.#client) {
|
|
196
224
|
this.LogInfo(`OPCUAIf: Disconnect\n`);
|
|
197
225
|
await this.#client.Disconnect()
|
|
198
226
|
this.LogInfo(`OPCUAIf: Disconnected\n`);
|
|
@@ -202,7 +230,7 @@ export class OPCUAIf extends BaseIf {
|
|
|
202
230
|
let self = this
|
|
203
231
|
this.#client = OPCUAClient.create({
|
|
204
232
|
applicationName: "NodeOPCUA-Client",
|
|
205
|
-
|
|
233
|
+
|
|
206
234
|
endpointMustExist: false,
|
|
207
235
|
// keepSessionAlive: true,
|
|
208
236
|
requestedSessionTimeout: 60 * 1000,
|
|
@@ -213,44 +241,44 @@ export class OPCUAIf extends BaseIf {
|
|
|
213
241
|
maxDelay: 5000,
|
|
214
242
|
initialDelay: 2500
|
|
215
243
|
},
|
|
216
|
-
|
|
244
|
+
|
|
217
245
|
defaultSecureTokenLifetime: 20000,
|
|
218
246
|
tokenRenewalInterval: 1000
|
|
219
247
|
});
|
|
220
|
-
|
|
248
|
+
|
|
221
249
|
this.#client.on("backoff", (retry, delay) => {
|
|
222
|
-
if((retry%10) == 0)
|
|
250
|
+
if ((retry % 10) == 0)
|
|
223
251
|
self.LogInfo(`OPCUAIf Try Reconnection ${retry} next attempt in ${delay}ms ${self.#endpointurl}\n`);
|
|
224
252
|
});
|
|
225
|
-
|
|
253
|
+
|
|
226
254
|
this.#client.on("connection_lost", () => {
|
|
227
255
|
self.LogInfo(`OPCUAIf: Connection lost\n`);
|
|
228
256
|
});
|
|
229
|
-
|
|
257
|
+
|
|
230
258
|
this.#client.on("connection_reestablished", () => {
|
|
231
259
|
self.LogInfo(`OPCUAIf: Connection re-established\n`);
|
|
232
260
|
});
|
|
233
|
-
|
|
261
|
+
|
|
234
262
|
this.#client.on("connection_failed", () => {
|
|
235
263
|
self.LogInfo(`OPCUAIf: Connection failed\n`);
|
|
236
264
|
});
|
|
237
265
|
this.#client.on("start_reconnection", () => {
|
|
238
266
|
self.LogInfo(`OPCUAIf: Starting reconnection\n`);
|
|
239
267
|
});
|
|
240
|
-
|
|
268
|
+
|
|
241
269
|
this.#client.on("after_reconnection", (err) => {
|
|
242
270
|
self.LogInfo(`OPCUAIf: After Reconnection event => ${err}\n`);
|
|
243
271
|
});
|
|
244
272
|
this.#client.on("security_token_renewed", () => {
|
|
245
273
|
self.LogDebug(`OPCUAIf: security_token_renewed\n`);
|
|
246
274
|
})
|
|
247
|
-
this.#client.on("lifetime_75", (token) => {})
|
|
248
|
-
|
|
275
|
+
this.#client.on("lifetime_75", (token) => { })
|
|
276
|
+
|
|
249
277
|
this.LogInfo(`OPCUAIf: connecting client to ${url}\n`);//, this.#session.toString());
|
|
250
278
|
await this.#client.connect(url);
|
|
251
|
-
|
|
279
|
+
|
|
252
280
|
this.#session = await this.#client.createSession();
|
|
253
|
-
|
|
281
|
+
|
|
254
282
|
this.#session.on("session_closed", (statusCode) => {
|
|
255
283
|
self.LogInfo(`OPCUAIf: Session has been closed\n`);
|
|
256
284
|
})
|
|
@@ -263,13 +291,14 @@ export class OPCUAIf extends BaseIf {
|
|
|
263
291
|
this.#session.on("keepalive_failure", () => {
|
|
264
292
|
self.LogInfo(`OPCUAIf: KeepAlive failure\n`);
|
|
265
293
|
});
|
|
266
|
-
|
|
294
|
+
|
|
295
|
+
// create subscription with a custom publishing interval:
|
|
267
296
|
this.#sub = await this.#session.createSubscription2({
|
|
268
297
|
maxNotificationsPerPublish: 9000,
|
|
269
298
|
publishingEnabled: true,
|
|
270
299
|
requestedLifetimeCount: 10,
|
|
271
300
|
requestedMaxKeepAliveCount: 10,
|
|
272
|
-
requestedPublishingInterval:
|
|
301
|
+
requestedPublishingInterval: this.#publishingInterval
|
|
273
302
|
});
|
|
274
303
|
|
|
275
304
|
this.LogInfo(`OPCUAIf: session created\n`);//, this.#session.toString());
|
|
@@ -278,7 +307,7 @@ export class OPCUAIf extends BaseIf {
|
|
|
278
307
|
this.#connected = true
|
|
279
308
|
this.#endpointurl = url
|
|
280
309
|
}
|
|
281
|
-
|
|
310
|
+
|
|
282
311
|
async Subscribe(nodeID) {
|
|
283
312
|
// install monitored item
|
|
284
313
|
|
|
@@ -286,7 +315,7 @@ export class OPCUAIf extends BaseIf {
|
|
|
286
315
|
for (let i = 0; i < keys.length; i++) {
|
|
287
316
|
const key = keys[i]
|
|
288
317
|
const elem = this.monitoreditems[key]
|
|
289
|
-
if (elem == nodeID){
|
|
318
|
+
if (elem == nodeID) {
|
|
290
319
|
// already registered => return itemid
|
|
291
320
|
return key
|
|
292
321
|
}
|
|
@@ -301,14 +330,14 @@ export class OPCUAIf extends BaseIf {
|
|
|
301
330
|
discardOldest: true,
|
|
302
331
|
queueSize: 10
|
|
303
332
|
};
|
|
304
|
-
|
|
305
|
-
if (!this.#sub){
|
|
333
|
+
|
|
334
|
+
if (!this.#sub) {
|
|
306
335
|
this.LogError(`OPCUAIf: not register monitored items $itemToMonitor\n`);
|
|
307
336
|
return
|
|
308
337
|
}
|
|
309
338
|
const monitoredItem = await this.#sub.monitor(itemToMonitor, monitoringParameters, TimestampsToReturn.Both);
|
|
310
339
|
this.monitoreditems[monitoredItem.monitoredItemId] = nodeID
|
|
311
|
-
var self=this
|
|
340
|
+
var self = this
|
|
312
341
|
monitoredItem.on("changed", function (dataValue) {
|
|
313
342
|
var nodeId = self.monitoreditems[this.monitoredItemId]
|
|
314
343
|
var buttonID = self.buttons[this.monitoredItemId]
|
|
@@ -316,55 +345,59 @@ export class OPCUAIf extends BaseIf {
|
|
|
316
345
|
self.types[nodeId] = dataValue.value.dataType
|
|
317
346
|
// publish the value to subscribers:
|
|
318
347
|
self.LogInfo(`OPCUAIf: monitored item changed: ${nodeId} => ${dataValue.value.value}\n`);
|
|
319
|
-
self.emit('monitored item changed',buttonID,nodeId, dataValue.value.value)
|
|
348
|
+
self.emit('monitored item changed', buttonID, nodeId, dataValue.value.value)
|
|
320
349
|
});
|
|
321
350
|
|
|
322
351
|
return monitoredItem.monitoredItemId;
|
|
323
352
|
}
|
|
324
|
-
|
|
353
|
+
|
|
325
354
|
async Read(nodeID) {
|
|
326
355
|
const nodeToRead = {
|
|
327
356
|
nodeId: nodeID,
|
|
328
357
|
attributeId: AttributeIds.Value
|
|
329
358
|
};
|
|
330
|
-
if (!this.#connected){
|
|
359
|
+
if (!this.#connected) {
|
|
331
360
|
this.LogError(`OPCUAIf: not connected, cannot read ${nodeID}\n`);
|
|
332
361
|
return
|
|
333
362
|
}
|
|
334
363
|
const dataValue2 = await this.#session.read(nodeToRead, 0);
|
|
335
|
-
this.LogError("OPCUAIf: read nodeID ",nodeID, dataValue2.toString(),"\n");
|
|
364
|
+
this.LogError("OPCUAIf: read nodeID ", nodeID, dataValue2.toString(), "\n");
|
|
336
365
|
return dataValue2
|
|
337
366
|
}
|
|
338
367
|
|
|
339
|
-
async Write(nodeID,value,datatype=DataType.String) {
|
|
368
|
+
async Write(nodeID, value, datatype = DataType.String) {
|
|
340
369
|
let self = this
|
|
341
|
-
if (!this.#connected){
|
|
342
|
-
self.LogError("OPCUAIf: not connected, cannot write",nodeID, value,"\n");
|
|
370
|
+
if (!this.#connected) {
|
|
371
|
+
self.LogError("OPCUAIf: not connected, cannot write", nodeID, value, "\n");
|
|
343
372
|
return
|
|
344
373
|
}
|
|
345
374
|
var nodesToWrite = [{
|
|
346
375
|
nodeId: nodeID,
|
|
347
376
|
attributeId: AttributeIds.Value,
|
|
348
377
|
indexRange: null,
|
|
349
|
-
value: {
|
|
350
|
-
value: {
|
|
378
|
+
value: {
|
|
379
|
+
value: {
|
|
351
380
|
dataType: datatype,
|
|
352
381
|
value: value
|
|
353
382
|
}
|
|
354
|
-
|
|
383
|
+
}
|
|
355
384
|
}];
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if (
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
385
|
+
try {
|
|
386
|
+
await this.#session.write(nodesToWrite, function (err, statusCodes) {
|
|
387
|
+
if (!err) {
|
|
388
|
+
if (statusCodes && statusCodes[0].value != 0) {
|
|
389
|
+
self.LogInfo(`OPCUAIf: error with Node: "${nodeID}", status ${statusCodes[0]}\n`);
|
|
390
|
+
} else {
|
|
391
|
+
self.LogInfo(`OPCUAIf: wrote ${nodeID} => ${value}\n`);
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
self.LogError("OPCUAIf: write NOT ok", nodeID, value, "\n");
|
|
395
|
+
self.LogError(err)
|
|
362
396
|
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
}
|
|
397
|
+
});
|
|
398
|
+
} catch (err) {
|
|
399
|
+
self.LogError("OPCUAIf: write NOT ok", nodeID, value, "\n");
|
|
400
|
+
self.LogError(err)
|
|
401
|
+
}
|
|
368
402
|
}
|
|
369
403
|
}
|
|
370
|
-
|
package/interfaces/shellif.mjs
CHANGED
|
@@ -6,23 +6,23 @@ import { BaseIf } from './baseif.mjs'
|
|
|
6
6
|
* Our Special-Handler just used the Default - and adds Vibration after triggers through Button-Releases
|
|
7
7
|
*/
|
|
8
8
|
export class SHELLif extends BaseIf {
|
|
9
|
-
async call
|
|
9
|
+
async call(cmd, options = {}) {
|
|
10
10
|
var res = this.Check(options)
|
|
11
|
-
if (res<0){
|
|
12
|
-
|
|
11
|
+
if (res < 0) {
|
|
12
|
+
return false
|
|
13
13
|
}
|
|
14
14
|
let formattedCmd = super.call(cmd, options)
|
|
15
15
|
return await this.sh(formattedCmd)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
async stop(){
|
|
18
|
+
async stop() {
|
|
19
19
|
this.LogInfo("SHELLif: Stopping")
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
Check(options) {
|
|
23
|
-
var res= super.Check(options)
|
|
24
|
-
if (res <0){
|
|
25
|
-
this.LogError(`SHELLif: mandatory parameter missing\n`)
|
|
23
|
+
var res = super.Check(options)
|
|
24
|
+
if (res < 0) {
|
|
25
|
+
this.LogError(`SHELLif: mandatory parameter missing\n`)
|
|
26
26
|
return res
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -32,22 +32,27 @@ export class SHELLif extends BaseIf {
|
|
|
32
32
|
* @param {*} cmd
|
|
33
33
|
* @returns
|
|
34
34
|
*/
|
|
35
|
-
async sh
|
|
36
|
-
let self = this;
|
|
35
|
+
async sh(cmd) {
|
|
36
|
+
let self = this;
|
|
37
37
|
this.LogDebug(`ShellIf: runCmd: ${cmd}\n`)
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
return new Promise(function (resolve, reject) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
40
|
+
try {
|
|
41
|
+
exec(cmd, (err, stdout, stderr) => {
|
|
42
|
+
if (stdout.length > 0)
|
|
43
|
+
self.LogInfo(`SHELLif Out: ${stdout}`)
|
|
44
|
+
if (stderr.length > 0)
|
|
45
|
+
self.LogError(`SHELLif Err: ${stderr}`)
|
|
46
|
+
if (err) {
|
|
47
|
+
resolve(err)
|
|
48
|
+
} else {
|
|
49
|
+
resolve({ stdout, stderr })
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.error(`Error in script: ${e}`)
|
|
54
|
+
resolve()
|
|
55
|
+
}
|
|
51
56
|
})
|
|
52
57
|
}
|
|
53
58
|
}
|
package/package.json
CHANGED
package/profile-1.json
CHANGED
|
@@ -229,7 +229,7 @@
|
|
|
229
229
|
"states": {
|
|
230
230
|
"on": {
|
|
231
231
|
"cmd": "echo \"{id} {state}\"",
|
|
232
|
-
"opcua": "ns=
|
|
232
|
+
"opcua": "ns=1;s={simnbr}.Function1"
|
|
233
233
|
}
|
|
234
234
|
},
|
|
235
235
|
"group": ""
|
|
@@ -263,6 +263,7 @@
|
|
|
263
263
|
"hostname": "127.0.0.1",
|
|
264
264
|
"simnbr": "1",
|
|
265
265
|
"endpointurl": "opc.tcp://{hostname}:4840",
|
|
266
|
+
"publishingInterval" : 200,
|
|
266
267
|
"nodeid" : "ns=0;s=nodeID",
|
|
267
268
|
"verbose": true
|
|
268
269
|
}
|
package/numbers/README.md
DELETED
package/numbers/five_9278222.png
DELETED
|
Binary file
|
package/numbers/four_9278183.png
DELETED
|
Binary file
|
package/numbers/one_9278045.png
DELETED
|
Binary file
|
package/numbers/six_9278279.png
DELETED
|
Binary file
|
|
Binary file
|
package/numbers/two_9278103.png
DELETED
|
Binary file
|
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "profile-1",
|
|
3
|
-
"profile": "example",
|
|
4
|
-
"description": "",
|
|
5
|
-
"touch": {
|
|
6
|
-
"center": {
|
|
7
|
-
"0": {
|
|
8
|
-
"states": {
|
|
9
|
-
"off": {
|
|
10
|
-
"color": "#000099",
|
|
11
|
-
"image": "icons/home.png",
|
|
12
|
-
"cmd": "echo \"{id} {state}\""
|
|
13
|
-
},
|
|
14
|
-
"on": {
|
|
15
|
-
"color": "#00ff00",
|
|
16
|
-
"image": "icons/home.png",
|
|
17
|
-
"cmd": "echo \"{id} {state}\""
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"group": ""
|
|
21
|
-
},
|
|
22
|
-
"1": {
|
|
23
|
-
"states": {
|
|
24
|
-
"off": {
|
|
25
|
-
"color": "#000099",
|
|
26
|
-
"image": "icons/home.png",
|
|
27
|
-
"cmd": "echo \"{id} {state}\""
|
|
28
|
-
},
|
|
29
|
-
"on": {
|
|
30
|
-
"color": "#00ff00",
|
|
31
|
-
"image": "icons/home.png",
|
|
32
|
-
"cmd": "echo \"{id} {state}\""
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
"group": ""
|
|
36
|
-
},
|
|
37
|
-
"2": {
|
|
38
|
-
"states": {
|
|
39
|
-
"off": {
|
|
40
|
-
"color": "#000099",
|
|
41
|
-
"image": "icons/home.png",
|
|
42
|
-
"cmd": "echo \"{id} {state}\""
|
|
43
|
-
},
|
|
44
|
-
"on": {
|
|
45
|
-
"color": "#00ff00",
|
|
46
|
-
"image": "icons/home.png",
|
|
47
|
-
"cmd": "echo \"{id} {state}\""
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
"group": ""
|
|
51
|
-
},
|
|
52
|
-
"3": {
|
|
53
|
-
"states": {
|
|
54
|
-
"off": {
|
|
55
|
-
"color": "#000099",
|
|
56
|
-
"image": "icons/home.png",
|
|
57
|
-
"cmd": "echo \"{id} {state}\""
|
|
58
|
-
},
|
|
59
|
-
"on": {
|
|
60
|
-
"color": "#00ff00",
|
|
61
|
-
"image": "icons/home.png",
|
|
62
|
-
"cmd": "echo \"{id} {state}\""
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
"group": ""
|
|
66
|
-
},
|
|
67
|
-
"4": {
|
|
68
|
-
"states": {
|
|
69
|
-
"off": {
|
|
70
|
-
"color": "#000099",
|
|
71
|
-
"image": "icons/home.png",
|
|
72
|
-
"cmd": "echo \"{id} {state}\""
|
|
73
|
-
},
|
|
74
|
-
"on": {
|
|
75
|
-
"color": "#00ff00",
|
|
76
|
-
"image": "icons/home.png",
|
|
77
|
-
"cmd": "echo \"{id} {state}\""
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
"group": ""
|
|
81
|
-
},
|
|
82
|
-
"5": {
|
|
83
|
-
"states": {
|
|
84
|
-
"off": {
|
|
85
|
-
"color": "#000099",
|
|
86
|
-
"image": "icons/home.png",
|
|
87
|
-
"cmd": "echo \"{id} {state}\""
|
|
88
|
-
},
|
|
89
|
-
"on": {
|
|
90
|
-
"color": "#00ff00",
|
|
91
|
-
"image": "icons/home.png",
|
|
92
|
-
"cmd": "echo \"{id} {state}\""
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
"group": ""
|
|
96
|
-
},
|
|
97
|
-
"6": {
|
|
98
|
-
"states": {
|
|
99
|
-
"off": {
|
|
100
|
-
"color": "#000099",
|
|
101
|
-
"image": "icons/home.png",
|
|
102
|
-
"cmd": "echo \"{id} {state}\""
|
|
103
|
-
},
|
|
104
|
-
"on": {
|
|
105
|
-
"color": "#00ff00",
|
|
106
|
-
"image": "icons/home.png",
|
|
107
|
-
"cmd": "echo \"{id} {state}\""
|
|
108
|
-
}
|
|
109
|
-
},
|
|
110
|
-
"group": ""
|
|
111
|
-
},
|
|
112
|
-
"7": {
|
|
113
|
-
"states": {
|
|
114
|
-
"off": {
|
|
115
|
-
"color": "#000099",
|
|
116
|
-
"image": "icons/home.png",
|
|
117
|
-
"cmd": "echo \"{id} {state}\""
|
|
118
|
-
},
|
|
119
|
-
"on": {
|
|
120
|
-
"color": "#00ff00",
|
|
121
|
-
"image": "icons/home.png",
|
|
122
|
-
"cmd": "echo \"{id} {state}\""
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
"group": ""
|
|
126
|
-
},
|
|
127
|
-
"8": {
|
|
128
|
-
"states": {
|
|
129
|
-
"off": {
|
|
130
|
-
"color": "#000099",
|
|
131
|
-
"image": "icons/home.png",
|
|
132
|
-
"cmd": "echo \"{id} {state}\""
|
|
133
|
-
},
|
|
134
|
-
"on": {
|
|
135
|
-
"color": "#00ff00",
|
|
136
|
-
"image": "icons/home.png",
|
|
137
|
-
"cmd": "echo \"{id} {state}\""
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
"group": ""
|
|
141
|
-
},
|
|
142
|
-
"9": {
|
|
143
|
-
"states": {
|
|
144
|
-
"off": {
|
|
145
|
-
"color": "#000099",
|
|
146
|
-
"image": "icons/home.png",
|
|
147
|
-
"cmd": "echo \"{id} {state}\""
|
|
148
|
-
},
|
|
149
|
-
"on": {
|
|
150
|
-
"color": "#00ff00",
|
|
151
|
-
"image": "icons/home.png",
|
|
152
|
-
"cmd": "echo \"{id} {state}\""
|
|
153
|
-
}
|
|
154
|
-
},
|
|
155
|
-
"group": ""
|
|
156
|
-
},
|
|
157
|
-
"10": {
|
|
158
|
-
"states": {
|
|
159
|
-
"off": {
|
|
160
|
-
"color": "#000099",
|
|
161
|
-
"image": "icons/home.png",
|
|
162
|
-
"cmd": "echo \"{id} {state}\""
|
|
163
|
-
},
|
|
164
|
-
"on": {
|
|
165
|
-
"color": "#00ff00",
|
|
166
|
-
"image": "icons/home.png",
|
|
167
|
-
"cmd": "echo \"{id} {state}\""
|
|
168
|
-
}
|
|
169
|
-
},
|
|
170
|
-
"group": ""
|
|
171
|
-
},
|
|
172
|
-
"11": {
|
|
173
|
-
"states": {
|
|
174
|
-
"off": {
|
|
175
|
-
"color": "#000099",
|
|
176
|
-
"image": "icons/home.png",
|
|
177
|
-
"cmd": "echo \"{id} {state}\""
|
|
178
|
-
},
|
|
179
|
-
"on": {
|
|
180
|
-
"color": "#00ff00",
|
|
181
|
-
"image": "icons/home.png",
|
|
182
|
-
"cmd": "echo \"{id} {state}\""
|
|
183
|
-
}
|
|
184
|
-
},
|
|
185
|
-
"group": ""
|
|
186
|
-
}
|
|
187
|
-
},
|
|
188
|
-
"left": {
|
|
189
|
-
"0": {
|
|
190
|
-
"states": {
|
|
191
|
-
"on": {
|
|
192
|
-
"color": "#000000",
|
|
193
|
-
"cmd": "echo \"{id} {state}\""
|
|
194
|
-
}
|
|
195
|
-
},
|
|
196
|
-
"group": ""
|
|
197
|
-
}
|
|
198
|
-
},
|
|
199
|
-
"right": {
|
|
200
|
-
"0": {
|
|
201
|
-
"states": {
|
|
202
|
-
"on": {
|
|
203
|
-
"color": "#000000",
|
|
204
|
-
"cmd": "echo \"{id} {state}\""
|
|
205
|
-
}
|
|
206
|
-
},
|
|
207
|
-
"group": ""
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
},
|
|
211
|
-
"knobs": {
|
|
212
|
-
"knobTL": {
|
|
213
|
-
"states": {
|
|
214
|
-
"on": {
|
|
215
|
-
"cmd": "echo \"{id} {state}\""
|
|
216
|
-
}
|
|
217
|
-
},
|
|
218
|
-
"group": ""
|
|
219
|
-
},
|
|
220
|
-
"knobCL": {
|
|
221
|
-
"states": {
|
|
222
|
-
"on": {
|
|
223
|
-
"cmd": "echo \"{id} {state}\""
|
|
224
|
-
}
|
|
225
|
-
},
|
|
226
|
-
"group": ""
|
|
227
|
-
},
|
|
228
|
-
"knobBL": {
|
|
229
|
-
"states": {
|
|
230
|
-
"on": {
|
|
231
|
-
"cmd": "echo \"{id} {state}\""
|
|
232
|
-
}
|
|
233
|
-
},
|
|
234
|
-
"group": ""
|
|
235
|
-
},
|
|
236
|
-
"knobTR": {
|
|
237
|
-
"states": {
|
|
238
|
-
"on": {
|
|
239
|
-
"cmd": "echo \"{id} {state}\""
|
|
240
|
-
}
|
|
241
|
-
},
|
|
242
|
-
"group": ""
|
|
243
|
-
},
|
|
244
|
-
"knobCR": {
|
|
245
|
-
"states": {
|
|
246
|
-
"on": {
|
|
247
|
-
"cmd": "echo \"{id} {state}\""
|
|
248
|
-
}
|
|
249
|
-
},
|
|
250
|
-
"group": ""
|
|
251
|
-
},
|
|
252
|
-
"knobBR": {
|
|
253
|
-
"states": {
|
|
254
|
-
"on": {
|
|
255
|
-
"cmd": "echo \"{id} {state}\""
|
|
256
|
-
}
|
|
257
|
-
},
|
|
258
|
-
"group": ""
|
|
259
|
-
}
|
|
260
|
-
},
|
|
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
|
-
}
|
|
363
|
-
},
|
|
364
|
-
"group": ""
|
|
365
|
-
}
|
|
366
|
-
},
|
|
367
|
-
"parameters": {
|
|
368
|
-
"hostname": "127.0.0.1",
|
|
369
|
-
"endpointurl": "opc.tcp://{hostname}:4840",
|
|
370
|
-
"nodeid": "ns=0;s=nodeID"
|
|
371
|
-
},
|
|
372
|
-
"loaded": false
|
|
373
|
-
}
|