USB Host Shield 2.0
XBOXONESParser.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2020 Kristian Sloth Lauszus. All rights reserved.
2 
3  This software may be distributed and modified under the terms of the GNU
4  General Public License version 2 (GPL2) as published by the Free Software
5  Foundation and appearing in the file GPL2.TXT included in the packaging of
6  this file. Please note that GPL2 Section 2[b] requires that all works based
7  on this software must also be made publicly available under the terms of
8  the GPL2 ("Copyleft").
9 
10  Contact information
11  -------------------
12 
13  Kristian Sloth Lauszus
14  Web : https://lauszus.com
15  e-mail : lauszus@gmail.com
16  */
17 
18 #include "XBOXONESParser.h"
19 
20 // To enable serial debugging see "settings.h"
21 //#define PRINTREPORT // Uncomment to print the report send by the Xbox One S Controller
22 
24 const uint8_t XBOX_ONE_S_BUTTONS[] PROGMEM = {
25  UP, // UP
26  RIGHT, // RIGHT
27  DOWN, // DOWN
28  LEFT, // LEFT
29 
30  0x0E, // VIEW
31  0x0F, // MENU
32  0x10, // L3
33  0x11, // R3
34 
35  0, 0, // Skip L2 and R2 as these are analog buttons
36  0x0C, // L1
37  0x0D, // R1
38 
39  0x09, // B
40  0x08, // A
41  0x0A, // X
42  0x0B, // Y
43  0, // XBOX - this is sent in another report
44 };
45 
46 enum DPADEnum {
47  DPAD_OFF = 0x0,
48  DPAD_UP = 0x1,
50  DPAD_RIGHT = 0x3,
52  DPAD_DOWN = 0x5,
54  DPAD_LEFT = 0x7,
55  DPAD_LEFT_UP = 0x8,
56 };
57 
58 int8_t XBOXONESParser::getButtonIndexXboxOneS(ButtonEnum b) {
59  const int8_t index = ButtonIndex(b);
60  if ((uint8_t) index >= (sizeof(XBOX_ONE_S_BUTTONS) / sizeof(XBOX_ONE_S_BUTTONS[0]))) return -1;
61  return index;
62 }
63 
64 bool XBOXONESParser::checkDpad(ButtonEnum b) {
65  switch (b) {
66  case UP:
67  return xboxOneSData.btn.dpad == DPAD_LEFT_UP || xboxOneSData.btn.dpad == DPAD_UP || xboxOneSData.btn.dpad == DPAD_UP_RIGHT;
68  case RIGHT:
69  return xboxOneSData.btn.dpad == DPAD_UP_RIGHT || xboxOneSData.btn.dpad == DPAD_RIGHT || xboxOneSData.btn.dpad == DPAD_RIGHT_DOWN;
70  case DOWN:
71  return xboxOneSData.btn.dpad == DPAD_RIGHT_DOWN || xboxOneSData.btn.dpad == DPAD_DOWN || xboxOneSData.btn.dpad == DPAD_DOWN_LEFT;
72  case LEFT:
73  return xboxOneSData.btn.dpad == DPAD_DOWN_LEFT || xboxOneSData.btn.dpad == DPAD_LEFT || xboxOneSData.btn.dpad == DPAD_LEFT_UP;
74  default:
75  return false;
76  }
77 }
78 
80  const int8_t index = getButtonIndexXboxOneS(b); if (index < 0) return 0;
81  if (index == ButtonIndex(L2))
82  return xboxOneSData.trigger[0];
83  else if (index == ButtonIndex(R2))
84  return xboxOneSData.trigger[1];
85  else if (index <= LEFT) // Dpad
86  return checkDpad(b);
87  else if (index == ButtonIndex(XBOX))
88  return xboxButtonState;
89  return xboxOneSData.btn.val & (1UL << pgm_read_byte(&XBOX_ONE_S_BUTTONS[index]));
90 }
91 
93  const int8_t index = getButtonIndexXboxOneS(b); if (index < 0) return 0;
94  if(index == ButtonIndex(L2)) {
95  if(L2Clicked) {
96  L2Clicked = false;
97  return true;
98  }
99  return false;
100  } else if(index == ButtonIndex(R2)) {
101  if(R2Clicked) {
102  R2Clicked = false;
103  return true;
104  }
105  return false;
106  } else if (index == ButtonIndex(XBOX)) {
107  bool click = xboxbuttonClickState;
108  xboxbuttonClickState = 0; // Clear "click" event
109  return click;
110  }
111  uint32_t mask = 1UL << pgm_read_byte(&XBOX_ONE_S_BUTTONS[index]);
112  bool click = buttonClickState.val & mask;
113  buttonClickState.val &= ~mask; // Clear "click" event
114  return click;
115 }
116 
118  return xboxOneSData.hatValue[(uint8_t)a] - 32768; // Convert to signed integer
119 }
120 
121 void XBOXONESParser::Parse(uint8_t len, uint8_t *buf) {
122  if (len > 1 && buf) {
123 #ifdef PRINTREPORT
124  Notify(PSTR("\r\n"), 0x80);
125  for (uint8_t i = 0; i < len; i++) {
126  D_PrintHex<uint8_t > (buf[i], 0x80);
127  Notify(PSTR(" "), 0x80);
128  }
129 #endif
130 
131  if (buf[0] == 0x01) // Check report ID
132  memcpy(&xboxOneSData, buf + 1, min((uint8_t)(len - 1), MFK_CASTUINT8T sizeof(xboxOneSData)));
133  else if (buf[0] == 0x02) { // This report contains the Xbox button
134  xboxButtonState = buf[1];
135  if(xboxButtonState != xboxOldButtonState) {
136  xboxbuttonClickState = xboxButtonState & ~xboxOldButtonState; // Update click state variable
137  xboxOldButtonState = xboxButtonState;
138  }
139  return;
140  } else if (buf[0] == 0x04) // Heartbeat
141  return;
142  else {
143 #ifdef DEBUG_USB_HOST
144  Notify(PSTR("\r\nUnknown report id: "), 0x80);
145  D_PrintHex<uint8_t > (buf[0], 0x80);
146 #endif
147  return;
148  }
149 
150  if (xboxOneSData.btn.val != oldButtonState.val) { // Check if anything has changed
151  buttonClickState.val = xboxOneSData.btn.val & ~oldButtonState.val; // Update click state variable
152  oldButtonState.val = xboxOneSData.btn.val;
153 
154  // The DPAD buttons does not set the different bits, but set a value corresponding to the buttons pressed, we will simply set the bits ourself
155  uint8_t newDpad = 0;
156  if (checkDpad(UP))
157  newDpad |= 1 << UP;
158  if (checkDpad(RIGHT))
159  newDpad |= 1 << RIGHT;
160  if (checkDpad(DOWN))
161  newDpad |= 1 << DOWN;
162  if (checkDpad(LEFT))
163  newDpad |= 1 << LEFT;
164  if (newDpad != oldDpad) {
165  buttonClickState.dpad = newDpad & ~oldDpad; // Override values
166  oldDpad = newDpad;
167  }
168  }
169 
170  // Handle click detection for triggers
171  if(xboxOneSData.trigger[0] != 0 && triggerOld[0] == 0)
172  L2Clicked = true;
173  triggerOld[0] = xboxOneSData.trigger[0];
174  if(xboxOneSData.trigger[1] != 0 && triggerOld[1] == 0)
175  R2Clicked = true;
176  triggerOld[1] = xboxOneSData.trigger[1];
177  }
178 }
179 
181  uint8_t i;
182  for (i = 0; i < sizeof(xboxOneSData.hatValue) / sizeof(xboxOneSData.hatValue[0]); i++)
183  xboxOneSData.hatValue[i] = 32768; // Center value
184  xboxOneSData.btn.val = 0;
185  oldButtonState.val = 0;
186  for (i = 0; i < sizeof(xboxOneSData.trigger) / sizeof(xboxOneSData.trigger[0]); i++)
187  xboxOneSData.trigger[i] = 0;
188 
189  xboxOneSData.btn.dpad = DPAD_OFF;
190  oldButtonState.dpad = DPAD_OFF;
191  buttonClickState.dpad = 0;
192  oldDpad = 0;
193 };
194 
196  // See: https://lore.kernel.org/patchwork/patch/973394/
197  uint8_t buf[8];
198  buf[0] = 0x0F; // Disable all rumble motors
199  buf[1] = 0;
200  buf[2] = 0;
201  buf[3] = 0;
202  buf[4] = 0;
203  buf[5] = 0; // Duration of effect in 10 ms
204  buf[6] = 0; // Start delay in 10 ms
205  buf[7] = 0; // Loop count
206  sendOutputReport(buf, sizeof(buf));
207 }
208 
209 void XBOXONESParser::setRumbleOn(uint8_t leftTrigger, uint8_t rightTrigger, uint8_t leftMotor, uint8_t rightMotor) {
210  // See: https://lore.kernel.org/patchwork/patch/973394/
211  uint8_t buf[8];
212  buf[0] = 1 << 3 /* Left trigger */ | 1 << 2 /* Right trigger */ | 1 << 1 /* Left motor */ | 1 << 0 /* Right motor */;
213  buf[1] = leftTrigger;
214  buf[2] = rightTrigger;
215  buf[3] = leftMotor;
216  buf[4] = rightMotor;
217  buf[5] = 255; // Duration of effect in 10 ms
218  buf[6] = 0; // Start delay in 10 ms
219  buf[7] = 255; // Loop count
220  sendOutputReport(buf, sizeof(buf));
221 }
DPADEnum
Definition: PS4Parser.cpp:20
@ DPAD_DOWN_LEFT
@ DPAD_RIGHT_DOWN
@ DPAD_UP
@ DPAD_DOWN
@ DPAD_UP_RIGHT
@ DPAD_LEFT_UP
@ DPAD_OFF
@ DPAD_RIGHT
@ DPAD_LEFT
const uint8_t XBOX_ONE_S_BUTTONS[]
void setRumbleOn(uint8_t leftTrigger, uint8_t rightTrigger, uint8_t leftMotor, uint8_t rightMotor)
virtual void sendOutputReport(uint8_t *data, uint8_t nbytes)=0
int16_t getAnalogHat(AnalogHatEnum a)
void Parse(uint8_t len, uint8_t *buf)
uint16_t getButtonPress(ButtonEnum b)
bool getButtonClick(ButtonEnum b)
constexpr int8_t ButtonIndex(ButtonEnum key)
AnalogHatEnum
ButtonEnum
@ L2
@ R2
@ DOWN
@ UP
@ XBOX
@ LEFT
@ RIGHT
#define Notify(...)
Definition: message.h:51
#define MFK_CASTUINT8T
Definition: settings.h:200
uint16_t hatValue[4]
uint16_t trigger[2]
XboxOneSButtons btn
#define pgm_read_byte(addr)
#define PSTR(str)