summaryrefslogtreecommitdiff
path: root/tmk_core/protocol/usb_hid/USB_Host_Shield_2.0/PS4Parser.h
blob: 51f0806361cd5fd9f795e787d3f11387f8e8ed83 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
/* Copyright (C) 2014 Kristian Lauszus, TKJ Electronics. All rights reserved.

 This software may be distributed and modified under the terms of the GNU
 General Public License version 2 (GPL2) as published by the Free Software
 Foundation and appearing in the file GPL2.TXT included in the packaging of
 this file. Please note that GPL2 Section 2[b] requires that all works based
 on this software must also be made publicly available under the terms of
 the GPL2 ("Copyleft").

 Contact information
 -------------------

 Kristian Lauszus, TKJ Electronics
 Web      :  http://www.tkjelectronics.com
 e-mail   :  kristianl@tkjelectronics.com
 */

#ifndef _ps4parser_h_
#define _ps4parser_h_

#include "Usb.h"
#include "controllerEnums.h"

/** Buttons on the controller */
const uint8_t PS4_BUTTONS[] PROGMEM = {
        UP, // UP
        RIGHT, // RIGHT
        DOWN, // DOWN
        LEFT, // LEFT

        0x0C, // SHARE
        0x0D, // OPTIONS
        0x0E, // L3
        0x0F, // R3

        0x0A, // L2
        0x0B, // R2
        0x08, // L1
        0x09, // R1

        0x07, // TRIANGLE
        0x06, // CIRCLE
        0x05, // CROSS
        0x04, // SQUARE

        0x10, // PS
        0x11, // TOUCHPAD
};

union PS4Buttons {
        struct {
                uint8_t dpad : 4;
                uint8_t square : 1;
                uint8_t cross : 1;
                uint8_t circle : 1;
                uint8_t triangle : 1;

                uint8_t l1 : 1;
                uint8_t r1 : 1;
                uint8_t l2 : 1;
                uint8_t r2 : 1;
                uint8_t share : 1;
                uint8_t options : 1;
                uint8_t l3 : 1;
                uint8_t r3 : 1;

                uint8_t ps : 1;
                uint8_t touchpad : 1;
                uint8_t reportCounter : 6;
        } __attribute__((packed));
        uint32_t val : 24;
} __attribute__((packed));

struct touchpadXY {
        uint8_t dummy; // I can not figure out what this data is for, it seems to change randomly, maybe a timestamp?
        struct {
                uint8_t counter : 7; // Increments every time a finger is touching the touchpad
                uint8_t touching : 1; // The top bit is cleared if the finger is touching the touchpad
                uint16_t x : 12;
                uint16_t y : 12;
        } __attribute__((packed)) finger[2]; // 0 = first finger, 1 = second finger
} __attribute__((packed));

struct PS4Status {
        uint8_t battery : 4;
        uint8_t usb : 1;
        uint8_t audio : 1;
        uint8_t mic : 1;
        uint8_t unknown : 1; // Extension port?
} __attribute__((packed));

struct PS4Data {
        /* Button and joystick values */
        uint8_t hatValue[4];
        PS4Buttons btn;
        uint8_t trigger[2];

        /* Gyro and accelerometer values */
        uint8_t dummy[3]; // First two looks random, while the third one might be some kind of status - it increments once in a while
        int16_t gyroY, gyroZ, gyroX;
        int16_t accX, accZ, accY;

        uint8_t dummy2[5];
        PS4Status status;
        uint8_t dummy3[3];

        /* The rest is data for the touchpad */
        touchpadXY xy[3]; // It looks like it sends out three coordinates each time, this might be because the microcontroller inside the PS4 controller is much faster than the Bluetooth connection.
                          // The last data is read from the last position in the array while the oldest measurement is from the first position.
                          // The first position will also keep it's value after the finger is released, while the other two will set them to zero.
                          // Note that if you read fast enough from the device, then only the first one will contain any data.

        // The last three bytes are always: 0x00, 0x80, 0x00
} __attribute__((packed));

struct PS4Output {
        uint8_t bigRumble, smallRumble; // Rumble
        uint8_t r, g, b; // RGB
        uint8_t flashOn, flashOff; // Time to flash bright/dark (255 = 2.5 seconds)
        bool reportChanged; // The data is send when data is received from the controller
} __attribute__((packed));

enum DPADEnum {
        DPAD_UP = 0x0,
        DPAD_UP_RIGHT = 0x1,
        DPAD_RIGHT = 0x2,
        DPAD_RIGHT_DOWN = 0x3,
        DPAD_DOWN = 0x4,
        DPAD_DOWN_LEFT = 0x5,
        DPAD_LEFT = 0x6,
        DPAD_LEFT_UP = 0x7,
        DPAD_OFF = 0x8,
};

/** This class parses all the data sent by the PS4 controller */
class PS4Parser {
public:
        /** Constructor for the PS4Parser class. */
        PS4Parser() {
                Reset();
        };

