@thalaguer/buzzer 1.0.0
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/LICENSE +21 -0
- package/Readme.md +198 -0
- package/index.js +3 -0
- package/package.json +52 -0
- package/src/Buzzer.js +507 -0
- package/src/config.js +14 -0
- package/src/constants.js +51 -0
- package/src/error/CustomError.js +20 -0
- package/src/event.js +15 -0
- package/src/utils.js +20 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thalaguer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/Readme.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# @thalaguer/buzzer
|
|
2
|
+
|
|
3
|
+
**The easiest way to control your Buzz! wireless controllers in Node.js**
|
|
4
|
+
Light up the LEDs, detect every button press, and bring back the PS2 game show vibes — with zero hassle. 🔔🔥
|
|
5
|
+
|
|
6
|
+
> **Note**: This library supports the **wired** Buzz! controllers (with USB dongle), not the wireless versions.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- ✅ Automatic detection and initialization
|
|
11
|
+
- ✅ Real-time button press/release events
|
|
12
|
+
- ✅ Individual LED control for up to 4 players
|
|
13
|
+
- ✅ Clean event-based API
|
|
14
|
+
- ✅ Cross-platform support (Windows, macOS, Linux)
|
|
15
|
+
- ✅ Built-in startup LED animation
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
This package depends on `node-hid` which requires native compilation.
|
|
20
|
+
|
|
21
|
+
**Recommended installation command:**
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @thalaguer/buzzer --ignore-scripts
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
import Buzzer from '@thalaguer/buzzer';
|
|
31
|
+
|
|
32
|
+
const buzzers = Buzzer();
|
|
33
|
+
|
|
34
|
+
// Called when the system is fully initialized
|
|
35
|
+
buzzers.onReady(() => {
|
|
36
|
+
console.log("Buzzers are ready & functional.");
|
|
37
|
+
|
|
38
|
+
// Turn on player 1's LED
|
|
39
|
+
buzzers.setLeds(true, false, false, false);
|
|
40
|
+
|
|
41
|
+
// Or using array syntax
|
|
42
|
+
buzzers.setLedsarray([true, false, true, false]); // Players 1 & 3 on
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Button press events
|
|
46
|
+
buzzers.onPress((data) => {
|
|
47
|
+
console.log(`Buzzer #${data.controller} pressed the ${data.color} button(${data.button}).`);
|
|
48
|
+
console.log(`Full event: ${JSON.stringify(data)}`);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Button release events
|
|
52
|
+
buzzers.onRelease((data) => {
|
|
53
|
+
console.log(`Buzzer #${data.controller} released the ${data.color} button.`);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Any state change (press or release)
|
|
57
|
+
buzzers.onChange((data) => {
|
|
58
|
+
console.log(`Buzzer #${data.controller} ${data.state} the ${data.color} button.`);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
buzzers.onError((data) => {
|
|
62
|
+
console.log(`An error occurred : ${data.message}`);
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// Clean up when done (optional)
|
|
66
|
+
// await buzzers.close();
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## API Reference
|
|
70
|
+
|
|
71
|
+
### `Buzzer()`
|
|
72
|
+
Creates a new buzzer manager instance.
|
|
73
|
+
|
|
74
|
+
### `onReady(callback)`
|
|
75
|
+
Registers a callback when the system is fully initialized. The startup LED animation will play when ready.
|
|
76
|
+
|
|
77
|
+
### `onPress(callback)`
|
|
78
|
+
Registers a callback for button press events. The callback receives an event object with:
|
|
79
|
+
- `controller`: Player number (1-4)
|
|
80
|
+
- `color`: Button color ('RED', 'BLUE', 'ORANGE', 'GREEN', 'YELLOW')
|
|
81
|
+
- `button`: Button identifier ( 0=> red, 1=> blue, 2=> orange, 3=> green, 4=> yellow)
|
|
82
|
+
- `state`: Event state ('press', 'released') - mostly useful for `onChange(callback)`.
|
|
83
|
+
### `onRelease(callback)`
|
|
84
|
+
Registers a callback for button release events (same event object structure as `onPress`).
|
|
85
|
+
|
|
86
|
+
### `onChange(callback)`
|
|
87
|
+
Registers a callback for any button state change (both press and release).
|
|
88
|
+
|
|
89
|
+
### `onError(callback)`
|
|
90
|
+
Registers a callback for any error happening.
|
|
91
|
+
|
|
92
|
+
### `setLeds(player1, player2, player3, player4)`
|
|
93
|
+
Controls the red LEDs for each player:
|
|
94
|
+
- `player1` - Player 1 LED (boolean)
|
|
95
|
+
- `player2` - Player 2 LED (boolean)
|
|
96
|
+
- `player3` - Player 3 LED (boolean)
|
|
97
|
+
- `player4` - Player 4 LED (boolean)
|
|
98
|
+
|
|
99
|
+
### `setLedsarray([player1, player2, player3, player4])`
|
|
100
|
+
Convenience method that accepts an array of boolean values.
|
|
101
|
+
|
|
102
|
+
### `close()`
|
|
103
|
+
Closes connections and releases resources. Returns a Promise.
|
|
104
|
+
|
|
105
|
+
## Windows Driver Setup (ZADIG)
|
|
106
|
+
|
|
107
|
+
On Windows, the Buzz! dongle might not work with the default driver. You need to install the WinUSB driver using ZADIG:
|
|
108
|
+
|
|
109
|
+
1. **Download ZADIG** from https://zadig.akeo.ie/
|
|
110
|
+
|
|
111
|
+
2. **Run ZADIG** as Administrator
|
|
112
|
+
|
|
113
|
+
3. **Configure ZADIG**:
|
|
114
|
+
- Go to `Options` → `List All Devices`
|
|
115
|
+
- Check both `Ignore Hubs or Composite Parents` and `Show only current configuration`
|
|
116
|
+
|
|
117
|
+
4. **Select the Buzz! Dongle**:
|
|
118
|
+
- From the dropdown, select the Buzz! device (should appear as "Buzz!" or with VID `054C` and PID `1000`)
|
|
119
|
+
- If you don't see it, try:
|
|
120
|
+
- Unplugging and replugging the dongle
|
|
121
|
+
- Checking if it appears under a different name
|
|
122
|
+
- Trying with and without controllers connected
|
|
123
|
+
|
|
124
|
+
5. **Install/Replace Driver**:
|
|
125
|
+
- Ensure the driver selected is `WinUSB` (not libusb)
|
|
126
|
+
- Click `Replace Driver` or `Install Driver`
|
|
127
|
+
- Wait for the installation to complete
|
|
128
|
+
|
|
129
|
+
6. **Test**:
|
|
130
|
+
- Unplug and replug the dongle
|
|
131
|
+
- Run your application again
|
|
132
|
+
|
|
133
|
+
**Note**: If you have multiple Buzz! dongles, repeat these steps for each one.
|
|
134
|
+
|
|
135
|
+
## Linux Permissions
|
|
136
|
+
|
|
137
|
+
On Linux, you may need to add a udev rule or run with `sudo`:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# Temporary solution (run with sudo)
|
|
141
|
+
sudo node your-app.js
|
|
142
|
+
|
|
143
|
+
# Permanent solution (create udev rule):
|
|
144
|
+
sudo nano /etc/udev/rules.d/99-buzz.rules
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Add this line:
|
|
148
|
+
```
|
|
149
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="054c", ATTR{idProduct}=="1000", MODE="0666"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Then reload udev rules:
|
|
153
|
+
```bash
|
|
154
|
+
sudo udevadm control --reload-rules
|
|
155
|
+
sudo udevadm trigger
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Troubleshooting
|
|
159
|
+
|
|
160
|
+
### Dongle not found
|
|
161
|
+
1. Check the USB connection
|
|
162
|
+
2. Verify the driver is installed (Windows: ZADIG)
|
|
163
|
+
3. Check if another application is using the device
|
|
164
|
+
4. Try a different USB port
|
|
165
|
+
|
|
166
|
+
### Different VID/PID
|
|
167
|
+
Most Buzz! dongles use `VID: 0x054c` and `PID: 0x1000`. If yours is different:
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
// In your node_modules/@thalaguer/buzzer/src/config.js
|
|
171
|
+
export const VID = 0x054c; // ← change to your VID
|
|
172
|
+
export const PID = 0x1000; // ← change to your PID
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
To find your dongle's VID/PID:
|
|
176
|
+
- **Windows**: Device Manager → Properties → Details → Hardware IDs
|
|
177
|
+
- **Linux**: `lsusb` command
|
|
178
|
+
- **macOS**: System Information → USB
|
|
179
|
+
|
|
180
|
+
### No button events
|
|
181
|
+
1. Ensure controllers are connected to the dongle (press any button to pair)
|
|
182
|
+
2. Check that the dongle LED is solid (not blinking)
|
|
183
|
+
3. Verify your event listeners are set up before initialization completes
|
|
184
|
+
|
|
185
|
+
## Supported Controllers
|
|
186
|
+
|
|
187
|
+
This library supports the **wireless** Buzz! controllers that come with a USB dongle. Each dongle supports up to 4 controllers.
|
|
188
|
+
|
|
189
|
+
**Known compatible models:**
|
|
190
|
+
- Buzz! Buzzers Wireless (PS2/PS3/PC)
|
|
191
|
+
- Most Buzz! controllers with model number "BUZZ001" or similar
|
|
192
|
+
|
|
193
|
+
**Not tested:**
|
|
194
|
+
- Buzz! Wireless controllers
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
This project is licensed under the **MIT License** — see the [LICENSE](LICENSE) file for details.
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thalaguer/buzzer",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Node.js driver for Sony Buzz! wireless controllers (PS2/PS3 quiz buzzers) using HID/USB",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18.0.0"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"node-hid": "^3.2.0",
|
|
15
|
+
"usb": "^2.16.0"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"buzz",
|
|
19
|
+
"buzzer",
|
|
20
|
+
"ps2",
|
|
21
|
+
"ps3",
|
|
22
|
+
"playstation",
|
|
23
|
+
"hid",
|
|
24
|
+
"usb",
|
|
25
|
+
"node-hid",
|
|
26
|
+
"game-controller",
|
|
27
|
+
"quiz-controller"
|
|
28
|
+
],
|
|
29
|
+
"author": "Thalaguer",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/thalaguer/buzzer.git"
|
|
34
|
+
},
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/thalaguer/buzzer/issues"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/thalaguer/buzzer#readme",
|
|
39
|
+
"files": [
|
|
40
|
+
"index.js",
|
|
41
|
+
"src/**/*.js",
|
|
42
|
+
"!**/*.test.js",
|
|
43
|
+
"!**/__tests__/**",
|
|
44
|
+
"README.md",
|
|
45
|
+
"LICENSE"
|
|
46
|
+
],
|
|
47
|
+
"scripts": {
|
|
48
|
+
"test": "echo \"No tests yet – coming soon!\" && exit 0",
|
|
49
|
+
"prepublishOnly": "npm run lint && npm run test",
|
|
50
|
+
"lint": "echo \"Add eslint/prettier later\" && exit 0"
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/Buzzer.js
ADDED
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
// ──────────────────────────────────────────────────────────────
|
|
2
|
+
// Error handling
|
|
3
|
+
// ──────────────────────────────────────────────────────────────
|
|
4
|
+
const isDebug = process.env.DEBUG === 'true' || process.argv.includes('--debug');
|
|
5
|
+
|
|
6
|
+
process.on('uncaughtException', (error) => {
|
|
7
|
+
if (isDebug) {
|
|
8
|
+
console.error(colors.red + colors.bright + '┌────────────────────────────────────────────────────────────┐' + colors.reset);
|
|
9
|
+
console.error(colors.red + colors.bright + '│ CRITICAL: Uncaught EXCEPTION (synchronous) │' + colors.reset);
|
|
10
|
+
console.error(colors.red + colors.bright + '└────────────────────────────────────────────────────────────┘' + colors.reset);
|
|
11
|
+
console.error(colors.yellow + 'Message:' + colors.reset, error.message || error);
|
|
12
|
+
console.error(colors.yellow + 'Stack:' + colors.reset, error.stack || error);
|
|
13
|
+
console.error(colors.dim + `Time: ${new Date().toISOString()}` + colors.reset);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
18
|
+
if (isDebug) {
|
|
19
|
+
console.error(colors.magenta + colors.bright + '┌────────────────────────────────────────────────────────────┐' + colors.reset);
|
|
20
|
+
console.error(colors.magenta + colors.bright + '│ UNHANDLED PROMISE REJECTION │' + colors.reset);
|
|
21
|
+
console.error(colors.magenta + colors.bright + '└────────────────────────────────────────────────────────────┘' + colors.reset);
|
|
22
|
+
const error = reason instanceof Error ? reason : new Error(String(reason));
|
|
23
|
+
if (error instanceof CustomError) {
|
|
24
|
+
console.error(colors.yellow + 'Message:' + colors.reset, `[${error.timestamp}] ${error.name}: ${error.message}`);
|
|
25
|
+
console.error(colors.yellow + 'Details:' + colors.reset, error.details || 'none');
|
|
26
|
+
console.error(colors.yellow + 'Stack:' + colors.reset, error.stack);
|
|
27
|
+
} else {
|
|
28
|
+
console.error(colors.yellow + 'Unexpected rejection:' + colors.reset, error.message);
|
|
29
|
+
console.error(error.stack || error);
|
|
30
|
+
}
|
|
31
|
+
console.error(colors.dim + `Time: ${new Date().toISOString()}` + colors.reset);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
import { findByIds } from "usb";
|
|
36
|
+
import { EventEmitter } from 'node:events';
|
|
37
|
+
import HID from 'node-hid';
|
|
38
|
+
|
|
39
|
+
import { VID, PID, prefix } from './config.js';
|
|
40
|
+
import { BUTTONS_DATA, colors } from "./constants.js";
|
|
41
|
+
import { EVENTS } from "./event.js";
|
|
42
|
+
import { formatEvent } from "./utils.js";
|
|
43
|
+
import { CustomError } from "./error/CustomError.js";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Creates and manages a Buzz! controller dongle instance.
|
|
47
|
+
*
|
|
48
|
+
* Handles connection to the Buzz! wireless dongle, button event detection,
|
|
49
|
+
* LED control, and provides an event-based API for button interactions.
|
|
50
|
+
*
|
|
51
|
+
* @returns {Object} Buzzer manager object with public methods.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* const buzz = Buzzer();
|
|
55
|
+
*
|
|
56
|
+
* // Wait for initialization
|
|
57
|
+
* buzz.onReady(() => {
|
|
58
|
+
* console.log('Buzzers ready!');
|
|
59
|
+
* buzz.setLeds(true, false, false, false); // Turn on player 1 LED
|
|
60
|
+
* });
|
|
61
|
+
*
|
|
62
|
+
* // Listen for button events
|
|
63
|
+
* buzz.onPress((event) => {
|
|
64
|
+
* console.log(`Button pressed: ${event.button} on controller ${event.controller}`);
|
|
65
|
+
* });
|
|
66
|
+
*
|
|
67
|
+
* // Clean up when done
|
|
68
|
+
* // await buzz.close();
|
|
69
|
+
*/
|
|
70
|
+
export default function Buzzer() {
|
|
71
|
+
const ee = new EventEmitter();
|
|
72
|
+
|
|
73
|
+
let device = null;
|
|
74
|
+
let iface = null;
|
|
75
|
+
let endpoint = null;
|
|
76
|
+
let hidDevice = null;
|
|
77
|
+
let currentStates = new Array(BUTTONS_DATA.length).fill(false);
|
|
78
|
+
|
|
79
|
+
// "Ready" state management
|
|
80
|
+
let isReady = false;
|
|
81
|
+
let readyPromise = null;
|
|
82
|
+
|
|
83
|
+
// ──────────────────────────────────────────────────────────────
|
|
84
|
+
// USB device management
|
|
85
|
+
// ──────────────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Opens the USB device with the specified VID/PID and claims interface 0.
|
|
89
|
+
*
|
|
90
|
+
* This function:
|
|
91
|
+
* - Searches for the Buzz! dongle using VID and PID
|
|
92
|
+
* - Opens the device
|
|
93
|
+
* - Gets interface 0
|
|
94
|
+
* - Detaches kernel driver if active (Linux/macOS only)
|
|
95
|
+
* - Claims the interface for exclusive access
|
|
96
|
+
*
|
|
97
|
+
* @async
|
|
98
|
+
* @returns {Promise<{device: import('usb').Device, iface: import('usb').Interface}>}
|
|
99
|
+
* Object containing the opened device and claimed interface
|
|
100
|
+
* @throws {CustomError} If the device is not found
|
|
101
|
+
* @throws {CustomError} If interface 0 is not available
|
|
102
|
+
* @throws {CustomError} If any other USB operation fails (open, claim, etc.)
|
|
103
|
+
*/
|
|
104
|
+
async function openAndClaim() {
|
|
105
|
+
try {
|
|
106
|
+
device = findByIds(VID, PID);
|
|
107
|
+
if (!device) {
|
|
108
|
+
const errMsg = "Dongle not found. Check connection and VID/PID.";
|
|
109
|
+
ee.emit(EVENTS.ERROR, errMsg);
|
|
110
|
+
throw new CustomError(errMsg);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
device.open();
|
|
114
|
+
|
|
115
|
+
iface = device.interface(0);
|
|
116
|
+
if (!iface) {
|
|
117
|
+
const errMsg = "Interface 0 not found";
|
|
118
|
+
ee.emit(EVENTS.ERROR, errMsg);
|
|
119
|
+
throw new CustomError(errMsg);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (process.platform !== 'win32' && iface.isKernelDriverActive?.()) {
|
|
123
|
+
iface.detachKernelDriver();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
iface.claim();
|
|
127
|
+
|
|
128
|
+
return { device, iface };
|
|
129
|
+
} catch (err) {
|
|
130
|
+
const errMsg = "Failed to open the connection to the dongle.";
|
|
131
|
+
ee.emit(EVENTS.ERROR, errMsg);
|
|
132
|
+
throw new CustomError(errMsg, { cause: err });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Finds and returns the interrupt IN endpoint from the given USB interface.
|
|
138
|
+
*
|
|
139
|
+
* This function searches through all endpoints of the interface for one that:
|
|
140
|
+
* - Has direction 'in' (device → host)
|
|
141
|
+
* - Uses interrupt transfer type (transferType === 3)
|
|
142
|
+
*
|
|
143
|
+
* If no suitable endpoint is found, it throws an error with a detailed list
|
|
144
|
+
* of all available endpoints for debugging purposes.
|
|
145
|
+
*
|
|
146
|
+
* @param {import('usb').Interface} iface - The USB interface to search endpoints in
|
|
147
|
+
* @returns {import('usb').Endpoint} The interrupt IN endpoint found
|
|
148
|
+
* @throws {CustomError} If no interrupt IN endpoint is found.
|
|
149
|
+
* The error message includes a formatted list of all available endpoints.
|
|
150
|
+
*/
|
|
151
|
+
function findInterruptEndpoint(iface) {
|
|
152
|
+
const endpoint = iface.endpoints.find(ep =>
|
|
153
|
+
ep.direction === 'in' && ep.transferType === 3
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
if (!endpoint) {
|
|
157
|
+
const errMsg = "No INTERRUPT IN endpoint found!\n" +
|
|
158
|
+
"Available endpoints:\n" +
|
|
159
|
+
iface.endpoints.map(e => ` - ${e.direction} ${e.transferType} (addr 0x${e.address.toString(16)})`).join("\n");
|
|
160
|
+
ee.emit(EVENTS.ERROR, errMsg);
|
|
161
|
+
throw new CustomError(errMsg);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return endpoint;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ──────────────────────────────────────────────────────────────
|
|
168
|
+
// LEDs
|
|
169
|
+
// ──────────────────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Internal LED control without initialization check.
|
|
173
|
+
* Used during boot sequence to avoid circular dependencies.
|
|
174
|
+
*
|
|
175
|
+
* @private
|
|
176
|
+
* @param {boolean} [player1=false] - Turn on the LED for player 1
|
|
177
|
+
* @param {boolean} [player2=false] - Turn on the LED for player 2
|
|
178
|
+
* @param {boolean} [player3=false] - Turn on the LED for player 3
|
|
179
|
+
* @param {boolean} [player4=false] - Turn on the LED for player 4
|
|
180
|
+
* @returns {void}
|
|
181
|
+
*/
|
|
182
|
+
function privateSetLeds(player1 = false, player2 = false, player3 = false, player4 = false) {
|
|
183
|
+
if (!hidDevice) {
|
|
184
|
+
console.warn(`${prefix} HID device not available`);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const report = Buffer.alloc(8);
|
|
189
|
+
report[0] = 0x00;
|
|
190
|
+
report[1] = 0x00;
|
|
191
|
+
report[2] = player1 ? 0xFF : 0x00;
|
|
192
|
+
report[3] = player2 ? 0xFF : 0x00;
|
|
193
|
+
report[4] = player3 ? 0xFF : 0x00;
|
|
194
|
+
report[5] = player4 ? 0xFF : 0x00;
|
|
195
|
+
report[6] = 0x00;
|
|
196
|
+
report[7] = 0x00;
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
hidDevice.write(report);
|
|
200
|
+
} catch (err) {
|
|
201
|
+
const shortReport = Buffer.from([
|
|
202
|
+
0x00, 0x00,
|
|
203
|
+
player1 ? 0xFF : 0x00,
|
|
204
|
+
player2 ? 0xFF : 0x00,
|
|
205
|
+
player3 ? 0xFF : 0x00,
|
|
206
|
+
player4 ? 0xFF : 0x00
|
|
207
|
+
]);
|
|
208
|
+
try {
|
|
209
|
+
hidDevice.write(shortReport);
|
|
210
|
+
} catch (innerErr) {
|
|
211
|
+
ee.emit(EVENTS.ERROR, "Failed to set LEDs");
|
|
212
|
+
if (isDebug) {
|
|
213
|
+
console.error(`${prefix} LED set error:`, innerErr.message);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Controls the LEDs on the Buzz! controllers.
|
|
221
|
+
*
|
|
222
|
+
* Ensures the system is ready before setting LEDs.
|
|
223
|
+
*
|
|
224
|
+
* @param {boolean} [player1=false] - Turn on LED for player 1
|
|
225
|
+
* @param {boolean} [player2=false] - Turn on LED for player 2
|
|
226
|
+
* @param {boolean} [player3=false] - Turn on LED for player 3
|
|
227
|
+
* @param {boolean} [player4=false] - Turn on LED for player 4
|
|
228
|
+
* @returns {Promise<void>}
|
|
229
|
+
*/
|
|
230
|
+
async function setLeds(player1 = false, player2 = false, player3 = false, player4 = false) {
|
|
231
|
+
await ensureReady();
|
|
232
|
+
privateSetLeds(player1, player2, player3, player4);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Controls the LEDs using an array of boolean values.
|
|
237
|
+
*
|
|
238
|
+
* @param {boolean[]} players - Array of 4 booleans for players 1-4
|
|
239
|
+
* @returns {Promise<void>}
|
|
240
|
+
* @throws {CustomError} If array length is not 4
|
|
241
|
+
*/
|
|
242
|
+
async function setLedsarray(players) {
|
|
243
|
+
if (!Array.isArray(players) || players.length !== 4) {
|
|
244
|
+
throw new CustomError("Invalid array: must be boolean[4]");
|
|
245
|
+
}
|
|
246
|
+
await setLeds(...players);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ──────────────────────────────────────────────────────────────
|
|
250
|
+
// Setup
|
|
251
|
+
// ──────────────────────────────────────────────────────────────
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Sets up the listener for Buzz! controller events.
|
|
255
|
+
*
|
|
256
|
+
* Initializes USB connection, HID device, starts polling for button data,
|
|
257
|
+
* and emits events on button press/release.
|
|
258
|
+
*
|
|
259
|
+
* @async
|
|
260
|
+
* @returns {Promise<void>}
|
|
261
|
+
* @throws {CustomError} If setup fails
|
|
262
|
+
* @fires press When a button is pressed (payload: button object)
|
|
263
|
+
* @fires release When a button is released (payload: button object)
|
|
264
|
+
* @fires error When a critical error occurs during setup
|
|
265
|
+
*/
|
|
266
|
+
async function setupBuzzListener() {
|
|
267
|
+
try {
|
|
268
|
+
const result = await openAndClaim();
|
|
269
|
+
device = result.device;
|
|
270
|
+
iface = result.iface;
|
|
271
|
+
|
|
272
|
+
hidDevice = new HID.HID(VID, PID);
|
|
273
|
+
|
|
274
|
+
endpoint = findInterruptEndpoint(iface);
|
|
275
|
+
|
|
276
|
+
endpoint.startPoll(3, endpoint.descriptor.wMaxPacketSize);
|
|
277
|
+
|
|
278
|
+
endpoint.on('data', (data) => {
|
|
279
|
+
if (data.length < 5) return;
|
|
280
|
+
|
|
281
|
+
const previousStates = [...currentStates];
|
|
282
|
+
const pressedBits = data.readUInt16LE(2) | (data.readUInt8(4) << 16);
|
|
283
|
+
|
|
284
|
+
BUTTONS_DATA.forEach((button, index) => {
|
|
285
|
+
const isPressedNow = (pressedBits & button.bit) !== 0;
|
|
286
|
+
const wasPressed = previousStates[index];
|
|
287
|
+
|
|
288
|
+
currentStates[index] = isPressedNow;
|
|
289
|
+
|
|
290
|
+
if (isPressedNow !== wasPressed) {
|
|
291
|
+
const eventType = isPressedNow ? EVENTS.PRESS : EVENTS.RELEASE;
|
|
292
|
+
ee.emit(eventType, { ...button, type: eventType });
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
if(isDebug) console.log(colors.magenta + colors.bright + `${prefix} Listening started` + colors.reset);
|
|
298
|
+
} catch (err) {
|
|
299
|
+
throw new CustomError("Buzzers setup failed", { cause: err });
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ──────────────────────────────────────────────────────────────
|
|
304
|
+
// Events methods
|
|
305
|
+
// ──────────────────────────────────────────────────────────────
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Internal function to ensure the buzzer system is ready.
|
|
309
|
+
* Initializes the system if not already initialized.
|
|
310
|
+
*
|
|
311
|
+
* @private
|
|
312
|
+
* @async
|
|
313
|
+
* @returns {Promise<void>}
|
|
314
|
+
* @throws {CustomError} If initialization fails
|
|
315
|
+
*/
|
|
316
|
+
async function ensureReady() {
|
|
317
|
+
if (isReady) return;
|
|
318
|
+
if (readyPromise) return readyPromise;
|
|
319
|
+
|
|
320
|
+
readyPromise = setupBuzzListener()
|
|
321
|
+
.then(() => {
|
|
322
|
+
const cycles = 3;
|
|
323
|
+
const onTime = 350; // clearly visible
|
|
324
|
+
const offTime = 200;
|
|
325
|
+
|
|
326
|
+
return new Promise((resolve) => {
|
|
327
|
+
let delay = 0;
|
|
328
|
+
for (let i = 0; i < cycles; i++) {
|
|
329
|
+
setTimeout(() => {
|
|
330
|
+
privateSetLeds(true, true, true, true);
|
|
331
|
+
}, delay);
|
|
332
|
+
delay += onTime;
|
|
333
|
+
|
|
334
|
+
setTimeout(() => {
|
|
335
|
+
privateSetLeds(false, false, false, false);
|
|
336
|
+
}, delay);
|
|
337
|
+
delay += offTime;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
setTimeout(() => {
|
|
341
|
+
isReady = true;
|
|
342
|
+
if(isDebug) console.log(colors.yellow + colors.bright + `${prefix} Buzzers are ready` + colors.reset);
|
|
343
|
+
ee.emit(EVENTS.READY);
|
|
344
|
+
resolve();
|
|
345
|
+
}, delay + 200);
|
|
346
|
+
});
|
|
347
|
+
})
|
|
348
|
+
.catch(err => {
|
|
349
|
+
const customError = new CustomError(err.message || "Setup failed", { cause: err });
|
|
350
|
+
ee.emit(EVENTS.ERROR, customError);
|
|
351
|
+
throw customError;
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
return readyPromise;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Registers a callback to be called when the buzzer system is fully initialized.
|
|
359
|
+
*
|
|
360
|
+
* @param {function} callback - Function called when system is ready
|
|
361
|
+
* @returns {void}
|
|
362
|
+
*/
|
|
363
|
+
function onReady(callback) {
|
|
364
|
+
ee.on(EVENTS.READY, callback);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Registers a callback to be called when any buzzer button is pressed.
|
|
369
|
+
*
|
|
370
|
+
* The callback receives a simplified event object with the following properties:
|
|
371
|
+
* - `controller`: The controller number (1 to 4)
|
|
372
|
+
* - `button`: The button name (e.g., 'RED', 'BLUE', etc.)
|
|
373
|
+
* - `Name`: The full button identifier (e.g., 'C1_RED', 'C2_BLUE')
|
|
374
|
+
*
|
|
375
|
+
* Returns a cleanup function to remove the listener.
|
|
376
|
+
*
|
|
377
|
+
* @param {function(Object): void} callback - Function called when a button is pressed
|
|
378
|
+
* @returns {function(): void} Cleanup function to remove the listener
|
|
379
|
+
*/
|
|
380
|
+
function onPress(callback) {
|
|
381
|
+
ensureReady().catch((err) => {
|
|
382
|
+
ee.emit(EVENTS.ERROR, err);
|
|
383
|
+
});
|
|
384
|
+
const handler = (originalEvent) => {
|
|
385
|
+
callback(formatEvent(originalEvent, EVENTS.PRESS));
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
ee.on(EVENTS.PRESS, handler);
|
|
389
|
+
return () => ee.off(EVENTS.PRESS, handler);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Registers a callback to be called when any buzzer button is released.
|
|
394
|
+
*
|
|
395
|
+
* The callback receives a simplified event object with the following properties:
|
|
396
|
+
* - `controller`: The controller number (1 to 4)
|
|
397
|
+
* - `button`: The button name (e.g., 'RED', 'BLUE', etc.)
|
|
398
|
+
* - `Name`: The full button identifier (e.g., 'C1_RED', 'C2_BLUE')
|
|
399
|
+
*
|
|
400
|
+
* Returns a cleanup function to remove the listener.
|
|
401
|
+
*
|
|
402
|
+
* @param {function(Object): void} callback - Function called when a button is released
|
|
403
|
+
* @returns {function(): void} Cleanup function to remove the listener
|
|
404
|
+
*/
|
|
405
|
+
function onRelease(callback) {
|
|
406
|
+
ensureReady().catch((err) => {
|
|
407
|
+
ee.emit(EVENTS.ERROR, err);
|
|
408
|
+
});
|
|
409
|
+
const handler = (originalEvent) => {
|
|
410
|
+
callback(formatEvent(originalEvent, EVENTS.RELEASE));
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
ee.on(EVENTS.RELEASE, handler);
|
|
414
|
+
return () => ee.off(EVENTS.RELEASE, handler);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Registers a callback to be called whenever a button state changes
|
|
419
|
+
* (either pressed or released).
|
|
420
|
+
*
|
|
421
|
+
* The callback receives a simplified event object with the following properties:
|
|
422
|
+
* - `controller`: The controller number (1 to 4)
|
|
423
|
+
* - `button`: The button name (e.g., 'RED', 'BLUE', etc.)
|
|
424
|
+
* - `Name`: The full button identifier (e.g., 'C1_RED', 'C2_BLUE')
|
|
425
|
+
*
|
|
426
|
+
* Returns a cleanup function to remove the listener.
|
|
427
|
+
*
|
|
428
|
+
* @param {function(Object): void} callback - Function called on any button state change
|
|
429
|
+
* @returns {function(): void} Cleanup function to remove the listener
|
|
430
|
+
*/
|
|
431
|
+
function onChange(callback) {
|
|
432
|
+
ensureReady().catch((err) => {
|
|
433
|
+
ee.emit(EVENTS.ERROR, err);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
const handler = (originalEvent) => {
|
|
437
|
+
callback(formatEvent(originalEvent, originalEvent.type));
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
ee.on(EVENTS.PRESS, handler);
|
|
441
|
+
ee.on(EVENTS.RELEASE, handler);
|
|
442
|
+
|
|
443
|
+
return () => {
|
|
444
|
+
ee.off(EVENTS.PRESS, handler);
|
|
445
|
+
ee.off(EVENTS.RELEASE, handler);
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Registers a callback to be called when an error occurs.
|
|
451
|
+
*
|
|
452
|
+
* @param {function(CustomError|string): void} callback - Function called on error
|
|
453
|
+
* @returns {function(): void} Cleanup function to remove the listener
|
|
454
|
+
*/
|
|
455
|
+
function onError(callback) {
|
|
456
|
+
ensureReady().catch((err) => {
|
|
457
|
+
callback(err);
|
|
458
|
+
});
|
|
459
|
+
ee.on(EVENTS.ERROR, callback);
|
|
460
|
+
return () => ee.off(EVENTS.ERROR, callback);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Closes all connections and releases resources.
|
|
465
|
+
* Should be called when the application is done using the buzzer system.
|
|
466
|
+
*
|
|
467
|
+
* @async
|
|
468
|
+
* @returns {Promise<void>}
|
|
469
|
+
*/
|
|
470
|
+
async function close() {
|
|
471
|
+
return new Promise((resolve) => {
|
|
472
|
+
try {
|
|
473
|
+
if (endpoint) endpoint.stopPoll();
|
|
474
|
+
if (iface) {
|
|
475
|
+
iface.release(true, () => {
|
|
476
|
+
if (device) device.close();
|
|
477
|
+
if (hidDevice) hidDevice.close();
|
|
478
|
+
isReady = false;
|
|
479
|
+
readyPromise = null;
|
|
480
|
+
if(isDebug) console.log(`${prefix} Resources released`);
|
|
481
|
+
resolve();
|
|
482
|
+
});
|
|
483
|
+
} else {
|
|
484
|
+
resolve();
|
|
485
|
+
}
|
|
486
|
+
} catch (err) {
|
|
487
|
+
console.warn(`${prefix} Close error:`, err.message);
|
|
488
|
+
resolve();
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// ──────────────────────────────────────────────────────────────
|
|
494
|
+
// public API
|
|
495
|
+
// ──────────────────────────────────────────────────────────────
|
|
496
|
+
|
|
497
|
+
return {
|
|
498
|
+
setLeds,
|
|
499
|
+
setLedsarray,
|
|
500
|
+
onReady,
|
|
501
|
+
onPress,
|
|
502
|
+
onRelease,
|
|
503
|
+
onChange,
|
|
504
|
+
onError,
|
|
505
|
+
close
|
|
506
|
+
};
|
|
507
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration constants for the Buzz! controller dongle.
|
|
3
|
+
*
|
|
4
|
+
* @module config
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** USB Vendor ID for the Buzz! dongle */
|
|
8
|
+
export const VID = 0x054c;
|
|
9
|
+
|
|
10
|
+
/** USB Product ID for the Buzz! dongle */
|
|
11
|
+
export const PID = 0x1000;
|
|
12
|
+
|
|
13
|
+
/** Prefix used for logging messages */
|
|
14
|
+
export const prefix = '[Buzzer]';
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constants for button data and console colors.
|
|
3
|
+
*
|
|
4
|
+
* @module constants
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Array of button configurations for all controllers */
|
|
8
|
+
export const BUTTONS_DATA = [
|
|
9
|
+
// Controller 1
|
|
10
|
+
{ name: 'C1_RED', color: 'red', controller: 1, button: 0, bit: 0x000001 },
|
|
11
|
+
{ name: 'C1_BLUE', color: 'blue', controller: 1, button: 1, bit: 0x000010 },
|
|
12
|
+
{ name: 'C1_ORANGE', color: 'orange', controller: 1, button: 2, bit: 0x000008 },
|
|
13
|
+
{ name: 'C1_GREEN', color: 'green', controller: 1, button: 3, bit: 0x000004 },
|
|
14
|
+
{ name: 'C1_YELLOW', color: 'yellow', controller: 1, button: 4, bit: 0x000002 },
|
|
15
|
+
|
|
16
|
+
// Controller 2
|
|
17
|
+
{ name: 'C2_RED', color: 'red', controller: 2, button: 0, bit: 0x000020 },
|
|
18
|
+
{ name: 'C2_BLUE', color: 'blue', controller: 2, button: 1, bit: 0x000200 },
|
|
19
|
+
{ name: 'C2_ORANGE', color: 'orange', controller: 2, button: 2, bit: 0x000100 },
|
|
20
|
+
{ name: 'C2_GREEN', color: 'green', controller: 2, button: 3, bit: 0x000080 },
|
|
21
|
+
{ name: 'C2_YELLOW', color: 'yellow', controller: 2, button: 4, bit: 0x000040 },
|
|
22
|
+
|
|
23
|
+
// Controller 3
|
|
24
|
+
{ name: 'C3_RED', color: 'red', controller: 3, button: 0, bit: 0x000400 },
|
|
25
|
+
{ name: 'C3_BLUE', color: 'blue', controller: 3, button: 1, bit: 0x004000 },
|
|
26
|
+
{ name: 'C3_ORANGE', color: 'orange', controller: 3, button: 2, bit: 0x002000 },
|
|
27
|
+
{ name: 'C3_GREEN', color: 'green', controller: 3, button: 3, bit: 0x001000 },
|
|
28
|
+
{ name: 'C3_YELLOW', color: 'yellow', controller: 3, button: 4, bit: 0x000800 },
|
|
29
|
+
|
|
30
|
+
// Controller 4
|
|
31
|
+
{ name: 'C4_RED', color: 'red', controller: 4, button: 0, bit: 0x008000 },
|
|
32
|
+
{ name: 'C4_BLUE', color: 'blue', controller: 4, button: 1, bit: 0x080000 },
|
|
33
|
+
{ name: 'C4_ORANGE', color: 'orange', controller: 4, button: 2, bit: 0x040000 },
|
|
34
|
+
{ name: 'C4_GREEN', color: 'green', controller: 4, button: 3, bit: 0x020000 },
|
|
35
|
+
{ name: 'C4_YELLOW', color: 'yellow', controller: 4, button: 4, bit: 0x010000 },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
/** ANSI color codes for console logging */
|
|
39
|
+
export const colors = {
|
|
40
|
+
reset: '\x1b[0m',
|
|
41
|
+
bright: '\x1b[1m',
|
|
42
|
+
dim: '\x1b[2m',
|
|
43
|
+
red: '\x1b[31m',
|
|
44
|
+
green: '\x1b[32m',
|
|
45
|
+
yellow: '\x1b[33m',
|
|
46
|
+
blue: '\x1b[34m',
|
|
47
|
+
magenta: '\x1b[35m',
|
|
48
|
+
cyan: '\x1b[36m',
|
|
49
|
+
white: '\x1b[37m',
|
|
50
|
+
bgRed: '\x1b[41m',
|
|
51
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error class that extends the built-in Error class.
|
|
3
|
+
* Includes additional details and a timestamp.
|
|
4
|
+
*
|
|
5
|
+
* @module CustomError
|
|
6
|
+
*/
|
|
7
|
+
export class CustomError extends Error {
|
|
8
|
+
/**
|
|
9
|
+
* Creates a new CustomError instance.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} message - The error message.
|
|
12
|
+
* @param {Object} [details={}] - Additional details about the error.
|
|
13
|
+
*/
|
|
14
|
+
constructor(message, details = {}) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'CustomError';
|
|
17
|
+
this.details = details;
|
|
18
|
+
this.timestamp = new Date().toISOString();
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/event.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event types for buzzer interactions.
|
|
3
|
+
*
|
|
4
|
+
* @module event
|
|
5
|
+
*/
|
|
6
|
+
export const EVENTS = {
|
|
7
|
+
/** Event emitted when a button is pressed */
|
|
8
|
+
PRESS: 'press',
|
|
9
|
+
/** Event emitted when a button is released */
|
|
10
|
+
RELEASE: 'release',
|
|
11
|
+
/** Event emitted when the buzzer system is ready */
|
|
12
|
+
READY: 'ready',
|
|
13
|
+
/** Event emitted when an error occurs */
|
|
14
|
+
ERROR: 'error'
|
|
15
|
+
};
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { EVENTS } from "./event.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Formats the original button event object into a simplified structure.
|
|
5
|
+
*
|
|
6
|
+
* @param {Object} original - The original event object from the endpoint data.
|
|
7
|
+
* @param {string} eventType - The type of event (PRESS or RELEASE).
|
|
8
|
+
* @returns {Object} Formatted event object with controller, button, name, color, pressed, and state.
|
|
9
|
+
*/
|
|
10
|
+
export function formatEvent(original, eventType) {
|
|
11
|
+
const isPressed = eventType === EVENTS.PRESS;
|
|
12
|
+
return {
|
|
13
|
+
controller: original.controller,
|
|
14
|
+
button: original.button,
|
|
15
|
+
Name: original.name,
|
|
16
|
+
color: original.color,
|
|
17
|
+
pressed: isPressed,
|
|
18
|
+
state: isPressed ? 'pressed' : 'released'
|
|
19
|
+
};
|
|
20
|
+
}
|