USB Host Shield 2.0
SwitchProParser.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2021 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 "SwitchProParser.h"
19 
20 // To enable serial debugging see "settings.h"
21 //#define PRINTREPORT // Uncomment to print the report send by the Switch Pro Controller
22 
23 int8_t SwitchProParser::getButtonIndexSwitchPro(ButtonEnum b) {
24  const int8_t index = ButtonIndex(b);
25  if ((uint8_t) index >= (sizeof(SWITCH_PRO_BUTTONS) / sizeof(SWITCH_PRO_BUTTONS[0]))) return -1;
26  return index;
27 }
28 
30  const int8_t index = getButtonIndexSwitchPro(b); if (index < 0) return 0;
31  return switchProData.btn.val & (1UL << pgm_read_byte(&SWITCH_PRO_BUTTONS[index]));
32 }
33 
35  const int8_t index = getButtonIndexSwitchPro(b); if (index < 0) return 0;
36  uint32_t mask = 1UL << pgm_read_byte(&SWITCH_PRO_BUTTONS[index]);
37  bool click = buttonClickState.val & mask;
38  buttonClickState.val &= ~mask; // Clear "click" event
39  return click;
40 }
41 
43  switch((uint8_t)a) {
44  case 0:
45  return switchProData.leftHatX - 2048; // Subtract the center value
46  case 1:
47  return 2048 - switchProData.leftHatY; // Invert, so it follows the same coordinate as the simple report
48  case 2:
49  return switchProData.rightHatX - 2048; // Subtract the center value
50  default:
51  return 2048 - switchProData.rightHatY; // Invert, so it follows the same coordinate as the simple report
52  }
53 }
54 
55 void SwitchProParser::Parse(uint8_t len, uint8_t *buf) {
56  if (len > 0 && buf) {
57 #ifdef PRINTREPORT
58  Notify(PSTR("\r\nLen: "), 0x80); Notify(len, 0x80);
59  Notify(PSTR(", data: "), 0x80);
60  for (uint8_t i = 0; i < len; i++) {
61  D_PrintHex<uint8_t > (buf[i], 0x80);
62  Notify(PSTR(" "), 0x80);
63  }
64 #endif
65 
66  // This driver always uses the standard full report that includes the IMU data.
67  // The downside is that it requires more processing power, as the data is send contentiously
68  // while the simple input report is only send when the button state changes however the simple
69  // input report is not available via USB and does not include the IMU data.
70 
71  if (buf[0] == 0x3F) // Simple input report via Bluetooth
72  switchProOutput.enableFullReportMode = true; // Switch over to the full report
73  else if (buf[0] == 0x30) { // Standard full mode
74  if (len < 3) {
75 #ifdef DEBUG_USB_HOST
76  Notify(PSTR("\r\nReport is too short: "), 0x80);
77  D_PrintHex<uint8_t > (len, 0x80);
78 #endif
79  return;
80  }
81  memcpy(&switchProData, buf + 2, min((uint8_t)(len - 2), MFK_CASTUINT8T sizeof(switchProData)));
82 
83  if (switchProData.btn.val != oldButtonState.val) { // Check if anything has changed
84  buttonClickState.val = switchProData.btn.val & ~oldButtonState.val; // Update click state variable
85  oldButtonState.val = switchProData.btn.val;
86  }
87 
88  message_counter++;
89  } else if (buf[0] == 0x21) {
90  // Subcommand reply via Bluetooth
91  } else if (buf[0] == 0x81) {
92  // Subcommand reply via USB
93  } else {
94 #ifdef DEBUG_USB_HOST
95  Notify(PSTR("\r\nUnknown report id: "), 0x80);
96  D_PrintHex<uint8_t > (buf[0], 0x80);
97  Notify(PSTR(", len: "), 0x80);
98  D_PrintHex<uint8_t > (len, 0x80);
99 #endif
100  }
101  }
102 
104  sendHandshake();
106  disableTimeout();
109  sendOutputCmd();
111  // We need to send the rumble report repeatedly to keep it on
112  uint32_t now = millis();
113  if (now - rumble_on_timer > 1000) {
114  rumble_on_timer = now;
115  sendRumbleOutputReport();
116  }
117  }
118 }
119 
120 void SwitchProParser::sendOutputCmd() {
121  // See: https://github.com/Dan611/hid-procon
122  // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
123  // https://github.com/HisashiKato/USB_Host_Shield_Library_2.0_BTXBOX/blob/master/src/SWProBTParser.h#L152-L153
124  uint8_t buf[14] = { 0 };
125  buf[0x00] = 0x01; // Report ID - PROCON_CMD_AND_RUMBLE
126  buf[0x01] = output_sequence_counter++; // Lowest 4-bit is a sequence number, which needs to be increased for every report
127 
128  // Left rumble data
130  buf[0x02 + 0] = 0x28;
131  buf[0x02 + 1] = 0x88;
132  buf[0x02 + 2] = 0x60;
133  buf[0x02 + 3] = 0x61;
134  } else {
135  buf[0x02 + 0] = 0x00;
136  buf[0x02 + 1] = 0x01;
137  buf[0x02 + 2] = 0x40;
138  buf[0x02 + 3] = 0x40;
139  }
140 
141  // Right rumble data
143  buf[0x02 + 4] = 0x28;
144  buf[0x02 + 5] = 0x88;
145  buf[0x02 + 6] = 0x60;
146  buf[0x02 + 7] = 0x61;
147  } else {
148  buf[0x02 + 4] = 0x00;
149  buf[0x02 + 5] = 0x01;
150  buf[0x02 + 6] = 0x40;
151  buf[0x02 + 7] = 0x40;
152  }
153 
154  // Sub commands
157 
158  // See: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/bluetooth_hid_subcommands_notes.md#subcommand-0x30-set-player-lights
159  buf[0x0A + 0] = 0x30; // PROCON_CMD_LED
160 
161  buf[0x0A + 1] = switchProOutput.ledMask; // Lower 4-bits sets the LEDs constantly on, the higher 4-bits can be used to flash the LEDs
162 
163  sendOutputReport(buf, 10 + 2);
166 
167  // It is possible set up to 15 mini cycles, but we simply just set the LED constantly on/off
168  // See: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/bluetooth_hid_subcommands_notes.md#subcommand-0x38-set-home-light
169  buf[0x0A + 0] = 0x38; // PROCON_CMD_LED_HOME
170 
171  buf[0x0A + 1] = (0 /* Number of cycles */ << 4) | (switchProOutput.ledHome ? 0xF : 0) /* Global mini cycle duration */;
172  buf[0x0A + 2] = (0xF /* LED start intensity */ << 4) | 0x0 /* Number of full cycles */;
173  buf[0x0A + 3] = (0xF /* Mini Cycle 1 LED intensity */ << 4) | 0x0 /* Mini Cycle 2 LED intensity */;
174 
175  sendOutputReport(buf, 10 + 4);
178 
179  // See: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/bluetooth_hid_subcommands_notes.md#subcommand-0x03-set-input-report-mode
180  buf[0x0A + 0] = 0x03; // PROCON_CMD_MODE
181  buf[0x0A + 1] = 0x30; // PROCON_ARG_INPUT_FULL
182 
183  sendOutputReport(buf, 10 + 2);
184  } else if (switchProOutput.enableImu != -1) {
185  // See: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/bluetooth_hid_subcommands_notes.md#subcommand-0x40-enable-imu-6-axis-sensor
186  buf[0x0A + 0] = 0x40; // PROCON_CMD_GYRO
187  buf[0x0A + 1] = switchProOutput.enableImu ? 1 : 0; // The new state is stored in the variable
189 
190  sendOutputReport(buf, 12);
191  }
192 }
193 
194 void SwitchProParser::sendRumbleOutputReport() {
195  // See: https://github.com/Dan611/hid-procon
196  // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
197  // https://github.com/HisashiKato/USB_Host_Shield_Library_2.0_BTXBOX/blob/master/src/SWProBTParser.h#L152-L153
198  uint8_t buf[10] = { 0 };
199  buf[0x00] = 0x10; // Report ID - PROCON_CMD_RUMBLE_ONLY
200  buf[0x01] = output_sequence_counter++; // Lowest 4-bit is a sequence number, which needs to be increased for every report
201 
202  // Left rumble data
204  buf[0x02 + 0] = 0x28;
205  buf[0x02 + 1] = 0x88;
206  buf[0x02 + 2] = 0x60;
207  buf[0x02 + 3] = 0x61;
208  } else {
209  buf[0x02 + 0] = 0x00;
210  buf[0x02 + 1] = 0x01;
211  buf[0x02 + 2] = 0x40;
212  buf[0x02 + 3] = 0x40;
213  }
214 
215  // Right rumble data
217  buf[0x02 + 4] = 0x28;
218  buf[0x02 + 5] = 0x88;
219  buf[0x02 + 6] = 0x60;
220  buf[0x02 + 7] = 0x61;
221  } else {
222  buf[0x02 + 4] = 0x00;
223  buf[0x02 + 5] = 0x01;
224  buf[0x02 + 6] = 0x40;
225  buf[0x02 + 7] = 0x40;
226  }
227 
228  sendOutputReport(buf, 10);
229 }
230 
232  // Center joysticks
233  switchProData.leftHatX = switchProData.leftHatY = switchProData.rightHatX = switchProData.rightHatY = 2048;
234 
235  // Reset buttons variables
236  switchProData.btn.val = 0;
237  oldButtonState.val = 0;
238  buttonClickState.val = 0;
239 
240  output_sequence_counter = 0;
241  rumble_on_timer = 0;
242 
246  switchProOutput.ledHome = false;
253 }
const uint8_t SWITCH_PRO_BUTTONS[]
virtual void sendOutputReport(uint8_t *data, uint8_t len)=0
int16_t getAnalogHat(AnalogHatEnum a)
bool getButtonClick(ButtonEnum b)
bool getButtonPress(ButtonEnum b)
virtual void disableTimeout()
virtual void sendHandshake()
void Parse(uint8_t len, uint8_t *buf)
SwitchProOutput switchProOutput
constexpr int8_t ButtonIndex(ButtonEnum key)
AnalogHatEnum
ButtonEnum
#define Notify(...)
Definition: message.h:51
#define MFK_CASTUINT8T
Definition: settings.h:200
uint16_t rightHatY
uint16_t rightHatX
SwitchProButtons btn
#define pgm_read_byte(addr)
#define PSTR(str)