From 102746ef6bb2fe5f5cce428522673be1591c2b6b Mon Sep 17 00:00:00 2001 From: Kristian Sloth Lauszus Date: Sun, 15 Nov 2020 17:29:54 +0100 Subject: [PATCH] Xbox One S controller support is now finally working --- BTD.cpp | 186 +++++++++------------ BTD.h | 34 ++-- BTHID.cpp | 207 ++++-------------------- README.md | 11 +- SPP.cpp | 21 +++ XBOXONESBT.h | 100 ++++++++++++ XBOXONESParser.cpp | 200 +++++++++++++++++++++++ XBOXONESParser.h | 127 +++++++++++++++ controllerEnums.h | 6 + examples/Xbox/XBOXONESBT/XBOXONESBT.ino | 135 ++++++++++++++++ keywords.txt | 4 + settings.h | 2 +- 12 files changed, 726 insertions(+), 307 deletions(-) create mode 100644 XBOXONESBT.h create mode 100644 XBOXONESParser.cpp create mode 100644 XBOXONESParser.h create mode 100644 examples/Xbox/XBOXONESBT/XBOXONESBT.ino diff --git a/BTD.cpp b/BTD.cpp index ade16f4c..bd2d2587 100644 --- a/BTD.cpp +++ b/BTD.cpp @@ -17,7 +17,7 @@ #include "BTD.h" // To enable serial debugging see "settings.h" -#define EXTRADEBUG // Uncomment to get even more debugging data +//#define EXTRADEBUG // Uncomment to get even more debugging data const uint8_t BTD::BTD_CONTROL_PIPE = 0; const uint8_t BTD::BTD_EVENT_PIPE = 1; @@ -542,7 +542,6 @@ void BTD::HCI_event_task() { if(classOfDevice[0] & 0x08) Notify(PSTR("\r\nGamepad found"), 0x80); #endif - for(uint8_t j = 0; j < 6; j++) disc_bdaddr[j] = hcibuf[j + 3 + 6 * i]; @@ -673,62 +672,49 @@ void BTD::HCI_event_task() { } break; - case EV_READ_REMOTE_EXTENDED_FEATURES_COMPLETE: - if(!hcibuf[2]) { // Check if connected OK - if(!hci_check_flag(HCI_FLAG_REMOTE_EXTENDED_FEATURES)) { + case EV_IO_CAPABILITY_REQUEST: +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nReceived IO Capability Request"), 0x80); +#endif + hci_io_capability_request_reply(); + break; + + case EV_IO_CAPABILITY_RESPONSE: #ifdef EXTRADEBUG - Notify(PSTR("\r\nPage number: "), 0x80); - D_PrintHex (hcibuf[5], 0x80); - Notify(PSTR("\r\nMaximum page number: "), 0x80); - D_PrintHex (hcibuf[6], 0x80); - Notify(PSTR("\r\nExtended LMP features:"), 0x80); - for(uint8_t i = 0; i < 8; i++) { - Notify(PSTR(" "), 0x80); - D_PrintHex (hcibuf[7 + i], 0x80); - } + Notify(PSTR("\r\nReceived IO Capability Response: "), 0x80); + Notify(PSTR("\r\nIO capability: "), 0x80); + D_PrintHex (hcibuf[8], 0x80); + Notify(PSTR("\r\nOOB data present: "), 0x80); + D_PrintHex (hcibuf[9], 0x80); + Notify(PSTR("\r\nAuthentication request: "), 0x80); + D_PrintHex (hcibuf[10], 0x80); #endif -#ifdef DEBUG_USB_HOST - if(hcibuf[5] == 0) { // Page 0 - Notify(PSTR("\r\nRemote "), 0x80); - if(hcibuf[7 + 6] & (1U << 3)) - Notify(PSTR("supports"), 0x80); - else - Notify(PSTR("does NOT support"), 0x80); - Notify(PSTR(" secure simple pairing (controller support)"), 0x80); - } else if(hcibuf[5] == 1) { // Page 1 - Notify(PSTR("\r\nRemote "), 0x80); - if(hcibuf[7 + 0] & (1U << 0)) - Notify(PSTR("supports"), 0x80); - else - Notify(PSTR("\r\ndoes NOT support"), 0x80); - Notify(PSTR(" secure simple pairing (host support)"), 0x80); - } -#endif - } - hci_set_flag(HCI_FLAG_REMOTE_EXTENDED_FEATURES); - } else { - hci_state = HCI_CHECK_DEVICE_SERVICE; -#ifdef DEBUG_USB_HOST - Notify(PSTR("\r\nFailed to read remote extended featues: "), 0x80); - D_PrintHex (hcibuf[2], 0x80); -#endif - } break; case EV_USER_CONFIRMATION_REQUEST: #ifdef DEBUG_USB_HOST - Notify(PSTR("\r\nUser confirmation Request: "), 0x80); -#endif + Notify(PSTR("\r\nUser confirmation Request"), 0x80); #ifdef EXTRADEBUG - Notify(PSTR("\r\nNumeric value: "), 0x80); + Notify(PSTR(": \r\nNumeric value: "), 0x80); for(uint8_t i = 0; i < 4; i++) { Notify(PSTR(" "), 0x80); D_PrintHex (hcibuf[8 + i], 0x80); } #endif - +#endif + // Simply confirm the connection, as the host has no "NoInputNoOutput" capabilities hci_user_confirmation_request_reply(); - hci_state = HCI_SCANNING_STATE; + break; + + case EV_SIMPLE_PAIRING_COMPLETE: +#ifdef EXTRADEBUG + if(!hcibuf[2]) { // Check if connected OK + Notify(PSTR("\r\nSimple Pairing succeeded"), 0x80); + } else { + Notify(PSTR("\r\nSimple Pairing failed: "), 0x80); + D_PrintHex (hcibuf[2], 0x80); + } +#endif break; /* We will just ignore the following events */ @@ -756,7 +742,6 @@ void BTD::HCI_event_task() { if(hcibuf[0] != 0x00) { Notify(PSTR("\r\nUnmanaged HCI Event: "), 0x80); D_PrintHex (hcibuf[0], 0x80); - Notify(PSTR(", data: "), 0x80); for(uint16_t i = 0; i < hcibuf[1]; i++) { D_PrintHex (hcibuf[2 + i], 0x80); @@ -872,6 +857,16 @@ void BTD::HCI_task() { if(hci_check_flag(HCI_FLAG_CMD_COMPLETE)) { #ifdef DEBUG_USB_HOST Notify(PSTR("\r\nSimple pairing was enabled"), 0x80); +#endif + hci_set_event_mask(); + hci_state = HCI_SET_EVENT_MASK_STATE; + } + break; + + case HCI_SET_EVENT_MASK_STATE: + if(hci_check_flag(HCI_FLAG_CMD_COMPLETE)) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nSet event mask completed"), 0x80); #endif hci_state = HCI_CHECK_DEVICE_SERVICE; } @@ -943,9 +938,8 @@ void BTD::HCI_task() { else Notify(PSTR("\r\nConnected to HID device"), 0x80); #endif - hci_read_remote_extended_features(0); // "Requests the normal LMP features as returned by the HCI_Read_Remote_Supported_Features command" - //hci_read_remote_extended_features(1); // Read page 1 - hci_state = HCI_REMOTE_EXTENDED_FEATURES_STATE; + hci_authentication_request(); // This will start the pairing with the device + hci_state = HCI_SCANNING_STATE; } else { #ifdef DEBUG_USB_HOST Notify(PSTR("\r\nTrying to connect one more time..."), 0x80); @@ -955,19 +949,6 @@ void BTD::HCI_task() { } break; - case HCI_REMOTE_EXTENDED_FEATURES_STATE: - if(hci_check_flag(HCI_FLAG_REMOTE_EXTENDED_FEATURES)) { - /*hci_read_remote_version_information(); - delay(1); - hci_change_connection_packet_type_command(); - delay(1); - hci_write_link_policy_settings(); - delay(1);*/ - hci_authentication_request(); - hci_state = HCI_SCANNING_STATE; - } - break; - case HCI_SCANNING_STATE: if(!connectToWii && !pairWithWii && !connectToHIDDevice && !pairWithHIDDevice) { #ifdef DEBUG_USB_HOST @@ -1218,18 +1199,6 @@ void BTD::hci_remote_name() { HCI_Command(hcibuf, 13); } -void BTD::hci_read_remote_extended_features(uint8_t page_number) { - hci_clear_flag(HCI_FLAG_REMOTE_EXTENDED_FEATURES); - hcibuf[0] = 0x1C; // HCI OCF = 1C - hcibuf[1] = 0x01 << 2; // HCI OGF = 1 - hcibuf[2] = 0x03; // parameter length = 3 - hcibuf[3] = (uint8_t)(hci_handle & 0xFF); //connection handle - low byte - hcibuf[4] = (uint8_t)((hci_handle >> 8) & 0x0F); //connection handle - high byte - hcibuf[5] = page_number; - - HCI_Command(hcibuf, 6); -} - void BTD::hci_write_local_name(const char* name) { hcibuf[0] = 0x13; // HCI OCF = 13 hcibuf[1] = 0x03 << 2; // HCI OGF = 3 @@ -1242,6 +1211,24 @@ void BTD::hci_write_local_name(const char* name) { HCI_Command(hcibuf, 4 + strlen(name)); } +void BTD::hci_set_event_mask() { + hcibuf[0] = 0x01; // HCI OCF = 01 + hcibuf[1] = 0x03 << 2; // HCI OGF = 3 + hcibuf[2] = 0x08; + // The first 6 bytes are the default of 1FFF FFFF FFFF + // However we need to set bits 48-55 for simple pairing to work + hcibuf[3] = 0xFF; + hcibuf[4] = 0xFF; + hcibuf[5] = 0xFF; + hcibuf[6] = 0xFF; + hcibuf[7] = 0xFF; + hcibuf[8] = 0x1F; + hcibuf[9] = 0xFF; // Enable bits 48-55 used for simple pairing + hcibuf[10] = 0x00; + + HCI_Command(hcibuf, 11); +} + void BTD::hci_write_simple_pairing_mode(bool enable) { hcibuf[0] = 0x56; // HCI OCF = 56 hcibuf[1] = 0x03 << 2; // HCI OGF = 3 @@ -1299,40 +1286,6 @@ void BTD::hci_connect(uint8_t *bdaddr) { HCI_Command(hcibuf, 16); } -void BTD::hci_read_remote_version_information() { - hcibuf[0] = 0x1D; // HCI OCF = 1D - hcibuf[1] = 0x01 << 2; // HCI OGF = 1 - hcibuf[2] = 0x02; // parameter Total Length = 2 - hcibuf[3] = (uint8_t)(hci_handle & 0xFF); //connection handle - low byte - hcibuf[4] = (uint8_t)((hci_handle >> 8) & 0x0F); //connection handle - high byte - - HCI_Command(hcibuf, 5); -} - -void BTD::hci_change_connection_packet_type_command() { - hcibuf[0] = 0x0F; // HCI OCF = 0F - hcibuf[1] = 0x01 << 2; // HCI OGF = 1 - hcibuf[2] = 0x04; // parameter Total Length = 4 - hcibuf[3] = (uint8_t)(hci_handle & 0xFF); //connection handle - low byte - hcibuf[4] = (uint8_t)((hci_handle >> 8) & 0x0F); //connection handle - high byte - hcibuf[5] = 0x18; // DM1 or DH1 may be used - hcibuf[6] = 0xCC; // DM3, DH3, DM5, DH5 may be used - - HCI_Command(hcibuf, 7); -} - -void BTD::hci_write_link_policy_settings() { - hcibuf[0] = 0x0D; // HCI OCF = 0D - hcibuf[1] = 0x02 << 2; // HCI OGF = 2 - hcibuf[2] = 0x04; // parameter Total Length = 4 - hcibuf[3] = (uint8_t)(hci_handle & 0xFF); //connection handle - low byte - hcibuf[4] = (uint8_t)((hci_handle >> 8) & 0x0F); //connection handle - high byte - hcibuf[5] = 0x0F; // Enable role switch, enable hold mode, enable sniff mode, enable park mode - hcibuf[6] = 0x00; - - HCI_Command(hcibuf, 7); -} - void BTD::hci_pin_code_request_reply() { hcibuf[0] = 0x0D; // HCI OCF = 0D hcibuf[1] = 0x01 << 2; // HCI OGF = 1 @@ -1397,6 +1350,23 @@ void BTD::hci_link_key_request_negative_reply() { HCI_Command(hcibuf, 9); } +void BTD::hci_io_capability_request_reply() { + hcibuf[0] = 0x2B; // HCI OCF = 2B + hcibuf[1] = 0x01 << 2; // HCI OGF = 1 + hcibuf[2] = 0x09; + hcibuf[3] = disc_bdaddr[0]; // 6 octet bdaddr + hcibuf[4] = disc_bdaddr[1]; + hcibuf[5] = disc_bdaddr[2]; + hcibuf[6] = disc_bdaddr[3]; + hcibuf[7] = disc_bdaddr[4]; + hcibuf[8] = disc_bdaddr[5]; + hcibuf[9] = 0x03; // NoInputNoOutput + hcibuf[10] = 0x00; // OOB authentication data not present + hcibuf[11] = 0x00; // MITM Protection Not Required – No Bonding. Numeric comparison with automatic accept allowed + + HCI_Command(hcibuf, 12); +} + void BTD::hci_user_confirmation_request_reply() { hcibuf[0] = 0x2C; // HCI OCF = 2C hcibuf[1] = 0x01 << 2; // HCI OGF = 1 diff --git a/BTD.h b/BTD.h index 4ba59ef3..e0b3a46d 100644 --- a/BTD.h +++ b/BTD.h @@ -61,7 +61,7 @@ #define HCI_DISCONNECT_STATE 16 #define HCI_LOCAL_EXTENDED_FEATURES_STATE 17 #define HCI_WRITE_SIMPLE_PAIRING_STATE 18 -#define HCI_REMOTE_EXTENDED_FEATURES_STATE 19 +#define HCI_SET_EVENT_MASK_STATE 19 /* HCI event flags*/ #define HCI_FLAG_CMD_COMPLETE (1UL << 0) @@ -74,7 +74,6 @@ #define HCI_FLAG_DEVICE_FOUND (1UL << 7) #define HCI_FLAG_CONNECT_EVENT (1UL << 8) #define HCI_FLAG_LOCAL_EXTENDED_FEATURES (1UL << 9) -#define HCI_FLAG_REMOTE_EXTENDED_FEATURES (1UL << 10) /* Macros for HCI event flag tests */ #define hci_check_flag(flag) (hci_event_flag & (flag)) @@ -91,6 +90,10 @@ #define EV_REMOTE_NAME_COMPLETE 0x07 #define EV_ENCRYPTION_CHANGE 0x08 #define EV_CHANGE_CONNECTION_LINK 0x09 +#define EV_READ_REMOTE_VERSION_INFORMATION_COMPLETE 0x0C +#define EV_QOS_SETUP_COMPLETE 0x0D +#define EV_COMMAND_COMPLETE 0x0E +#define EV_COMMAND_STATUS 0x0F #define EV_ROLE_CHANGED 0x12 #define EV_NUM_COMPLETE_PKT 0x13 #define EV_PIN_CODE_REQUEST 0x16 @@ -98,15 +101,13 @@ #define EV_LINK_KEY_NOTIFICATION 0x18 #define EV_DATA_BUFFER_OVERFLOW 0x1A #define EV_MAX_SLOTS_CHANGE 0x1B -#define EV_READ_REMOTE_VERSION_INFORMATION_COMPLETE 0x0C -#define EV_QOS_SETUP_COMPLETE 0x0D -#define EV_COMMAND_COMPLETE 0x0E -#define EV_COMMAND_STATUS 0x0F #define EV_LOOPBACK_COMMAND 0x19 #define EV_PAGE_SCAN_REP_MODE 0x20 #define EV_READ_REMOTE_EXTENDED_FEATURES_COMPLETE 0x23 +#define EV_IO_CAPABILITY_REQUEST 0x31 +#define EV_IO_CAPABILITY_RESPONSE 0x32 #define EV_USER_CONFIRMATION_REQUEST 0x33 - +#define EV_SIMPLE_PAIRING_COMPLETE 0x36 /* Bluetooth states for the different Bluetooth drivers */ #define L2CAP_WAIT 0 @@ -339,23 +340,23 @@ public: void hci_read_bdaddr(); /** Read the HCI Version of the Bluetooth dongle. */ void hci_read_local_version_information(); - + /** Used to check if the dongle supports simple paring */ void hci_read_local_extended_features(uint8_t page_number); /** * Set the local name of the Bluetooth dongle. * @param name Desired name. */ void hci_write_local_name(const char* name); - + /** Used to enable simply paring if the dongle supports it */ void hci_write_simple_pairing_mode(bool enable); + /** Used to enable events related to simple paring */ + void hci_set_event_mask(); /** Enable visibility to other Bluetooth devices. */ void hci_write_scan_enable(); /** Disable visibility to other Bluetooth devices. */ void hci_write_scan_disable(); /** Read the remote devices name. */ void hci_remote_name(); - - void hci_read_remote_extended_features(uint8_t page_number); /** Accept the connection with the Bluetooth device. */ void hci_accept_connection(); /** @@ -376,7 +377,7 @@ public: * if the Host does not have a stored Link Key for the connection. */ void hci_link_key_request_negative_reply(); - + /** Used to during simple paring to confirm that the we want to connect */ void hci_user_confirmation_request_reply(); /** Used to try to authenticate with the remote device. */ void hci_authentication_request(); @@ -386,13 +387,8 @@ public: void hci_inquiry_cancel(); /** Connect to last device communicated with. */ void hci_connect(); - - void hci_read_remote_version_information(); - - void hci_change_connection_packet_type_command(); - - void hci_write_link_policy_settings(); - + /** Used during simple paring to reply to a IO capability request */ + void hci_io_capability_request_reply(); /** * Connect to device. * @param bdaddr Bluetooth address of the device. diff --git a/BTHID.cpp b/BTHID.cpp index 6f9e0492..ad2a0ac2 100644 --- a/BTHID.cpp +++ b/BTHID.cpp @@ -17,7 +17,7 @@ #include "BTHID.h" // To enable serial debugging see "settings.h" -#define EXTRADEBUG // Uncomment to get even more debugging data +//#define EXTRADEBUG // Uncomment to get even more debugging data //#define PRINTREPORT // Uncomment to print the report send by the HID device BTHID::BTHID(BTD *p, bool pair, const char *pin) : @@ -61,19 +61,6 @@ void BTHID::disconnect() { // Use this void to disconnect the device } void BTHID::ACLData(uint8_t* l2capinbuf) { -/* - Notify(PSTR("\r\nL2CAP Data - Channel ID: "), 0x80); - D_PrintHex (l2capinbuf[7], 0x80); - Notify(PSTR(" "), 0x80); - D_PrintHex (l2capinbuf[6], 0x80); - - Notify(PSTR(", data: "), 0x80); - for(uint16_t i = 0; i < ((uint16_t)l2capinbuf[5] << 8 | l2capinbuf[4]); i++) { - D_PrintHex (l2capinbuf[i + 8], 0x80); - Notify(PSTR(" "), 0x80); - } -*/ - if(!connected) { if(l2capinbuf[8] == L2CAP_CMD_CONNECTION_REQUEST) { if((l2capinbuf[12] | (l2capinbuf[13] << 8)) == SDP_PSM && !pBtd->sdpConnectionClaimed) { @@ -116,24 +103,29 @@ void BTHID::ACLData(uint8_t* l2capinbuf) { } else if(l2capinbuf[8] == L2CAP_CMD_CONNECTION_RESPONSE) { if(((l2capinbuf[16] | (l2capinbuf[17] << 8)) == 0x0000) && ((l2capinbuf[18] | (l2capinbuf[19] << 8)) == SUCCESSFUL)) { // Success if(l2capinbuf[14] == sdp_dcid[0] && l2capinbuf[15] == sdp_dcid[1]) { +#ifdef EXTRADEBUG Notify(PSTR("\r\nSDP Connection Complete"), 0x80); +#endif identifier = l2capinbuf[9]; sdp_scid[0] = l2capinbuf[12]; sdp_scid[1] = l2capinbuf[13]; - #ifdef DEBUG_USB_HOST Notify(PSTR("\r\nSend SDP Config Request"), 0x80); #endif identifier++; pBtd->l2cap_config_request(hci_handle, identifier, sdp_scid); } else if(l2capinbuf[14] == control_dcid[0] && l2capinbuf[15] == control_dcid[1]) { +#ifdef EXTRADEBUG Notify(PSTR("\r\nHID Control Connection Complete"), 0x80); +#endif identifier = l2capinbuf[9]; control_scid[0] = l2capinbuf[12]; control_scid[1] = l2capinbuf[13]; l2cap_set_flag(L2CAP_FLAG_CONTROL_CONNECTED); } else if(l2capinbuf[14] == interrupt_dcid[0] && l2capinbuf[15] == interrupt_dcid[1]) { +#ifdef EXTRADEBUG Notify(PSTR("\r\nHID Interrupt Connection Complete"), 0x80); +#endif identifier = l2capinbuf[9]; interrupt_scid[0] = l2capinbuf[12]; interrupt_scid[1] = l2capinbuf[13]; @@ -172,28 +164,40 @@ void BTHID::ACLData(uint8_t* l2capinbuf) { } else if(l2capinbuf[8] == L2CAP_CMD_CONFIG_RESPONSE) { if((l2capinbuf[16] | (l2capinbuf[17] << 8)) == 0x0000) { // Success if(l2capinbuf[12] == sdp_dcid[0] && l2capinbuf[13] == sdp_dcid[1]) { +#ifdef EXTRADEBUG Notify(PSTR("\r\nSDP Configuration Complete"), 0x80); +#endif identifier = l2capinbuf[9]; l2cap_set_flag(L2CAP_FLAG_CONFIG_SDP_SUCCESS); } else if(l2capinbuf[12] == control_dcid[0] && l2capinbuf[13] == control_dcid[1]) { +#ifdef EXTRADEBUG Notify(PSTR("\r\nHID Control Configuration Complete"), 0x80); +#endif identifier = l2capinbuf[9]; l2cap_set_flag(L2CAP_FLAG_CONFIG_CONTROL_SUCCESS); } else if(l2capinbuf[12] == interrupt_dcid[0] && l2capinbuf[13] == interrupt_dcid[1]) { +#ifdef EXTRADEBUG Notify(PSTR("\r\nHID Interrupt Configuration Complete"), 0x80); +#endif identifier = l2capinbuf[9]; l2cap_set_flag(L2CAP_FLAG_CONFIG_INTERRUPT_SUCCESS); } } } else if(l2capinbuf[8] == L2CAP_CMD_CONFIG_REQUEST) { if(l2capinbuf[12] == sdp_dcid[0] && l2capinbuf[13] == sdp_dcid[1]) { +#ifdef EXTRADEBUG Notify(PSTR("\r\nSDP Configuration Request"), 0x80); +#endif pBtd->l2cap_config_response(hci_handle, l2capinbuf[9], sdp_scid); } else if(l2capinbuf[12] == control_dcid[0] && l2capinbuf[13] == control_dcid[1]) { +#ifdef EXTRADEBUG Notify(PSTR("\r\nHID Control Configuration Request"), 0x80); +#endif pBtd->l2cap_config_response(hci_handle, l2capinbuf[9], control_scid); } else if(l2capinbuf[12] == interrupt_dcid[0] && l2capinbuf[13] == interrupt_dcid[1]) { +#ifdef EXTRADEBUG Notify(PSTR("\r\nHID Interrupt Configuration Request"), 0x80); +#endif pBtd->l2cap_config_response(hci_handle, l2capinbuf[9], interrupt_scid); } } else if(l2capinbuf[8] == L2CAP_CMD_DISCONNECT_REQUEST) { @@ -220,15 +224,21 @@ void BTHID::ACLData(uint8_t* l2capinbuf) { } } else if(l2capinbuf[8] == L2CAP_CMD_DISCONNECT_RESPONSE) { if(l2capinbuf[12] == sdp_scid[0] && l2capinbuf[13] == sdp_scid[1]) { - //Notify(PSTR("\r\nDisconnect Response: SDP Channel"), 0x80); +#ifdef EXTRADEBUG + Notify(PSTR("\r\nDisconnect Response: SDP Channel"), 0x80); +#endif identifier = l2capinbuf[9]; l2cap_set_flag(L2CAP_FLAG_DISCONNECT_RESPONSE); } else if(l2capinbuf[12] == control_scid[0] && l2capinbuf[13] == control_scid[1]) { - //Notify(PSTR("\r\nDisconnect Response: Control Channel"), 0x80); +#ifdef EXTRADEBUG + Notify(PSTR("\r\nDisconnect Response: Control Channel"), 0x80); +#endif identifier = l2capinbuf[9]; l2cap_set_flag(L2CAP_FLAG_DISCONNECT_CONTROL_RESPONSE); } else if(l2capinbuf[12] == interrupt_scid[0] && l2capinbuf[13] == interrupt_scid[1]) { - //Notify(PSTR("\r\nDisconnect Response: Interrupt Channel"), 0x80); +#ifdef EXTRADEBUG + Notify(PSTR("\r\nDisconnect Response: Interrupt Channel"), 0x80); +#endif identifier = l2capinbuf[9]; l2cap_set_flag(L2CAP_FLAG_DISCONNECT_INTERRUPT_RESPONSE); } @@ -254,7 +264,7 @@ void BTHID::ACLData(uint8_t* l2capinbuf) { l2capoutbuf[0] = SDP_SERVICE_SEARCH_RESPONSE; l2capoutbuf[1] = l2capinbuf[9];//transactionIDHigh; l2capoutbuf[2] = l2capinbuf[10];//transactionIDLow; -#if 1 + l2capoutbuf[3] = 0x00; // MSB Parameter Length l2capoutbuf[4] = 0x05; // LSB Parameter Length = 5 @@ -267,29 +277,6 @@ void BTHID::ACLData(uint8_t* l2capinbuf) { l2capoutbuf[9] = 0x00; // No continuation state SDP_Command(l2capoutbuf, 10); -#else - l2capoutbuf[3] = 0x00; // MSB Parameter Length - l2capoutbuf[4] = 0x09; // LSB Parameter Length = 9 - - l2capoutbuf[5] = 0x00; // MSB TotalServiceRecordCount - l2capoutbuf[6] = 0x01; // LSB TotalServiceRecordCount = 1 - - l2capoutbuf[7] = 0x00; // MSB CurrentServiceRecordCount - l2capoutbuf[8] = 0x01; // LSB CurrentServiceRecordCount = 1 - - l2capoutbuf[9] = 0x00; // ServiceRecordHandleList - l2capoutbuf[10] = 0x01; // ServiceRecordHandleList - l2capoutbuf[11] = 0x00; // ServiceRecordHandleList - l2capoutbuf[12] = 0x01; // ServiceRecordHandleList: 0x10001. - - l2capoutbuf[13] = 0x00; // No continuation state - - SDP_Command(l2capoutbuf, 14); -#endif - - //pBtd->hci_authentication_request(); - //identifier++; - //pBtd->l2cap_connection_request(hci_handle, identifier, sdp_dcid, SDP_PSM); } else if(l2capinbuf[8] == SDP_SERVICE_ATTRIBUTE_REQUEST) { Notify(PSTR("\r\nSDP_SERVICE_ATTRIBUTE_REQUEST"), 0x80); @@ -298,7 +285,6 @@ void BTHID::ACLData(uint8_t* l2capinbuf) { l2capoutbuf[1] = l2capinbuf[9];//transactionIDHigh; l2capoutbuf[2] = l2capinbuf[10];//transactionIDLow; -#if 1 l2capoutbuf[3] = 0x00; // MSB Parameter Length l2capoutbuf[4] = 0x05; // LSB Parameter Length = 5 @@ -312,141 +298,9 @@ void BTHID::ACLData(uint8_t* l2capinbuf) { l2capoutbuf[9] = 0x00; // No continuation state SDP_Command(l2capoutbuf, 10); -#else - size_t i = 3; - l2capoutbuf[i++] = 0x00; - l2capoutbuf[i++] = 0x5a; - l2capoutbuf[i++] = 0x00; - l2capoutbuf[i++] = 0x57; - l2capoutbuf[i++] = 0x35; - l2capoutbuf[i++] = 0x55; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0x00; - l2capoutbuf[i++] = 0x00; - l2capoutbuf[i++] = 0x0a; - l2capoutbuf[i++] = 0x00; - l2capoutbuf[i++] = 0x01; - l2capoutbuf[i++] = 0x00; - l2capoutbuf[i++] = 0x01; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0x00; - l2capoutbuf[i++] = 0x01; - l2capoutbuf[i++] = 0x35; - l2capoutbuf[i++] = 0x03; - l2capoutbuf[i++] = 0x19; - l2capoutbuf[i++] = 0x12; - l2capoutbuf[i++] = 0x00; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0x00; - l2capoutbuf[i++] = 0x05; - l2capoutbuf[i++] = 0x35; - l2capoutbuf[i++] = 0x03; - l2capoutbuf[i++] = 0x19; - l2capoutbuf[i++] = 0x10; - l2capoutbuf[i++] = 0x02; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0x00; - l2capoutbuf[i++] = 0x06; - l2capoutbuf[i++] = 0x35; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0x65; - l2capoutbuf[i++] = 0x6e; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0x00; - l2capoutbuf[i++] = 0x6a; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0x01; - l2capoutbuf[i++] = 0x00; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0x02; - l2capoutbuf[i++] = 0x00; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0x01; - l2capoutbuf[i++] = 0x03; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0x02; - l2capoutbuf[i++] = 0x01; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0x00; - l2capoutbuf[i++] = 0x4c; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0x02; - l2capoutbuf[i++] = 0x02; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0x4a; - l2capoutbuf[i++] = 0x34; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0x02; - l2capoutbuf[i++] = 0x03; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0x10; - l2capoutbuf[i++] = 0x11; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0x02; - l2capoutbuf[i++] = 0x04; - l2capoutbuf[i++] = 0x28; - l2capoutbuf[i++] = 0x01; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0x02; - l2capoutbuf[i++] = 0x05; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0x00; - l2capoutbuf[i++] = 0x01; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0xa0; - l2capoutbuf[i++] = 0x00; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0x16; - l2capoutbuf[i++] = 0xc4; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0xaf; - l2capoutbuf[i++] = 0xff; - l2capoutbuf[i++] = 0x09; - l2capoutbuf[i++] = 0x00; - l2capoutbuf[i++] = 0x01; - l2capoutbuf[i++] = 0x00; - - Serial.print("\nLENGTH: "); - Serial.println(i); - - SDP_Command(l2capoutbuf, i); -#endif - -/* - l2capoutbuf[0] = SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST; - l2capoutbuf[1] = 0x00; // TODO: Do not hardcode - l2capoutbuf[2] = 0x01; - - l2capoutbuf[3] = 0x00; // MSB Parameter Length - l2capoutbuf[4] = 0x0F; // LSB Parameter Length = 15 - - i = 5; - l2capoutbuf[i++] = 0x35; - l2capoutbuf[i++] = 0x03; - l2capoutbuf[i++] = 0x19; - l2capoutbuf[i++] = 0x12; - l2capoutbuf[i++] = 0x00; - l2capoutbuf[i++] = 0xff; - l2capoutbuf[i++] = 0xff; - l2capoutbuf[i++] = 0x35; - l2capoutbuf[i++] = 0x05; - l2capoutbuf[i++] = 0x0a; - l2capoutbuf[i++] = 0x00; - l2capoutbuf[i++] = 0x00; - l2capoutbuf[i++] = 0xff; - l2capoutbuf[i++] = 0xff; - l2capoutbuf[i++] = 0x00; - - Serial.print("\nLENGTH: "); - Serial.println(i); - - SDP_Command(l2capoutbuf, i); -*/ } else if(l2capinbuf[8] == SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST) { - Notify(PSTR("\r\nSDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST"), 0x80); - #ifdef EXTRADEBUG + Notify(PSTR("\r\nSDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST"), 0x80); Notify(PSTR("\r\nUUID: "), 0x80); uint16_t uuid; if((l2capinbuf[16] << 8 | l2capinbuf[17]) == 0x0000) // Check if it's sending the UUID as a 128-bit UUID @@ -464,7 +318,6 @@ void BTHID::ACLData(uint8_t* l2capinbuf) { Notify(PSTR(" "), 0x80); } #endif - serviceNotSupported(l2capinbuf[9], l2capinbuf[10]); // The service is not supported } #ifdef EXTRADEBUG diff --git a/README.md b/README.md index 8b8a5d4b..ea42cdca 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ For more information about the hardware see the [Hardware Manual](http://www.cir * __Oleg Mazurov, Circuits@Home__ - * __Alexei Glushchenko, Circuits@Home__ - * Developers of the USB Core, HID, FTDI, ADK, ACM, and PL2303 libraries -* __Kristian Lauszus, TKJ Electronics__ - +* __Kristian Sloth Lauszus__ - * Developer of the [BTD](#bluetooth-libraries), [BTHID](#bthid-library), [SPP](#spp-library), [PS4](#ps4-library), [PS3](#ps3-library), [Wii](#wii-library), [Xbox](#xbox-library), and [PSBuzz](#ps-buzz-library) libraries * __Andrew Kroll__ - * Major contributor to mass storage code @@ -55,6 +55,7 @@ Help yourself by helping us support you! Many thousands of hours have been spent * [Xbox library](#xbox-library) * [Xbox 360 Library](#xbox-360-library) * [Xbox ONE Library](#xbox-one-library) + * [Xbox ONE S Library](#xbox-one-s-library) * [Wii library](#wii-library) * [PS Buzz Library](#ps-buzz-library) * [HID Libraries](#hid-libraries) @@ -262,12 +263,18 @@ All the information regarding the Xbox 360 controller protocol are form these si #### Xbox ONE Library -An Xbox ONE controller is supported via USB in the [XBOXONE](XBOXONE.cpp) class. It is heavily based on the 360 library above. In addition to cross referencing the above, information on the protocol was found at: +A Xbox ONE controller is supported via USB in the [XBOXONE](XBOXONE.cpp) class. It is heavily based on the 360 library above. In addition to cross referencing the above, information on the protocol was found at: * * * +#### Xbox ONE S Library + +A Xbox ONE controller is supported via Bluetooth in the [XBOXONESBT](XBOXONESBT.cpp) class. + +Special thanks to [HisashiKato](https://github.com/HisashiKato) for his help: . + ### [Wii library](Wii.cpp) The [Wii](Wii.cpp) library support the Wiimote, but also the Nunchuch and Motion Plus extensions via Bluetooth. The Wii U Pro Controller and Wii Balance Board are also supported via Bluetooth. diff --git a/SPP.cpp b/SPP.cpp index 5e421097..910f7db8 100644 --- a/SPP.cpp +++ b/SPP.cpp @@ -531,6 +531,27 @@ void SPP::RFCOMM_task() { /* SDP Commands */ /************************************************************/ +void SPP::SDP_Command(uint8_t* data, uint8_t nbytes) { // See page 223 in the Bluetooth specs + pBtd->L2CAP_Command(hci_handle, data, nbytes, sdp_scid[0], sdp_scid[1]); +} + +void SPP::serviceNotSupported(uint8_t transactionIDHigh, uint8_t transactionIDLow) { // See page 235 in the Bluetooth specs + l2capoutbuf[0] = SDP_SERVICE_SEARCH_ATTRIBUTE_RESPONSE; + l2capoutbuf[1] = transactionIDHigh; + l2capoutbuf[2] = transactionIDLow; + l2capoutbuf[3] = 0x00; // MSB Parameter Length + l2capoutbuf[4] = 0x05; // LSB Parameter Length = 5 + l2capoutbuf[5] = 0x00; // MSB AttributeListsByteCount + l2capoutbuf[6] = 0x02; // LSB AttributeListsByteCount = 2 + + /* Attribute ID/Value Sequence: */ + l2capoutbuf[7] = 0x35; // Data element sequence - length in next byte + l2capoutbuf[8] = 0x00; // Length = 0 + l2capoutbuf[9] = 0x00; // No continuation state + + SDP_Command(l2capoutbuf, 10); +} + void SPP::serialPortResponse1(uint8_t transactionIDHigh, uint8_t transactionIDLow) { l2capoutbuf[0] = SDP_SERVICE_SEARCH_ATTRIBUTE_RESPONSE; l2capoutbuf[1] = transactionIDHigh; diff --git a/XBOXONESBT.h b/XBOXONESBT.h new file mode 100644 index 00000000..82b17d24 --- /dev/null +++ b/XBOXONESBT.h @@ -0,0 +1,100 @@ +/* Copyright (C) 2020 Kristian Sloth Lauszus. 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 Sloth Lauszus + Web : https://lauszus.com + e-mail : lauszus@gmail.com + */ + +#ifndef _xboxonesbt_h_ +#define _xboxonesbt_h_ + +#include "BTHID.h" +#include "XBOXONESParser.h" + +/** + * This class implements support for the Xbox One S controller via Bluetooth. + * It uses the BTHID class for all the Bluetooth communication. + */ +class XBOXONESBT : public BTHID, public XBOXONESParser { +public: + /** + * Constructor for the XBOXONESBT class. + * @param p Pointer to the BTD class instance. + * @param pair Set this to true in order to pair with the device. If the argument is omitted then it will not pair with it. One can use ::PAIR to set it to true. + */ + XBOXONESBT(BTD *p, bool pair = false) : + BTHID(p, pair) { + XBOXONESParser::Reset(); + }; + + /** + * Used to check if a Xbox One S controller is connected. + * @return Returns true if it is connected. + */ + bool connected() { + return BTHID::connected; + }; + +protected: + /** @name BTHID implementation */ + /** + * Used to parse Bluetooth HID data. + * @param len The length of the incoming data. + * @param buf Pointer to the data buffer. + */ + virtual void ParseBTHIDData(uint8_t len, uint8_t *buf) { + XBOXONESParser::Parse(len, buf); + }; + + /** + * Called when a device is successfully initialized. + * Use attachOnInit(void (*funcOnInit)(void)) to call your own function. + * This is useful for instance if you want to set the LEDs in a specific way. + */ + virtual void OnInitBTHID() { + XBOXONESParser::Reset(); + if (pFuncOnInit) + pFuncOnInit(); // Call the user function + }; + + /** Used to reset the different buffers to there default values */ + virtual void ResetBTHID() { + XBOXONESParser::Reset(); + }; + /**@}*/ + +#if 0 + /** @name XBOXONESParser implementation */ + virtual void sendOutputReport(uint8_t *data, uint8_t nbytes) { + // See: https://lore.kernel.org/patchwork/patch/973394/ + uint8_t buf[nbytes + 2]; + + buf[0] = 0x52; // HID BT Set_report (0x50) | Report Type (Output 0x02) + buf[1] = 0x03; // Report ID + + memcpy(buf + 2, data, nbytes); + + Notify(PSTR("\r\n"), 0x80); + for (uint8_t i = 0; i < sizeof(buf); i++) { + D_PrintHex (buf[i], 0x80); + Notify(PSTR(" "), 0x80); + } + Notify(PSTR("\r\n"), 0x80); + + //pBtd->L2CAP_Command(hci_handle, data, nbytes, interrupt_scid[0], interrupt_scid[1]); + pBtd->L2CAP_Command(hci_handle, buf, sizeof(buf), control_scid[0], control_scid[1]); + }; + /**@}*/ +#endif +}; +#endif diff --git a/XBOXONESParser.cpp b/XBOXONESParser.cpp new file mode 100644 index 00000000..e8f0064f --- /dev/null +++ b/XBOXONESParser.cpp @@ -0,0 +1,200 @@ +/* Copyright (C) 2020 Kristian Sloth Lauszus. 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 Sloth Lauszus + Web : https://lauszus.com + e-mail : lauszus@gmail.com + */ + +#include "XBOXONESParser.h" + +// To enable serial debugging see "settings.h" +//#define PRINTREPORT // Uncomment to print the report send by the Xbox One S Controller + +/** Buttons on the controller */ +const uint8_t XBOX_ONE_S_BUTTONS[] PROGMEM = { + UP, // UP + RIGHT, // RIGHT + DOWN, // DOWN + LEFT, // LEFT + + 0x0E, // VIEW + 0x0F, // MENU + 0x10, // L3 + 0x11, // R3 + + 0, 0, // Skip L2 and R2 as these are analog buttons + 0x0C, // L1 + 0x0D, // R1 + + 0x09, // B + 0x08, // A + 0x0A, // X + 0x0B, // Y +}; + +enum DPADEnum { + DPAD_OFF = 0x0, + DPAD_UP = 0x1, + DPAD_UP_RIGHT = 0x2, + DPAD_RIGHT = 0x3, + DPAD_RIGHT_DOWN = 0x4, + DPAD_DOWN = 0x5, + DPAD_DOWN_LEFT = 0x6, + DPAD_LEFT = 0x7, + DPAD_LEFT_UP = 0x8, +}; + +bool XBOXONESParser::checkDpad(ButtonEnum b) { + switch (b) { + case UP: + return xboxOneSData.btn.dpad == DPAD_LEFT_UP || xboxOneSData.btn.dpad == DPAD_UP || xboxOneSData.btn.dpad == DPAD_UP_RIGHT; + case RIGHT: + return xboxOneSData.btn.dpad == DPAD_UP_RIGHT || xboxOneSData.btn.dpad == DPAD_RIGHT || xboxOneSData.btn.dpad == DPAD_RIGHT_DOWN; + case DOWN: + return xboxOneSData.btn.dpad == DPAD_RIGHT_DOWN || xboxOneSData.btn.dpad == DPAD_DOWN || xboxOneSData.btn.dpad == DPAD_DOWN_LEFT; + case LEFT: + return xboxOneSData.btn.dpad == DPAD_DOWN_LEFT || xboxOneSData.btn.dpad == DPAD_LEFT || xboxOneSData.btn.dpad == DPAD_LEFT_UP; + default: + return false; + } +} + +uint16_t XBOXONESParser::getButtonPress(ButtonEnum b) { + if (b == L2) + return xboxOneSData.trigger[0]; + else if (b == R2) + return xboxOneSData.trigger[1]; + else if (b <= LEFT) // Dpad + return checkDpad(b); + else if (b == XBOX) + return xboxButtonState; + return xboxOneSData.btn.val & (1UL << pgm_read_byte(&XBOX_ONE_S_BUTTONS[(uint8_t)b])); +} + +bool XBOXONESParser::getButtonClick(ButtonEnum b) { + if(b == L2) { + if(L2Clicked) { + L2Clicked = false; + return true; + } + return false; + } else if(b == R2) { + if(R2Clicked) { + R2Clicked = false; + return true; + } + return false; + } else if (b == XBOX) { + bool click = xboxbuttonClickState; + xboxbuttonClickState = 0; // Clear "click" event + return click; + } + uint32_t mask = 1UL << pgm_read_byte(&XBOX_ONE_S_BUTTONS[(uint8_t)b]); + bool click = buttonClickState.val & mask; + buttonClickState.val &= ~mask; // Clear "click" event + return click; +} + +int16_t XBOXONESParser::getAnalogHat(AnalogHatEnum a) { + return xboxOneSData.hatValue[(uint8_t)a] - 32768; // Convert to signed integer +} + +void XBOXONESParser::Parse(uint8_t len, uint8_t *buf) { + if (len > 1 && buf) { +#ifdef PRINTREPORT + Notify(PSTR("\r\n"), 0x80); + for (uint8_t i = 0; i < len; i++) { + D_PrintHex (buf[i], 0x80); + Notify(PSTR(" "), 0x80); + } +#endif + + if (buf[0] == 0x01) // Check report ID + memcpy(&xboxOneSData, buf + 1, min((uint8_t)(len - 1), MFK_CASTUINT8T sizeof(xboxOneSData))); + else if (buf[0] == 0x02) { // This report contains the Xbox button + xboxButtonState = buf[1]; + if(xboxButtonState != xboxOldButtonState) { + xboxbuttonClickState = xboxButtonState & ~xboxOldButtonState; // Update click state variable + xboxOldButtonState = xboxButtonState; + } + return; + } else if (buf[0] == 0x04) // Heartbeat + return; + else { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nUnknown report id: "), 0x80); + D_PrintHex (buf[0], 0x80); +#endif + return; + } + + if (xboxOneSData.btn.val != oldButtonState.val) { // Check if anything has changed + buttonClickState.val = xboxOneSData.btn.val & ~oldButtonState.val; // Update click state variable + oldButtonState.val = xboxOneSData.btn.val; + + // 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 + uint8_t newDpad = 0; + if (checkDpad(UP)) + newDpad |= 1 << UP; + if (checkDpad(RIGHT)) + newDpad |= 1 << RIGHT; + if (checkDpad(DOWN)) + newDpad |= 1 << DOWN; + if (checkDpad(LEFT)) + newDpad |= 1 << LEFT; + if (newDpad != oldDpad) { + buttonClickState.dpad = newDpad & ~oldDpad; // Override values + oldDpad = newDpad; + } + } + + // Handle click detection for triggers + if(xboxOneSData.trigger[0] != 0 && triggerOld[0] == 0) + L2Clicked = true; + triggerOld[0] = xboxOneSData.trigger[0]; + if(xboxOneSData.trigger[1] != 0 && triggerOld[1] == 0) + R2Clicked = true; + triggerOld[1] = xboxOneSData.trigger[1]; + } +} + +void XBOXONESParser::Reset() { + uint8_t i; + for (i = 0; i < sizeof(xboxOneSData.hatValue) / sizeof(xboxOneSData.hatValue[0]); i++) + xboxOneSData.hatValue[i] = 32768; // Center value + xboxOneSData.btn.val = 0; + oldButtonState.val = 0; + for (i = 0; i < sizeof(xboxOneSData.trigger) / sizeof(xboxOneSData.trigger[0]); i++) + xboxOneSData.trigger[i] = 0; + + xboxOneSData.btn.dpad = DPAD_OFF; + oldButtonState.dpad = DPAD_OFF; + buttonClickState.dpad = 0; + oldDpad = 0; +}; + +#if 0 +void XBOXONESParser::setRumbleOn(uint8_t leftTrigger, uint8_t rightTrigger, uint8_t leftMotor, uint8_t rightMotor) { + // See: https://lore.kernel.org/patchwork/patch/973394/ + uint8_t buf[8]; + buf[0] = 0x0F;//1 << 1 | 1 << 0; // Enable weak and strong feedback + buf[1] = leftTrigger; + buf[2] = rightTrigger; + buf[3] = leftMotor; + buf[4] = rightMotor; + buf[5] = 255; // Duration of effect in 10 ms + buf[6] = 0; // Start delay in 10 ms + buf[7] = 255; // Loop count + sendOutputReport(buf, sizeof(buf)); +} +#endif diff --git a/XBOXONESParser.h b/XBOXONESParser.h new file mode 100644 index 00000000..41dc30b2 --- /dev/null +++ b/XBOXONESParser.h @@ -0,0 +1,127 @@ +/* Copyright (C) 2020 Kristian Sloth Lauszus. 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 Sloth Lauszus + Web : https://lauszus.com + e-mail : lauszus@gmail.com + */ + +#ifndef _xboxonesparser_h_ +#define _xboxonesparser_h_ + +#include "Usb.h" +#include "controllerEnums.h" + +union XboxOneSButtons { + struct { + uint8_t dpad : 4; + uint8_t reserved : 4; + + uint8_t a : 1; + uint8_t b : 1; + uint8_t x : 1; + uint8_t y : 1; + + uint8_t l1 : 1; + uint8_t r1 : 1; + uint8_t view : 1; + uint8_t menu : 1; + + uint8_t l3 : 1; + uint8_t r3 : 1; + uint8_t reserved2 : 6; + } __attribute__((packed)); + uint32_t val : 24; +} __attribute__((packed)); + +struct XboxOneSData { + /* Button and joystick values */ + uint16_t hatValue[4]; + uint16_t trigger[2]; + XboxOneSButtons btn; +} __attribute__((packed)); + +/** This class parses all the data sent by the Xbox One S controller */ +class XBOXONESParser { +public: + /** Constructor for the XBOXONESParser class. */ + XBOXONESParser() { + Reset(); + }; + + /** @name Xbox One S 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. + */ + uint16_t getButtonPress(ButtonEnum b); + bool getButtonClick(ButtonEnum b); + /**@}*/ + + /** + * Used to read the analog joystick. + * @param a ::LeftHatX, ::LeftHatY, ::RightHatX, and ::RightHatY. + * @return Return the analog value as a 16-bit signed integer. + */ + int16_t getAnalogHat(AnalogHatEnum a); + + /** Used to set the rumble off. */ + //void setRumbleOff(); + + /** + * Used to turn on rumble continuously. + * @param leftTrigger Left trigger force. + * @param rightTrigger Right trigger force. + * @param leftMotor Left motor force. + * @param rightMotor Right motor force. + */ + //void setRumbleOn(uint8_t leftTrigger, uint8_t rightTrigger, uint8_t leftMotor, uint8_t rightMotor); + +protected: + /** + * Used to parse data sent from the Xbox One S 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(); + + /** + * Send the output to the Xbox One S controller. This is implemented in XBOXONESBT.h. + * @param output Pointer to data buffer. + * @param nbytes Bytes to send. + */ + //virtual void sendOutputReport(uint8_t *data, uint8_t nbytes) = 0; + +private: + bool checkDpad(ButtonEnum b); // Used to check Xbox One S DPAD buttons + + XboxOneSData xboxOneSData; + XboxOneSButtons oldButtonState, buttonClickState; + uint8_t oldDpad; + + // The Xbox button is sent in a separate report + uint8_t xboxButtonState, xboxOldButtonState, xboxbuttonClickState; + + uint16_t triggerOld[2]; + bool L2Clicked; // These buttons are analog, so we use we use these bools to check if they where clicked or not + bool R2Clicked; +}; +#endif diff --git a/controllerEnums.h b/controllerEnums.h index 631825b2..c1a5a497 100644 --- a/controllerEnums.h +++ b/controllerEnums.h @@ -144,6 +144,12 @@ enum ButtonEnum { WHITE = 9, // Available on the original Xbox controller /**@}*/ + /**@{*/ + /** Xbox One S buttons */ + VIEW = 4, + MENU = 5, + /**@}*/ + /** PS Buzz controllers */ RED = 0, YELLOW = 1, diff --git a/examples/Xbox/XBOXONESBT/XBOXONESBT.ino b/examples/Xbox/XBOXONESBT/XBOXONESBT.ino new file mode 100644 index 00000000..c691e9bf --- /dev/null +++ b/examples/Xbox/XBOXONESBT/XBOXONESBT.ino @@ -0,0 +1,135 @@ +/* + Example sketch for the Xbox One S Bluetooth library - developed by Kristian Sloth Lauszus + For more information visit the Github repository: github.com/felis/USB_Host_Shield_2.0 or + send me an e-mail: lauszus@gmail.com + */ + +#include +#include + +// Satisfy the IDE, which needs to see the include statement in the ino too. +#ifdef dobogusinclude +#include +#endif +#include + +USB Usb; +//USBHub Hub1(&Usb); // Some dongles have a hub inside +BTD Btd(&Usb); // You have to create the Bluetooth Dongle instance like so + +/* You can create the instance of the XBOXONESBT class in two ways */ +// This will start an inquiry and then pair with the Xbox One S controller - you only have to do this once +// You will need to hold down the Sync and Xbox button at the same time, the Xbox One S controller will then start to blink rapidly indicating that it is in pairing mode +XBOXONESBT Xbox(&Btd, PAIR); + +// After that you can simply create the instance like so and then press the Xbox button on the device +//XBOXONESBT Xbox(&Btd); + +void setup() { + Serial.begin(115200); +#if !defined(__MIPSEL__) + while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection +#endif + if (Usb.Init() == -1) { + Serial.print(F("\r\nOSC did not start")); + while (1); //halt + } + Serial.print(F("\r\nXbox One S Bluetooth Library Started")); +} +void loop() { + Usb.Task(); + + if (Xbox.connected()) { + if (Xbox.getAnalogHat(LeftHatX) > 7500 || Xbox.getAnalogHat(LeftHatX) < -7500 || Xbox.getAnalogHat(LeftHatY) > 7500 || Xbox.getAnalogHat(LeftHatY) < -7500 || Xbox.getAnalogHat(RightHatX) > 7500 || Xbox.getAnalogHat(RightHatX) < -7500 || Xbox.getAnalogHat(RightHatY) > 7500 || Xbox.getAnalogHat(RightHatY) < -7500) { + if (Xbox.getAnalogHat(LeftHatX) > 7500 || Xbox.getAnalogHat(LeftHatX) < -7500) { + Serial.print(F("LeftHatX: ")); + Serial.print(Xbox.getAnalogHat(LeftHatX)); + Serial.print("\t"); + } + if (Xbox.getAnalogHat(LeftHatY) > 7500 || Xbox.getAnalogHat(LeftHatY) < -7500) { + Serial.print(F("LeftHatY: ")); + Serial.print(Xbox.getAnalogHat(LeftHatY)); + Serial.print("\t"); + } + if (Xbox.getAnalogHat(RightHatX) > 7500 || Xbox.getAnalogHat(RightHatX) < -7500) { + Serial.print(F("RightHatX: ")); + Serial.print(Xbox.getAnalogHat(RightHatX)); + Serial.print("\t"); + } + if (Xbox.getAnalogHat(RightHatY) > 7500 || Xbox.getAnalogHat(RightHatY) < -7500) { + Serial.print(F("RightHatY: ")); + Serial.print(Xbox.getAnalogHat(RightHatY)); + } + Serial.println(); + } + + if (Xbox.getButtonPress(L2) > 0 || Xbox.getButtonPress(R2) > 0) { + if (Xbox.getButtonPress(L2) > 0) { + Serial.print(F("L2: ")); + Serial.print(Xbox.getButtonPress(L2)); + Serial.print("\t"); + } + if (Xbox.getButtonPress(R2) > 0) { + Serial.print(F("R2: ")); + Serial.print(Xbox.getButtonPress(R2)); + Serial.print("\t"); + } + Serial.println(); + } + +#if 0 // This is currently not working + // Set rumble effect + static uint16_t oldL2Value, oldR2Value; + if (Xbox.getButtonPress(L2) != oldL2Value || Xbox.getButtonPress(R2) != oldR2Value) { + oldL2Value = Xbox.getButtonPress(L2); + oldR2Value = Xbox.getButtonPress(R2); + uint8_t leftRumble = map(oldL2Value, 0, 1023, 0, 255); // Map the trigger values into a byte + uint8_t rightRumble = map(oldR2Value, 0, 1023, 0, 255); + if (leftRumble > 0 || rightRumble > 0) + Xbox.setRumbleOn(leftRumble, rightRumble, leftRumble, rightRumble); + else + Xbox.setRumbleOff(); + } +#endif + + if (Xbox.getButtonClick(UP)) + Serial.println(F("Up")); + if (Xbox.getButtonClick(DOWN)) + Serial.println(F("Down")); + if (Xbox.getButtonClick(LEFT)) + Serial.println(F("Left")); + if (Xbox.getButtonClick(RIGHT)) + Serial.println(F("Right")); + + if (Xbox.getButtonClick(VIEW)) + Serial.println(F("View")); + if (Xbox.getButtonClick(MENU)) + Serial.println(F("Menu")); + if (Xbox.getButtonClick(XBOX)) { + Serial.println(F("Xbox")); + Xbox.disconnect(); + } + + if (Xbox.getButtonClick(L1)) + Serial.println(F("L1")); + if (Xbox.getButtonClick(R1)) + Serial.println(F("R1")); + if (Xbox.getButtonClick(L2)) + Serial.println(F("L2")); + if (Xbox.getButtonClick(R2)) + Serial.println(F("R2")); + if (Xbox.getButtonClick(L3)) + Serial.println(F("L3")); + if (Xbox.getButtonClick(R3)) + Serial.println(F("R3")); + + if (Xbox.getButtonClick(A)) + Serial.println(F("A")); + if (Xbox.getButtonClick(B)) + Serial.println(F("B")); + if (Xbox.getButtonClick(X)) + Serial.println(F("X")); + if (Xbox.getButtonClick(Y)) + Serial.println(F("Y")); + } +} diff --git a/keywords.txt b/keywords.txt index eed0eb5c..2513e98e 100644 --- a/keywords.txt +++ b/keywords.txt @@ -196,6 +196,7 @@ XBOXUSB KEYWORD1 XBOXONE KEYWORD1 XBOXOLD KEYWORD1 XBOXRECV KEYWORD1 +XBOXONESBT KEYWORD1 #################################################### # Methods and Functions (KEYWORD2) @@ -227,6 +228,9 @@ BACK LITERAL1 XBOX LITERAL1 SYNC LITERAL1 +VIEW LITERAL1 +MENU LITERAL1 + BLACK LITERAL1 WHITE LITERAL1 diff --git a/settings.h b/settings.h index 63be1770..2d176053 100644 --- a/settings.h +++ b/settings.h @@ -39,7 +39,7 @@ e-mail : support@circuitsathome.com //////////////////////////////////////////////////////////////////////////////// /* Set this to 1 to activate serial debugging */ -#define ENABLE_UHS_DEBUGGING 1 +#define ENABLE_UHS_DEBUGGING 0 /* This can be used to select which serial port to use for debugging if * multiple serial ports are available.