USB Host Shield 2.0
SwitchProParser.h
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 #ifndef _switch_pro_parser_h_
19 #define _switch_pro_parser_h_
20 
21 #include "Usb.h"
22 #include "controllerEnums.h"
23 
25 const uint8_t SWITCH_PRO_LEDS[] PROGMEM = {
26  0x00, // OFF
27  0x01, // LED1
28  0x02, // LED2
29  0x04, // LED3
30  0x08, // LED4
31 
32  0x09, // LED5
33  0x0A, // LED6
34  0x0C, // LED7
35  0x0D, // LED8
36  0x0E, // LED9
37  0x0F, // LED10
38 };
39 
41 const uint8_t SWITCH_PRO_BUTTONS[] PROGMEM = {
42  0x11, // UP
43  0x12, // RIGHT
44  0x10, // DOWN
45  0x13, // LEFT
46 
47  0x0D, // Capture
48  0x09, // PLUS
49  0x0B, // L3
50  0x0A, // R3
51 
52  0x08, // MINUS
53  0x0C, // HOME
54  0, 0, // Skip
55 
56  0x02, // B
57  0x03, // A
58  0x01, // X
59  0x00, // Y
60 
61  0x16, // L
62  0x06, // R
63  0x17, // ZL
64  0x07, // ZR
65 };
66 
67 // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/bluetooth_hid_notes.md#standard-input-report-format
69  struct {
70  uint8_t y : 1;
71  uint8_t x : 1;
72  uint8_t b : 1;
73  uint8_t a : 1;
74 
75  uint8_t dummy1 : 2;
76  uint8_t r : 1;
77  uint8_t zr : 1;
78 
79  uint8_t minus : 1;
80  uint8_t plus : 1;
81  uint8_t r3 : 1;
82  uint8_t l3 : 1;
83 
84  uint8_t home : 1;
85  uint8_t capture : 1;
86  uint8_t dummy2 : 2;
87 
88  uint8_t dpad : 4;
89 
90  uint8_t dummy3 : 2;
91  uint8_t l : 1;
92  uint8_t zl : 1;
93  } __attribute__((packed));
94  uint32_t val : 24;
95 } __attribute__((packed));
96 
97 struct ImuData {
98  int16_t accX, accY, accZ;
99  int16_t gyroX, gyroY, gyroZ;
100 } __attribute__((packed));
101 
103  struct {
104  uint8_t connection_info : 4;
105  uint8_t battery_level : 4;
106  } __attribute__((packed));
107 
108  /* Button and joystick values */
109  SwitchProButtons btn; // Bytes 3-5
110 
111  // Bytes 6-11
112  uint16_t leftHatX : 12;
113  uint16_t leftHatY : 12;
114  uint16_t rightHatX : 12;
115  uint16_t rightHatY : 12;
116 
117  uint8_t vibratorInput; // What is this used for?
118 
119  // Bytes 13-48
120  // Three samples of the IMU is sent in one message
121  // See: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md
123 } __attribute__((packed));
124 
128  uint8_t ledMask; // Higher nibble flashes the LEDs, lower nibble sets them on/off
129  bool ledHome;
130 
131  // Used to send the reports at the same rate as the controller is sending messages
135  int8_t enableImu; // -1 == Do nothing, 0 == disable IMU, 1 == enable IMU
138 } __attribute__((packed));
139 
142 public:
144  SwitchProParser() : output_sequence_counter(0) {
145  Reset();
146  };
147 
159  bool getButtonPress(ButtonEnum b);
160  bool getButtonClick(ButtonEnum b);
168  int16_t getAnalogHat(AnalogHatEnum a);
169 
174  void enableImu(bool enable) {
175  // TODO: Should we just always enable it?
176  switchProOutput.enableImu = enable ? 1 : 0;
177  }
178 
184  float getAngle(AngleEnum a) {
185  if (a == Pitch)
186  return (atan2f(-switchProData.imu[0].accY, -switchProData.imu[0].accZ) + PI) * RAD_TO_DEG;
187  else
188  return (atan2f(switchProData.imu[0].accX, -switchProData.imu[0].accZ) + PI) * RAD_TO_DEG;
189  };
190 
196  int16_t getSensor(SensorEnum s) {
197  switch(s) {
198  case gX:
199  return switchProData.imu[0].gyroX;
200  case gY:
201  return switchProData.imu[0].gyroY;
202  case gZ:
203  return switchProData.imu[0].gyroZ;
204  case aX:
205  return switchProData.imu[0].accX;
206  case aY:
207  return switchProData.imu[0].accY;
208  case aZ:
209  return switchProData.imu[0].accZ;
210  default:
211  return 0;
212  }
213  };
214 
216  void setAllOff() {
217  setRumbleOff();
218  setLedOff();
219  setLedHomeOff();
220  };
221 
223  void setRumbleOff() {
224  setRumble(false, false);
225  }
226 
230  }
231 
237  void setRumble(bool leftRumbleOn, bool rightRumbleOn) {
238  switchProOutput.leftRumbleOn = leftRumbleOn;
239  switchProOutput.rightRumbleOn = rightRumbleOn;
240  switchProOutput.ledReportChanged = true; // Set this, so the rumble effect gets changed immediately
241  }
242 
247  void setRumbleLeft(bool on) {
249  switchProOutput.ledReportChanged = true; // Set this, so the rumble effect gets changed immediately
250  }
251 
256  void setRumbleRight(bool on) {
258  switchProOutput.ledReportChanged = true; // Set this, so the rumble effect gets changed immediately
259  }
260 
266  void setLedRaw(uint8_t mask) {
267  switchProOutput.ledMask = mask;
269  }
270 
272  void setLedOff() {
273  setLedRaw(0);
274  }
275 
280  void setLedOff(LEDEnum a) {
281  switchProOutput.ledMask &= ~((uint8_t)(pgm_read_byte(&SWITCH_PRO_LEDS[(uint8_t)a]) & 0x0f));
283  }
284 
289  void setLedOn(LEDEnum a) {
290  switchProOutput.ledMask |= (uint8_t)(pgm_read_byte(&SWITCH_PRO_LEDS[(uint8_t)a]) & 0x0f);
292  }
293 
299  switchProOutput.ledMask ^= (uint8_t)(pgm_read_byte(&SWITCH_PRO_LEDS[(uint8_t)a]) & 0x0f);
301  }
302 
304  void setLedHomeOff() {
305  switchProOutput.ledHome = false;
307  }
308 
310  void setLedHomeOn() {
311  switchProOutput.ledHome = true;
313  }
314 
319  }
320 
322  uint16_t getMessageCounter() {
323  return message_counter;
324  }
325 
331  uint8_t getBatteryLevel() {
332  return switchProData.battery_level >> 1;
333  }
334 
339  bool isCharging() {
340  return switchProData.battery_level & 0x01;
341  }
342 
343 protected:
349  void Parse(uint8_t len, uint8_t *buf);
350 
352  void Reset();
353 
359  virtual void sendOutputReport(uint8_t *data, uint8_t len) = 0;
360 
362  virtual void sendHandshake() {}
363 
368  virtual void disableTimeout() {}
369 
372 
373 private:
374  static int8_t getButtonIndexSwitchPro(ButtonEnum b);
375 
376  void sendOutputCmd();
377  void sendRumbleOutputReport();
378 
379  SwitchProData switchProData;
380  SwitchProButtons oldButtonState, buttonClickState;
381  uint16_t message_counter = 0;
382  uint8_t output_sequence_counter : 4;
383  uint32_t rumble_on_timer = 0;
384 };
385 #endif
const uint8_t SWITCH_PRO_BUTTONS[]
const uint8_t SWITCH_PRO_LEDS[]
virtual void sendOutputReport(uint8_t *data, uint8_t len)=0
int16_t getAnalogHat(AnalogHatEnum a)
uint8_t getBatteryLevel()
bool getButtonClick(ButtonEnum b)
void setRumble(bool leftRumbleOn, bool rightRumbleOn)
float getAngle(AngleEnum a)
bool getButtonPress(ButtonEnum b)
void setLedOff(LEDEnum a)
void enableImu(bool enable)
int16_t getSensor(SensorEnum s)
void setLedRaw(uint8_t mask)
virtual void disableTimeout()
void setLedToggle(LEDEnum a)
void setLedOn(LEDEnum a)
void setRumbleRight(bool on)
uint16_t getMessageCounter()
virtual void sendHandshake()
void Parse(uint8_t len, uint8_t *buf)
SwitchProOutput switchProOutput
void setRumbleLeft(bool on)
SensorEnum
@ gY
@ gX
@ aX
@ aZ
@ gZ
@ aY
AnalogHatEnum
LEDEnum
AngleEnum
@ Pitch
ButtonEnum
int16_t accX
int16_t gyroX
int16_t gyroZ
int16_t accZ
int16_t gyroY
int16_t accY
uint8_t vibratorInput
uint16_t rightHatY
uint16_t rightHatX
uint8_t battery_level
SwitchProButtons btn
ImuData imu[3]
uint8_t connection_info
#define pgm_read_byte(addr)