        /** @name PS4 Controller functions */
        /**
         * getButtonPress(ButtonEnum b) will return true as long as the button is held down.
         *
         * While getButtonClick(ButtonEnum b) will only return it once.
         *
         * So you instance if you need to increase a variable once you would use getButtonClick(ButtonEnum b),
         * but if you need to drive a robot forward you would use getButtonPress(ButtonEnum b).
         * @param  b          ::ButtonEnum to read.
         * @return            getButtonPress(ButtonEnum b) will return a true as long as a button is held down, while getButtonClick(ButtonEnum b) will return true once for each button press.
         */
        bool getButtonPress(ButtonEnum b);
        bool getButtonClick(ButtonEnum b);
        /**@}*/
        /** @name PS4 Controller functions */
        /**
         * Used to get the analog value from button presses.
         * @param  b The ::ButtonEnum to read.
         * The supported buttons are:
         * ::UP, ::RIGHT, ::DOWN, ::LEFT, ::L1, ::L2, ::R1, ::R2,
         * ::TRIANGLE, ::CIRCLE, ::CROSS, ::SQUARE, and ::T.
         * @return   Analog value in the range of 0-255.
         */
        uint8_t getAnalogButton(ButtonEnum b);

        /**
         * Used to read the analog joystick.
         * @param  a ::LeftHatX, ::LeftHatY, ::RightHatX, and ::RightHatY.
         * @return   Return the analog value in the range of 0-255.
         */
        uint8_t getAnalogHat(AnalogHatEnum a);

        /**
         * Get the x-coordinate of the touchpad. Position 0 is in the top left.
         * @param  finger 0 = first finger, 1 = second finger. If omitted, then 0 will be used.
         * @param  xyId   The controller sends out three packets with the same structure.
         *                The third one will contain the last measure, but if you read from the controller then there is only be data in the first one.
         *                For that reason it will be set to 0 if the argument is omitted.
         * @return        Returns the x-coordinate of the finger.
         */
        uint16_t getX(uint8_t finger = 0, uint8_t xyId = 0) {
                return ps4Data.xy[xyId].finger[finger].x;
        };

        /**
         * Get the y-coordinate of the touchpad. Position 0 is in the top left.
         * @param  finger 0 = first finger, 1 = second finger. If omitted, then 0 will be used.
         * @param  xyId   The controller sends out three packets with the same structure.
         *                The third one will contain the last measure, but if you read from the controller then there is only be data in the first one.
         *                For that reason it will be set to 0 if the argument is omitted.
         * @return        Returns the y-coordinate of the finger.
         */
        uint16_t getY(uint8_t finger = 0, uint8_t xyId = 0) {
                return ps4Data.xy[xyId].finger[finger].y;
        };

        /**
         * Returns whenever the user is toucing the touchpad.
         * @param  finger 0 = first finger, 1 = second finger. If omitted, then 0 will be used.
         * @param  xyId   The controller sends out three packets with the same structure.
         *                The third one will contain the last measure, but if you read from the controller then there is only be data in the first one.
         *                For that reason it will be set to 0 if the argument is omitted.
         * @return        Returns true if the specific finger is touching the touchpad.
         */
        bool isTouching(uint8_t finger = 0, uint8_t xyId = 0) {
                return !(ps4Data.xy[xyId].finger[finger].touching); // The bit is cleared when a finger is touching the touchpad
        };

        /**
         * This counter increments every time a finger touches the touchpad.
         * @param  finger 0 = first finger, 1 = second finger. If omitted, then 0 will be used.
         * @param  xyId   The controller sends out three packets with the same structure.
         *                The third one will contain the last measure, but if you read from the controller then there is only be data in the first one.
         *                For that reason it will be set to 0 if the argument is omitted.
         * @return        Return the value of the counter, note that it is only a 7-bit value.
         */
        uint8_t getTouchCounter(uint8_t finger = 0, uint8_t xyId = 0) {
                return ps4Data.xy[xyId].finger[finger].counter;
        };

        /**
         * Get the angle of the controller calculated using the accelerometer.
         * @param  a Either ::Pitch or ::Roll.
         * @return   Return the angle in the range of 0-360.
         */
        double getAngle(AngleEnum a) {
                if (a == Pitch)
                        return (atan2(ps4Data.accY, ps4Data.accZ) + PI) * RAD_TO_DEG;
                else
                        return (atan2(ps4Data.accX, ps4Data.accZ) + PI) * RAD_TO_DEG;
        };

        /**
         * Used to get the raw values from the 3-axis gyroscope and 3-axis accelerometer inside the PS4 controller.
         * @param  s The sensor to read.
         * @return   Returns the raw sensor reading.
         */
        int16_t getSensor(SensorEnum s) {
                switch(s) {
                        case gX:
                                return ps4Data.gyroX;
                        case gY:
                                return ps4Data.gyroY;
                        case gZ:
                                return ps4Data.gyroZ;
                        case aX:
                                return ps4Data.accX;
                        case aY:
                                return ps4Data.accY;
                        case aZ:
                                return ps4Data.accZ;
                        default:
                                return 0;
                }
        };

