loupedeck-commander 1.0.2 → 1.2.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 +34 -5
- package/common/ApplicationConfig.mjs +3 -0
- package/common/BaseLoupeDeckHandler.mjs +152 -28
- package/common/touchbuttons.mjs +161 -21
- package/eslint.config.mjs +9 -0
- package/example/ExampleDeviceHandler.mjs +2 -3
- package/example/example.mjs +13 -5
- package/interfaces/baseif.mjs +68 -0
- package/interfaces/httpif.mjs +81 -0
- package/interfaces/opcuaif.mjs +283 -0
- package/interfaces/shellif.mjs +47 -0
- package/package.json +10 -12
- package/profile-1.json +229 -16
- package/test.mjs +23 -0
package/README.md
CHANGED
|
@@ -53,15 +53,26 @@ import { BaseLoupeDeckHandler } from 'loupedeck-commander'
|
|
|
53
53
|
|
|
54
54
|
const handler = new BaseLoupeDeckHandler('config.json')
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Stop the handlers when a signal like SIGINT or SIGTERM arrive
|
|
58
|
+
* @param {*} signal
|
|
59
|
+
*/
|
|
60
|
+
const stopHandler = async(signal) => {
|
|
61
|
+
console.log(`Receiving ${signal} => Stopping processes.`)
|
|
62
|
+
await handler.stop()
|
|
59
63
|
}
|
|
60
64
|
|
|
61
|
-
// Initiating
|
|
62
|
-
|
|
65
|
+
// Initiating the signal handlers:
|
|
66
|
+
// see https://www.tutorialspoint.com/unix/unix-signals-traps.htm
|
|
67
|
+
process.on('SIGINT', async (signal) => { stopHandler(signal) })
|
|
68
|
+
process.on('SIGTERM', async (signal) => { stopHandler(signal) })
|
|
63
69
|
|
|
70
|
+
// Start it
|
|
64
71
|
await handler.start()
|
|
72
|
+
|
|
73
|
+
// Take care of running background processes and stop them accordingly:
|
|
74
|
+
await new Promise((resolve) => process.once("SIGINT", resolve));
|
|
75
|
+
|
|
65
76
|
```
|
|
66
77
|
|
|
67
78
|
Run the script using the following command:
|
|
@@ -72,8 +83,26 @@ node index.mjs
|
|
|
72
83
|
|
|
73
84
|
If you end up with errors related to canvas module - please install the dependencies as mentioned below, and run `npm install -s` again
|
|
74
85
|
|
|
86
|
+
## Thanks
|
|
87
|
+
|
|
88
|
+
Big thanks go out to [foxxyz](https://github.com/foxxyz/loupedeck) and his team maintaining a great javascript loopdeck module
|
|
89
|
+
|
|
75
90
|
## Hints
|
|
76
91
|
|
|
92
|
+
### Cannot connect to Loupedeck device
|
|
93
|
+
|
|
94
|
+
You may need to add your user to the `dialout` group to allow access to the LoupeDeck device, you could do su using the `usermod` as shown below
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# ls -la /dev/ttyACM0
|
|
98
|
+
crw-rw---- 1 root dialout 166, 0 Nov 30 12:59 /dev/ttyACM0
|
|
99
|
+
|
|
100
|
+
# add the current user to the dialout group:
|
|
101
|
+
# sudo usermod -a -G dialout $USER
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
After modifying users group you need to logout and login again to activate this change
|
|
105
|
+
|
|
77
106
|
### CANVAS Module - additional effort needed
|
|
78
107
|
|
|
79
108
|
The library is using [canvas](https://www.npmjs.com/package/canvas) to load images and render graphical content on the LoupeDeck devices.
|
|
@@ -24,6 +24,7 @@ class Profile {
|
|
|
24
24
|
touch = {}
|
|
25
25
|
knobs = {}
|
|
26
26
|
buttons = {}
|
|
27
|
+
parameters = {}
|
|
27
28
|
#file = ''
|
|
28
29
|
#loaded = false
|
|
29
30
|
#error = false
|
|
@@ -48,6 +49,8 @@ class Profile {
|
|
|
48
49
|
this.touch = new TouchConfig(config.touch)
|
|
49
50
|
// Load the Configurations for Button-Areas
|
|
50
51
|
this.buttons = config.buttons
|
|
52
|
+
// Load Parameters
|
|
53
|
+
this.parameters = config.parameters
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {discover, HAPTIC,LoupedeckDevice } from 'loupedeck'
|
|
2
2
|
import { ApplicationConfig } from './ApplicationConfig.mjs'
|
|
3
|
-
import { ButtonField } from './touchbuttons.mjs'
|
|
4
|
-
const { discover, HAPTIC } = pkg
|
|
3
|
+
import { ButtonField, StopInterfaces, InitializeInterfaces,opcuainterface} from './touchbuttons.mjs'
|
|
5
4
|
|
|
6
5
|
export class BaseLoupeDeckHandler {
|
|
7
6
|
device = undefined
|
|
@@ -11,25 +10,43 @@ export class BaseLoupeDeckHandler {
|
|
|
11
10
|
screens = {}
|
|
12
11
|
buttons = {}
|
|
13
12
|
screenUpdate = {}
|
|
14
|
-
|
|
15
|
-
touchButtons = undefined
|
|
13
|
+
stopping = false
|
|
16
14
|
|
|
17
15
|
constructor (config) {
|
|
18
16
|
console.log(`INIT with config ${config}`)
|
|
19
17
|
this.loadConfig(config)
|
|
20
18
|
}
|
|
21
19
|
|
|
20
|
+
/**
|
|
21
|
+
* event handler - start
|
|
22
|
+
*/
|
|
22
23
|
async start () {
|
|
23
24
|
console.info('Start')
|
|
24
|
-
while (!this.device) {
|
|
25
|
+
while (!this.device && !this.stopping) {
|
|
25
26
|
try {
|
|
27
|
+
var list = await LoupedeckDevice.list()
|
|
28
|
+
if (list.length>0){
|
|
29
|
+
var element = list[0]
|
|
30
|
+
console.log("Connecting to device")
|
|
31
|
+
console.log("Path: ", element.path)
|
|
32
|
+
console.log("vendorId: ", element.vendorId, element.productId)
|
|
33
|
+
console.log("serialNumber: ", element.serialNumber)
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
this.device = await discover()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
27
40
|
} catch (e) {
|
|
28
41
|
console.error(`${e}. Reattempting in 3 seconds...`)
|
|
29
|
-
await new Promise(
|
|
42
|
+
await new Promise(resolve => setTimeout(resolve, 3000))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (this.stopping){
|
|
46
|
+
return
|
|
30
47
|
}
|
|
31
48
|
}
|
|
32
|
-
console.info(`✅
|
|
49
|
+
console.info(`✅ Discovered Device ${this.device.type}`)
|
|
33
50
|
|
|
34
51
|
const self = this
|
|
35
52
|
|
|
@@ -40,51 +57,89 @@ export class BaseLoupeDeckHandler {
|
|
|
40
57
|
this.device.on('touchstart', (event) => { self.onTouchStart(event) })
|
|
41
58
|
this.device.on('touchmove', (event) => { self.onTouchMove(event) })
|
|
42
59
|
this.device.on('touchend', (event) => { self.onTouchEnd(event) })
|
|
60
|
+
this.device.on('disconnect', (event) => { self.disconnectDevice(event) })
|
|
61
|
+
|
|
62
|
+
console.info(`✅ Registered callbacks`)
|
|
63
|
+
}
|
|
43
64
|
|
|
44
|
-
|
|
65
|
+
/**
|
|
66
|
+
* event handler - loupedeck device disconnected
|
|
67
|
+
*/
|
|
68
|
+
async disconnectDevice (event) {
|
|
69
|
+
console.info("Device Disconnected")
|
|
70
|
+
this.device=undefined
|
|
45
71
|
}
|
|
46
72
|
|
|
47
|
-
|
|
73
|
+
/**
|
|
74
|
+
* stop handler - close the dipslay-connection and also stop all interface handlers
|
|
75
|
+
*/
|
|
76
|
+
async stop () {
|
|
48
77
|
console.info('Stopping Handler')
|
|
49
|
-
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
console.info(`Closing Device`)
|
|
81
|
+
this.stopping = true
|
|
82
|
+
if (this.device){
|
|
83
|
+
this.device.vibrate(HAPTIC.DESCEND_MED)
|
|
84
|
+
await new Promise(resolve => setTimeout(resolve, 250))
|
|
85
|
+
}
|
|
86
|
+
if (this.device){
|
|
87
|
+
this.device.setBrightness(0)
|
|
88
|
+
await new Promise(resolve => setTimeout(resolve, 250))
|
|
89
|
+
}
|
|
90
|
+
if (this.device){
|
|
91
|
+
this.device.reconnectInterval = 0
|
|
92
|
+
await this.device.close()
|
|
93
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
94
|
+
console.info(`Device Closed`)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.info(`Stopping interfaces`)
|
|
98
|
+
await StopInterfaces()
|
|
99
|
+
await new Promise(resolve => setTimeout(resolve, 500))
|
|
100
|
+
} catch (e) {}
|
|
101
|
+
|
|
102
|
+
process.exit()
|
|
103
|
+
|
|
50
104
|
}
|
|
51
105
|
|
|
106
|
+
/**
|
|
107
|
+
* load Application config file from JSON
|
|
108
|
+
* @param filename
|
|
109
|
+
*/
|
|
52
110
|
loadConfig (fileName) {
|
|
53
111
|
console.info(`Loading Config File ${fileName}`)
|
|
54
112
|
this.appConfig = new ApplicationConfig()
|
|
55
113
|
this.appConfig.loadFromFile(fileName)
|
|
56
114
|
}
|
|
57
115
|
|
|
116
|
+
/**
|
|
117
|
+
* activate the profile with the givven ID
|
|
118
|
+
*/
|
|
58
119
|
async activateProfile (id) {
|
|
59
120
|
// todo Profile-change implementation
|
|
121
|
+
var oldProfile = this.currentProfile
|
|
60
122
|
if (this.appConfig.profiles.length >= id) {
|
|
61
123
|
this.currentProfile = id
|
|
124
|
+
}else{
|
|
125
|
+
return
|
|
62
126
|
}
|
|
63
|
-
await this.buttons.draw(this.device)
|
|
64
|
-
}
|
|
65
127
|
|
|
66
|
-
getCurrentProfile () {
|
|
67
|
-
return this.appConfig.profiles[this.currentProfile]
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Events:
|
|
71
|
-
async onConnected (address) {
|
|
72
|
-
console.info('Connected: ', address)
|
|
73
128
|
const dLeft = this.device.displays.left
|
|
74
129
|
const dRight = this.device.displays.right
|
|
75
130
|
const dCenter = this.device.displays.center
|
|
76
131
|
const dKnob = this.device.displays.knob
|
|
77
|
-
|
|
132
|
+
|
|
78
133
|
const profile = this.getCurrentProfile()
|
|
79
|
-
this.screens.center = new ButtonField('center', this.device.rows, this.device.columns, dCenter.width, dCenter.height, profile.touch.center)
|
|
80
|
-
this.screens.left = new ButtonField('left', 1, 1, dLeft.width, dLeft.height, profile.touch.left)
|
|
81
|
-
this.screens.right = new ButtonField('right', 1, 1, dRight.width, dRight.height, profile.touch.right)
|
|
134
|
+
this.screens.center = new ButtonField('center', this.device.rows, this.device.columns, dCenter.width, dCenter.height, profile.touch.center,profile)
|
|
135
|
+
this.screens.left = new ButtonField('left', 1, 1, dLeft.width, dLeft.height, profile.touch.left,profile)
|
|
136
|
+
this.screens.right = new ButtonField('right', 1, 1, dRight.width, dRight.height, profile.touch.right,profile)
|
|
82
137
|
// knob Display is only available in the CT-version - not with live:
|
|
83
138
|
if (dKnob) {
|
|
84
|
-
this.screens.knob = new ButtonField('knob', 1, 1, dKnob.width, dKnob.height, profile.touch.knob)
|
|
139
|
+
this.screens.knob = new ButtonField('knob', 1, 1, dKnob.width, dKnob.height, profile.touch.knob,profile)
|
|
85
140
|
}
|
|
86
141
|
|
|
87
|
-
this.buttons = new ButtonField('buttons', 1, 1, 0, 0, profile.buttons)
|
|
142
|
+
this.buttons = new ButtonField('buttons', 1, 1, 0, 0, profile.buttons,profile)
|
|
88
143
|
|
|
89
144
|
await this.screens.center.load()
|
|
90
145
|
await this.screens.right.load()
|
|
@@ -98,15 +153,47 @@ export class BaseLoupeDeckHandler {
|
|
|
98
153
|
if (dKnob) {
|
|
99
154
|
await this.screens.knob.draw(this.device)
|
|
100
155
|
}
|
|
101
|
-
|
|
156
|
+
|
|
157
|
+
this.buttons.setState(this.currentProfile+1,1)
|
|
158
|
+
this.buttons.setState(oldProfile+1,1)
|
|
159
|
+
|
|
102
160
|
await this.buttons.draw(this.device)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* get the dictionary iwth the current profile settings
|
|
165
|
+
*/
|
|
166
|
+
getCurrentProfile () {
|
|
167
|
+
return this.appConfig.profiles[this.currentProfile]
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Triggered, when LoupeDeck Device is connected
|
|
172
|
+
* - Initialize the profile from config-file and
|
|
173
|
+
* - start the interface handlers accordingly
|
|
174
|
+
*/
|
|
175
|
+
async onConnected (address) {
|
|
176
|
+
console.info(`✅ Connected to ${this.device.type}, ${address}`)
|
|
177
|
+
|
|
178
|
+
await this.activateProfile(0)
|
|
179
|
+
|
|
180
|
+
var profile = this.getCurrentProfile()
|
|
181
|
+
await InitializeInterfaces(profile,this.buttons.setState)
|
|
182
|
+
|
|
183
|
+
const self = this
|
|
184
|
+
|
|
185
|
+
// Register callback on monitored item change:
|
|
186
|
+
opcuainterface.myEmitter.on("monitored item changed",(buttonID,nodeid,val) => { self.buttonStateChanged(buttonID,nodeid,val) })
|
|
103
187
|
|
|
104
188
|
this.device.setBrightness(1)
|
|
105
189
|
this.device.vibrate(HAPTIC.ASCEND_MED)
|
|
106
190
|
|
|
107
|
-
console.info('Done initializing')
|
|
191
|
+
console.info('✅ Done initializing')
|
|
108
192
|
}
|
|
109
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Fore an update of all screens
|
|
196
|
+
*/
|
|
110
197
|
async updateScreens () {
|
|
111
198
|
const keys = Object.keys(this.screenUpdate)
|
|
112
199
|
for (let i = 0; i < keys.length; i++) {
|
|
@@ -116,6 +203,9 @@ export class BaseLoupeDeckHandler {
|
|
|
116
203
|
this.screenUpdate = {}
|
|
117
204
|
}
|
|
118
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Button Down Handler - triggered through Event Button Down - connected to LoupeDeck Event
|
|
208
|
+
*/
|
|
119
209
|
async onButtonDown (event) {
|
|
120
210
|
let ok = false
|
|
121
211
|
const id = event.id
|
|
@@ -124,20 +214,33 @@ export class BaseLoupeDeckHandler {
|
|
|
124
214
|
return ok
|
|
125
215
|
}
|
|
126
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Button Up Handler - triggered through Event Button Up - connected to LoupeDeck Event
|
|
219
|
+
*/
|
|
127
220
|
async onButtonUp (event) {
|
|
128
221
|
let ok = false
|
|
129
222
|
const id = event.id
|
|
130
223
|
ok = await this.buttons.released(id)
|
|
131
224
|
await this.buttons.draw(this.device)
|
|
225
|
+
|
|
226
|
+
/*if (this.currentProfile != id){
|
|
227
|
+
this.activateProfile(id-1)
|
|
228
|
+
}*/
|
|
132
229
|
return ok
|
|
133
230
|
}
|
|
134
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Button Rotate Handler - triggered through Event Rotate available with knob-Buttons - connected to LoupeDeck Event
|
|
234
|
+
*/
|
|
135
235
|
async onRotate (event) {
|
|
136
236
|
const id = event.id
|
|
137
237
|
const delta = event.delta
|
|
138
238
|
return await this.buttons.rotated(id, delta)
|
|
139
239
|
}
|
|
140
240
|
|
|
241
|
+
/**
|
|
242
|
+
* TouchStart Handler - triggered through Event touch available on all touchbuttons on the loupedeck display - connected to LoupeDeck Event
|
|
243
|
+
*/
|
|
141
244
|
async onTouchStart (event) {
|
|
142
245
|
let ok = false
|
|
143
246
|
const changedTouches = event.changedTouches
|
|
@@ -154,6 +257,9 @@ export class BaseLoupeDeckHandler {
|
|
|
154
257
|
return ok
|
|
155
258
|
}
|
|
156
259
|
|
|
260
|
+
/**
|
|
261
|
+
* TouchMove Handler - triggered through Event touch available on all touchbuttons on the loupedeck display - connected to LoupeDeck Event
|
|
262
|
+
*/
|
|
157
263
|
async onTouchMove (event) {
|
|
158
264
|
let ok = false
|
|
159
265
|
const changedTouches = event.changedTouches
|
|
@@ -170,6 +276,9 @@ export class BaseLoupeDeckHandler {
|
|
|
170
276
|
return ok
|
|
171
277
|
}
|
|
172
278
|
|
|
279
|
+
/**
|
|
280
|
+
* TouchEnd Handler - triggered through Event touch available on all touchbuttons on the loupedeck display - connected to LoupeDeck Event
|
|
281
|
+
*/
|
|
173
282
|
async onTouchEnd (event) {
|
|
174
283
|
let ok = false
|
|
175
284
|
const changedTouches = event.changedTouches
|
|
@@ -186,4 +295,19 @@ export class BaseLoupeDeckHandler {
|
|
|
186
295
|
await this.updateScreens()
|
|
187
296
|
return ok
|
|
188
297
|
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Handler for StateChanged through OPC/UA Interface - only connected with touch-buttons on center field (yet)
|
|
301
|
+
*/
|
|
302
|
+
async buttonStateChanged(buttonID,nodeid,val) {
|
|
303
|
+
let ok = false
|
|
304
|
+
this.screenUpdate["center"] = true
|
|
305
|
+
|
|
306
|
+
ok = await this.screens.center.changed(buttonID,nodeid,val)
|
|
307
|
+
|
|
308
|
+
await this.updateScreens()
|
|
309
|
+
return ok
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
|
|
189
313
|
}
|
package/common/touchbuttons.mjs
CHANGED
|
@@ -1,8 +1,38 @@
|
|
|
1
1
|
import { loadImage } from 'canvas'
|
|
2
|
-
import {
|
|
2
|
+
//import { loadImage } from "https://deno.land/x/canvas/mod.ts";
|
|
3
|
+
|
|
4
|
+
import * as shellif from '../interfaces/shellif.mjs'
|
|
5
|
+
import * as httpif from '../interfaces/httpif.mjs'
|
|
6
|
+
import * as opcuaif from '../interfaces/opcuaif.mjs'
|
|
3
7
|
import format from 'string-template'
|
|
4
8
|
import { calcDelta } from './utils.mjs'
|
|
5
9
|
|
|
10
|
+
export var opcuainterface = undefined
|
|
11
|
+
var httpinterface = undefined
|
|
12
|
+
var shellinterface = undefined
|
|
13
|
+
|
|
14
|
+
export async function InitializeInterfaces(appConfig,callbackFunction){
|
|
15
|
+
if (opcuainterface === undefined ){
|
|
16
|
+
opcuainterface = new opcuaif.OPCUAIf()
|
|
17
|
+
opcuainterface.init(appConfig.parameters,appConfig,callbackFunction)
|
|
18
|
+
}
|
|
19
|
+
if (httpinterface === undefined)
|
|
20
|
+
httpinterface = new httpif.HTTPif()
|
|
21
|
+
if (shellinterface === undefined)
|
|
22
|
+
shellinterface = new shellif.SHELLif()
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function StopInterfaces(){
|
|
27
|
+
if (opcuainterface !== undefined )
|
|
28
|
+
await opcuainterface.stop()
|
|
29
|
+
if (httpinterface !== undefined)
|
|
30
|
+
await httpinterface.stop()
|
|
31
|
+
if (shellinterface !== undefined)
|
|
32
|
+
await shellinterface.stop()
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
|
|
6
36
|
export const ButtonIndex = {
|
|
7
37
|
BUTN_0: 0,
|
|
8
38
|
BUTN_1: 1,
|
|
@@ -31,7 +61,9 @@ export class ButtonField {
|
|
|
31
61
|
#keys = []
|
|
32
62
|
#type
|
|
33
63
|
#name
|
|
34
|
-
|
|
64
|
+
#config
|
|
65
|
+
|
|
66
|
+
constructor (name, rows, columns, width, height, data, config) {
|
|
35
67
|
console.info(`ButtonField ${name.padEnd(10, ' ')} Buttons: ${rows} x ${columns} , Pixels ${width} x ${height}`)
|
|
36
68
|
this.#name = name
|
|
37
69
|
this.width = width
|
|
@@ -45,13 +77,18 @@ export class ButtonField {
|
|
|
45
77
|
const keys = Object.keys(data)
|
|
46
78
|
for (let i = 0; i < keys.length; i++) {
|
|
47
79
|
const key = keys[i]
|
|
48
|
-
const tb = new Button(`${this.#type}-${key}`, width / columns, height / rows, data[key])
|
|
80
|
+
const tb = new Button(`${this.#type}-${key}`, width / columns, height / rows, data[key],key,config.parameters)
|
|
49
81
|
this.#buttons[key] = tb
|
|
50
82
|
}
|
|
51
83
|
|
|
52
84
|
this.#keys = keys
|
|
85
|
+
this.#config = config
|
|
53
86
|
}
|
|
54
87
|
|
|
88
|
+
//setProfileConfig (config) {
|
|
89
|
+
// this.#config = config
|
|
90
|
+
// }
|
|
91
|
+
|
|
55
92
|
async draw (device) {
|
|
56
93
|
if (!this.#screen) {
|
|
57
94
|
// physical buttons:
|
|
@@ -75,6 +112,10 @@ export class ButtonField {
|
|
|
75
112
|
}
|
|
76
113
|
}
|
|
77
114
|
|
|
115
|
+
setState (id, val) {
|
|
116
|
+
this.#buttons[id].setState(val)
|
|
117
|
+
}
|
|
118
|
+
|
|
78
119
|
setIntState (id, val) {
|
|
79
120
|
this.#buttons[id].setIntState(val)
|
|
80
121
|
}
|
|
@@ -83,10 +124,10 @@ export class ButtonField {
|
|
|
83
124
|
for (let i = 0; i < this.#keys.length; i++) {
|
|
84
125
|
const key = this.#keys[i]
|
|
85
126
|
if (isNaN(key)) {
|
|
86
|
-
await this.#buttons[key].load()
|
|
127
|
+
await this.#buttons[key].load(this.#config)
|
|
87
128
|
} else {
|
|
88
129
|
const iVal = parseInt(key, 10)
|
|
89
|
-
await this.#buttons[iVal].load()
|
|
130
|
+
await this.#buttons[iVal].load(this.#config)
|
|
90
131
|
}
|
|
91
132
|
}
|
|
92
133
|
}
|
|
@@ -119,6 +160,13 @@ export class ButtonField {
|
|
|
119
160
|
return result
|
|
120
161
|
}
|
|
121
162
|
|
|
163
|
+
async changed(buttonID,nodeid,val){
|
|
164
|
+
for (let i = 0; i < this.#keys.length; i++) {
|
|
165
|
+
let bID = this.#keys[i]
|
|
166
|
+
const result = await this.#buttons[bID].changed(i,nodeid,val)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
122
170
|
async rotated (id, delta) {
|
|
123
171
|
this.checkAndCreateButton(id)
|
|
124
172
|
const result = await this.#buttons[id].rotated(delta)
|
|
@@ -142,6 +190,7 @@ export class ButtonField {
|
|
|
142
190
|
}
|
|
143
191
|
|
|
144
192
|
export class Button {
|
|
193
|
+
#config
|
|
145
194
|
width = 0
|
|
146
195
|
height = 0
|
|
147
196
|
|
|
@@ -151,8 +200,10 @@ export class Button {
|
|
|
151
200
|
#max = 100
|
|
152
201
|
#value = 50
|
|
153
202
|
#name = undefined
|
|
203
|
+
#nodeid = ""
|
|
154
204
|
|
|
155
205
|
#index = 0
|
|
206
|
+
#event
|
|
156
207
|
#keys
|
|
157
208
|
#states
|
|
158
209
|
|
|
@@ -176,9 +227,11 @@ export class Button {
|
|
|
176
227
|
timeHold
|
|
177
228
|
// Minimum ammount of time in ms to press a button:
|
|
178
229
|
minPressed = 25
|
|
230
|
+
key = -1
|
|
179
231
|
|
|
180
|
-
constructor (id, width, height, data) {
|
|
232
|
+
constructor (id, width, height, data,key,params) {
|
|
181
233
|
this.id = id
|
|
234
|
+
this.key = key
|
|
182
235
|
this.width = width
|
|
183
236
|
this.height = height
|
|
184
237
|
this.#index = 0
|
|
@@ -204,6 +257,10 @@ export class Button {
|
|
|
204
257
|
this.#states = {}
|
|
205
258
|
this.#keys = []
|
|
206
259
|
}
|
|
260
|
+
if (data.nodeid){
|
|
261
|
+
this.#nodeid = format(data.nodeid, params)
|
|
262
|
+
}
|
|
263
|
+
|
|
207
264
|
}
|
|
208
265
|
|
|
209
266
|
setState (index = 0) {
|
|
@@ -219,13 +276,16 @@ export class Button {
|
|
|
219
276
|
const b = parseInt(elem.color.slice(5, 7), 16)
|
|
220
277
|
|
|
221
278
|
try {
|
|
279
|
+
var idx = parseInt(id, 10);
|
|
280
|
+
|
|
222
281
|
const val = {
|
|
223
|
-
id,
|
|
282
|
+
id:idx,
|
|
224
283
|
color: `rgba(${r}, ${g}, ${b})`
|
|
225
284
|
}
|
|
285
|
+
//console.log(' Set Button Color',val.id, val.color)
|
|
226
286
|
device.setButtonColor(val)
|
|
227
287
|
} catch (error) {
|
|
228
|
-
|
|
288
|
+
console.error(' Error', error)
|
|
229
289
|
}
|
|
230
290
|
}
|
|
231
291
|
|
|
@@ -250,7 +310,8 @@ export class Button {
|
|
|
250
310
|
// ctx.fillText(this.text, x + 10, y - 10)
|
|
251
311
|
}
|
|
252
312
|
|
|
253
|
-
async load () {
|
|
313
|
+
async load (globalConfig) {
|
|
314
|
+
this.#config = globalConfig
|
|
254
315
|
for (let i = 0; i < this.#keys.length; i++) {
|
|
255
316
|
const key = this.#keys[i]
|
|
256
317
|
const elem = this.#states[key]
|
|
@@ -263,6 +324,10 @@ export class Button {
|
|
|
263
324
|
return false
|
|
264
325
|
}
|
|
265
326
|
}
|
|
327
|
+
//const uastate = elem.state
|
|
328
|
+
//if (uastate){
|
|
329
|
+
// await opcuainterface.Subscribe(uastate)
|
|
330
|
+
//}
|
|
266
331
|
}
|
|
267
332
|
}
|
|
268
333
|
|
|
@@ -283,7 +348,7 @@ export class Button {
|
|
|
283
348
|
this.timeStampPressed = Date.now()
|
|
284
349
|
|
|
285
350
|
this.#index++
|
|
286
|
-
this.#index
|
|
351
|
+
this.updateState(this.#index,"pressed")
|
|
287
352
|
return true
|
|
288
353
|
}
|
|
289
354
|
|
|
@@ -311,17 +376,65 @@ export class Button {
|
|
|
311
376
|
|
|
312
377
|
break
|
|
313
378
|
}
|
|
379
|
+
|
|
380
|
+
this.updateState(this.#index,"released")
|
|
381
|
+
|
|
382
|
+
return true // this.runCommand()
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
updateState(index,eventType){
|
|
386
|
+
this.#index = index
|
|
387
|
+
this.#event = eventType
|
|
388
|
+
// Update the State according to the correctly pressed state
|
|
389
|
+
if (this.#index < 0) { this.#index = this.#keys.length - 1 }
|
|
390
|
+
this.#index %= this.#keys.length
|
|
314
391
|
this.runCommand()
|
|
392
|
+
//console.log("TODO: expect newState", newState)
|
|
315
393
|
return true // this.runCommand()
|
|
316
394
|
}
|
|
317
395
|
|
|
318
396
|
async rotated (delta) {
|
|
319
397
|
if (!this.getCurrentElement()) { return false }
|
|
320
398
|
|
|
399
|
+
this.#event = "rotated"
|
|
321
400
|
this.#value = calcDelta(this.#value, delta, this.#max)
|
|
322
401
|
return this.runCommand()
|
|
323
402
|
}
|
|
324
403
|
|
|
404
|
+
async changed(buttonID,nodeid,val){
|
|
405
|
+
// Only handle updates within the same group identified by nodeid
|
|
406
|
+
if (nodeid !== this.#nodeid){
|
|
407
|
+
return
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
this.#index = 0;
|
|
411
|
+
for (let i = 0; i < this.#keys.length; i++) {
|
|
412
|
+
let key = this.#keys[i]
|
|
413
|
+
// check if the state-name is same as the value we get from outside:
|
|
414
|
+
if (val == key){
|
|
415
|
+
this.#index = i;
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// check if the nodeid is the same and the value is one of the states
|
|
420
|
+
let state = this.#states[key]
|
|
421
|
+
if (!state.value)
|
|
422
|
+
continue
|
|
423
|
+
|
|
424
|
+
const params = {
|
|
425
|
+
id: buttonID,
|
|
426
|
+
key: buttonID,
|
|
427
|
+
...state
|
|
428
|
+
}
|
|
429
|
+
let val1 = format(state.value,params)
|
|
430
|
+
if (val1 === val){
|
|
431
|
+
this.#index = i;
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
325
438
|
async touchmove (x, y) {
|
|
326
439
|
// if (!this.getCurrentElement()) { return false }
|
|
327
440
|
|
|
@@ -347,27 +460,54 @@ export class Button {
|
|
|
347
460
|
return false
|
|
348
461
|
}
|
|
349
462
|
|
|
350
|
-
runCommand () {
|
|
463
|
+
async runCommand () {
|
|
351
464
|
const elem = this.getCurrentElement()
|
|
352
|
-
if
|
|
465
|
+
// Only continue, if we have an element, that contains some kind of command:
|
|
466
|
+
if (!elem || (!elem.cmd && !elem.http && !elem.opcua)) {
|
|
353
467
|
return
|
|
354
468
|
}
|
|
355
|
-
//
|
|
469
|
+
// Filter for Event Type:
|
|
470
|
+
if (elem.filter && elem.filter != this.#event){
|
|
471
|
+
return
|
|
472
|
+
}
|
|
473
|
+
// Call an action - include dynamic parameters
|
|
474
|
+
// and also all attributes of elem + global config
|
|
356
475
|
const params = {
|
|
476
|
+
text: this.getCurrentText(),
|
|
477
|
+
...this.#config.parameters,
|
|
478
|
+
...elem,
|
|
357
479
|
id: this.id,
|
|
480
|
+
key: this.key,
|
|
481
|
+
event: this.#event,
|
|
358
482
|
state: this.#keys[this.#index],
|
|
359
483
|
min: this.#min,
|
|
360
484
|
max: this.#max,
|
|
361
|
-
value: this.#value
|
|
362
|
-
text: this.getCurrentText()
|
|
485
|
+
value: this.#value
|
|
363
486
|
}
|
|
364
|
-
/* const keys = Object.keys(this.#data.config)
|
|
365
|
-
keys.forEach(key => {
|
|
366
|
-
params[key] = this.#data.config[key]
|
|
367
|
-
}) */
|
|
368
487
|
|
|
369
|
-
|
|
488
|
+
let res = ''
|
|
489
|
+
if ('cmd' in elem) {
|
|
490
|
+
if (shellinterface){
|
|
491
|
+
res = await shellinterface.call(elem.cmd, params)
|
|
492
|
+
}else{
|
|
493
|
+
console.warn("shellinterface not started")
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
if ('http' in elem) {
|
|
497
|
+
if (httpinterface){
|
|
498
|
+
res = await httpinterface.call(elem.http, params)
|
|
499
|
+
}else{
|
|
500
|
+
console.warn("httpinterface not started")
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if ('opcua' in elem) {
|
|
504
|
+
if (opcuainterface){
|
|
505
|
+
res = await opcuainterface.call(elem.opcua, params)
|
|
506
|
+
}else{
|
|
507
|
+
console.warn("opcuainterface not started")
|
|
508
|
+
}
|
|
509
|
+
}
|
|
370
510
|
|
|
371
|
-
return
|
|
511
|
+
return res
|
|
372
512
|
}
|
|
373
513
|
}
|