mode_switch_hori.cpp 4.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. #include "mode_switch_hori.h"
  2. #include "triton.h"
  3. #include "gamepad_util.h"
  4. #include "config.h"
  5. #include <Adafruit_TinyUSB.h>
  6. #include <Arduino.h>
  7. SwitchHoriController g_switchHori;
  8. // 8-byte input report: [btn_lo][btn_hi][hat][LX][LY][RX][RY][vendor], sticks uint8 center 0x80.
  9. static const uint8_t SWITCH_HID_DESC[]={
  10. 0x05,0x01, 0x09,0x05, 0xA1,0x01, // Usage Page (Generic Desktop), Usage (Game Pad), Collection (App)
  11. 0x15,0x00, 0x25,0x01, 0x35,0x00, 0x45,0x01, 0x75,0x01, 0x95,0x10, // 16 x 1-bit buttons
  12. 0x05,0x09, 0x19,0x01, 0x29,0x10, 0x81,0x02,
  13. 0x05,0x01, 0x25,0x07, 0x46,0x3B,0x01, 0x75,0x04, 0x95,0x01, 0x65,0x14, 0x09,0x39, 0x81,0x42, // hat switch (4 bits)
  14. 0x65,0x00, 0x95,0x01, 0x81,0x01, // 4-bit padding to a byte
  15. 0x26,0xFF,0x00, 0x46,0xFF,0x00, 0x09,0x30, 0x09,0x31, 0x09,0x32, 0x09,0x35, 0x75,0x08, 0x95,0x04, 0x81,0x02, // X,Y,Z,Rz
  16. 0x06,0x00,0xFF, 0x09,0x20, 0x95,0x01, 0x81,0x02, // vendor byte (input)
  17. 0x0A,0x21,0x26, 0x95,0x08, 0x91,0x02, // vendor (output, 8 bytes; unused)
  18. 0xC0
  19. };
  20. static Adafruit_USBD_HID g_switch; // HORIPAD gamepad HID interface
  21. static unsigned long g_swLastMs=0;
  22. // back-paddle code (g_back[]) -> Switch button bit. 0=none 1=A 2=B 3=X 4=Y 5=LB 6=RB 7=L3 8=R3 9=Back(Minus) 10=Start(Plus) 11=Guide(Home)
  23. static uint16_t codeToSwitch(uint8_t c, uint16_t fA,uint16_t fB,uint16_t fX,uint16_t fY){
  24. switch(c){ case 1:return fA; case 2:return fB; case 3:return fX; case 4:return fY;
  25. case 5:return 0x10; case 6:return 0x20; case 7:return 0x400; case 8:return 0x800;
  26. case 9:return 0x100; case 10:return 0x200; case 11:return 0x1000; default:return 0; }
  27. }
  28. // Back-paddle code 12..15 map to D-pad Up/Down/Left/Right; fold them into the hat direction flags.
  29. static inline void backCodeToHatDirs(uint8_t c, bool& u, bool& d, bool& l, bool& r){
  30. if(c==12) u=true;
  31. else if(c==13) d=true;
  32. else if(c==14) l=true;
  33. else if(c==15) r=true;
  34. }
  35. // HORIPAD/Switch button bits: Y=1 B=2 A=4 X=8 L=10 R=20 ZL=40 ZR=80 Minus=100 Plus=200 LClick=400 RClick=800 Home=1000 Capture=2000
  36. static void switchBuildHoripad(uint8_t out[8]){
  37. uint32_t b=g_in.buttons; uint16_t btn=0;
  38. if(g_qamMap && (b&TB_MENU)){ b &= ~(uint32_t)TB_MENU; b |= tritonFromCode(g_qamMap); }
  39. // Mode-switch chord (all 4 back + A/X/Y): don't pass the face press to the console while the back-4 are held.
  40. if((b&CHORD_BACK4)==CHORD_BACK4) b &= ~(uint32_t)(TB_A|TB_X|TB_Y);
  41. // face buttons with optional A/B + X/Y swap (Nintendo physical-vs-label layout)
  42. uint16_t fY = g_abSwap?0x08:0x01, fB = g_abSwap?0x04:0x02, fA = g_abSwap?0x02:0x04, fX = g_abSwap?0x01:0x08;
  43. if(b&TB_Y)btn|=fY; if(b&TB_B)btn|=fB; if(b&TB_A)btn|=fA; if(b&TB_X)btn|=fX;
  44. if(b&TB_LB)btn|=0x10; if(b&TB_RB)btn|=0x20; // L, R
  45. // ZL/ZR digital: trip on the analog threshold (activates early) OR the full-press click bit
  46. if((g_in.lt>=SW_TRIG_ON)||(b&0x8000000u))btn|=0x40; if((g_in.rt>=SW_TRIG_ON)||(b&0x800000u))btn|=0x80;
  47. if(b&TB_MENU)btn|=0x100; if(b&TB_VIEW)btn|=0x200; // Minus, Plus
  48. if(b&TB_L3)btn|=0x400; if(b&TB_R3)btn|=0x800; // LClick, RClick
  49. if(b&TB_STEAM)btn|=0x1000; // Home
  50. // back paddles -> configurable mapping (same g_back[] as Xbox: default L4->LB R4->RB L5->L3 R5->R3)
  51. if(b&TB_L4)btn|=codeToSwitch(g_back[0],fA,fB,fX,fY); if(b&TB_R4)btn|=codeToSwitch(g_back[1],fA,fB,fX,fY);
  52. if(b&TB_L5)btn|=codeToSwitch(g_back[2],fA,fB,fX,fY); if(b&TB_R5)btn|=codeToSwitch(g_back[3],fA,fB,fX,fY);
  53. bool u=b&TB_DUP,d=b&TB_DDN,l=b&TB_DLF,r=b&TB_DRT; // hat: 0=N..7=NW, 8=neutral
  54. if(b&TB_L4) backCodeToHatDirs(g_back[0],u,d,l,r);
  55. if(b&TB_R4) backCodeToHatDirs(g_back[1],u,d,l,r);
  56. if(b&TB_L5) backCodeToHatDirs(g_back[2],u,d,l,r);
  57. if(b&TB_R5) backCodeToHatDirs(g_back[3],u,d,l,r);
  58. uint8_t hat=8;
  59. if(u&&r)hat=1; else if(r&&d)hat=3; else if(d&&l)hat=5; else if(l&&u)hat=7;
  60. else if(u)hat=0; else if(r)hat=2; else if(d)hat=4; else if(l)hat=6;
  61. out[0]=btn&0xFF; out[1]=btn>>8; out[2]=hat;
  62. out[3]=swStick(g_in.lx,false); out[4]=swStick(g_in.ly,true); // HID Y is down-positive -> invert
  63. out[5]=swStick(g_in.rx,false); out[6]=swStick(g_in.ry,true);
  64. out[7]=0;
  65. }
  66. void SwitchHoriController::begin(){
  67. USBDevice.setID(0x0F0D, 0x0092);
  68. USBDevice.setDeviceVersion(0x0201); // bumped from 0x0200 for the added wake-mouse interface (Windows caches config by VID:PID:bcdDevice)
  69. USBDevice.setManufacturerDescriptor("HORI CO.,LTD.");
  70. USBDevice.setProductDescriptor("POKKEN CONTROLLER");
  71. g_switch.enableOutEndpoint(true);
  72. g_switch.setReportDescriptor(SWITCH_HID_DESC, sizeof SWITCH_HID_DESC);
  73. g_switch.setPollInterval(8);
  74. g_switch.begin();
  75. }
  76. void SwitchHoriController::task(){ // stream the 8-byte HORIPAD report at ~250Hz (no handshake needed)
  77. if(!g_switch.ready()) return;
  78. if(millis()-g_swLastMs < USB_STREAM_MS) return; g_swLastMs=millis();
  79. uint8_t p[8]; switchBuildHoripad(p);
  80. g_switch.sendReport(0, p, sizeof p); // report-id-less descriptor
  81. }