-
Notifications
You must be signed in to change notification settings - Fork 1
/
space-driver.js
111 lines (93 loc) · 3.18 KB
/
space-driver.js
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
// @ts-check
const deviceFilter = { vendorId: 0x046d };
const requestParams = { filters: [deviceFilter] };
// SpaceDriver
// Tested with Spaceball 5000 (USB) and SpaceExplorer
// but should work with other devices in the same range.
//
// Reads x, y, z, rx, ry, rz and buttons
// Dispatches to listeners via CustomEvents
//
// TODO:
// - Support multiple devices connected in parallel
// - Support sending settings (e.g. control LEDs) to devices.
export const SpaceDriver = new class extends EventTarget {
#device // Just allow one device, for now
constructor() {
super();
this.handleInputReport = this.handleInputReport.bind(this);
// See if a paired device is already connected
navigator.hid.getDevices().then((devices) => {
devices.filter(d => d.vendorId === deviceFilter.vendorId).forEach(this.openDevice.bind(this));
});
navigator.hid.addEventListener('disconnect', evt => {
const device = evt.device;
console.log('disconnected', device);
if (device === this.#device) {
this.disconnect();
}
});
}
openDevice(device) {
this.disconnect(); // If another device is connected - close it
device.open().then(() => {
console.log('Opened device: ' + device.productName);
device.addEventListener('inputreport', this.handleInputReport);
this.#device = device;
this.dispatchEvent(new CustomEvent('connect', {detail: { device }}));
});
}
disconnect() {
this.#device?.close();
this.#device = undefined;
this.dispatchEvent(new Event('disconnect'));
}
scan() {
navigator.hid.requestDevice(requestParams).then(devices => {
if (devices.length == 0) return;
this.openDevice(devices[0]);
});
}
handleInputReport(e) {
switch(e.reportId) {
case 1: // x, y, z
this.handleTranslation(new Int16Array(e.data.buffer));
break;
case 2: // yaw, pitch, roll
this.handleRotation(new Int16Array(e.data.buffer));
break;
case 3: // buttons
this.handleButtons(new Uint16Array(e.data.buffer)[0]);
break;
}
}
handleTranslation(val) {
this.dispatchEvent(new CustomEvent('translate', {
detail: {
x: val[0],
y: val[1],
z: val[2]
}
}));
}
handleRotation(val) {
this.dispatchEvent(new CustomEvent('rotate', {
detail: {
rx: -val[0],
ry: -val[1],
rz: val[2]
}
}));
}
handleButtons(val) {
const buttons = [];
for(let i=0;i<16;i++) {
if(val & (1<<i)) buttons.push(`[${(i+1).toString(16).toUpperCase()}]`);
}
this.dispatchEvent(new CustomEvent('buttons', {
detail: { buttons }
}));
}
}
// TODO: Add to class logic when working (note: 'connect' doesn't seem to work properly, Linux/Chrome 89)
navigator.hid.addEventListener('connect', evt => console.log('connect', evt.device));