        /**
         * Return the battery level of the PS4 controller.
         * @return The battery level in the range 0-15.
         */
        uint8_t getBatteryLevel() {
                return ps4Data.status.battery;
        };

        /**
         * Use this to check if an USB cable is connected to the PS4 controller.
         * @return Returns true if an USB cable is connected.
         */
        bool getUsbStatus() {
                return ps4Data.status.usb;
        };

        /**
         * Use this to check if an audio jack cable is connected to the PS4 controller.
         * @return Returns true if an audio jack cable is connected.
         */
        bool getAudioStatus() {
                return ps4Data.status.audio;
        };

        /**
         * Use this to check if a microphone is connected to the PS4 controller.
         * @return Returns true if a microphone is connected.
         */
        bool getMicStatus() {
                return ps4Data.status.mic;
        };

        /** Turn both rumble and the LEDs off. */
        void setAllOff() {
                setRumbleOff();
                setLedOff();
        };

        /** Set rumble off. */
        void setRumbleOff() {
                setRumbleOn(0, 0);
        };

        /**
         * Turn on rumble.
         * @param mode Either ::RumbleHigh or ::RumbleLow.
         */
        void setRumbleOn(RumbleEnum mode) {
                if (mode == RumbleLow)
                        setRumbleOn(0x00, 0xFF);
                else
                        setRumbleOn(0xFF, 0x00);
        };

        /**
         * Turn on rumble.
         * @param bigRumble   Value for big motor.
         * @param smallRumble Value for small motor.
         */
        void setRumbleOn(uint8_t bigRumble, uint8_t smallRumble) {
                ps4Output.bigRumble = bigRumble;
                ps4Output.smallRumble = smallRumble;
                ps4Output.reportChanged = true;
        };

        /** Turn all LEDs off. */
        void setLedOff() {
                setLed(0, 0, 0);
        };

        /**
         * Use this to set the color using RGB values.
         * @param r,g,b RGB value.
         */
        void setLed(uint8_t r, uint8_t g, uint8_t b) {
                ps4Output.r = r;
                ps4Output.g = g;
                ps4Output.b = b;
                ps4Output.reportChanged = true;
        };

        /**
         * Use this to set the color using the predefined colors in ::ColorsEnum.
         * @param color The desired color.
         */
        void setLed(ColorsEnum color) {
                setLed((uint8_t)(color >> 16), (uint8_t)(color >> 8), (uint8_t)(color));
        };

        /**
         * Set the LEDs flash time.
         * @param flashOn  Time to flash bright (255 = 2.5 seconds).
         * @param flashOff Time to flash dark (255 = 2.5 seconds).
         */
        void setLedFlash(uint8_t flashOn, uint8_t flashOff) {
                ps4Output.flashOn = flashOn;
                ps4Output.flashOff = flashOff;
                ps4Output.reportChanged = true;
        };
        /**@}*/

protected:
        /**
         * Used to parse data sent from the PS4 controller.
         * @param len Length of the data.
         * @param buf Pointer to the data buffer.
         */
        void Parse(uint8_t len, uint8_t *buf);

        /** Used to reset the different buffers to their default values */
        void Reset() {
                uint8_t i;
                for (i = 0; i < sizeof(ps4Data.hatValue); i++)
                        ps4Data.hatValue[i] = 127; // Center value
                ps4Data.btn.val = 0;
                oldButtonState.val = 0;
                for (i = 0; i < sizeof(ps4Data.trigger); i++)
                        ps4Data.trigger[i] = 0;
                for (i = 0; i < sizeof(ps4Data.xy)/sizeof(ps4Data.xy[0]); i++) {
                        for (uint8_t j = 0; j < sizeof(ps4Data.xy[0].finger)/sizeof(ps4Data.xy[0].finger[0]); j++)
                                ps4Data.xy[i].finger[j].touching = 1; // The bit is cleared if the finger is touching the touchpad
                }

                ps4Data.btn.dpad = DPAD_OFF;
                oldButtonState.dpad = DPAD_OFF;
                buttonClickState.dpad = 0;
                oldDpad = 0;

                ps4Output.bigRumble = ps4Output.smallRumble = 0;
                ps4Output.r = ps4Output.g = ps4Output.b = 0;
                ps4Output.flashOn = ps4Output.flashOff = 0;
                ps4Output.reportChanged = false;
        };

        /**
         * Send the output to the PS4 controller. This is implemented in PS4BT.h and PS4USB.h.
         * @param output Pointer to PS4Output buffer;
         */
        virtual void sendOutputReport(PS4Output *output) = 0;

private:
        bool checkDpad(ButtonEnum b); // Used to check PS4 DPAD buttons

        PS4Data ps4Data;
        PS4Buttons oldButtonState, buttonClickState;
        PS4Output ps4Output;
        uint8_t oldDpad;
};
#endif