├── .editorconfig
├── .gitignore
├── .travis.yml
├── README.md
├── controllerConfigurations
├── README.md
├── dualShock3.json
├── dualShock4-alternate-driver.json
├── dualShock4-generic-driver.json
└── dualShock4.json
├── examples
├── README.md
├── consolePrintControllerEvents.js
├── consolePrintDualShock3.js
├── consolePrintDualShock4.js
├── deviceDiscoveryHelp.js
└── deviceDiscoveryHelpLinux.js
├── gruntfile.js
├── package.json
├── src
├── analogs.js
├── buttons.js
├── config.js
├── controller.js
├── dualshock.js
├── gyro.js
├── smoothing.js
├── status.js
├── touchpad.js
└── utilities.js
└── test
├── analog.tests.js
├── buttons.tests.js
├── config.tests.js
├── dualshock.tests.js
├── gyro.tests.js
├── smoothing.tests.js
└── status.tests.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 4
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | end_of_line = lf
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.log
3 | coverage
4 | *.iml
5 | *.idea
6 | .DS_Store
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "6.9.2"
4 | before_script:
5 | install: echo "node-hid installation will fail in travis. Overiding `npm install`"
6 | compiler: clang
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | node-dualshock-controller
2 | =========================
3 | [](https://travis-ci.org/rdepena/node-dualshock-controller) [](https://codeclimate.com/github/rdepena/node-dualshock-controller)
4 |
5 | `dualshock-controller` Eventing API layer over HID for the Sony DualShock 3 and DualShock 4 controllers
6 |
7 | ## Installation:
8 |
9 | #### OSX/Windows:
10 |
11 | ```bash
12 | npm install dualshock-controller
13 | ```
14 | #### Linux:
15 |
16 | Review the [Linux support](#linux-support) section.
17 |
18 | ## Using the DualShock library
19 |
20 | `Important: THE CONTROLLER WILL NOT SEND ANY DATA IF YOU DO NOT PRESS THE PS BUTTON.`
21 |
22 | Also, to use the touchpad, rumble and LED capabilities of the controller you
23 | must connect the controller to your computer using a micro-USB cable.
24 |
25 | ~~~~ javascript
26 | var dualShock = require('dualshock-controller');
27 |
28 | //pass options to init the controller.
29 | var controller = dualShock(
30 | {
31 | //you can use a ds4 by uncommenting this line.
32 | //config: "dualshock4-generic-driver",
33 | //if the above configuration doesn't work for you,
34 | //try uncommenting the following line instead.
35 | //config: "dualshock4-alternate-driver"
36 | //if using ds4 comment this line.
37 | config: "dualShock3",
38 | //smooths the output from the acelerometers (moving averages) defaults to true
39 | accelerometerSmoothing: true,
40 | //smooths the output from the analog sticks (moving averages) defaults to false
41 | analogStickSmoothing: false
42 | });
43 |
44 | //make sure you add an error event handler
45 | controller.on('error', err => console.log(err));
46 |
47 | //DualShock 4 control rumble and light settings for the controller
48 | controller.setExtras({
49 | rumbleLeft: 0, // 0-255 (Rumble left intensity)
50 | rumbleRight: 0, // 0-255 (Rumble right intensity)
51 | red: 0, // 0-255 (Red intensity)
52 | green: 75, // 0-255 (Blue intensity)
53 | blue: 225, // 0-255 (Green intensity)
54 | flashOn: 40, // 0-255 (Flash on time)
55 | flashOff: 10 // 0-255 (Flash off time)
56 | });
57 |
58 | //DualShock 3 control rumble and light settings for the controller
59 | controller.setExtras({
60 | rumbleLeft: 0, // 0-1 (Rumble left on/off)
61 | rumbleRight: 0, // 0-255 (Rumble right intensity)
62 | led: 2 // 2 | 4 | 8 | 16 (Leds 1-4 on/off, bitmasked)
63 | });
64 |
65 | //add event handlers:
66 | controller.on('left:move', data => console.log('left Moved: ' + data.x + ' | ' + data.y));
67 |
68 | controller.on('right:move', data => console.log('right Moved: ' + data.x + ' | ' + data.y));
69 |
70 | controller.on('connected', () => console.log('connected'));
71 |
72 | controller.on('square:press', ()=> console.log('square press'));
73 |
74 | controller.on('square:release', () => console.log('square release'));
75 |
76 | //sixasis motion events:
77 | //the object returned from each of the movement events is as follows:
78 | //{
79 | // direction : values can be: 1 for right, forward and up. 2 for left, backwards and down.
80 | // value : values will be from 0 to 120 for directions right, forward and up and from 0 to -120 for left, backwards and down.
81 | //}
82 |
83 | //DualShock 4 TouchPad
84 | //finger 1 is x1 finger 2 is x2
85 | controller.on('touchpad:x1:active', () => console.log('touchpad one finger active'));
86 |
87 | controller.on('touchpad:x2:active', () => console.log('touchpad two fingers active'));
88 |
89 | controller.on('touchpad:x2:inactive', () => console.log('touchpad back to single finger'));
90 |
91 | controller.on('touchpad:x1', data => console.log('touchpad x1:', data.x, data.y));
92 |
93 | controller.on('touchpad:x2', data => console.log('touchpad x2:', data.x, data.y));
94 |
95 |
96 | //right-left movement
97 | controller.on('rightLeft:motion', data => console.log(data));
98 |
99 | //forward-back movement
100 | controller.on('forwardBackward:motion', data => console.log(data));
101 |
102 | //up-down movement
103 | controller.on('upDown:motion', data => console.log(data));
104 |
105 | //controller status
106 | //as of version 0.6.2 you can get the battery %, if the controller is connected and if the controller is charging
107 | controller.on('battery:change', data => console.log(data));
108 |
109 | controller.on('connection:change', data => console.log(data));
110 |
111 | controller.on('charging:change', data => console.log(data));
112 |
113 | ~~~~
114 |
115 | ## Linux support:
116 |
117 | In order to provide Rumble/Gyro and LED support for all platforms the linux specific joystick implementation has been removed. This means you will need to:
118 |
119 | * [Install node-hid build requirements](#node-hid-build)
120 | * [Install node-hid with hidraw support](#node-hid-hidraw)
121 | * [create udev rules](#create-udev-rules)
122 |
123 | #### Install node-hid build requirements
124 |
125 | To build node-hid you will need to install:
126 |
127 | * libudev-dev
128 | * libusb-1.0-0
129 | * libusb-1.0-0-dev
130 | * build-essential
131 | * git
132 | * node-gyp
133 | * node-pre-gyp
134 |
135 | Using apt-get:
136 |
137 | ```bash
138 | sudo apt-get install libudev-dev libusb-1.0-0 libusb-1.0-0-dev build-essential git
139 | ```
140 |
141 | ```bash
142 | npm install -g node-gyp node-pre-gyp
143 | ```
144 |
145 | #### Install node-hid with hidraw support
146 |
147 | Once you have run the installation scripts above you can install the node-dualshock module, then replace the installed node-hid with hidraw support enabled node-hid:
148 |
149 | ```bash
150 | npm install dualshock-controller
151 | ```
152 |
153 | ```bash
154 | npm install node-hid --driver=hidraw --build-from-source
155 | ```
156 |
157 | #### Create udev rules
158 |
159 | You will need to create a udev rule to be able to access the hid stream as a non root user.
160 |
161 | Write the following file in `/etc/udev/rules.d/61-dualshock.rules`
162 |
163 | ```
164 | SUBSYSTEM=="input", GROUP="input", MODE="0666"
165 | SUBSYSTEM=="usb", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="0268", MODE:="666", GROUP="plugdev"
166 | KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0664", GROUP="plugdev"
167 |
168 | SUBSYSTEM=="input", GROUP="input", MODE="0666"
169 | SUBSYSTEM=="usb", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="05c4", MODE:="666", GROUP="plugdev"
170 | KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0664", GROUP="plugdev"
171 | ```
172 |
173 | Reload the rules `sudo udevadm control --reload-rules`, then disconnect/connect the controller.
174 |
175 | The MIT License (MIT)
176 |
177 | Copyright (c) 2017 Ricardo de Pena
178 |
179 | Permission is hereby granted, free of charge, to any person obtaining a copy of
180 | this software and associated documentation files (the "Software"), to deal in
181 | the Software without restriction, including without limitation the rights to
182 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
183 | the Software, and to permit persons to whom the Software is furnished to do so,
184 | subject to the following conditions:
185 |
186 | The above copyright notice and this permission notice shall be included in all
187 | copies or substantial portions of the Software.
188 |
189 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
190 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
191 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
192 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
193 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
194 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
195 |
--------------------------------------------------------------------------------
/controllerConfigurations/README.md:
--------------------------------------------------------------------------------
1 | These json files are used to map the controller:
2 | =========================
3 |
4 | ## To add a new controller:
5 | Use DeviceDiscoveryHelp.js in the examples folder, using node-hid you can obtain the values you need to create a controller.json file and leverage the library to wire it up.
6 |
7 | ### To connect the right controller
8 | the vendorId and the productId need to be set to the right values, you can use node-hid to determine what these are.
9 |
10 | ### Analogs are mapped as:
11 | ~~~~ js
12 | "analogSticks" : [
13 | {
14 | "name" : "left",
15 | "x" : 7,
16 | "y" : 6
17 | },
18 | {
19 | "name" : "right",
20 | "x" : 9,
21 | "y" : 8
22 | }
23 | ]
24 |
25 | ### Buttons are usually grouped by a block but should be added as:
26 |
27 | ~~~~ js
28 | "buttons" : [
29 | {
30 | "name": name of the button used for events,
31 | "buttonBlock": int representing the button block,
32 | "buttonValue": bit value,
33 | "analogPin" : int representing the pin used for analog.
34 | },
35 | ]
36 |
--------------------------------------------------------------------------------
/controllerConfigurations/dualShock3.json:
--------------------------------------------------------------------------------
1 | {
2 | "vendorId" : 1356,
3 | "productId" : 616,
4 | "analogSticks" : [
5 | {
6 | "name" : "left",
7 | "x" : 6,
8 | "y" : 7,
9 | "joystickXNumber" : 0,
10 | "joystickYNumber" : 1
11 | },
12 | {
13 | "name" : "right",
14 | "x" : 8,
15 | "y" : 9,
16 | "joystickXNumber" : 2,
17 | "joystickYNumber" : 3
18 | }
19 | ],
20 | "buttons" : [
21 | {
22 | "name": "l2",
23 | "buttonBlock": 3,
24 | "buttonValue": "0x01",
25 | "analogPin" : 18,
26 | "joystickNumber": 8
27 | },
28 | {
29 | "name": "r2",
30 | "buttonBlock": 3,
31 | "buttonValue": "0x02",
32 | "analogPin" : 19,
33 | "joystickNumber" : 9
34 | },
35 | {
36 | "name": "l1",
37 | "buttonBlock": 3,
38 | "buttonValue": "0x04",
39 | "analogPin" : 20,
40 | "joystickNumber" : 10
41 | },
42 | {
43 | "name":"r1",
44 | "buttonBlock": 3,
45 | "buttonValue": "0x08",
46 | "analogPin" : 21,
47 | "joystickNumber" : 11
48 | },
49 | {
50 | "name": "triangle",
51 | "buttonBlock": 3,
52 | "buttonValue": "0x10",
53 | "analogPin" : 22,
54 | "joystickNumber" : 12
55 | },
56 | {
57 | "name": "circle",
58 | "buttonBlock": 3,
59 | "buttonValue": "0x20",
60 | "analogPin" : 23,
61 | "joystickNumber" : 13
62 | },
63 | {
64 | "name": "x",
65 | "buttonBlock": 3,
66 | "buttonValue": "0x40",
67 | "analogPin" : 24,
68 | "joystickNumber" : 14
69 | },
70 | {
71 | "name": "square",
72 | "buttonBlock": 3,
73 | "buttonValue": "0x80",
74 | "analogPin": 25,
75 | "joystickNumber" : 15
76 | },
77 | {
78 | "name": "select",
79 | "buttonBlock": 2,
80 | "buttonValue": "0x1",
81 | "joystickNumber" : 0
82 | },
83 | {
84 | "name": "leftAnalogBump",
85 | "buttonBlock": 2,
86 | "buttonValue": "0x2",
87 | "joystickNumber" : 1
88 | },
89 | {
90 | "name": "rightAnalogBump",
91 | "buttonBlock": 2,
92 | "buttonValue": "0x4",
93 | "joystickNumber" : 2
94 | },
95 | {
96 | "name": "start",
97 | "buttonBlock": 2,
98 | "buttonValue": "0x08",
99 | "joystickNumber" : 3
100 | },
101 | {
102 | "name": "dpadUp",
103 | "buttonBlock": 2,
104 | "buttonValue": "0x10",
105 | "analogPin": 14,
106 | "joystickNumber" : 4
107 | },
108 | {
109 | "name": "dpadRight",
110 | "buttonBlock": 2,
111 | "buttonValue": "0x20",
112 | "analogPin": 15,
113 | "joystickNumber" : 5
114 | },
115 | {
116 | "name": "dpadDown",
117 | "buttonBlock": 2,
118 | "buttonValue" : "0x40",
119 | "analogPin" : 16,
120 | "joystickNumber" : 6
121 | },
122 | {
123 | "name": "dpadLeft",
124 | "buttonBlock": 2,
125 | "buttonValue": "0x80",
126 | "analogPin": 17,
127 | "joystickNumber" : 7
128 | },
129 | {
130 | "name": "psxButton",
131 | "buttonBlock":4,
132 | "buttonValue": "0x01",
133 | "joystickNumber" : 16
134 | }
135 | ],
136 | "motionInputs": [
137 | {
138 | "name" : "rightLeft",
139 | "directionPin" : 41,
140 | "valuePin" : 42
141 | },
142 | {
143 | "name" : "forwardBackward",
144 | "directionPin" : 43,
145 | "valuePin" : 44
146 | },
147 | {
148 | "name" : "upDown",
149 | "directionPin" : 45,
150 | "valuePin" : 46
151 | },
152 | {
153 | "name" : "yaw",
154 | "directionPin" : 47,
155 | "valuePin" : 48
156 | }
157 | ],
158 | "status": [
159 | {
160 | "name" : "charging",
161 | "pin" : 29,
162 | "states" : [
163 | {
164 | "value" : 0,
165 | "state" : "Charging"
166 | },
167 | {
168 | "value" : 2,
169 | "state" : "Charging"
170 | },
171 | {
172 | "value" : 3,
173 | "state" : "Not Charging"
174 | }
175 | ]
176 | },
177 | {
178 | "name" : "battery",
179 | "pin" : 30,
180 | "states" : [
181 | {
182 | "value" : 238,
183 | "state" : "Charging"
184 | },
185 | {
186 | "value" : 0,
187 | "state" : "No charge"
188 | },
189 | {
190 | "value" : 1,
191 | "state" : "20%"
192 | },
193 | {
194 | "value" : 2,
195 | "state" : "40%"
196 | },
197 | {
198 | "value" : 3,
199 | "state" : "60%"
200 | },
201 | {
202 | "value" : 4,
203 | "state" : "80%"
204 | },
205 | {
206 | "value" : 5,
207 | "state" : "100%"
208 | }
209 | ]
210 | },
211 | {
212 | "name" : "connection",
213 | "pin" : 31,
214 | "states" : [
215 | {
216 | "value" : 18,
217 | "state" : "Usb"
218 | },
219 | {
220 | "value" : 22,
221 | "state" : "Bluetooth"
222 | },
223 | {
224 | "value" : 20,
225 | "state" : "Rumbling"
226 | }
227 | ]
228 | }
229 | ],
230 | "output": {
231 | "defaultBuffer":[
232 | 1, 0, 254, 0, 254, 0, 0, 0, 0, 0, 0,
233 | 255, 39, 16, 0, 50, 255, 39, 16, 0, 50, 255,
234 | 39, 16, 0, 50, 255, 39, 16, 0, 50, 0, 0,
235 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
236 | 0, 0, 0, 0, 0
237 | ],
238 | "indexes": {
239 | "rumbleLeft": 3,
240 | "rumbleRight": 5,
241 | "led": 10
242 | }
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/controllerConfigurations/dualShock4-alternate-driver.json:
--------------------------------------------------------------------------------
1 | {
2 | "vendorId" : 1356,
3 | "productId" : 2508,
4 | "analogSticks" : [
5 | {
6 | "name" : "left",
7 | "x" : 1,
8 | "y" : 2
9 | },
10 | {
11 | "name" : "right",
12 | "x" : 3,
13 | "y" : 4
14 | }
15 | ],
16 | "buttons" : [
17 | {
18 | "name": "l2",
19 | "buttonBlock": 6,
20 | "buttonValue": "0x04",
21 | "analogPin" : 8
22 | },
23 | {
24 | "name": "r2",
25 | "buttonBlock": 6,
26 | "buttonValue": "0x08",
27 | "analogPin" : 9
28 | },
29 | {
30 | "name": "l1",
31 | "buttonBlock": 6,
32 | "buttonValue": "0x01"
33 | },
34 | {
35 | "name": "r1",
36 | "buttonBlock": 6,
37 | "buttonValue": "0x02"
38 | },
39 | {
40 | "name": "leftAnalogBump",
41 | "buttonBlock": 6,
42 | "buttonValue": "0x04"
43 | },
44 | {
45 | "name": "rightAnalogBump",
46 | "buttonBlock": 6,
47 | "buttonValue": "0x08"
48 | },
49 | {
50 | "name": "psxButton",
51 | "buttonBlock": 7,
52 | "buttonValue": "0x01"
53 | },
54 | {
55 | "name": "touchPad",
56 | "buttonBlock": 7,
57 | "buttonValue": "0x02"
58 | },
59 | {
60 | "name": "square",
61 | "buttonBlock": 5,
62 | "buttonValue": "0x10"
63 | },
64 | {
65 | "name": "triangle",
66 | "buttonBlock": 5,
67 | "buttonValue": "0x80"
68 | },
69 | {
70 | "name": "circle",
71 | "buttonBlock": 5,
72 | "buttonValue": "0x40"
73 | },
74 | {
75 | "name": "x",
76 | "buttonBlock": 5,
77 | "buttonValue": "0x20"
78 | },
79 | {
80 | "name": "dpadUp",
81 | "buttonBlock": 5,
82 | "buttonValue": "0x00",
83 | "mask": "0xF"
84 | },
85 | {
86 | "name": "dpadUpRight",
87 | "buttonBlock": 5,
88 | "buttonValue": "0x01",
89 | "mask": "0xF"
90 | },
91 | {
92 | "name": "dpadRight",
93 | "buttonBlock": 5,
94 | "buttonValue": "0x02",
95 | "mask": "0xF"
96 | },
97 | {
98 | "name": "dpadDownRight",
99 | "buttonBlock": 5,
100 | "buttonValue": "0x03",
101 | "mask": "0xF"
102 | },
103 | {
104 | "name": "dpadDown",
105 | "buttonBlock": 5,
106 | "buttonValue" : "0x04",
107 | "mask": "0xF"
108 | },
109 | {
110 | "name": "dpadDownLeft",
111 | "buttonBlock": 5,
112 | "buttonValue" : "0x05",
113 | "mask": "0xF"
114 | },
115 | {
116 | "name": "dpadLeft",
117 | "buttonBlock": 5,
118 | "buttonValue": "0x06",
119 | "mask": "0xF"
120 | },
121 | {
122 | "name": "dpadUpLeft",
123 | "buttonBlock": 5,
124 | "buttonValue": "0x07",
125 | "mask": "0xF"
126 | },
127 | {
128 | "name": "share",
129 | "buttonBlock": 6,
130 | "buttonValue": "0x10"
131 | },
132 | {
133 | "name": "options",
134 | "buttonBlock": 6,
135 | "buttonValue": "0x20"
136 | },
137 | {
138 | "name": "leftStick",
139 | "buttonBlock": 6,
140 | "buttonValue": "0x40"
141 | },
142 | {
143 | "name": "rightStick",
144 | "buttonBlock": 6,
145 | "buttonValue": "0x80"
146 | }
147 | ],
148 | "motionInputs" : [],
149 | "status" : [],
150 | "output": {
151 | "defaultBuffer":[
152 | 5,255,4,0,0,0,0,0,0,0,0
153 | ],
154 | "indexes": {
155 | "rumbleLeft": 4,
156 | "rumbleRight": 5,
157 | "red": 6,
158 | "green": 7,
159 | "blue": 8,
160 | "flashOn": 9,
161 | "flashOff": 10
162 | }
163 | },
164 | "touchPad": [{
165 | "name":"x1",
166 | "activePin": 35,
167 | "dataPinA": 37,
168 | "dataPinB": 36,
169 | "dataPinC": 38
170 | },{
171 | "name": "x2",
172 | "activePin": 39,
173 | "dataPinA": 41,
174 | "dataPinB": 40,
175 | "dataPinC": 42
176 | }]
177 | }
178 |
--------------------------------------------------------------------------------
/controllerConfigurations/dualShock4-generic-driver.json:
--------------------------------------------------------------------------------
1 | {
2 | "vendorId" : 1356,
3 | "productId" : 1476,
4 | "analogSticks" : [
5 | {
6 | "name" : "left",
7 | "x" : 1,
8 | "y" : 2
9 | },
10 | {
11 | "name" : "right",
12 | "x" : 3,
13 | "y" : 4
14 | }
15 | ],
16 | "buttons" : [
17 | {
18 | "name": "l2",
19 | "buttonBlock": 6,
20 | "buttonValue": "0x04",
21 | "analogPin" : 8
22 | },
23 | {
24 | "name": "r2",
25 | "buttonBlock": 6,
26 | "buttonValue": "0x08",
27 | "analogPin" : 9
28 | },
29 | {
30 | "name": "l1",
31 | "buttonBlock": 6,
32 | "buttonValue": "0x01"
33 | },
34 | {
35 | "name": "r1",
36 | "buttonBlock": 6,
37 | "buttonValue": "0x02"
38 | },
39 | {
40 | "name": "leftAnalogBump",
41 | "buttonBlock": 6,
42 | "buttonValue": "0x04"
43 | },
44 | {
45 | "name": "rightAnalogBump",
46 | "buttonBlock": 6,
47 | "buttonValue": "0x08"
48 | },
49 | {
50 | "name": "psxButton",
51 | "buttonBlock": 7,
52 | "buttonValue": "0x01"
53 | },
54 | {
55 | "name": "touchPad",
56 | "buttonBlock": 7,
57 | "buttonValue": "0x02"
58 | },
59 | {
60 | "name": "square",
61 | "buttonBlock": 5,
62 | "buttonValue": "0x10"
63 | },
64 | {
65 | "name": "triangle",
66 | "buttonBlock": 5,
67 | "buttonValue": "0x80"
68 | },
69 | {
70 | "name": "circle",
71 | "buttonBlock": 5,
72 | "buttonValue": "0x40"
73 | },
74 | {
75 | "name": "x",
76 | "buttonBlock": 5,
77 | "buttonValue": "0x20"
78 | },
79 | {
80 | "name": "dpadUp",
81 | "buttonBlock": 5,
82 | "buttonValue": "0x00",
83 | "mask": "0xF"
84 | },
85 | {
86 | "name": "dpadUpRight",
87 | "buttonBlock": 5,
88 | "buttonValue": "0x01",
89 | "mask": "0xF"
90 | },
91 | {
92 | "name": "dpadRight",
93 | "buttonBlock": 5,
94 | "buttonValue": "0x02",
95 | "mask": "0xF"
96 | },
97 | {
98 | "name": "dpadDownRight",
99 | "buttonBlock": 5,
100 | "buttonValue": "0x03",
101 | "mask": "0xF"
102 | },
103 | {
104 | "name": "dpadDown",
105 | "buttonBlock": 5,
106 | "buttonValue" : "0x04",
107 | "mask": "0xF"
108 | },
109 | {
110 | "name": "dpadDownLeft",
111 | "buttonBlock": 5,
112 | "buttonValue" : "0x05",
113 | "mask": "0xF"
114 | },
115 | {
116 | "name": "dpadLeft",
117 | "buttonBlock": 5,
118 | "buttonValue": "0x06",
119 | "mask": "0xF"
120 | },
121 | {
122 | "name": "dpadUpLeft",
123 | "buttonBlock": 5,
124 | "buttonValue": "0x07",
125 | "mask": "0xF"
126 | },
127 | {
128 | "name": "share",
129 | "buttonBlock": 6,
130 | "buttonValue": "0x10"
131 | },
132 | {
133 | "name": "options",
134 | "buttonBlock": 6,
135 | "buttonValue": "0x20"
136 | },
137 | {
138 | "name": "leftStick",
139 | "buttonBlock": 6,
140 | "buttonValue": "0x40"
141 | },
142 | {
143 | "name": "rightStick",
144 | "buttonBlock": 6,
145 | "buttonValue": "0x80"
146 | }
147 | ],
148 | "motionInputs" : [],
149 | "status" : [],
150 | "output": {
151 | "defaultBuffer":[
152 | 5,255,4,0,0,0,0,0,0,0,0
153 | ],
154 | "indexes": {
155 | "rumbleLeft": 4,
156 | "rumbleRight": 5,
157 | "red": 6,
158 | "green": 7,
159 | "blue": 8,
160 | "flashOn": 9,
161 | "flashOff": 10
162 | }
163 | },
164 | "touchPad": [{
165 | "name":"x1",
166 | "activePin": 35,
167 | "dataPinA": 37,
168 | "dataPinB": 36,
169 | "dataPinC": 38
170 | },{
171 | "name": "x2",
172 | "activePin": 39,
173 | "dataPinA": 41,
174 | "dataPinB": 40,
175 | "dataPinC": 42
176 | }]
177 | }
178 |
--------------------------------------------------------------------------------
/controllerConfigurations/dualShock4.json:
--------------------------------------------------------------------------------
1 | {
2 | "vendorId" : 1356,
3 | "productId" : 1476,
4 | "analogSticks" : [
5 | {
6 | "name" : "left",
7 | "x" : 1,
8 | "y" : 2,
9 | "joystickXNumber" : 0,
10 | "joystickYNumber" : 1
11 | },
12 | {
13 | "name" : "right",
14 | "x" : 3,
15 | "y" : 4,
16 | "joystickXNumber" : 2,
17 | "joystickYNumber" : 5
18 | },
19 | {
20 | "name" : "l2Analog",
21 | "x" : 100,
22 | "joystickXNumber" : 3
23 | },
24 | {
25 | "name" : "r2Analog",
26 | "x" : 101,
27 | "joystickXNumber" : 4
28 | }
29 | ],
30 | "buttons" : [
31 | {
32 | "name": "l2",
33 | "buttonBlock": 6,
34 | "buttonValue": "0x04",
35 | "analogPin" : 8,
36 | "joystickNumber": 6
37 | },
38 | {
39 | "name": "r2",
40 | "buttonBlock": 6,
41 | "buttonValue": "0x08",
42 | "analogPin" : 9,
43 | "joystickNumber": 7
44 | },
45 | {
46 | "name": "l1",
47 | "buttonBlock": 6,
48 | "buttonValue": "0x01",
49 | "joystickNumber": 4
50 | },
51 | {
52 | "name": "r1",
53 | "buttonBlock": 6,
54 | "buttonValue": "0x02",
55 | "joystickNumber": 5
56 | },
57 | {
58 | "name": "psxButton",
59 | "buttonBlock": 7,
60 | "buttonValue": "0x01",
61 | "joystickNumber": 12
62 | },
63 | {
64 | "name": "touchPad",
65 | "buttonBlock": 7,
66 | "buttonValue": "0x02",
67 | "joystickNumber": 13
68 | },
69 | {
70 | "name": "square",
71 | "buttonBlock": 5,
72 | "buttonValue": "0x10",
73 | "joystickNumber": 0
74 | },
75 | {
76 | "name": "triangle",
77 | "buttonBlock": 5,
78 | "buttonValue": "0x80",
79 | "joystickNumber": 3
80 | },
81 | {
82 | "name": "circle",
83 | "buttonBlock": 5,
84 | "buttonValue": "0x40",
85 | "joystickNumber": 2
86 | },
87 | {
88 | "name": "x",
89 | "buttonBlock": 5,
90 | "buttonValue": "0x20",
91 | "joystickNumber": 1
92 | },
93 | {
94 | "name": "dpadUp",
95 | "buttonBlock": 5,
96 | "buttonValue": "0x00",
97 | "mask": "0xF"
98 | },
99 | {
100 | "name": "dpadUpRight",
101 | "buttonBlock": 5,
102 | "buttonValue": "0x01",
103 | "mask": "0xF"
104 | },
105 | {
106 | "name": "dpadRight",
107 | "buttonBlock": 5,
108 | "buttonValue": "0x02",
109 | "mask": "0xF"
110 | },
111 | {
112 | "name": "dpadDownRight",
113 | "buttonBlock": 5,
114 | "buttonValue": "0x03",
115 | "mask": "0xF"
116 | },
117 | {
118 | "name": "dpadDown",
119 | "buttonBlock": 5,
120 | "buttonValue" : "0x04",
121 | "mask": "0xF"
122 | },
123 | {
124 | "name": "dpadDownLeft",
125 | "buttonBlock": 5,
126 | "buttonValue" : "0x05",
127 | "mask": "0xF"
128 | },
129 | {
130 | "name": "dpadLeft",
131 | "buttonBlock": 5,
132 | "buttonValue": "0x06",
133 | "mask": "0xF"
134 | },
135 | {
136 | "name": "dpadUpLeft",
137 | "buttonBlock": 5,
138 | "buttonValue": "0x07",
139 | "mask": "0xF"
140 | },
141 | {
142 | "name": "share",
143 | "buttonBlock": 6,
144 | "buttonValue": "0x10",
145 | "joystickNumber": 8
146 | },
147 | {
148 | "name": "options",
149 | "buttonBlock": 6,
150 | "buttonValue": "0x20",
151 | "joystickNumber": 9
152 | },
153 | {
154 | "name": "leftStick",
155 | "buttonBlock": 6,
156 | "buttonValue": "0x40",
157 | "joystickNumber": 10
158 | },
159 | {
160 | "name": "rightStick",
161 | "buttonBlock": 6,
162 | "buttonValue": "0x80",
163 | "joystickNumber": 11
164 | }
165 | ],
166 | "motionInputs" : [],
167 | "status" : [],
168 | "output": {
169 | "defaultBuffer":[
170 | 5,255,4,0,0,0,0,0,0,0,0
171 | ],
172 | "indexes": {
173 | "rumbleLeft": 4,
174 | "rumbleRight": 5,
175 | "red": 6,
176 | "green": 7,
177 | "blue": 8,
178 | "flashOn": 9,
179 | "flashOff": 10
180 | }
181 | },
182 | "touchPad": [{
183 | "name":"x1",
184 | "activePin": 35,
185 | "dataPinA": 37,
186 | "dataPinB": 36,
187 | "dataPinC": 38
188 | },{
189 | "name": "x2",
190 | "activePin": 39,
191 | "dataPinA": 41,
192 | "dataPinB": 40,
193 | "dataPinC": 42
194 | }]
195 | }
196 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | Examples:
2 | =========================
3 |
4 | ## Don't use this code as a starting point.
5 | I do things here that are not neccesary, you are better off looking at the readme in root as it has a cleaner implementation, this is here just so you can test it right away.
6 |
7 | If you'd like to see more examples for the DualShock 4, feel free to check out
8 | [ds-examples](https://github.com/itaisteinherz/ds-examples).
9 |
--------------------------------------------------------------------------------
/examples/consolePrintControllerEvents.js:
--------------------------------------------------------------------------------
1 | var printcontrollerEvents = function(controller, controllerConfiguration) {
2 | 'use strict';
3 | controller.on('left:move', function(data) {
4 | console.log('left Moved');
5 | console.log(data);
6 | });
7 | controller.on('right:move', function(data) {
8 | console.log('right Moved');
9 | console.log(data);
10 | });
11 | controller.on('connected', function() {
12 | console.log('connected');
13 | });
14 |
15 | controller.on('error', function(data) {
16 | console.log(data);
17 | });
18 |
19 | var pressed = function(data) {
20 | console.log(data + ": press");
21 | };
22 | var released = function(data) {
23 | console.log(data + ": release");
24 | };
25 | var analog = function(data) {
26 | console.log(data + ": analog");
27 | };
28 | var hold = function(data) {
29 | console.log(data + ": hold");
30 | };
31 | var motion = function(motionInput, data) {
32 | console.log(motionInput);
33 | console.log(data);
34 | };
35 | //subscribe to all the buttons:
36 | for (var i = 0; i < controllerConfiguration.buttons.length; i++) {
37 | controller.on(controllerConfiguration.buttons[i].name + ":press", pressed);
38 | controller.on(controllerConfiguration.buttons[i].name + ":release", released);
39 | controller.on(controllerConfiguration.buttons[i].name + ":analog", analog);
40 | controller.on(controllerConfiguration.buttons[i].name + ":hold", hold);
41 |
42 | }
43 | //subscribe to all the status events:
44 | if (controllerConfiguration.status && controllerConfiguration.status.length) {
45 | for (i = 0; i < controllerConfiguration.status.length; i++) {
46 | controller.on(controllerConfiguration.status[i].name + ":change", console.log);
47 | }
48 | }
49 | //subscribe to the motion events.
50 | controller.on('rightLeft' + ':motion', function(data) {
51 | motion('rightLeft', data);
52 | });
53 | controller.on('forwardBackward' + ':motion', function(data) {
54 | motion('forwardBackward', data);
55 | });
56 | controller.on('upDown' + ':motion', function(data) {
57 | motion('upDown', data);
58 | });
59 | };
60 |
61 | module.exports = printcontrollerEvents;
62 |
--------------------------------------------------------------------------------
/examples/consolePrintDualShock3.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var dualShock = require('./../src/dualshock.js');
3 | var dualShock3;
4 |
5 | //init the controller
6 | dualShock3 = dualShock({
7 | analogStickSmoothing: false,
8 | config: "dualShock3",
9 | logging: true
10 | });
11 |
12 | // //for a client implementation we do not need this, this is only to test the inputs.
13 | var controllerConfiguration = require('./../controllerConfigurations/dualShock3');
14 |
15 | // //init the print events
16 | var consolePrintEvents = require('./consolePrintControllerEvents')(dualShock3, controllerConfiguration);
17 |
18 | dualShock3.on("dpadup:press", () => {
19 | dualShock3.setExtras({
20 | led: 2
21 | });
22 | });
23 |
24 | dualShock3.on("dpadright:press", () => {
25 | dualShock3.setExtras({
26 | led: 4
27 | });
28 | });
29 |
30 | dualShock3.on("dpaddown:press", () => {
31 | dualShock3.setExtras({
32 | led: 8
33 | });
34 | });
35 |
36 | dualShock3.on("dpadleft:press", () => {
37 | dualShock3.setExtras({
38 | led: 16
39 | });
40 | });
41 |
42 | dualShock3.on("r2:analog", (d) => {
43 | dualShock3.setExtras({
44 | rumbleRight: d
45 | });
46 | });
47 |
48 | dualShock3.on("l2:press", (d) => {
49 | dualShock3.setExtras({
50 | rumbleLeft: 1
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/examples/consolePrintDualShock4.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var dualShock = require('./../src/dualshock.js');
3 |
4 | var dualShock4 = dualShock({
5 | config: "dualShock4",
6 | logging: true
7 | });
8 |
9 | //for a client implementation we do not need this, this is only to test the inputs.
10 | var controllerConfiguration = require('./../controllerConfigurations/dualShock4');
11 |
12 | //init the print events
13 | var consolePrintEvents = require('./consolePrintControllerEvents')(dualShock4, controllerConfiguration);
14 |
15 | dualShock4.on("dpadup:press", () => {
16 | dualShock4.setExtras({
17 | red: 255
18 | });
19 | });
20 |
21 | dualShock4.on("dpadright:press", () => {
22 | dualShock4.setExtras({
23 | green: 255
24 | });
25 | });
26 |
27 | dualShock4.on("dpaddown:press", () => {
28 | dualShock4.setExtras({
29 | blue: 255
30 | });
31 | });
32 |
33 | dualShock4.on("dpadleft:press", () => {
34 | dualShock4.setExtras({
35 | red: 255,
36 | green: 255,
37 | blue: 255
38 | });
39 | });
40 |
41 | dualShock4.on("x:press", (d) => {
42 | dualShock4.setExtras({
43 | red: 255,
44 | flashOn: 50,
45 | flashOff: 10
46 | });
47 | });
48 |
49 | dualShock4.on("r2:analog", (d) => {
50 | dualShock4.setExtras({
51 | rumbleRight: d
52 | });
53 | });
54 |
55 | dualShock4.on("l2:analog", (d) => {
56 | dualShock4.setExtras({
57 | rumbleLeft: d
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/examples/deviceDiscoveryHelp.js:
--------------------------------------------------------------------------------
1 | var HID = require('node-hid');
2 | console.log(HID.devices());
3 |
4 | var controller = new HID.HID(1356, 616);
5 |
6 | controller.on('data', function(data) {
7 | for (var i = 0; i < data.length; i++) {
8 | if (i === 30) {
9 | console.log(i + " " + data[i]);
10 | }
11 | }
12 | });
13 |
--------------------------------------------------------------------------------
/examples/deviceDiscoveryHelpLinux.js:
--------------------------------------------------------------------------------
1 | var joystick = require('joystick'),
2 | controller = new joystick(0, 256, 500);
3 |
4 | controller.on('button', console.log);
5 |
6 | controller.on('axis', console.log);
7 |
--------------------------------------------------------------------------------
/gruntfile.js:
--------------------------------------------------------------------------------
1 | const files = ['gruntfile.js',
2 | 'src/*.js',
3 | 'src/inputProcessors/*.js',
4 | 'test/*.js',
5 | 'examples/*.js'
6 | ];
7 | module.exports = function(grunt) {
8 | grunt.initConfig({
9 | watch: {
10 | files: files,
11 | tasks: ['default']
12 | },
13 | jshint: {
14 | files: files,
15 | options: {
16 | reporterOutput: "",
17 | esnext: true,
18 | node: true,
19 | globals: {
20 | describe: true,
21 | it: true,
22 | beforeEach: true
23 | }
24 | }
25 | },
26 | mochaTest: {
27 | test: {
28 | options: {
29 | reporter: 'spec'
30 | },
31 | src: ['test/*.js']
32 | }
33 | },
34 | jsbeautifier: {
35 | files: files,
36 | options: {
37 | js: {
38 | braceStyle: "collapse",
39 | breakChainedMethods: false,
40 | e4x: false,
41 | evalCode: false,
42 | indentChar: " ",
43 | indentLevel: 0,
44 | indentSize: 4,
45 | indentWithTabs: false,
46 | jslintHappy: false,
47 | keepArrayIndentation: false,
48 | keepFunctionIndentation: false,
49 | maxPreserveNewlines: 10,
50 | preserveNewlines: true,
51 | spaceBeforeConditional: true,
52 | spaceInParen: false,
53 | unescapeStrings: false,
54 | wrapLineLength: 0
55 | }
56 | }
57 | }
58 | });
59 |
60 | grunt.loadNpmTasks('grunt-contrib-watch');
61 | grunt.loadNpmTasks('grunt-contrib-jshint');
62 | grunt.loadNpmTasks('grunt-mocha-test');
63 | grunt.loadNpmTasks('grunt-jsbeautifier');
64 |
65 | grunt.registerTask('test', ['jshint', 'mochaTest']);
66 | grunt.registerTask('default', ['jshint', 'mochaTest', 'jsbeautifier']);
67 | };
68 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dualshock-controller",
3 | "version": "1.1.1",
4 | "description": "Eventing API layer over HID for the Sony DualShock 3 and DualShock 4 controllers ",
5 | "main": "./src/dualshock.js",
6 | "dependencies": {
7 | "node-hid": "^0.5.0"
8 | },
9 | "devDependencies": {
10 | "grunt": "^1.0.1",
11 | "grunt-cli": "^1.2.0",
12 | "grunt-contrib-jshint": "^1.1.0",
13 | "grunt-contrib-watch": "^1.0.0",
14 | "grunt-mocha-test": "^0.13.2",
15 | "mocha": "^3.2.0",
16 | "sinon": "^1.8",
17 | "nyc": "^10.0.0",
18 | "grunt-jsbeautifier": "^0.2.7",
19 | "jshint": "^2.5.0",
20 | "mockery": "^2.0.0"
21 | },
22 | "scripts": {
23 | "test": "grunt test",
24 | "coverage": "nyc npm test"
25 | },
26 | "engines": {
27 | "node": ">=6.9.2"
28 | },
29 | "repository": {
30 | "type": "git",
31 | "url": "https://github.com/rdepena/node-dualshock-controller.git"
32 | },
33 | "author": "ricardo de pena",
34 | "license": "MIT",
35 | "readmeFilename": "README.md",
36 | "keywords": [
37 | "ps3",
38 | "controller",
39 | "gamepad",
40 | "dualshock",
41 | "dualshock3",
42 | "dualshock4",
43 | "dual shock 3",
44 | "dual shock 4",
45 | "node-hid"
46 | ]
47 | }
48 |
--------------------------------------------------------------------------------
/src/analogs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | // Module dependencies.
3 | var dsutilities = require('./utilities'),
4 | Smoothing = require('./smoothing'),
5 | config = require('./config');
6 |
7 | //Proccess Analog stick events.
8 | var Analogs = function(controller) {
9 |
10 | var varianceThreshhold = 1,
11 | smoothInput = config.getOptions().analogStickSmoothing,
12 | outputSmoothing = new Smoothing(smoothInput),
13 | analogSticks = config.getControllerConfig().analogSticks;
14 |
15 | //Private methods
16 | var processStick = function(analogStick, data) {
17 | var currentValue = {
18 | x: data[analogStick.x],
19 | y: data[analogStick.y]
20 | },
21 | previousValue = {
22 | x: outputSmoothing.readLastPosition(analogStick.name + 'x'),
23 | y: outputSmoothing.readLastPosition(analogStick.name + 'y')
24 | };
25 |
26 | //we only raise an event if both
27 | if (dsutilities.isWithinVariance(previousValue.x, currentValue.x, varianceThreshhold) ||
28 | dsutilities.isWithinVariance(previousValue.y, currentValue.y, varianceThreshhold)) {
29 |
30 | currentValue.x = outputSmoothing.smooth(analogStick.name + 'x', currentValue.x);
31 | currentValue.y = outputSmoothing.smooth(analogStick.name + 'y', currentValue.y);
32 |
33 | // Update and emit
34 | if (controller[analogStick.name]) {
35 | controller[analogStick.name].x = currentValue.x;
36 | controller[analogStick.name].y = currentValue.y;
37 | }
38 | controller.emit(analogStick.name + ':move', currentValue);
39 | }
40 | };
41 |
42 | // Public methods
43 | //process all the analog events.
44 | this.process = function(data) {
45 | for (var i = 0; i < analogSticks.length; i++) {
46 | processStick(analogSticks[i], data);
47 | }
48 | };
49 | };
50 |
51 | module.exports = Analogs;
52 |
--------------------------------------------------------------------------------
/src/buttons.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | // Module dependencies.
3 | var config = require('./config'),
4 | dsutilities = require('./utilities');
5 |
6 | //Proccess button events.
7 | var Buttons = function(controller) {
8 |
9 | var buttons = config.getControllerConfig().buttons;
10 |
11 | // convert strings to numbers, e.g. "0x01" to 0x01
12 | // must be converted because JSON doesn't allow numbers with leading zeros
13 | buttons.forEach(function(button) {
14 | if (typeof button.buttonValue == "string") {
15 | button.buttonValue = parseInt(button.buttonValue);
16 | }
17 |
18 | if (typeof button.mask == "string") {
19 | button.mask = parseInt(button.mask);
20 | } else if (!(button.mask instanceof Number)) {
21 | button.mask = 0xFF;
22 | }
23 |
24 | //generate event name aliases:
25 | button.eventPrefixes = dsutilities.generateEventPrefixAliases(button.name);
26 | });
27 |
28 |
29 | var buffer = {};
30 |
31 | //Private methods
32 | var emitEvent = function(button, eventText, data) {
33 | button.eventPrefixes.forEach(function(eventPrefix) {
34 | controller.emit(eventPrefix + eventText, data);
35 | });
36 | };
37 | var processButton = function(button, data) {
38 | //make sure the data contains a value for the specified block
39 | //and bitwise operation for the button value
40 |
41 | var block = data[button.buttonBlock] & button.mask;
42 | var hit = (block & button.buttonValue) == button.buttonValue;
43 | var value = 0;
44 | var state = 0; // 0: up, 1: down, 2: hold
45 |
46 | // special case for the dualshock 4's dpadUp button as it causes the
47 | // lower 8 bits of it's block to be zeroed
48 | if (!button.buttonValue) {
49 | hit = !block;
50 | }
51 |
52 | // special case for dualshock 4's dpad - they are not bitmasked values as
53 | // they cannot be pressed together - ie. up, left and upleft are three
54 | // different values - upleft is not equal to up & left
55 | if (button.buttonBlock == 5 && block < 0x08) {
56 | hit = block == button.buttonValue;
57 | }
58 |
59 | if (hit) {
60 | value = 1;
61 |
62 | //if the button is in the released state.
63 | if (!buffer[button.name]) {
64 | state = 1;
65 | buffer[button.name] = true;
66 | emitEvent(button, ':press', button.name);
67 | } else {
68 | state = 2;
69 | emitEvent(button, ':hold', button.name);
70 | }
71 |
72 | //send the analog data
73 | if (button.analogPin && data[button.analogPin]) {
74 | emitEvent(button, ':analog', data[button.analogPin]);
75 | }
76 |
77 | } else if (buffer[button.name]) {
78 | //button was pressed and is not released
79 | buffer[button.name] = false;
80 |
81 | //button is no longer pressed, emit a analog 0 event.
82 | if (button.analogPin) {
83 | emitEvent(button, ':analog', 0);
84 | }
85 | //emit the released event.
86 | emitEvent(button, ':release', button.name);
87 | }
88 |
89 | if (controller[button.name]) {
90 | controller[button.name].value = value;
91 | controller[button.name].state = state;
92 | }
93 | };
94 |
95 | // Public methods
96 | //process all the analog events.
97 | this.process = function(data) {
98 | for (var i = 0; i < buttons.length; i++) {
99 | processButton(buttons[i], data);
100 | }
101 | };
102 | };
103 |
104 | module.exports = Buttons;
105 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | // Module dependencies.
3 | // we will expose these objects via the manager.
4 | var options,
5 | controllerConfig;
6 |
7 | //provides access to the current options and configs.
8 | var config = {
9 | setOptions: function(opts) {
10 | //no options were passed
11 | options = opts || {};
12 |
13 | // Defaults:
14 | var defaultValues = {
15 | config: "dualShock3",
16 | accelerometerSmoothing: true,
17 | analogStickSmoothing: false,
18 | logging: false,
19 | forceNodeHid: false,
20 | linuxJoystickId: 0
21 | };
22 |
23 | for (var name in defaultValues) {
24 | if (defaultValues.hasOwnProperty(name)) {
25 | var target = options[name];
26 | var orig = defaultValues[name];
27 |
28 | if (!target) {
29 | options[name] = orig;
30 | }
31 | }
32 | }
33 |
34 | var controllerConfiguration;
35 | //use passed config or load from built-in configs
36 | if (typeof options.config === "object") {
37 | controllerConfiguration = options.config;
38 | } else {
39 | controllerConfiguration = require('./../controllerConfigurations/' + options.config);
40 | }
41 |
42 | //set the current controllerConfiguration
43 | config.setControllerConfig(controllerConfiguration);
44 | },
45 | getOptions: function() {
46 | return options;
47 | },
48 | setControllerConfig: function(config) {
49 | controllerConfig = config;
50 | },
51 | getControllerConfig: function() {
52 | return controllerConfig;
53 | }
54 | };
55 |
56 | module.exports = config;
57 |
--------------------------------------------------------------------------------
/src/controller.js:
--------------------------------------------------------------------------------
1 | // Module dependencies.
2 | const util = require('util'),
3 | dsutilities = require('./utilities'),
4 | Emitter = require('events').EventEmitter,
5 | Gyro = require('./gyro'),
6 | Analogs = require('./analogs'),
7 | Buttons = require('./buttons'),
8 | Status = require('./status'),
9 | HID = require('node-hid'),
10 | config = require('./config'),
11 | TouchPad = require('./touchpad');
12 |
13 | //generic controller object, it will need a controller Configuration with a buttons array passed into its connect function.
14 | const Controller = function() {
15 | 'use strict';
16 | Emitter.call(this);
17 |
18 | const controllerConfig = config.getControllerConfig(),
19 | options = config.getOptions(),
20 | indexes = controllerConfig.output.indexes,
21 | analogs = new Analogs(this),
22 | buttons = new Buttons(this),
23 | gyro = new Gyro(this),
24 | status = new Status(this),
25 | touchPad = new TouchPad(this);
26 |
27 | let device = null;
28 |
29 | [{
30 | type: 'analogSticks',
31 | properties: [{
32 | name: 'x',
33 | initialValue: 0
34 | }, {
35 | name: 'y',
36 | initialValue: 0
37 | }]
38 | }, {
39 | type: 'buttons',
40 | properties: [{
41 | name: 'state',
42 | initialValue: 0
43 | }, {
44 | name: 'value',
45 | initialValue: 0
46 | }]
47 | }, {
48 | type: 'motionInputs',
49 | properties: [{
50 | name: 'value',
51 | initialValue: 0
52 | }, {
53 | name: 'direction',
54 | initialValue: 0
55 | }]
56 | }, {
57 | type: 'status',
58 | properties: [{
59 | name: 'state',
60 | initialValue: ''
61 | }]
62 | }].forEach(function(setup) {
63 | const entities = controllerConfig[setup.type],
64 | properties = setup.properties;
65 |
66 | if (entities.length) {
67 | entities.forEach(function(entity) {
68 | this[entity.name] = properties.reduce(function(accum, property) {
69 | return (accum[property.name] = property.initialValue, accum);
70 | }, {});
71 | }, this);
72 | }
73 | }, this);
74 |
75 | //Private methods
76 | //emit an error event or log it to the console.
77 | const handleException = function(ex) {
78 | //if exception was generated within our stream
79 | if (this && this.emit) {
80 | this.emit('error', ex);
81 | } else {
82 | dsutilities.warn(ex);
83 | throw (ex);
84 | }
85 | };
86 |
87 | //process data from HID connected device.
88 | const processFrame = function(data) {
89 | if (controllerConfig.motionInputs) {
90 | gyro.process(data);
91 | }
92 | if (controllerConfig.analogSticks) {
93 | analogs.process(data);
94 | }
95 | if (controllerConfig.buttons) {
96 | buttons.process(data);
97 | }
98 | if (controllerConfig.status) {
99 | status.process(data);
100 | }
101 | if (controllerConfig.touchPad) {
102 | touchPad.process(data);
103 | }
104 | };
105 |
106 | const isController = function(device) {
107 | return device.vendorId == controllerConfig.vendorId && device.productId == controllerConfig.productId;
108 | };
109 |
110 | // Public methods
111 | this.connect = function() {
112 | dsutilities.warn('connect method is deprecated, controller now connects upon declaration.');
113 | };
114 |
115 | this.disconnect = function() {
116 | if (device && device.close) {
117 | device.close();
118 | }
119 | this.emit('disconnecting');
120 | dsutilities.warn('node dualshock disconnecting');
121 | };
122 |
123 | // Used to set controller rumble and light
124 | this.setExtras = function(data) {
125 |
126 | let buff = controllerConfig.output.defaultBuffer.slice();
127 |
128 | Object.keys(data).forEach(k => {
129 | buff[indexes[k]] = data[k];
130 | });
131 | device.write(buff);
132 | };
133 |
134 | //connect to the controller.
135 | if (typeof options.device === 'undefined') {
136 | dsutilities.warn('node dualshock connecting');
137 |
138 | const deviceMeta = HID.devices()
139 | .filter(isController)[0];
140 | if (deviceMeta) {
141 | device = new HID.HID(deviceMeta.path);
142 | } else {
143 | handleException(new Error(`device with VID:${controllerConfig.vendorId} PID:${controllerConfig.productId} not found`));
144 | }
145 |
146 | } else {
147 | // Allow user-specified device
148 | device = options.device;
149 | }
150 |
151 | device.on('data', processFrame.bind(this));
152 | device.on('error', handleException.bind(this));
153 |
154 | //subscribe to the exit event:
155 | process.on('exit', this.disconnect.bind(this));
156 | };
157 |
158 | //need to inherit from event emiter.
159 | util.inherits(Controller, Emitter);
160 |
161 | module.exports = Controller;
162 |
--------------------------------------------------------------------------------
/src/dualshock.js:
--------------------------------------------------------------------------------
1 | // Module dependencies.
2 | var Controller = require('./controller'),
3 | config = require('./config');
4 |
5 | // This is the app entry point.
6 | // options you can pass:
7 | // {
8 | // config : "File from controllerConfigurations" or a JS object containing configuration,
9 | // accelerometerSmoothing : true/false, this will activate motion/acelerometer output smoothing. true by default.
10 | // analogStickSmoothing : true/false, this will activate analog thumb stick smoothing
11 | // }
12 | var dualShock = function(options) {
13 | 'use strict';
14 |
15 | //set the current options
16 | config.setOptions(options);
17 |
18 | //returns the controller.
19 | return new Controller();
20 | };
21 |
22 | module.exports = dualShock;
23 |
--------------------------------------------------------------------------------
/src/gyro.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | // Module dependencies.
3 | var dsutilities = require('./utilities'),
4 | Smoothing = require('./smoothing'),
5 | config = require('./config');
6 |
7 | //Proccess button events.
8 | var motionProcessor = function(controller) {
9 |
10 | var varianceThreshhold = 1,
11 | smoothInput = config.getOptions().accelerometerSmoothing,
12 | outputSmoothing = new Smoothing(smoothInput),
13 | motionInputs = config.getControllerConfig().motionInputs;
14 |
15 | //generate event name aliases:
16 | motionInputs.forEach(function(motionAxis) {
17 | motionAxis.eventPrefixes = dsutilities.generateEventPrefixAliases(motionAxis.name);
18 | });
19 |
20 | //Private methods
21 | //data corrections so that each dirrection has a 0 throug x value
22 | var correctData = function(motionAxis, data) {
23 | var value;
24 | //ensuring that both directions start from 0 and move to -x or x;
25 | if (data[motionAxis.directionPin] === 1) {
26 | //we need the values to be from 0 to x.
27 | value = 255 - data[motionAxis.valuePin];
28 | } else if (data[motionAxis.directionPin] === 2) {
29 | //going in the oposite direction, we need to values to be from 0 to -x;
30 | value = data[motionAxis.valuePin] * -1;
31 | }
32 |
33 | //return an object with both value and dirrection.
34 | return {
35 | direction: data[motionAxis.directionPin],
36 | value: value
37 | };
38 | };
39 |
40 | //process the axis movement.
41 | var processAxis = function(motionAxis, data) {
42 | //every motion will have a dirrection and a value
43 | var motionValue = correctData(motionAxis, data),
44 | lastPosition = outputSmoothing.readLastPosition(motionAxis.name);
45 |
46 | //check if the values are within variance
47 | if (dsutilities.isWithinVariance(lastPosition, motionValue.value, varianceThreshhold)) {
48 | motionValue.value = outputSmoothing.smooth(motionAxis.name, motionValue.value);
49 |
50 | // Don't assign motionValue directly to controller[motionAxis.name],
51 | // this will break the reference.
52 | if (controller[motionAxis.name]) {
53 | controller[motionAxis.name].value = motionValue.value;
54 | controller[motionAxis.name].direction = motionValue.direction;
55 | }
56 |
57 | motionAxis.eventPrefixes.forEach(function(eventPrefix) {
58 | controller.emit(eventPrefix + ':motion', motionValue);
59 | });
60 | }
61 | };
62 |
63 | // Public methods
64 | //process all configured motion inputs.
65 | this.process = function(data) {
66 | for (var i = 0; i < motionInputs.length; i++) {
67 | processAxis(motionInputs[i], data);
68 | }
69 | };
70 | };
71 |
72 | module.exports = motionProcessor;
73 |
--------------------------------------------------------------------------------
/src/smoothing.js:
--------------------------------------------------------------------------------
1 | // Module dependencies.
2 |
3 | //smooths data with moving average
4 | var outputSmoothing = function(smoothInput) {
5 | 'use strict';
6 |
7 | var buffer = {},
8 | maxFrames = 5;
9 |
10 | // Public methods
11 | this.readLastPosition = function(motionAxisName) {
12 | var axisBuffer = buffer[motionAxisName];
13 | return axisBuffer ? axisBuffer[axisBuffer.length - 1] : null;
14 | };
15 |
16 | this.addToBuffer = function(motionAxisName, value) {
17 | if (buffer[motionAxisName]) {
18 | //add the current value to the buffer
19 | buffer[motionAxisName].push(value);
20 |
21 | //remove the head of the buffer
22 | if (buffer[motionAxisName].length > maxFrames) {
23 | buffer[motionAxisName].shift();
24 | }
25 | } else {
26 | //create an array with the value.
27 | buffer[motionAxisName] = [value];
28 | }
29 | };
30 |
31 | //smooth using a moving average.
32 | this.smooth = function(motionAxisName, value) {
33 | this.addToBuffer(motionAxisName, value);
34 | var axisBuffer = buffer[motionAxisName],
35 | sum = 0,
36 | smoothedVal = value;
37 |
38 | if (smoothInput) {
39 | for (var i = 0; i < axisBuffer.length; i++) {
40 | sum += axisBuffer[i];
41 | }
42 | smoothedVal = Math.floor(sum / axisBuffer.length);
43 | }
44 |
45 | return smoothedVal;
46 |
47 | };
48 | };
49 |
50 | module.exports = outputSmoothing;
51 |
--------------------------------------------------------------------------------
/src/status.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | // Module dependencies.
3 |
4 | var config = require('./config');
5 |
6 | //Proccess button events.
7 | var Status = function(controller) {
8 |
9 | var buffer = {},
10 | status = config.getControllerConfig().status;
11 |
12 | var processControllerStatus = function(category, data) {
13 | var state;
14 | for (var i = 0; i < category.states.length; i++) {
15 | if (data[category.pin] === category.states[i].value) {
16 | state = category.states[i].state;
17 | }
18 | }
19 |
20 | if (buffer[category.name] !== state) {
21 | if (controller[category.name]) {
22 | controller[category.name].state = state;
23 | }
24 | controller.emit(category.name + ':change', state);
25 | }
26 | buffer[category.name] = state;
27 | };
28 |
29 | this.process = function(data) {
30 | for (var i = 0; i < status.length; i++) {
31 | processControllerStatus(status[i], data);
32 | }
33 | };
34 | };
35 |
36 | module.exports = Status;
37 |
--------------------------------------------------------------------------------
/src/touchpad.js:
--------------------------------------------------------------------------------
1 | const config = require('./config');
2 |
3 | function genpBufferFromConf(tpAxis) {
4 | return {
5 | name: tpAxis.name,
6 | active: false,
7 | data: {
8 | x: 0,
9 | y: 0
10 | }
11 | };
12 | }
13 |
14 | module.exports = function TouchPad(controller) {
15 | const touchPad = config.getControllerConfig().touchPad;
16 | let pBuffer = {};
17 |
18 | function processIsActive(buffer, tpAxis) {
19 | const active = buffer[tpAxis.activePin] < 128;
20 | const axisBuffer = pBuffer[tpAxis.name];
21 | const evt = active ? 'active' : 'inactive';
22 |
23 | if (active !== axisBuffer.active) {
24 | controller.emit(`touchpad:${tpAxis.name}:${evt}`);
25 | }
26 |
27 | axisBuffer.active = active;
28 | }
29 |
30 | function processData(buffer, tpAxis) {
31 | const axisBuffer = pBuffer[tpAxis.name];
32 |
33 | if (axisBuffer.active) {
34 | axisBuffer.data.x = ((buffer[tpAxis.dataPinA] & 15) << 8 | buffer[tpAxis.dataPinB]);
35 | axisBuffer.data.y = buffer[tpAxis.dataPinC] << 4 | ((buffer[tpAxis.dataPinA] & 240) >> 4);
36 | controller.emit(`touchpad:${tpAxis.name}`, axisBuffer.data);
37 | }
38 |
39 | }
40 |
41 | this.process = function process(buffer) {
42 | for (let i = 0; i < touchPad.length; i++) {
43 | //if we have not built a pBuffer profile for this axis lets build it.
44 | if (!pBuffer[touchPad[i].name]) {
45 | pBuffer[touchPad[i].name] = genpBufferFromConf(touchPad[i]);
46 | }
47 |
48 | processIsActive(buffer, touchPad[i]);
49 | processData(buffer, touchPad[i]);
50 | }
51 | };
52 | };
53 |
--------------------------------------------------------------------------------
/src/utilities.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | // Module dependencies.
3 |
4 | var config = require('./config');
5 |
6 | var unique = function unique(x) {
7 | var result = [];
8 | for (var i = 0; i < x.length; i++) {
9 | if ((result.indexOf(x[i]) < 0)) {
10 | result.push(x[i]);
11 | }
12 | }
13 | return result;
14 | };
15 |
16 | //provide a few utility functions.
17 | module.exports = {
18 |
19 | //reduces noise from the controller
20 | isWithinVariance: function(x, y, varianceThreshhold) {
21 | return Math.abs(x - y) > varianceThreshhold;
22 | },
23 | warn: function(message) {
24 | if (config.getOptions().logging) {
25 | console.log(message);
26 | }
27 | },
28 | generateEventPrefixAliases: function(eventPrefix) {
29 | return unique([
30 | eventPrefix,
31 | eventPrefix.toLowerCase()
32 | ]);
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/test/analog.tests.js:
--------------------------------------------------------------------------------
1 | // Module dependencies.
2 | var Analogs = require('../src/analogs'),
3 | assert = require('assert'),
4 | sinon = require('sinon'),
5 | EventEmitter = require('events').EventEmitter,
6 | config = require('../src/config');
7 |
8 | describe('the Analogs component', function() {
9 | 'use strict';
10 |
11 | var mockConfig = [{
12 | "name": "analog",
13 | "x": 0,
14 | "y": 1
15 | }],
16 | instance = [{
17 | name: 'process'
18 | }],
19 | dataA = [50, 65],
20 | dataB = [0, 0],
21 | analogs,
22 | emitter,
23 | spy;
24 |
25 | beforeEach(function() {
26 | emitter = new EventEmitter();
27 | config.setOptions({
28 | analogStickSmoothing: false
29 | });
30 | config.setControllerConfig({
31 | analogSticks: mockConfig
32 | });
33 | analogs = new Analogs(emitter);
34 | spy = new sinon.spy();
35 | });
36 |
37 | describe('object instance', function() {
38 | it('should have the following shape', function() {
39 | //make sure we find these functions.
40 | instance.forEach(function(method) {
41 | assert.equal(typeof analogs[method.name], 'function');
42 | });
43 | });
44 | });
45 |
46 | describe('move events', function() {
47 | it('should invoke the move event', function() {
48 | emitter.on('analog:move', spy);
49 | analogs.process(dataA);
50 |
51 | assert.equal(spy.called, true);
52 | });
53 | it('should not invoke the move event', function() {
54 | emitter.on('analog:move', spy);
55 | analogs.process(dataB);
56 |
57 | assert.equal(spy.called, false);
58 | });
59 | it('should invoke the move event with zero', function() {
60 | analogs.process(dataA);
61 | emitter.on('analog:move', spy);
62 | analogs.process(dataB);
63 |
64 | assert.equal(spy.called, true);
65 | });
66 | });
67 |
68 | describe('return values', function() {
69 | it('should return the analog values', function() {
70 | emitter.on('analog:move', spy);
71 | analogs.process(dataA);
72 | var expectedValue = {
73 | x: 50,
74 | y: 65
75 | },
76 | spyArgument = spy.args[0][0];
77 |
78 | assert.equal(expectedValue.x, spyArgument.x);
79 | assert.equal(expectedValue.y, spyArgument.y);
80 | assert.equal(spy.called, true);
81 | });
82 | });
83 |
84 | });
85 |
--------------------------------------------------------------------------------
/test/buttons.tests.js:
--------------------------------------------------------------------------------
1 | // Module dependencies.
2 | var Buttons = require('../src/buttons'),
3 | assert = require('assert'),
4 | sinon = require('sinon'),
5 | EventEmitter = require('events').EventEmitter,
6 | config = require('../src/config');
7 |
8 | describe('the Buttons component', function() {
9 | 'use strict';
10 |
11 | var mockConfig = [{
12 | "name": "buttonName",
13 | "buttonBlock": 0,
14 | "buttonValue": "0x08",
15 | "analogPin": 1
16 | }, {
17 | "name": "dpadUp",
18 | "buttonBlock": 5,
19 | "buttonValue": "0x00",
20 | "mask": "0xF"
21 | }, {
22 | "name": "dpadDown",
23 | "buttonBlock": 5,
24 | "buttonValue": "0x01",
25 | "mask": "0xF"
26 | }],
27 | instance = [{
28 | name: 'process'
29 | }],
30 | dataA = [8, 170],
31 | dataB = [0, 0],
32 | buttons,
33 | emitter,
34 | spy,
35 | spyLowerCaseEvents;
36 |
37 | beforeEach(function() {
38 | emitter = new EventEmitter();
39 | config.setControllerConfig({
40 | buttons: mockConfig
41 | });
42 | buttons = new Buttons(emitter);
43 | spy = new sinon.spy();
44 | spyLowerCaseEvents = new sinon.spy();
45 |
46 | });
47 |
48 | describe('object instance', function() {
49 | it('should have the following shape', function() {
50 | //make sure we find these functions.
51 | instance.forEach(function(method) {
52 | assert.equal(typeof buttons[method.name], 'function');
53 | });
54 | });
55 | });
56 |
57 | describe('press events', function() {
58 | it('should envoke the buttonName:press', function() {
59 | emitter.on('buttonName:press', spy);
60 | emitter.on('buttonname:press', spyLowerCaseEvents);
61 | buttons.process(dataA);
62 |
63 | assert.equal(spy.called, true);
64 | assert.equal(spyLowerCaseEvents.called, true);
65 | });
66 | it('should not envoke the buttonName:press', function() {
67 | emitter.on('buttonName:release', spy);
68 | emitter.on('buttonname:release', spyLowerCaseEvents);
69 | buttons.process(dataB);
70 |
71 | assert.equal(spy.called, false);
72 | assert.equal(spyLowerCaseEvents.called, false);
73 | });
74 | });
75 |
76 | describe('release events', function() {
77 | it('should envoke the buttonName:release', function() {
78 | emitter.on('buttonName:release', spy);
79 | emitter.on('buttonname:release', spyLowerCaseEvents);
80 | buttons.process(dataA);
81 | buttons.process(dataB);
82 |
83 | assert.equal(spy.called, true);
84 | assert.equal(spyLowerCaseEvents.called, true);
85 | });
86 | it('should not envoke the buttonName:release', function() {
87 | emitter.on('buttonName:release', spy);
88 | emitter.on('buttonname:release', spyLowerCaseEvents);
89 | buttons.process(dataA);
90 |
91 | assert.equal(spy.called, false);
92 | assert.equal(spyLowerCaseEvents.called, false);
93 | });
94 | });
95 |
96 | describe('button hold', function() {
97 | it('should raise the hold event', function() {
98 | emitter.on('buttonName:hold', spy);
99 | emitter.on('buttonname:hold', spyLowerCaseEvents);
100 | buttons.process(dataA);
101 | buttons.process(dataA);
102 | assert.equal(spy.args[0][0], 'buttonName');
103 | assert.equal(spy.called, true);
104 | assert.equal(spyLowerCaseEvents.called, true);
105 | });
106 |
107 | it('should not raise the hold event', function() {
108 | emitter.on('buttonName:hold', spy);
109 | emitter.on('buttonname:hold', spyLowerCaseEvents);
110 | buttons.process(dataB);
111 | buttons.process(dataB);
112 | assert.equal(spy.called, false);
113 | assert.equal(spyLowerCaseEvents.called, false);
114 | });
115 | });
116 |
117 | describe('button analog', function() {
118 | it('should raise the analog event', function() {
119 | emitter.on('buttonName:analog', spy);
120 | emitter.on('buttonname:analog', spyLowerCaseEvents);
121 | buttons.process(dataA);
122 |
123 | assert.equal(spy.args[0][0], dataA[1]);
124 | assert.equal(spy.called, true);
125 | assert.equal(spyLowerCaseEvents.called, true);
126 | });
127 |
128 | it('should not raise the analog event', function() {
129 | emitter.on('buttonName:analog', spy);
130 | emitter.on('buttonname:analog', spyLowerCaseEvents);
131 | buttons.process(dataB);
132 |
133 | assert.equal(spy.called, false);
134 | assert.equal(spyLowerCaseEvents.called, false);
135 | });
136 | });
137 |
138 | describe('ps4 dpad up button', function() {
139 | it('should emit the dpadUp:press event', function() {
140 | emitter.on('dpadUp:press', spy);
141 | emitter.on('dpadup:press', spyLowerCaseEvents);
142 | buttons.process([0, 0, 0, 0, 0, 0]);
143 |
144 | assert.equal(spy.called, true);
145 | assert.equal(spyLowerCaseEvents.called, true);
146 | });
147 |
148 | it('should not emit the dpadDown:press event', function() {
149 | emitter.on('dpadDown:press', spy);
150 | emitter.on('dpaddown:press', spyLowerCaseEvents);
151 | buttons.process([0, 0, 0, 0, 0, 0]);
152 |
153 | assert.equal(spy.called, false);
154 | assert.equal(spyLowerCaseEvents.called, false);
155 | });
156 | });
157 |
158 | describe('ps4 dpad down button', function() {
159 | it('should emit the dpadDown:press event', function() {
160 | emitter.on('dpadDown:press', spy);
161 | emitter.on('dpaddown:press', spyLowerCaseEvents);
162 | buttons.process([0, 0, 0, 0, 0, parseInt("00001001", 2)]);
163 |
164 | assert.equal(spy.called, true);
165 | assert.equal(spyLowerCaseEvents.called, true);
166 | });
167 |
168 | it('should not emit the dpadUp:press event', function() {
169 | emitter.on('dpadUp:press', spy);
170 | emitter.on('dpadup:press', spyLowerCaseEvents);
171 | buttons.process([0, 0, 0, 0, 0, parseInt("00001001", 2)]);
172 |
173 | assert.equal(spy.called, false);
174 | assert.equal(spyLowerCaseEvents.called, false);
175 | });
176 | });
177 |
178 | });
179 |
--------------------------------------------------------------------------------
/test/config.tests.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var assert = require('assert');
4 |
5 | describe('The Config component', function() {
6 | var mockConfig = {
7 | vendorId: 1556,
8 | productId: 616,
9 | output: []
10 | },
11 | mockOptions = {
12 | config: 'dualShock3',
13 | accelerometerSmoothing: true,
14 | logging: false
15 | },
16 | instance = [{
17 | name: 'setOptions'
18 | }, {
19 | name: 'getOptions'
20 | }, {
21 | name: 'setControllerConfig'
22 | }, {
23 | name: 'getControllerConfig'
24 | }],
25 | defaultOptionsInstance = [{
26 | name: 'config'
27 | }, {
28 | name: 'accelerometerSmoothing'
29 | }, {
30 | name: 'analogStickSmoothing'
31 | }],
32 | configA,
33 | configB;
34 |
35 | beforeEach(function() {
36 | configA = require('../src/config');
37 | configB = require('../src/config');
38 | });
39 |
40 |
41 | describe('object instance', function() {
42 | it('should have the following shape', function() {
43 | instance.forEach(function(method) {
44 | assert.equal(typeof configA[method.name], 'function');
45 | });
46 | });
47 | });
48 |
49 | describe('option methods', function() {
50 | it('should be able to save options', function() {
51 | configA.setOptions(mockOptions);
52 | assert.equal(configA.getOptions(), mockOptions);
53 | });
54 |
55 | it('should provide a single object accross instances', function() {
56 | configA.setOptions(mockOptions);
57 | assert.equal(configA.getOptions(), configB.getOptions());
58 | });
59 | });
60 |
61 | describe('controllerConfig methods', function() {
62 | it('should be able to save controllerConfig settings', function() {
63 | configA.setControllerConfig(mockConfig);
64 | //change the object
65 | mockConfig.vendorId = 22;
66 | assert.equal(configA.getControllerConfig(), mockConfig);
67 | });
68 |
69 | it('should provide a single object accross instances', function() {
70 | configA.setControllerConfig(mockConfig);
71 | assert.equal(configA.getControllerConfig(), configB.getControllerConfig());
72 | });
73 | });
74 |
75 | describe('default values', function() {
76 | beforeEach(function() {
77 | configA.setOptions();
78 | });
79 | it('should apply default values', function() {
80 | var ops = configA.getOptions();
81 | defaultOptionsInstance.forEach(function(property) {
82 | assert.notEqual(ops[property.name], void 0);
83 | });
84 | });
85 | it('should load default config', function() {
86 | var controllerConfig = configA.getControllerConfig();
87 | assert.notEqual(controllerConfig, null);
88 | assert.notEqual(controllerConfig, void 0);
89 | });
90 | });
91 | });
92 |
--------------------------------------------------------------------------------
/test/dualshock.tests.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert'),
2 | Emitter = require('events').EventEmitter,
3 | config = require('../src/config'),
4 | mockery = require('mockery');
5 |
6 | function Device() {
7 | Emitter.call(this);
8 | }
9 |
10 | Device.prototype = Object.create(Emitter.prototype, {
11 | constructor: {
12 | value: Device
13 | },
14 | close: {
15 | value: function() {}
16 | }
17 | });
18 |
19 | var config = {
20 | analogSticks: [{
21 | name: 'foo',
22 | x: 0,
23 | y: 1
24 | }],
25 | buttons: [{
26 | name: 'bar',
27 | buttonBlock: 0,
28 | buttonValue: 0x08,
29 | analogPin: 1
30 | }],
31 | motionInputs: [{
32 | name: 'baz',
33 | directionPin: 0,
34 | valuePin: 1
35 | }],
36 | status: [{
37 | name: 'quz',
38 | pin: 5,
39 | states: [{
40 | value: 0,
41 | state: 'Charging'
42 | }, {
43 | value: 2,
44 | state: 'Charging'
45 | }, {
46 | value: 3,
47 | state: '40%'
48 | }]
49 | }],
50 | output: []
51 | };
52 |
53 | var analogs = config.analogSticks;
54 | var buttons = config.buttons;
55 | var motions = config.motionInputs;
56 | var status = config.status;
57 |
58 | describe('the DualShock component', function() {
59 | //enable mockery and mock node-hid:
60 | mockery.enable();
61 | var nodeHidMock = {
62 | HID: function(vendor, productId) {
63 | return {
64 | on: function() {
65 | //could use this at some point. nothing atm.
66 | }
67 | };
68 | }
69 | };
70 | //register mock node-hid.
71 | mockery.registerMock('node-hid', nodeHidMock);
72 |
73 | //once mockery is up we can require the dualshock module.
74 | var DualShock = require('./../src/dualshock.js'),
75 | controller, device;
76 |
77 | before(function() {
78 | device = new Device();
79 | controller = DualShock({
80 | config: config,
81 | device: device
82 | });
83 | });
84 |
85 | //disable mockery so it does not interfere with other tests.
86 | after(function() {
87 | mockery.deregisterMock('node-hid');
88 | mockery.disable();
89 | });
90 |
91 | beforeEach(function() {
92 | controller.removeAllListeners(Object.keys(controller._events));
93 | });
94 |
95 | describe('analog properties', function() {
96 | analogs.forEach(function(analog) {
97 | it(analog.name, function() {
98 | assert.equal(controller[analog.name].x, 0);
99 | assert.equal(controller[analog.name].y, 0);
100 |
101 | device.emit('data', [100, 100]);
102 |
103 | assert.equal(controller[analog.name].x, 100);
104 | assert.equal(controller[analog.name].y, 100);
105 | });
106 | });
107 | });
108 |
109 | describe('button properties', function() {
110 | buttons.forEach(function(button) {
111 | it(button.name, function() {
112 | assert.equal(controller[button.name].value, 0);
113 | assert.equal(controller[button.name].state, 0);
114 |
115 | device.emit('data', [8, 170]);
116 |
117 | assert.equal(controller[button.name].value, 1);
118 | assert.equal(controller[button.name].state, 1);
119 |
120 | device.emit('data', [8, 170]);
121 |
122 | assert.equal(controller[button.name].value, 1);
123 | assert.equal(controller[button.name].state, 2);
124 |
125 | device.emit('data', [0, 0]);
126 |
127 | assert.equal(controller[button.name].value, 0);
128 | assert.equal(controller[button.name].state, 0);
129 | });
130 | });
131 | });
132 |
133 | describe('motion properties', function() {
134 | motions.forEach(function(motion) {
135 | it(motion.name, function() {
136 | assert.equal(controller[motion.name].value, 0);
137 | assert.equal(controller[motion.name].direction, 0);
138 |
139 | device.emit('data', [1, 233]);
140 |
141 | assert.equal(controller[motion.name].value, 22);
142 | assert.equal(controller[motion.name].direction, 1);
143 | });
144 | });
145 | });
146 |
147 | describe('status properties', function() {
148 | status.forEach(function(stat) {
149 | it(stat.name, function() {
150 | device.emit('data', [0, 0, 0, 0, 1, 3]);
151 | assert.equal(controller[stat.name].state, '40%');
152 |
153 | device.emit('data', [0, 0, 0, 0, 1, 2]);
154 | assert.equal(controller[stat.name].state, 'Charging');
155 | });
156 | });
157 | });
158 | });
159 |
--------------------------------------------------------------------------------
/test/gyro.tests.js:
--------------------------------------------------------------------------------
1 | // Module dependencies.
2 | var Gyro = require('../src/gyro'),
3 | assert = require('assert'),
4 | sinon = require('sinon'),
5 | EventEmitter = require('events').EventEmitter,
6 | config = require('../src/config');
7 |
8 | describe('the Gyro component', function() {
9 | 'use strict';
10 |
11 | var mockConfig = [{
12 | name: "dirrection",
13 | directionPin: 0,
14 | valuePin: 1
15 | }],
16 | instance = [{
17 | name: 'process'
18 | }],
19 | dataA = [1, 130],
20 | dataB = [2, 130],
21 | emitter,
22 | spy,
23 | gyro;
24 |
25 | beforeEach(function() {
26 | emitter = new EventEmitter();
27 | spy = sinon.spy();
28 | config.setOptions({
29 | accelerometerSmoothing: false
30 | });
31 | config.setControllerConfig({
32 | motionInputs: mockConfig
33 | });
34 | gyro = new Gyro(emitter);
35 | });
36 |
37 | describe('object instance', function() {
38 |
39 | it('should have the following shape', function() {
40 | //make sure we find these functions.
41 | instance.forEach(function(method) {
42 | assert.equal(typeof gyro[method.name], 'function');
43 | });
44 | });
45 | });
46 |
47 | describe('process()', function() {
48 |
49 | it('should envoke the dirrection:motion event with positive values', function() {
50 | emitter.on('dirrection:motion', spy);
51 | gyro.process(dataA);
52 | assert.equal(spy.called, true);
53 | var expectedValue = {
54 | direction: 1,
55 | value: 125
56 | },
57 | spyArgument = spy.args[0][0];
58 |
59 | assert.equal(spyArgument.direction, expectedValue.direction);
60 | assert.equal(spyArgument.value, expectedValue.value);
61 | });
62 |
63 | it('should envoke the dirrection event with negative values.', function() {
64 | emitter.on('dirrection:motion', spy);
65 | gyro.process(dataB);
66 | assert.equal(spy.called, true);
67 | var expectedValue = {
68 | direction: 2,
69 | value: -130
70 | },
71 | spyArgument = spy.args[0][0];
72 |
73 | assert.equal(spyArgument.direction, expectedValue.direction);
74 | assert.equal(spyArgument.value, expectedValue.value);
75 | });
76 | });
77 |
78 | });
79 |
--------------------------------------------------------------------------------
/test/smoothing.tests.js:
--------------------------------------------------------------------------------
1 | // Module dependencies.
2 | var Smoothing = require('../src/smoothing'),
3 | assert = require('assert');
4 |
5 | describe('the smoothing component', function() {
6 | 'use strict';
7 |
8 | var smoothing,
9 | nonSmoothing,
10 | //an instance should have the following functions.
11 | instance = [{
12 | name: 'readLastPosition'
13 | }, {
14 | name: 'addToBuffer'
15 | }, {
16 | name: 'smooth'
17 | }];
18 |
19 | beforeEach(function() {
20 | smoothing = new Smoothing(true);
21 | nonSmoothing = new Smoothing(false);
22 | for (var i = 0; i < 5; i++) {
23 | nonSmoothing.addToBuffer('testNonSmoothing', i);
24 | smoothing.addToBuffer('one', i);
25 | smoothing.addToBuffer('two', i + 1);
26 | smoothing.addToBuffer('testSmoothing', i);
27 | }
28 | });
29 |
30 | describe('object instance', function() {
31 | it('should have the following shape', function() {
32 | //make sure we find these functions.
33 | instance.forEach(function(method) {
34 | assert.equal(typeof smoothing[method.name], 'function');
35 | });
36 | });
37 | });
38 |
39 | describe('addToBuffer()', function() {
40 | it('should add values to the buffer', function() {
41 | smoothing.addToBuffer('one', 6);
42 | smoothing.addToBuffer('two', 7);
43 |
44 | assert.equal(smoothing.readLastPosition('one'), 6);
45 | assert.equal(smoothing.readLastPosition('two'), 7);
46 | });
47 | });
48 |
49 | describe('readLastPosition()', function() {
50 | it('should handle buffers for different objects', function() {
51 | assert.equal(smoothing.readLastPosition('one'), 4);
52 | assert.equal(smoothing.readLastPosition('two'), 5);
53 | });
54 | });
55 |
56 | describe('smooth()', function() {
57 | it('should return expected values when smoothing', function() {
58 | //with the data set smoothing of 6 should be 3.
59 | assert.equal(smoothing.smooth('testSmoothing', 6), 3);
60 | //with the data set smoothing of 8 should be 4.
61 | assert.equal(smoothing.smooth('testSmoothing', 8), 4);
62 | });
63 |
64 | it('should return expected values when not smoothing', function() {
65 | //with smoothing turned off 9 should return 9
66 | assert.equal(nonSmoothing.smooth('testNonSmoothing', 9), 9);
67 | //with smoothing turned off 6 should return 6
68 | assert.equal(nonSmoothing.smooth('testNonSmoothing', 6), 6);
69 | });
70 | });
71 | });
72 |
--------------------------------------------------------------------------------
/test/status.tests.js:
--------------------------------------------------------------------------------
1 | // Module dependencies.
2 | var Status = require('../src/status'),
3 | assert = require('assert'),
4 | sinon = require('sinon'),
5 | EventEmitter = require('events').EventEmitter,
6 | config = require('../src/config');
7 |
8 | describe('the status component', function() {
9 | var mockConfig = [{
10 | "name": "chargingState",
11 | "pin": 0,
12 | "states": [{
13 | "value": 0,
14 | "state": "Charging"
15 | }, {
16 | "value": 2,
17 | "state": "Charging"
18 | }, {
19 | "value": 3,
20 | "state": "40%"
21 | }]
22 | }],
23 | dataA = [0, 0],
24 | dataB = [0, 3],
25 | instance = [{
26 | name: 'process'
27 | }],
28 | status,
29 | emitter,
30 | spy;
31 |
32 | beforeEach(function() {
33 | emitter = new EventEmitter();
34 | spy = sinon.spy();
35 | config.setControllerConfig({
36 | status: mockConfig
37 | });
38 | status = new Status(emitter, mockConfig);
39 | });
40 |
41 | describe('object instance', function() {
42 | it('should have the following shape', function() {
43 | //make sure we find these functions.
44 | instance.forEach(function(method) {
45 | assert.equal(typeof status[method.name], 'function');
46 | });
47 | });
48 | });
49 |
50 | describe('process()', function() {
51 | it('process should return an object with the expected values', function() {
52 | emitter.on('chargingState:change', spy);
53 | status.process(dataA);
54 | var spyArgument = spy.args[0][0];
55 | assert.equal(typeof spyArgument, 'string');
56 | assert.equal(spyArgument, 'Charging');
57 | spyArgument = null;
58 | });
59 | });
60 | });
61 |
--------------------------------------------------------------------------------