{{ message }}
Repository navigation
Expand file tree
/
Copy pathGameController.js
More file actions
694 lines (547 loc) · 23.2 KB
/
Copy pathGameController.js
File metadata and controls
694 lines (547 loc) · 23.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
/****************************************************************************/
/* Name : GameController.js */
/* Function : Definition of a game controller */
/* Author : Alexandre Ribault, AlexandreRibault87@Gmail.com */
/* https://github.com/Alexandre-Rib */
/* Creation : 10/03/2024 */
/* Last update : 04/07/2024 */
/* Version : 0.9.9 */
/****************************************************************************/
/* */
/* DISPLAY OPTIMISED FOR NOTEPAD++ */
/* */
/****************************************************************************/
/*
Copyright (c) 2024 Alexandre Ribault
This file is part of the nodeJS API for game controller for Linux.
The nodeJS API for game controller for Linux is free library: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with the nodeJS API for game controller for Linux. If not, see <https://www.gnu.org/licenses/>
*/
/*
Based on works of :
- Ragnar Hojland Espinosa (Joystick API Documentation in C, no license identified)
https://www.kernel.org/doc/Documentation/input/joystick-api.txt
No Github account found
- Vojtech Pavlik (JSTEst.c, License GPL)
https://github.com/vojtech-pavlik?tab=repositories
- Jason White (Joystick.c, no license identified)
https://github.com/jasonwhite
- Linus Groh (xbox360controller python Lib, MIT License)
https://github.com/linusg/xbox360controller
Thank to them!
*/
/*
Known issue(s).
No issue know currently
*/
/*
Historic :
Version 0.9.9 : Creation of this file
*/
/**************************************************/
/* */
/* */
/* DEPENDENCIES */
/* */
/* */
/**************************************************/
import ioctl from 'ioctl-napi'
import fs from 'fs'
import EventEmitter from 'events'
import * as input from './input.js'
import * as ioctl_macros_and_constants from './ioctl_macros_and_constants.js'
import child_process from 'child_process';
/**************************************************/
/* */
/* Constants */
/* */
/**************************************************/
/** @const {Number} Index of the first value in version number's Buffer of driver version*/
const PRIMARY_driverVersion_NUMBER = 2
/** @const {Number} Index of the second value in version number's Buffer of driver version*/
const SECONDARY_driverVersion_NUMBER= 1
/** @const {Number} Index of the third value in version number's Buffer of driver version*/
const TERTIARY_driverVersion_NUMBER = 0
/** @const {Number} Number of bytes necessary to char*/
const NUMBER_OF_BYTES_IN_CHAR = 1
/** @const {Number} Size of the Buffer used to save controller Name */
const NAME_LENGTH = 128
/** @const {Number} Maximum value of unsigned integer of 16 bits */
const MAX_VALUE_UINT16 = 65535
/** @const {Number} Maximum value of signed integer of 16 bits */
const MAX_VALUE_INT16 = 32767
/** @const {Number} Maximum value of unsigned integer of 32 bits */
const MAX_VALUE_UINT32 = 4294967295
/** @const {Number} Number a bit in a byte.*/
const NUMBER_OF_BIT_IN_BYTE = 8
/** @const {Number} Number of bytes necessary to code an 64 bits unsigned integer. The syntax of the name is based on other Define syntax in original .c/.h file */
const uint64_t = 8
/** @const {Number} Number of bytes necessary to code an 32 bits unsigned integer. Name based on original .c/.h file */
const uint32_t = 4
/** @const {Number} Number of bytes necessary to code an 8 bits unsigned integer. Name based on original .c/.h file */
const __u8 = 1 // 1 byte is necessary to code an 8 bits unsigned integer
/** @const {Number} Number of bytes necessary to code an 16 bits unsigned integer. Name based on original .c/.h file */
const __u16 = 2
/** @const {Number} Bumber of bits necessary to code a char in C language */
const CHAR_BIT_SIZE = 8
//https://github.com/torvalds/linux/blob/master/include/uapi/linux/input.h#L453
/** @const {Number} Position of bit where the information "Has Rumble" is. Necessary to realize bit to bit operation on EVIOCGBIT operation's result */
const FF_RUMBLE = 0x50
// Constant from #Define in 'include/linux/joystick.h' file (after convertion MACROS integer)
/** @const {Number} Result of an IOCTL Macro necessary to get driver version */
const JSIOCGVERSION = ioctl_macros_and_constants._IOR('j', 0x01, uint32_t)
/** @const {Number} Result of an IOCTL Macro necessary to get the number of axes of a game controller */
const JSIOCGAXES = ioctl_macros_and_constants._IOR('j', 0x11, __u8)
/** @const {Number} Result of an IOCTL Macro necessary to get the number of buttons of a game controller */
const JSIOCGBUTTONS = ioctl_macros_and_constants._IOR('j', 0x12, __u8)
/** @const {Number} Result of an IOCTL Macro necessary to get the name of the game controller*/
const JSIOCGNAME = ioctl_macros_and_constants._IOC(ioctl_macros_and_constants._IOC_READ,'j', 0x13, NAME_LENGTH)
/** @const {Number} No documentation available in original .c/.h file */
const ABS_MAX = 0x3f
/** @const {Number} No documentation available in original .c/.h file */
const ABS_CNT = (ABS_MAX+1)
/** @const {Number} Result of an IOCTL Macro necessary to set axis mapping */
const JSIOCSAXMAP = ioctl_macros_and_constants._IOW('j', 0x31, __u8 * ABS_CNT)
/** @const {Number} Result of an IOCTL Macro necessary to get axis mapping */
const JSIOCGAXMAP = ioctl_macros_and_constants._IOR('j', 0x32, __u8 * ABS_CNT)
// Constants from '/include/uapi/linux/input-event-codes.h' file
const BTN_MISC = 0x100
/** @const {Number} No documentation available in original .c/.h file */
const KEY_MAX = 0x2ff
/** @const {Number} Result of an IOCTL Macro necessary to set Button mapping */
const JSIOCSBTNMAP = ioctl_macros_and_constants._IOW('j', 0x33, __u16 *(KEY_MAX - BTN_MISC + 1))
/** @const {Number} Result of an IOCTL Macro necessary to get Button mapping */
const JSIOCGBTNMAP = ioctl_macros_and_constants._IOR('j', 0x34, __u16 *(KEY_MAX - BTN_MISC + 1))
/* The following values come from include/joystick.h in the kernel source. */
/** @const {Number} No documentation available in original .c/.h file */
const KEY_MAX_SMALL = 0x1FF
/** @const {Number} Result of an IOCTL Macro necessary to set Button mapping */
const JSIOCSBTNMAP_SMALL = ioctl_macros_and_constants._IOW('j', 0x33, __u16 *(KEY_MAX_SMALL - BTN_MISC + 1))
/** @const {Number} Result of an IOCTL Macro necessary to get Button mapping */
const JSIOCGBTNMAP_SMALL = ioctl_macros_and_constants._IOR('j', 0x34, __u16 *(KEY_MAX_SMALL - BTN_MISC + 1))
/** @const {Number} Constant which permits to identify if a button is the event source*/
const JS_EVENT_BUTTON = 0x01
/** @const {Number} Constant which permits to identify if an axis is the event source*/
const JS_EVENT_AXIS = 0x02
/** @const {Number} Constant which permits to identify the game controller is the event source*/
const JS_EVENT_INIT = 0x80
/**************************************************/
/* */
/* FUNCTIONS */
/* */
/**************************************************/
/**************************************************/
/* Function :bufferToInt64 */
/**************************************************/
/**
* bufferToInt64 permit convert a Buffer to a int in 64 bit. Method not available in Buffer's class :-(
* @param {Buffer} Buffer ideally of maximum 8 bytes length. If buffer contains more than 8 bytes, they will be ignored
* @param {string} endianness Can only be "little' or 'big'
* @return {Number} value of a int on 64 bit
*/
function bufferToInt64(buffer, endianness){
if (typeof endianness == "undefined"){
endianness= 'little'
}
if (typeof endianness == "string"){
endianness.toLowerCase()
}else{
throw new Error(`endianness is not a string`)
}
const length = buffer.length > 8 ? 8 : buffer.length
var sum = 0
switch(endianness){
case 'little':
for (var i = 0 ; i< (buffer.length) ;i++){
sum += buffer[i] * 256 ** i
}
break
case 'big':
for (var i = 0 ; i< (buffer.length) ;i++){
sum += buffer[buffer.length-1-i] * 256 ** i
}
break
default:
throw new Error(`endianness value can only be 'big' or 'little'`)
}
return sum
}
/**************************************************/
/* Function :getEventFilePathByCharacterDevicePath*/
/**************************************************/
/**
* Permits to get the Event File path associated to the character device path ( They have not necessarily the same ID)
* @param {String} characterDevicePath ideally of maximum 8 bytes length. If buffer contains more than 8 bytes, they will be ignored
* @return {String} Path Event File of the associated
*/
function getEventFilePathByCharacterDevicePath(characterDevicePath){
const regexJSIdentifier = /.*\/(js\d+)/;
const regexFieldControllerName = / \d+:\d+\s*(.*)-joystick/;
const regexEventIdentifier = /.*\/(event\d+)/;
/* Parsing linux commands to get "eventY" corresponding to "jsX"*/
/* Y can be equal to X*/
var match = characterDevicePath.match(regexJSIdentifier);
var linuxCommandResultBuffer = child_process.execSync(`ls -l /dev/input/by-id | grep ${match[1]}`)
match = linuxCommandResultBuffer.toString().match(regexFieldControllerName);
linuxCommandResultBuffer = child_process.execSync(`ls -l /dev/input/by-id | grep ${match[1]}-event-joystick`)
match = linuxCommandResultBuffer.toString().match(regexEventIdentifier);
return `/dev/input/${match[1]}`
}
/**************************************************/
/* */
/* */
/* CLASSES */
/* */
/* */
/**************************************************/
export class GameController extends EventEmitter{
//the class constructor
/**
* constructor description
* @param {String} characterDevicePath Linux Path of the character device file as "/dev/input/js{{number}]"
* @param {Number } axisThresholdPercentage Number between 0 to 1 which the sensibility to the gameController object to rise event with Axis movement
* @param {String} JsonMappingFilePath path to a normalized JSON file which permits to help to determine . For for information see ??? ( Documentation currently not written)
*/
constructor(characterDevicePath,axisThresholdPercentage = 0.2, JsonMappingFilePath = null) {
super();
this.characterDevicePath = characterDevicePath
try {
this.fileDescriptor = fs.openSync(this.characterDevicePath, fs.constants.O_RDWR)
} catch (error) {
throw new Error(`Error opening ${this.characterDevicePath}. Check path and if controller is plugged.`)
return;
}
try {
this.mappingPath = JsonMappingFilePath
this.gameControllerMapping= JSON.parse(fs.readFileSync(this.mappingPath, { encoding: 'utf8', flag: 'r' }))
} catch (error) {
this.mappingPath = `(Not Valid Path ) ${JsonMappingFilePath}`
this.gameControllerMapping = null
}
this.eventFilePath = getEventFilePathByCharacterDevicePath(this.characterDevicePath)
this.eventFile = fs.openSync(this.eventFilePath, fs.constants.O_RDWR)
/*get driverVersion numbers*/
const bufferdriverVersionNumbers = new Buffer.alloc(uint64_t)
ioctl(this.fileDescriptor, JSIOCGVERSION, bufferdriverVersionNumbers)
this.driverVersion = `${bufferdriverVersionNumbers[PRIMARY_driverVersion_NUMBER]}.${bufferdriverVersionNumbers[SECONDARY_driverVersion_NUMBER]}.${bufferdriverVersionNumbers[TERTIARY_driverVersion_NUMBER]}`
/*get number of buttons on game controller */
var bufferButtonsNumber = new Buffer.alloc(uint64_t)
ioctl(this.fileDescriptor, JSIOCGBUTTONS ,bufferButtonsNumber);
this.buttonsNumber = bufferToInt64(bufferButtonsNumber)
/*get number of axes on game controller */
var bufferAxesNumber = new Buffer.alloc(uint64_t)
ioctl(this.fileDescriptor, JSIOCGAXES ,bufferAxesNumber);
this.axesNumber = bufferToInt64(bufferAxesNumber)
/*get name of the game controller */
var bufferControllerName = new Buffer.alloc(NAME_LENGTH)
ioctl(this.fileDescriptor, JSIOCGNAME ,bufferControllerName);
var lastCharPosition = 0
for (var i = 0 ; i< (bufferControllerName.length) ;i++){
if (bufferControllerName[i] == 0x00){
break
}
lastCharPosition++
}
this.controllerName = Buffer.copyBytesFrom(bufferControllerName, 0, lastCharPosition).toString()
fs.closeSync(this.fileDescriptor)
this.ButtonsList = Array(this.buttonsNumber)
this.AxesList = Array(this.axesNumber)
if (this.gameControllerMapping!= null){
for(let i =0; i< this.ButtonsList.length; i++){
this.ButtonsList[i] = new Button(i,this.gameControllerMapping.buttons[`_${i}`] != null ? this.gameControllerMapping.buttons[`_${i}`] : `button_${i}`)
}
for(let i =0; i< this.AxesList.length; i++){
this.AxesList[i] = new Axis(i,this.gameControllerMapping.axes[`_${i}`] != null ? this.gameControllerMapping.axes[`_${i}`] : `axis_${i}`)
}
} else {
for(let i =0; i< this.ButtonsList.length; i++){
this.ButtonsList[i] = new Button(i,`button_${i}`)
}
for(let i =0; i< this.AxesList.length; i++){
this.AxesList[i]= new Axis(i,`Axis_${i}`)
}
}
this.lastEvent ={
time :0,
value :0,
type :0,
elementID :0
};
this.hasRumble = this.checkRumbleAvailability()
this._ff_id = -1
this.setAxisThresholdPercentage(axisThresholdPercentage)
/*async Method which reads continuously the character device file and parse event from driver written in the file*/
this.readControllerEventDynamically()
}
/**
* Method which permits to send IOCTL commands to controller's driver, in order if the controller is able to rumble
*
* @returns {boolean} return True if controller is able to rumble
*/
checkRumbleAvailability(){
const Number_Of_UInt64_For_Buffer = 4
var bufferHasFF = new Buffer.alloc(Number_Of_UInt64_For_Buffer*uint64_t) //->
// Questions the driver to know if controllers has 'Force Feedback' feature
let has_ff = input.EVIOCGBIT(input.EV_FF, CHAR_BIT_SIZE * Number_Of_UInt64_For_Buffer )
/* Send request to driver, return -1 if error */
/* Driver answers is saved in 'bufferHasFF" */
if (ioctl(this.eventFile, has_ff, bufferHasFF) == -1){
return false
}
/* Extract the second Int in Buffer*/
const orderedBufferHasFF = Buffer.copyBytesFrom(bufferHasFF, 8, uint64_t); // 8 for 8th Bytes in Buffer
//operation to send the bit which permits to know if the controller can rumble,to the LSB position. Then erases all others bits
if (bufferToInt64(orderedBufferHasFF) >> FF_RUMBLE %(uint64_t * NUMBER_OF_BIT_IN_BYTE) & 0x1 === 1)
return true
else
return false
}
/**
* Permits to dinamically change axis event rising sensibility
* @param {Number} threshold Percentage threshold. Number between 0 and 1
**/
setAxisThresholdPercentage(threshold){
if (typeof threshold == 'number'){
if (threshold> 1) {
threshold = 1
}
if (threshold< 0) {
threshold = 0
}
this.axisThresholdPercentage= threshold
}else{
throw new Error ("The parameter 'axisThresholdPercentage' is not a number")
}
}
/**
* Method which permits to send IOCTL commands to controller's driver, in order if the controller is able to rumble
* @param {Number} leftMotorPercentage Intensity of rumble of Right motor in %
* @param {Number} rightMotorPercentage Intensity of rumble of Right motor in %
* @param {Number} duration Duration of Rumble in milliseconds
* No return Value
*/
rumble(leftMotorPercentage, rightMotorPercentage, duration){
if (this.hasRumble){
if (leftMotorPercentage > 1){
leftMotorPercentage =1
}
if (leftMotorPercentage < 0){
leftMotorPercentage =0
}
if (rightMotorPercentage > 1){
rightMotorPercentage =1
}
if (rightMotorPercentage < 0){
rightMotorPercentage =0
}
duration= Math.abs(duration)
var left_abs = Math.abs(Math.trunc(leftMotorPercentage*MAX_VALUE_UINT16))
var right_abs = Math.abs(Math.trunc(rightMotorPercentage*MAX_VALUE_UINT16))
var stop = input.input_event(input.EV_FF, this._ff_id, 0)
if (fs.writeFileSync(this.eventFilePath, stop)==-1){
return false
}
const effect = input.ff_effect(FF_RUMBLE, -1, duration, 0, left_abs, right_abs)
try{
ioctl(this.eventFile, input.EVIOCSFF, effect)
}catch{
this._ff_id = -1
fs.closeSync(this.eventFile)
this.eventFile = fs.openSync(this.eventFilePath, fs.constants.O_RDWR)
return this.rumble(leftMotorPercentage, rightMotorPercentage, duration)
}
this._ff_id= bufferToInt64(effect.subarray(1, 3), 'big')
var play = input.input_event(input.EV_FF, this._ff_id, 1)
if (fs.writeFileSync(this.eventFilePath, play)==-1){
return false
}
return true
}
}
/**
* parsed driver event
*/
eventReading(newEvent){
const eventsInByteArray= Array.from(Buffer.from(newEvent))
this.lastEvent.time = eventsInByteArray[3]*256^3 + eventsInByteArray[2]*256^2 + eventsInByteArray[1]*256^1 +eventsInByteArray[0]*256^0//u32
this.lastEvent.value = eventsInByteArray[5]* +256 + eventsInByteArray[4] //s16
// NodeJs use 32 bit signed integer.
// We have to realize a kind of conversion to have a Uint16 number representation :
if (this.lastEvent.value> MAX_VALUE_INT16){
this.lastEvent.value = (this.lastEvent.value | 0xFFFF0000)
this.lastEvent.value = (this.lastEvent.value & 0xFFFF7FFF) + MAX_VALUE_INT16 + 1
}
this.lastEvent.elementID = eventsInByteArray[7]//u8
this.lastEvent.type = eventsInByteArray[6]//u8
}
/**
* Rise JS event after having memorized button state
*/
buttonManagement(){
const button = this.lastEvent.elementID
this.ButtonsList[button].setState(this.lastEvent.value >= 1 ? true : false)
this.emit('GameControllerEvent', this);
}
/**
* Rise JS event after having memorized axis state
*/
axeManagement(){
const axis = this.lastEvent.elementID
this.AxesList[axis].value = this.lastEvent.value/ MAX_VALUE_INT16 // convert to % percentage
this.emit('GameControllerEvent', this);
}
/**
* Test Method to display controller state
*/
async readControllerEventDynamically(){
const readStream = fs.createReadStream(this.characterDevicePath);
for await(const newEvent of readStream) {
this.eventReading(newEvent)
switch (this.lastEvent.type) {
case JS_EVENT_BUTTON:
this.buttonManagement()
break
case JS_EVENT_AXIS:
this.axeManagement()
break
}
}
}
/**
* @return {Object} State of all controller's buttons and axis . The JSON objects depends on JsonMappingFilePath JSON's structure
*/
getControllerState(){
const controllerState ={
"buttons" :{},
"axes" :{}
}
for(let i =0; i< this.ButtonsList.length; i++){
controllerState.buttons[`${this.ButtonsList[i].name}`] = this.ButtonsList[i].state
}
for(let i =0; i< this.AxesList.length; i++){
controllerState.axes[`${this.AxesList[i].name}`] = this.AxesList[i].value
}
return controllerState
}
/**
* @override
* @return {String} return a string with mains data from controller
*/
toString(){
var canRumble
if (this.hasRumble){
canRumble = "Controller is able to rumble"
}else{
canRumble = "Controller can not rumble"
}
return `The controller ${this.controllerName}. Its driver is in version ${this.driverVersion}. It has ${this.buttonsNumber} buttons and ${this.axesNumber} axes.${canRumble}`
}
/**
* @override
* @return {Object} return a JSON Object with mains data from controller
*/
toJSON(){
return {
controllerName : this.controllerName,
characterDevicePath : this.characterDevicePath,
eventFilePath : this.eventFilePath,
driverVersion : this.driverVersion,
JSONmappingPath : this.mappingPath,
buttonsNumber : this.buttonsNumber,
axesNumber : this.axesNumber,
hasRumble : this.hasRumble,
lastEvent : this.lastEvent
}
}
}
class Button {
constructor(ID, name="") {
if (ID == null) {
throw new Error('ID not defined');
}
if (typeof ID != 'number'){
throw new Error('ID is not number');
}
if (name != null){
this.name = name
}
this.ID = ID
this.state = false
}
setState(state){
if (typeof state == 'boolean'){
this.state = state
}
}
getState(){
return this.state
}
rename(newName){
this.setName(newName)
}
setName(name){
if (typeof name == 'string'){
this.name = newName
}
}
/**
* @override
* @return {Object} return a string with mains data from button
*/
toString(){
}
/**
* @override
* @return {Object} return a JSON Object with mains data from button
*/
toJSON(){
return {
}
}
}
class Axis {
constructor(ID, name="") {
if (ID == null) {
throw new Error('ID not defined');
}
if (typeof ID != 'number'){
throw new Error('ID is not number');
}
if (name != null){
this.name = name
}
this.ID = ID
this.value = 0
}
setValue(value){
if (typeof value == 'number'){
this.value = value
}
}
getValue(){
return this.value
}
rename(newName){
this.setName(newName)
}
setName(name){
if (typeof name == 'string'){
this.name = newName
}
}
/**
* @override
* @return {Object} return a string with mains data from button
*/
toString(){
}
/**
* @override
* @return {Object} return a JSON Object with mains data from button
*/
toJSON(){
return {
}
}
}
You can’t perform that action at this time.
