dualsense-ts 2.2.48 → 3.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/README.md +9 -2
- package/dist/comparators.d.ts +13 -0
- package/dist/comparators.d.ts.map +1 -0
- package/dist/comparators.js +27 -0
- package/dist/comparators.js.map +1 -0
- package/dist/dualsense.d.ts +10 -1
- package/dist/dualsense.d.ts.map +1 -1
- package/dist/dualsense.js +46 -45
- package/dist/dualsense.js.map +1 -1
- package/dist/elements/analog.d.ts +1 -1
- package/dist/elements/analog.d.ts.map +1 -1
- package/dist/elements/analog.js +13 -3
- package/dist/elements/analog.js.map +1 -1
- package/dist/elements/dpad.d.ts +1 -1
- package/dist/elements/dpad.d.ts.map +1 -1
- package/dist/elements/dpad.js +6 -5
- package/dist/elements/dpad.js.map +1 -1
- package/dist/elements/touch.d.ts +1 -1
- package/dist/elements/touch.d.ts.map +1 -1
- package/dist/elements/touch.js.map +1 -1
- package/dist/elements/touchpad.d.ts +1 -1
- package/dist/elements/touchpad.d.ts.map +1 -1
- package/dist/elements/touchpad.js.map +1 -1
- package/dist/elements/trigger.d.ts.map +1 -1
- package/dist/elements/trigger.js +1 -1
- package/dist/elements/trigger.js.map +1 -1
- package/dist/elements/unisense.d.ts +1 -1
- package/dist/elements/unisense.d.ts.map +1 -1
- package/dist/elements/unisense.js +12 -5
- package/dist/elements/unisense.js.map +1 -1
- package/dist/hid/dualsense_hid.d.ts +16 -58
- package/dist/hid/dualsense_hid.d.ts.map +1 -1
- package/dist/hid/dualsense_hid.js +25 -101
- package/dist/hid/dualsense_hid.js.map +1 -1
- package/dist/hid/hid_provider.d.ts +86 -0
- package/dist/hid/hid_provider.d.ts.map +1 -0
- package/dist/hid/hid_provider.js +45 -0
- package/dist/hid/hid_provider.js.map +1 -0
- package/dist/hid/index.d.ts +4 -1
- package/dist/hid/index.d.ts.map +1 -1
- package/dist/hid/index.js +4 -1
- package/dist/hid/index.js.map +1 -1
- package/dist/hid/node_hid_provider.d.ts +11 -0
- package/dist/hid/node_hid_provider.d.ts.map +1 -0
- package/dist/hid/node_hid_provider.js +97 -0
- package/dist/hid/node_hid_provider.js.map +1 -0
- package/dist/hid/platform_hid_provider.d.ts +4 -0
- package/dist/hid/platform_hid_provider.d.ts.map +1 -0
- package/dist/hid/platform_hid_provider.js +7 -0
- package/dist/hid/platform_hid_provider.js.map +1 -0
- package/dist/hid/web_hid_provider.d.ts +10 -0
- package/dist/hid/web_hid_provider.d.ts.map +1 -0
- package/dist/hid/web_hid_provider.js +102 -0
- package/dist/hid/web_hid_provider.js.map +1 -0
- package/dist/{hid/ids.d.ts → id.d.ts} +6 -2
- package/dist/id.d.ts.map +1 -0
- package/dist/{hid/ids.js → id.js} +1 -1
- package/dist/id.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/input.d.ts +63 -45
- package/dist/input.d.ts.map +1 -1
- package/dist/input.js +123 -90
- package/dist/input.js.map +1 -1
- package/package.json +14 -8
- package/src/comparators.ts +26 -0
- package/src/dualsense.ts +61 -58
- package/src/elements/analog.ts +14 -8
- package/src/elements/dpad.ts +7 -6
- package/src/elements/touch.ts +1 -1
- package/src/elements/touchpad.ts +1 -1
- package/src/elements/trigger.ts +1 -1
- package/src/elements/unisense.ts +16 -15
- package/src/hid/dualsense_hid.ts +25 -156
- package/src/hid/{dualsense_hid.spec.ts → hid_provider.spec.ts} +1 -1
- package/src/hid/hid_provider.ts +100 -0
- package/src/hid/index.ts +4 -1
- package/src/hid/node_hid_provider.ts +108 -0
- package/src/hid/platform_hid_provider.ts +4 -0
- package/src/hid/web_hid_provider.ts +116 -0
- package/src/{hid/ids.ts → id.ts} +6 -1
- package/src/index.ts +2 -0
- package/src/input.ts +156 -138
- package/src/readme.spec.ts +6 -8
- package/webpack.config.js +42 -0
- package/dist/hid/ids.d.ts.map +0 -1
- package/dist/hid/ids.js.map +0 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input state change checker that always returns true.
|
|
3
|
+
*/
|
|
4
|
+
export function VirtualComparator(): boolean {
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Input state change checker that considers a numeric threshold.
|
|
10
|
+
*/
|
|
11
|
+
export function ThresholdComparator(
|
|
12
|
+
threshold: number,
|
|
13
|
+
state: unknown,
|
|
14
|
+
newState: unknown
|
|
15
|
+
): boolean {
|
|
16
|
+
if (typeof state !== "number" || typeof newState !== "number")
|
|
17
|
+
throw new Error("Bad threshold comparison");
|
|
18
|
+
return Math.abs(state - newState) > threshold;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Input state change checker for most values.
|
|
23
|
+
*/
|
|
24
|
+
export function BasicComparator(state: unknown, newState: unknown): boolean {
|
|
25
|
+
return state !== newState;
|
|
26
|
+
}
|
package/src/dualsense.ts
CHANGED
|
@@ -8,12 +8,19 @@ import {
|
|
|
8
8
|
Touchpad,
|
|
9
9
|
} from "./elements";
|
|
10
10
|
import { Input, InputSet, InputParams } from "./input";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
DualsenseHIDState,
|
|
13
|
+
DualsenseHID,
|
|
14
|
+
PlatformHIDProvider,
|
|
15
|
+
InputId,
|
|
16
|
+
} from "./hid";
|
|
12
17
|
|
|
13
18
|
export interface DualsenseParams extends InputParams {
|
|
19
|
+
/**
|
|
20
|
+
* Sets the source of HID events for the controller interface.
|
|
21
|
+
*/
|
|
14
22
|
hid?: DualsenseHID | null;
|
|
15
23
|
|
|
16
|
-
// Input param overrides
|
|
17
24
|
ps?: InputParams;
|
|
18
25
|
mute?: InputParams;
|
|
19
26
|
options?: InputParams;
|
|
@@ -28,6 +35,9 @@ export interface DualsenseParams extends InputParams {
|
|
|
28
35
|
touchpad?: InputParams;
|
|
29
36
|
}
|
|
30
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Represents a Dualsense controller.
|
|
40
|
+
*/
|
|
31
41
|
export class Dualsense extends Input<Dualsense> {
|
|
32
42
|
public readonly state: Dualsense = this;
|
|
33
43
|
|
|
@@ -49,12 +59,11 @@ export class Dualsense extends Input<Dualsense> {
|
|
|
49
59
|
|
|
50
60
|
public readonly touchpad: Touchpad;
|
|
51
61
|
|
|
52
|
-
public readonly hid: DualsenseHID
|
|
62
|
+
public readonly hid: DualsenseHID;
|
|
53
63
|
|
|
54
64
|
public get active(): boolean {
|
|
55
65
|
return Object.values(this).some(
|
|
56
|
-
(input
|
|
57
|
-
input instanceof Input && input !== this && input.active
|
|
66
|
+
(input) => input !== this && input instanceof Input && input.active
|
|
58
67
|
);
|
|
59
68
|
}
|
|
60
69
|
|
|
@@ -122,61 +131,55 @@ export class Dualsense extends Input<Dualsense> {
|
|
|
122
131
|
...(params.touchpad || {}),
|
|
123
132
|
});
|
|
124
133
|
|
|
125
|
-
|
|
126
|
-
|
|
134
|
+
this.hid = params.hid || new DualsenseHID(new PlatformHIDProvider());
|
|
135
|
+
this.hid.register((state: DualsenseHIDState) => {
|
|
136
|
+
this.processHID(state);
|
|
137
|
+
});
|
|
127
138
|
|
|
128
|
-
if (this.hid)
|
|
129
|
-
this.hid.on("input", () => {
|
|
130
|
-
this.processHID();
|
|
131
|
-
});
|
|
132
|
-
}
|
|
139
|
+
if (params.hid !== null) this.hid.provider.connect();
|
|
133
140
|
}
|
|
134
141
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
this.
|
|
140
|
-
|
|
141
|
-
this.
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
this.
|
|
145
|
-
|
|
146
|
-
this.
|
|
147
|
-
this.
|
|
148
|
-
|
|
149
|
-
this.
|
|
150
|
-
|
|
151
|
-
this.dpad.
|
|
152
|
-
this.dpad.
|
|
153
|
-
|
|
154
|
-
this.
|
|
155
|
-
|
|
156
|
-
this.touchpad.
|
|
157
|
-
this.touchpad.left.
|
|
158
|
-
this.touchpad.left.
|
|
159
|
-
this.touchpad.
|
|
160
|
-
this.touchpad.
|
|
161
|
-
this.touchpad.right.
|
|
162
|
-
|
|
163
|
-
);
|
|
164
|
-
this.touchpad.right.tracker[InputSet](
|
|
165
|
-
|
|
166
|
-
this.left.analog.x[InputSet](
|
|
167
|
-
this.left.analog.y[InputSet](
|
|
168
|
-
this.left.bumper[InputSet](
|
|
169
|
-
this.left.trigger[InputSet](
|
|
170
|
-
this.left.trigger.button[InputSet](
|
|
171
|
-
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
this.right.
|
|
175
|
-
this.right.
|
|
176
|
-
this.right.
|
|
177
|
-
this.right.trigger[InputSet](this.hid.state[InputId.RightTrigger]);
|
|
178
|
-
this.right.trigger.button[InputSet](
|
|
179
|
-
this.hid.state[InputId.RightTriggerButton]
|
|
180
|
-
);
|
|
142
|
+
/**
|
|
143
|
+
* Distributes input values to various elements.
|
|
144
|
+
*/
|
|
145
|
+
private processHID(state: DualsenseHIDState): void {
|
|
146
|
+
this.ps[InputSet](state[InputId.Playstation]);
|
|
147
|
+
this.options[InputSet](state[InputId.Options]);
|
|
148
|
+
this.create[InputSet](state[InputId.Create]);
|
|
149
|
+
|
|
150
|
+
this.mute[InputSet](state[InputId.Mute]);
|
|
151
|
+
this.mute.status[InputSet](state[InputId.Status]);
|
|
152
|
+
|
|
153
|
+
this.triangle[InputSet](state[InputId.Triangle]);
|
|
154
|
+
this.circle[InputSet](state[InputId.Circle]);
|
|
155
|
+
this.cross[InputSet](state[InputId.Cross]);
|
|
156
|
+
this.square[InputSet](state[InputId.Square]);
|
|
157
|
+
|
|
158
|
+
this.dpad.up[InputSet](state[InputId.Up]);
|
|
159
|
+
this.dpad.down[InputSet](state[InputId.Down]);
|
|
160
|
+
this.dpad.right[InputSet](state[InputId.Right]);
|
|
161
|
+
this.dpad.left[InputSet](state[InputId.Left]);
|
|
162
|
+
|
|
163
|
+
this.touchpad.button[InputSet](state[InputId.TouchButton]);
|
|
164
|
+
this.touchpad.left.x[InputSet](state[InputId.TouchX0]);
|
|
165
|
+
this.touchpad.left.y[InputSet](state[InputId.TouchY0]);
|
|
166
|
+
this.touchpad.left.contact[InputSet](state[InputId.TouchContact0]);
|
|
167
|
+
this.touchpad.left.tracker[InputSet](state[InputId.TouchId0]);
|
|
168
|
+
this.touchpad.right.x[InputSet](state[InputId.TouchX1]);
|
|
169
|
+
this.touchpad.right.y[InputSet](state[InputId.TouchY1]);
|
|
170
|
+
this.touchpad.right.contact[InputSet](state[InputId.TouchContact1]);
|
|
171
|
+
this.touchpad.right.tracker[InputSet](state[InputId.TouchId1]);
|
|
172
|
+
|
|
173
|
+
this.left.analog.x[InputSet](state[InputId.LeftAnalogX]);
|
|
174
|
+
this.left.analog.y[InputSet](state[InputId.LeftAnalogY]);
|
|
175
|
+
this.left.bumper[InputSet](state[InputId.LeftBumper]);
|
|
176
|
+
this.left.trigger[InputSet](state[InputId.LeftTrigger]);
|
|
177
|
+
this.left.trigger.button[InputSet](state[InputId.LeftTriggerButton]);
|
|
178
|
+
|
|
179
|
+
this.right.analog.x[InputSet](state[InputId.RightAnalogX]);
|
|
180
|
+
this.right.analog.y[InputSet](state[InputId.RightAnalogY]);
|
|
181
|
+
this.right.bumper[InputSet](state[InputId.RightBumper]);
|
|
182
|
+
this.right.trigger[InputSet](state[InputId.RightTrigger]);
|
|
183
|
+
this.right.trigger.button[InputSet](state[InputId.RightTriggerButton]);
|
|
181
184
|
}
|
|
182
185
|
}
|
package/src/elements/analog.ts
CHANGED
|
@@ -22,7 +22,7 @@ export interface AnalogParams extends InputParams {
|
|
|
22
22
|
* - Pushed all the way down and to the left, the stick's coordinates are [-1, -1]
|
|
23
23
|
*/
|
|
24
24
|
export class Analog extends Input<Analog> {
|
|
25
|
-
public readonly state:
|
|
25
|
+
public readonly state: this = this;
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* The left/right position of the input.
|
|
@@ -41,13 +41,19 @@ export class Analog extends Input<Analog> {
|
|
|
41
41
|
super(params);
|
|
42
42
|
const { button, x, y, threshold } = params || {};
|
|
43
43
|
|
|
44
|
-
this.button = new Momentary(
|
|
45
|
-
this.x = new Axis(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
);
|
|
44
|
+
this.button = new Momentary({ icon: "3", name: "Button", ...button });
|
|
45
|
+
this.x = new Axis({
|
|
46
|
+
icon: "↔",
|
|
47
|
+
name: "X",
|
|
48
|
+
threshold: threshold || 0.01,
|
|
49
|
+
...x,
|
|
50
|
+
});
|
|
51
|
+
this.y = new Axis({
|
|
52
|
+
icon: "↕",
|
|
53
|
+
name: "Y",
|
|
54
|
+
threshold: threshold || 0.01,
|
|
55
|
+
...y,
|
|
56
|
+
});
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
/**
|
package/src/elements/dpad.ts
CHANGED
|
@@ -9,20 +9,21 @@ export interface DpadParams extends InputParams {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export class Dpad extends Input<Dpad> {
|
|
12
|
-
public readonly state:
|
|
12
|
+
public readonly state: this = this;
|
|
13
13
|
|
|
14
14
|
public readonly up: Momentary;
|
|
15
15
|
public readonly down: Momentary;
|
|
16
16
|
public readonly left: Momentary;
|
|
17
17
|
public readonly right: Momentary;
|
|
18
18
|
|
|
19
|
-
constructor(params
|
|
19
|
+
constructor(params: DpadParams = {}) {
|
|
20
20
|
super(params);
|
|
21
|
+
const { up, down, left, right } = params
|
|
21
22
|
|
|
22
|
-
this.up = new Momentary(params?.up || { icon: "⮉", name: "Up" });
|
|
23
|
-
this.down = new Momentary(params?.down || { icon: "⮋", name: "Down" });
|
|
24
|
-
this.left = new Momentary(params?.left || { icon: "⮈", name: "Left" });
|
|
25
|
-
this.right = new Momentary(params?.right || { icon: "⮊", name: "Right" });
|
|
23
|
+
this.up = new Momentary(params?.up || { icon: "⮉", name: "Up", ...up});
|
|
24
|
+
this.down = new Momentary(params?.down || { icon: "⮋", name: "Down", ...down});
|
|
25
|
+
this.left = new Momentary(params?.left || { icon: "⮈", name: "Left", ...left });
|
|
26
|
+
this.right = new Momentary(params?.right || { icon: "⮊", name: "Right", ...right });
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
public get active(): boolean {
|
package/src/elements/touch.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { Increment } from "./increment";
|
|
|
6
6
|
* with [0,0] representing the center of the touchpad.
|
|
7
7
|
*/
|
|
8
8
|
export class Touch extends Analog {
|
|
9
|
-
public readonly state:
|
|
9
|
+
public readonly state: this = this;
|
|
10
10
|
public readonly contact = this.button;
|
|
11
11
|
public readonly tracker: Increment = new Increment();
|
|
12
12
|
|
package/src/elements/touchpad.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Touch } from "./touch";
|
|
|
3
3
|
import { Input, InputParams } from "../input";
|
|
4
4
|
|
|
5
5
|
export class Touchpad extends Input<Touchpad> {
|
|
6
|
-
public readonly state:
|
|
6
|
+
public readonly state: this = this;
|
|
7
7
|
|
|
8
8
|
public get active(): boolean {
|
|
9
9
|
return this.left.contact.active;
|
package/src/elements/trigger.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { Momentary } from "./momentary";
|
|
|
6
6
|
export class Trigger extends Input<Magnitude> {
|
|
7
7
|
public state: Magnitude = 0;
|
|
8
8
|
|
|
9
|
-
public button: Momentary = new Momentary(
|
|
9
|
+
public button: Momentary = new Momentary();
|
|
10
10
|
|
|
11
11
|
public get active(): boolean {
|
|
12
12
|
return this.state > 0;
|
package/src/elements/unisense.ts
CHANGED
|
@@ -12,29 +12,30 @@ export interface UnisenseParams extends InputParams {
|
|
|
12
12
|
|
|
13
13
|
// The name "Dualsense" clearly implies a composition of two Unisense elements 🤔
|
|
14
14
|
export class Unisense extends Input<Unisense> {
|
|
15
|
-
public readonly state:
|
|
15
|
+
public readonly state: this = this;
|
|
16
16
|
|
|
17
17
|
public readonly trigger: Trigger;
|
|
18
18
|
public readonly bumper: Momentary;
|
|
19
19
|
public readonly analog: Analog;
|
|
20
20
|
public readonly haptic: Haptic;
|
|
21
21
|
|
|
22
|
-
constructor(params
|
|
22
|
+
constructor(params: UnisenseParams = {}) {
|
|
23
23
|
super(params);
|
|
24
|
+
const { trigger, bumper, analog } = params;
|
|
24
25
|
|
|
25
|
-
this.trigger = new Trigger(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
);
|
|
32
|
-
this.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
);
|
|
26
|
+
this.trigger = new Trigger({
|
|
27
|
+
icon: "2",
|
|
28
|
+
name: "Trigger",
|
|
29
|
+
threshold: 1 / 255,
|
|
30
|
+
...trigger,
|
|
31
|
+
});
|
|
32
|
+
this.bumper = new Momentary({ icon: "1", name: "Bumper", ...bumper });
|
|
33
|
+
this.analog = new Analog({
|
|
34
|
+
icon: "⨁",
|
|
35
|
+
name: "Analog",
|
|
36
|
+
threshold: 1 / 128,
|
|
37
|
+
...analog,
|
|
38
|
+
});
|
|
38
39
|
this.haptic = new Haptic();
|
|
39
40
|
}
|
|
40
41
|
|
package/src/hid/dualsense_hid.ts
CHANGED
|
@@ -1,63 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { EventEmitter } from "events";
|
|
1
|
+
import { HIDProvider, DualsenseHIDState, InputId } from "./hid_provider";
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
export type HIDCallback = (state: DualsenseHIDState) => void;
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
[InputId.LeftTrigger]: number;
|
|
12
|
-
[InputId.RightTrigger]: number;
|
|
13
|
-
[InputId.Triangle]: boolean;
|
|
14
|
-
[InputId.Circle]: boolean;
|
|
15
|
-
[InputId.Cross]: boolean;
|
|
16
|
-
[InputId.Square]: boolean;
|
|
17
|
-
[InputId.Dpad]: number;
|
|
18
|
-
[InputId.Up]: boolean;
|
|
19
|
-
[InputId.Down]: boolean;
|
|
20
|
-
[InputId.Left]: boolean;
|
|
21
|
-
[InputId.Right]: boolean;
|
|
22
|
-
[InputId.RightAnalogButton]: boolean;
|
|
23
|
-
[InputId.LeftAnalogButton]: boolean;
|
|
24
|
-
[InputId.Options]: boolean;
|
|
25
|
-
[InputId.Create]: boolean;
|
|
26
|
-
[InputId.RightTriggerButton]: boolean;
|
|
27
|
-
[InputId.LeftTriggerButton]: boolean;
|
|
28
|
-
[InputId.RightBumper]: boolean;
|
|
29
|
-
[InputId.LeftBumper]: boolean;
|
|
30
|
-
[InputId.Playstation]: boolean;
|
|
31
|
-
[InputId.TouchButton]: boolean;
|
|
32
|
-
[InputId.Mute]: boolean;
|
|
33
|
-
[InputId.Status]: boolean;
|
|
34
|
-
[InputId.TouchX0]: number;
|
|
35
|
-
[InputId.TouchY0]: number;
|
|
36
|
-
[InputId.TouchContact0]: boolean;
|
|
37
|
-
[InputId.TouchId0]: number;
|
|
38
|
-
[InputId.TouchX1]: number;
|
|
39
|
-
[InputId.TouchY1]: number;
|
|
40
|
-
[InputId.TouchContact1]: boolean;
|
|
41
|
-
[InputId.TouchId1]: number;
|
|
42
|
-
[InputId.GyroX]: number;
|
|
43
|
-
[InputId.GyroY]: number;
|
|
44
|
-
[InputId.GyroZ]: number;
|
|
45
|
-
[InputId.AccelX]: number;
|
|
46
|
-
[InputId.AccelY]: number;
|
|
47
|
-
[InputId.AccelZ]: number;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Maps a HID input of 0...n to -1...1
|
|
51
|
-
export function mapAxis(value: number, max: number = 255): number {
|
|
52
|
-
return (2 / max) * Math.max(0, Math.min(max, value)) - 1;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Maps a HID input of 0...255 to 0...1
|
|
56
|
-
export function mapTrigger(value: number): number {
|
|
57
|
-
return (1 / 255) * Math.max(0, Math.min(255, value));
|
|
58
|
-
}
|
|
5
|
+
/**
|
|
6
|
+
* Coordinates a HIDProvider and tracks the latest HID state.
|
|
7
|
+
*/
|
|
8
|
+
export class DualsenseHID {
|
|
9
|
+
private readonly subscribers = new Set<HIDCallback>();
|
|
59
10
|
|
|
60
|
-
export class DualsenseHID extends EventEmitter {
|
|
61
11
|
public state: DualsenseHIDState = {
|
|
62
12
|
[InputId.LeftAnalogX]: 0,
|
|
63
13
|
[InputId.LeftAnalogY]: 0,
|
|
@@ -102,116 +52,35 @@ export class DualsenseHID extends EventEmitter {
|
|
|
102
52
|
[InputId.AccelZ]: 0,
|
|
103
53
|
};
|
|
104
54
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
static readonly vendorId: number = 1356;
|
|
109
|
-
static readonly productId: number = 3302;
|
|
110
|
-
|
|
111
|
-
constructor() {
|
|
112
|
-
super();
|
|
113
|
-
this.device = this.connect();
|
|
55
|
+
constructor(readonly provider: HIDProvider) {
|
|
56
|
+
provider.onData = this.set.bind(this);
|
|
57
|
+
provider.onError = this.handleError.bind(this);
|
|
114
58
|
}
|
|
115
59
|
|
|
116
60
|
/**
|
|
117
|
-
*
|
|
61
|
+
* Register a handler for HID state updates.
|
|
118
62
|
*/
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
63
|
+
public register(callback: (state: DualsenseHIDState) => void): void {
|
|
64
|
+
this.subscribers.add(callback);
|
|
65
|
+
}
|
|
122
66
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
state[InputId.RightTrigger] = mapTrigger(report.readUint8(5));
|
|
130
|
-
// 6 is a sequence byte
|
|
131
|
-
const mainButtons = report.readUint8(7);
|
|
132
|
-
state[InputId.Triangle] = (mainButtons & 128) > 0;
|
|
133
|
-
state[InputId.Circle] = (mainButtons & 64) > 0;
|
|
134
|
-
state[InputId.Cross] = (mainButtons & 32) > 0;
|
|
135
|
-
state[InputId.Square] = (mainButtons & 16) > 0;
|
|
136
|
-
state[InputId.Dpad] = (mainButtons << 4) >> 4;
|
|
137
|
-
state[InputId.Up] = state[InputId.Dpad] < 2 || state[InputId.Dpad] === 7;
|
|
138
|
-
state[InputId.Down] = state[InputId.Dpad] > 2 && state[InputId.Dpad] < 6;
|
|
139
|
-
state[InputId.Left] = state[InputId.Dpad] > 4 && state[InputId.Dpad] < 8;
|
|
140
|
-
state[InputId.Right] = state[InputId.Dpad] > 0 && state[InputId.Dpad] < 4;
|
|
141
|
-
const miscButtons = report.readUint8(8);
|
|
142
|
-
state[InputId.LeftTriggerButton] = (miscButtons & 4) > 0;
|
|
143
|
-
state[InputId.RightTriggerButton] = (miscButtons & 8) > 0;
|
|
144
|
-
state[InputId.LeftBumper] = (miscButtons & 1) > 0;
|
|
145
|
-
state[InputId.RightBumper] = (miscButtons & 2) > 0;
|
|
146
|
-
state[InputId.Create] = (miscButtons & 16) > 0;
|
|
147
|
-
state[InputId.Options] = (miscButtons & 32) > 0;
|
|
148
|
-
state[InputId.LeftAnalogButton] = (miscButtons & 64) > 0;
|
|
149
|
-
state[InputId.RightAnalogButton] = (miscButtons & 128) > 0;
|
|
150
|
-
const lastButtons = report.readUint8(9);
|
|
151
|
-
state[InputId.Playstation] = (lastButtons & 1) > 0;
|
|
152
|
-
state[InputId.TouchButton] = (lastButtons & 2) > 0;
|
|
153
|
-
state[InputId.Mute] = (lastButtons & 4) > 0;
|
|
154
|
-
// The other 5 bits are unused
|
|
155
|
-
// 5 reserved bytes
|
|
156
|
-
state[InputId.GyroX] = report.readUint16LE(15);
|
|
157
|
-
state[InputId.GyroY] = report.readUint16LE(17);
|
|
158
|
-
state[InputId.GyroZ] = report.readUint16LE(19);
|
|
159
|
-
state[InputId.AccelX] = report.readUint16LE(21);
|
|
160
|
-
state[InputId.AccelY] = report.readUint16LE(23);
|
|
161
|
-
state[InputId.AccelZ] = report.readUint16LE(25);
|
|
162
|
-
// 4 bytes for sensor timestamp (32LE)
|
|
163
|
-
// 1 reserved byte
|
|
164
|
-
state[InputId.TouchId0] = report.readUint8(32) & 0x7f;
|
|
165
|
-
state[InputId.TouchContact0] = (report.readUint8(32) & 0x80) === 0;
|
|
166
|
-
state[InputId.TouchX0] = mapAxis(
|
|
167
|
-
(report.readUint16LE(33) << 20) >> 20,
|
|
168
|
-
1920
|
|
169
|
-
);
|
|
170
|
-
state[InputId.TouchY0] = mapAxis(report.readUint16LE(34) >> 4, 1080);
|
|
171
|
-
state[InputId.TouchId1] = report.readUint8(36) & 0x7f;
|
|
172
|
-
state[InputId.TouchContact1] = (report.readUint8(36) & 0x80) === 0;
|
|
173
|
-
state[InputId.TouchX1] = mapAxis(
|
|
174
|
-
(report.readUint16LE(37) << 20) >> 20,
|
|
175
|
-
1920
|
|
176
|
-
);
|
|
177
|
-
state[InputId.TouchY1] = mapAxis(report.readUint16LE(38) >> 4, 1080);
|
|
178
|
-
// 12 reserved bytes
|
|
179
|
-
state[InputId.Status] = (report.readUint8(53) & 4) > 0;
|
|
67
|
+
/**
|
|
68
|
+
* Cancel a previously registered handler.
|
|
69
|
+
*/
|
|
70
|
+
public unregister(callback: (state: DualsenseHIDState) => void): void {
|
|
71
|
+
this.subscribers.delete(callback);
|
|
72
|
+
}
|
|
180
73
|
|
|
181
|
-
|
|
74
|
+
private set(state: DualsenseHIDState): void {
|
|
75
|
+
this.state = state;
|
|
76
|
+
this.subscribers.forEach((callback) => callback(state));
|
|
182
77
|
}
|
|
183
78
|
|
|
184
79
|
private handleError(error: unknown): void {
|
|
185
80
|
console.error(error);
|
|
186
81
|
setTimeout(() => {
|
|
187
|
-
this.
|
|
82
|
+
this.provider.disconnect();
|
|
83
|
+
this.provider.connect();
|
|
188
84
|
}, 50);
|
|
189
85
|
}
|
|
190
|
-
|
|
191
|
-
private disconnect(): void {
|
|
192
|
-
if (this.device) {
|
|
193
|
-
try {
|
|
194
|
-
this.device.removeAllListeners();
|
|
195
|
-
this.device.close();
|
|
196
|
-
} catch (e) {
|
|
197
|
-
console.error(e);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
private connect(): HID {
|
|
203
|
-
this.disconnect();
|
|
204
|
-
|
|
205
|
-
const controllers = devices(DualsenseHID.vendorId, DualsenseHID.productId);
|
|
206
|
-
if (controllers.length === 0 || !controllers[0].path) {
|
|
207
|
-
throw new Error(`No controllers (${devices().length} other devices)`);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (controllers[0].interface === -1) this.wireless = true;
|
|
211
|
-
|
|
212
|
-
const controller = new HID(controllers[0].path);
|
|
213
|
-
controller.on("data", this.process.bind(this));
|
|
214
|
-
controller.on("error", this.handleError.bind(this));
|
|
215
|
-
return controller;
|
|
216
|
-
}
|
|
217
86
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { InputId } from "../id";
|
|
2
|
+
|
|
3
|
+
export * from "../id";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Maps a HID input of 0...n to -1...1
|
|
7
|
+
*/
|
|
8
|
+
export function mapAxis(value: number, max: number = 255): number {
|
|
9
|
+
return (2 / max) * Math.max(0, Math.min(max, value)) - 1;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Maps a HID input of 0...255 to 0...1
|
|
14
|
+
*/
|
|
15
|
+
export function mapTrigger(value: number): number {
|
|
16
|
+
return (1 / 255) * Math.max(0, Math.min(255, value));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Describes an observation of the input state of a Dualsense controller.
|
|
21
|
+
*/
|
|
22
|
+
export interface DualsenseHIDState {
|
|
23
|
+
[InputId.LeftAnalogX]: number;
|
|
24
|
+
[InputId.LeftAnalogY]: number;
|
|
25
|
+
[InputId.RightAnalogX]: number;
|
|
26
|
+
[InputId.RightAnalogY]: number;
|
|
27
|
+
[InputId.LeftTrigger]: number;
|
|
28
|
+
[InputId.RightTrigger]: number;
|
|
29
|
+
[InputId.Triangle]: boolean;
|
|
30
|
+
[InputId.Circle]: boolean;
|
|
31
|
+
[InputId.Cross]: boolean;
|
|
32
|
+
[InputId.Square]: boolean;
|
|
33
|
+
[InputId.Dpad]: number;
|
|
34
|
+
[InputId.Up]: boolean;
|
|
35
|
+
[InputId.Down]: boolean;
|
|
36
|
+
[InputId.Left]: boolean;
|
|
37
|
+
[InputId.Right]: boolean;
|
|
38
|
+
[InputId.RightAnalogButton]: boolean;
|
|
39
|
+
[InputId.LeftAnalogButton]: boolean;
|
|
40
|
+
[InputId.Options]: boolean;
|
|
41
|
+
[InputId.Create]: boolean;
|
|
42
|
+
[InputId.RightTriggerButton]: boolean;
|
|
43
|
+
[InputId.LeftTriggerButton]: boolean;
|
|
44
|
+
[InputId.RightBumper]: boolean;
|
|
45
|
+
[InputId.LeftBumper]: boolean;
|
|
46
|
+
[InputId.Playstation]: boolean;
|
|
47
|
+
[InputId.TouchButton]: boolean;
|
|
48
|
+
[InputId.Mute]: boolean;
|
|
49
|
+
[InputId.Status]: boolean;
|
|
50
|
+
[InputId.TouchX0]: number;
|
|
51
|
+
[InputId.TouchY0]: number;
|
|
52
|
+
[InputId.TouchContact0]: boolean;
|
|
53
|
+
[InputId.TouchId0]: number;
|
|
54
|
+
[InputId.TouchX1]: number;
|
|
55
|
+
[InputId.TouchY1]: number;
|
|
56
|
+
[InputId.TouchContact1]: boolean;
|
|
57
|
+
[InputId.TouchId1]: number;
|
|
58
|
+
[InputId.GyroX]: number;
|
|
59
|
+
[InputId.GyroY]: number;
|
|
60
|
+
[InputId.GyroZ]: number;
|
|
61
|
+
[InputId.AccelX]: number;
|
|
62
|
+
[InputId.AccelY]: number;
|
|
63
|
+
[InputId.AccelZ]: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Supports a connection to a physical or logical Dualsense device.
|
|
68
|
+
*/
|
|
69
|
+
export abstract class HIDProvider {
|
|
70
|
+
static readonly vendorId: number = 1356;
|
|
71
|
+
static readonly productId: number = 3302;
|
|
72
|
+
|
|
73
|
+
public onData: (state: DualsenseHIDState) => void = () => {};
|
|
74
|
+
public onError: (error: Error) => void = () => {};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Search for a controller and connect to it.
|
|
78
|
+
*/
|
|
79
|
+
abstract connect(): void;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Stop accepting input from the controller.
|
|
83
|
+
*/
|
|
84
|
+
abstract disconnect(): void;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Returns true if a device is currently connected and working.
|
|
88
|
+
*/
|
|
89
|
+
abstract get connected(): boolean;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Returns true if a device is connected wirelessly.
|
|
93
|
+
*/
|
|
94
|
+
abstract get wireless(): boolean;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Converts the HID report to a usable state entity.
|
|
98
|
+
*/
|
|
99
|
+
abstract process(input: unknown): DualsenseHIDState;
|
|
100
|
+
}
|
package/src/hid/index.ts
CHANGED