From a3ad5f2cddf40210ebec9bfcf0650c2407b11119 Mon Sep 17 00:00:00 2001 From: Kristian Lauszus Date: Tue, 21 Aug 2012 14:31:11 +0200 Subject: [PATCH] Added support for Wiimote --- BTD.cpp | 211 +++++++++++++-- BTD.h | 41 ++- EXTRAREADME | 5 +- PS3BT.h | 4 - SPP.h | 4 - Wii.cpp | 359 +++++++++++++++++++++++++ Wii.h | 138 ++++++++++ examples/Bluetooth/Wiimote/Wiimote.ino | 77 ++++++ keywords.txt | 20 +- 9 files changed, 816 insertions(+), 43 deletions(-) create mode 100644 Wii.cpp create mode 100644 Wii.h create mode 100644 examples/Bluetooth/Wiimote/Wiimote.ino diff --git a/BTD.cpp b/BTD.cpp index 18932b69..1ed3e84c 100644 --- a/BTD.cpp +++ b/BTD.cpp @@ -38,7 +38,9 @@ bPollEnable(false) // Don't start polling before dongle is connected } if (pUsb) // register in USB subsystem - pUsb->RegisterDeviceClass(this); //set devConfig[] entry + pUsb->RegisterDeviceClass(this); //set devConfig[] entry + + wiiServiceID = -1; } uint8_t BTD::Init(uint8_t parent, uint8_t port, bool lowspeed) { @@ -325,7 +327,7 @@ void BTD::HCI_event_task() { { switch (hcibuf[0]) //switch on event type { - case EV_COMMAND_COMPLETE: + case EV_COMMAND_COMPLETE: if (!hcibuf[5]) { // Check if command succeeded hci_event_flag |= HCI_FLAG_CMD_COMPLETE; // set command complete flag if((hcibuf[3] == 0x01) && (hcibuf[4] == 0x10)) { // parameters from read local version information @@ -352,11 +354,51 @@ void BTD::HCI_event_task() { } break; + case EV_INQUIRY_COMPLETE: // We don't use this for anything + break; + + case EV_INQUIRY_RESULT: + if (hcibuf[2]) { // Check that there is more than zero responses +#ifdef EXTRADEBUG + Notify(PSTR("\r\nNumber of responses: ")); + Serial.print(hcibuf[2]); +#endif + for(uint8_t i = 0; i < hcibuf[2]; i++) { + if(hcibuf[4+8*hcibuf[2]+3*i] == 0x04 && hcibuf[5+8*hcibuf[2]+3*i] == 0x25 && hcibuf[6+8*hcibuf[2]+3*i] == 0x00) { // See http://bluetooth-pentest.narod.ru/software/bluetooth_class_of_device-service_generator.html + disc_bdaddr[0] = hcibuf[3+6*i]; + disc_bdaddr[1] = hcibuf[4+6*i]; + disc_bdaddr[2] = hcibuf[5+6*i]; + disc_bdaddr[3] = hcibuf[6+6*i]; + disc_bdaddr[4] = hcibuf[7+6*i]; + disc_bdaddr[5] = hcibuf[8+6*i]; + hci_event_flag |= HCI_FLAG_WII_FOUND; + break; + } +#ifdef EXTRADEBUG + else { + Notify(PSTR("\r\nClass of device: ")); + PrintHex(hcibuf[6+8*hcibuf[2]+3*i]); + Notify(PSTR(" ")); + PrintHex(hcibuf[5+8*hcibuf[2]+3*i]); + Notify(PSTR(" ")); + PrintHex(hcibuf[4+8*hcibuf[2]+3*i]); + } +#endif + } + } + break; + case EV_CONNECT_COMPLETE: + hci_event_flag |= HCI_FLAG_CONNECT_EVENT; if (!hcibuf[2]) { // check if connected OK - hci_handle = hcibuf[3] | hcibuf[4] << 8; // store the handle for the ACL connection + hci_handle = hcibuf[3] | ((hcibuf[4] & 0x0F) << 8); // store the handle for the ACL connection hci_event_flag |= HCI_FLAG_CONN_COMPLETE; // set connection complete flag } +#ifdef EXTRADEBUG + else { + Notify(PSTR("\r\nConnection Failed")); + } +#endif break; case EV_DISCONNECT_COMPLETE: @@ -479,7 +521,7 @@ void BTD::HCI_task() { Notify(PSTR("\r\nLocal Bluetooth Address: ")); for(int8_t i = 5; i > 0;i--) { PrintHex(my_bdaddr[i]); - Serial.print(":"); + Notify(PSTR(":")); } PrintHex(my_bdaddr[0]); #endif @@ -494,7 +536,7 @@ void BTD::HCI_task() { hci_set_local_name(btdName); hci_state = HCI_SET_NAME_STATE; } else - hci_state = HCI_SCANNING_STATE; + hci_state = HCI_CHECK_WII_SERVICE; } break; @@ -504,17 +546,82 @@ void BTD::HCI_task() { Notify(PSTR("\r\nThe name is set to: ")); Serial.print(btdName); #endif - hci_state = HCI_SCANNING_STATE; + hci_state = HCI_CHECK_WII_SERVICE; + } + break; + + case HCI_CHECK_WII_SERVICE: + if(wiiServiceID != -1) { // Check if it should try to connect to a wiimote + if(disc_bdaddr[5] == 0 && disc_bdaddr[4] == 0 && disc_bdaddr[3] == 0 && disc_bdaddr[2] == 0 && disc_bdaddr[1] == 0 && disc_bdaddr[0] == 0) { +#ifdef DEBUG + Notify(PSTR("\r\nStarting inquiry\r\nPress A & B on the Wiimote")); +#endif + hci_inquiry(); + hci_state = HCI_INQUIRY_STATE; + } + else + hci_state = HCI_CONNECT_WII_STATE; + } + else + hci_state = HCI_SCANNING_STATE; // Don't try to connect to a Wiimote + break; + + case HCI_INQUIRY_STATE: + if(hci_wii_found) { + hci_inquiry_cancel(); // Stop inquiry +#ifdef DEBUG + Notify(PSTR("\r\nWiimote found")); + Notify(PSTR("\r\nCreate the instance like so to connect automatically:")); + Notify(PSTR("\r\nWII Wii(&Btd,")); + for(int8_t i = 5; i>0;i--) { + Notify(PSTR("0x")); + PrintHex(disc_bdaddr[i]); + Notify(PSTR(",")); + } + Notify(PSTR("0x")); + PrintHex(disc_bdaddr[0]); + Notify(PSTR(");")); +#endif + hci_state = HCI_CONNECT_WII_STATE; + } + break; + + case HCI_CONNECT_WII_STATE: + if(!hci_wii_found || hci_cmd_complete) { +#ifdef DEBUG + Notify(PSTR("\r\nConnecting to Wiimote")); +#endif + hci_connect(); + hci_state = HCI_CONNECTED_WII_STATE; + } + break; + + case HCI_CONNECTED_WII_STATE: + if(hci_connect_event) { + if(hci_connect_complete) { +#ifdef DEBUG + Notify(PSTR("\r\nConnected to Wiimote")); +#endif + connectToWii = true; // Only send the ACL data to the Wii service + hci_state = HCI_SCANNING_STATE; + } else { +#ifdef DEBUG + Notify(PSTR("\r\nTrying to connect one more time...")); +#endif + hci_connect(); // Try to connect one more time + } } break; case HCI_SCANNING_STATE: + if(!connectToWii) { #ifdef DEBUG - Notify(PSTR("\r\nWait For Incoming Connection Request")); + Notify(PSTR("\r\nWait For Incoming Connection Request")); #endif - hci_write_scan_enable(); - watingForConnection = true; - hci_state = HCI_CONNECT_IN_STATE; + hci_write_scan_enable(); + watingForConnection = true; + hci_state = HCI_CONNECT_IN_STATE; + } break; case HCI_CONNECT_IN_STATE: @@ -533,8 +640,7 @@ void BTD::HCI_task() { if(hci_remote_name_complete) { #ifdef DEBUG Notify(PSTR("\r\nRemote Name: ")); - for (uint8_t i = 0; i < 30; i++) - { + for (uint8_t i = 0; i < 30; i++) { if(remote_name[i] == NULL) break; Serial.write(remote_name[i]); @@ -549,10 +655,9 @@ void BTD::HCI_task() { if (hci_connect_complete) { #ifdef DEBUG Notify(PSTR("\r\nConnected to Device: ")); - for(int8_t i = 5; i>0;i--) - { + for(int8_t i = 5; i>0;i--) { PrintHex(disc_bdaddr[i]); - Serial.print(":"); + Notify(PSTR(":")); } PrintHex(disc_bdaddr[0]); #endif @@ -604,9 +709,13 @@ void BTD::ACL_event_task() { uint16_t MAX_BUFFER_SIZE = BULK_MAXPKTSIZE; uint8_t rcode = pUsb->inTransfer(bAddress, epInfo[ BTD_DATAIN_PIPE ].epAddr, &MAX_BUFFER_SIZE, l2capinbuf); // input on endpoint 2 if(!rcode) { // Check for errors - for (uint8_t i=0; iACLData(l2capinbuf); + if(connectToWii) // Only send the data to the Wii service + btService[wiiServiceID]->ACLData(l2capinbuf); + else { + for (uint8_t i=0; iACLData(l2capinbuf); + } } #ifdef EXTRADEBUG else if (rcode != hrNAK) { @@ -664,6 +773,7 @@ void BTD::hci_read_local_version_information() { HCI_Command(hcibuf, 3); } void BTD::hci_accept_connection() { + hci_event_flag &= ~HCI_FLAG_CONN_COMPLETE; hcibuf[0] = 0x09; // HCI OCF = 9 hcibuf[1] = 0x01 << 2; // HCI OGF = 1 hcibuf[2] = 0x07; // parameter length 7 @@ -706,6 +816,47 @@ void BTD::hci_set_local_name(const char* name) { HCI_Command(hcibuf, 4+strlen(name)); } +void BTD::hci_inquiry() { + hci_event_flag &= ~HCI_FLAG_WII_FOUND; + hcibuf[0] = 0x01; + hcibuf[1] = 0x01 << 2; // HCI OGF = 1 + hcibuf[2] = 0x05; // Parameter Total Length = 5 + hcibuf[3] = 0x33; // LAP: Genera/Unlimited Inquiry Access Code (GIAC = 0x9E8B33) - see https://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm + hcibuf[4] = 0x8B; + hcibuf[5] = 0x9E; + hcibuf[6] = 0x0A; // Inquiry time = 12.8 sec + hcibuf[7] = 0x03; // 3 number of responses + + HCI_Command(hcibuf, 8); +} +void BTD::hci_inquiry_cancel() { + hcibuf[0] = 0x02; + hcibuf[1] = 0x01 << 2; // HCI OGF = 1 + hcibuf[2] = 0x0; // Parameter Total Length = 0 + + HCI_Command(hcibuf, 3); +} +void BTD::hci_connect() { + hci_event_flag &= ~(HCI_FLAG_CONN_COMPLETE | HCI_FLAG_CONNECT_EVENT); + hcibuf[0] = 0x05; + hcibuf[1] = 0x01 << 2; // HCI OGF = 1 + hcibuf[2] = 0x0D; // parameter Total Length = 13 + 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] = 0x18; // DM1 or DH1 may be used + hcibuf[10] = 0xCC; // DM3, DH3, DM5, DH5 may be used + hcibuf[11] = 0x01; // Page repetition mode R1 + hcibuf[12] = 0x00; // Reserved + hcibuf[13] = 0x00; // Clock offset + hcibuf[14] = 0x00; // Invalid clock offset + hcibuf[15] = 0x00; // Do not allow role switch + + HCI_Command(hcibuf, 16); +} void BTD::hci_pin_code_request_reply(const char* key) { hcibuf[0] = 0x0D; // HCI OCF = 0D hcibuf[1] = 0x01 << 2; // HCI OGF = 1 @@ -814,6 +965,18 @@ void BTD::L2CAP_Command(uint16_t handle, uint8_t* data, uint8_t nbytes, uint8_t #endif } } +void BTD::l2cap_connection_request(uint16_t handle, uint8_t rxid, uint8_t* scid, uint16_t psm) { + l2capoutbuf[0] = L2CAP_CMD_CONNECTION_REQUEST; // Code + l2capoutbuf[1] = rxid; // Identifier + l2capoutbuf[2] = 0x04; // Length + l2capoutbuf[3] = 0x00; + l2capoutbuf[4] = (uint8_t)(psm & 0xff); // PSM + l2capoutbuf[5] = (uint8_t)(psm >> 8); + l2capoutbuf[6] = scid[0]; // Source CID + l2capoutbuf[7] = scid[1]; + + L2CAP_Command(handle, l2capoutbuf, 8); +} void BTD::l2cap_connection_response(uint16_t handle, uint8_t rxid, uint8_t* dcid, uint8_t* scid, uint8_t result) { l2capoutbuf[0] = L2CAP_CMD_CONNECTION_RESPONSE; // Code l2capoutbuf[1] = rxid; // Identifier @@ -856,7 +1019,7 @@ void BTD::l2cap_config_response(uint16_t handle, uint8_t rxid, uint8_t* scid) { l2capoutbuf[6] = 0x00; // Flag l2capoutbuf[7] = 0x00; l2capoutbuf[8] = 0x00; // Result - l2capoutbuf[9] = 0x00; + l2capoutbuf[9] = 0x00; l2capoutbuf[10] = 0x01; // Config l2capoutbuf[11] = 0x02; l2capoutbuf[12] = 0xA0; @@ -915,10 +1078,9 @@ void BTD::setBdaddr(uint8_t* BDADDR) { pUsb->ctrlReq(bAddress,epInfo[BTD_CONTROL_PIPE].epAddr, bmREQ_HID_OUT, HID_REQUEST_SET_REPORT, 0xF5, 0x03, 0x00, 8, 8, buf, NULL); #ifdef DEBUG Notify(PSTR("\r\nBluetooth Address was set to: ")); - for(int8_t i = 5; i > 0; i--) - { + for(int8_t i = 5; i > 0; i--) { PrintHex(my_bdaddr[i]); - Serial.print(":"); + Notify(PSTR(":")); } PrintHex(my_bdaddr[0]); #endif @@ -939,10 +1101,9 @@ void BTD::setMoveBdaddr(uint8_t* BDADDR) { pUsb->ctrlReq(bAddress,epInfo[BTD_CONTROL_PIPE].epAddr, bmREQ_HID_OUT, HID_REQUEST_SET_REPORT, 0x05, 0x03, 0x00,11,11, buf, NULL); #ifdef DEBUG Notify(PSTR("\r\nBluetooth Address was set to: ")); - for(int8_t i = 5; i > 0; i--) - { + for(int8_t i = 5; i > 0; i--) { PrintHex(my_bdaddr[i]); - Serial.print(":"); + Notify(PSTR(":")); } PrintHex(my_bdaddr[0]); #endif diff --git a/BTD.h b/BTD.h index f3e394cc..168b742d 100644 --- a/BTD.h +++ b/BTD.h @@ -42,13 +42,19 @@ #define HCI_BDADDR_STATE 2 #define HCI_LOCAL_VERSION_STATE 3 #define HCI_SET_NAME_STATE 4 -#define HCI_SCANNING_STATE 5 -#define HCI_CONNECT_IN_STATE 6 -#define HCI_REMOTE_NAME_STATE 7 -#define HCI_CONNECTED_STATE 8 -#define HCI_DISABLE_SCAN_STATE 9 -#define HCI_DONE_STATE 10 -#define HCI_DISCONNECT_STATE 11 +#define HCI_CHECK_WII_SERVICE 5 + +#define HCI_INQUIRY_STATE 6 // These three states are only used if it should connect to a Wii controller +#define HCI_CONNECT_WII_STATE 7 +#define HCI_CONNECTED_WII_STATE 8 + +#define HCI_SCANNING_STATE 9 +#define HCI_CONNECT_IN_STATE 10 +#define HCI_REMOTE_NAME_STATE 11 +#define HCI_CONNECTED_STATE 12 +#define HCI_DISABLE_SCAN_STATE 13 +#define HCI_DONE_STATE 14 +#define HCI_DISCONNECT_STATE 15 /* HCI event flags*/ #define HCI_FLAG_CMD_COMPLETE 0x01 @@ -58,6 +64,8 @@ #define HCI_FLAG_INCOMING_REQUEST 0x10 #define HCI_FLAG_READ_BDADDR 0x20 #define HCI_FLAG_READ_VERSION 0x40 +#define HCI_FLAG_WII_FOUND 0x80 +#define HCI_FLAG_CONNECT_EVENT 0x100 /*Macros for HCI event flag tests */ #define hci_cmd_complete (hci_event_flag & HCI_FLAG_CMD_COMPLETE) @@ -67,8 +75,12 @@ #define hci_incoming_connect_request (hci_event_flag & HCI_FLAG_INCOMING_REQUEST) #define hci_read_bdaddr_complete (hci_event_flag & HCI_FLAG_READ_BDADDR) #define hci_read_version_complete (hci_event_flag & HCI_FLAG_READ_VERSION) +#define hci_wii_found (hci_event_flag & HCI_FLAG_WII_FOUND) +#define hci_connect_event (hci_event_flag & HCI_FLAG_CONNECT_EVENT) /* HCI Events managed */ +#define EV_INQUIRY_COMPLETE 0x01 +#define EV_INQUIRY_RESULT 0x02 #define EV_CONNECT_COMPLETE 0x03 #define EV_INCOMING_CONNECT 0x04 #define EV_DISCONNECT_COMPLETE 0x05 @@ -105,6 +117,12 @@ #define PENDING 0x01 #define SUCCESSFUL 0x00 +/* Bluetooth L2CAP PSM - see http://www.bluetooth.org/Technical/AssignedNumbers/logical_link.htm */ +#define SDP_PSM 0x01 // Service Discovery Protocol PSM Value +#define RFCOMM_PSM 0x03 // RFCOMM PSM Value +#define HID_CTRL_PSM 0x11 // HID_Control PSM Value +#define HID_INTR_PSM 0x13 // HID_Interrupt PSM Value + // Used to determine if it is a Bluetooth dongle #define WI_SUBCLASS_RF 0x01 // RF Controller #define WI_PROTOCOL_BT 0x01 // Bluetooth Programming Interface @@ -163,6 +181,9 @@ public: uint8_t remote_name[30]; // First 30 chars of last remote name uint8_t hci_version; + int8_t wiiServiceID; // Stores the service ID of the Wii service + bool connectToWii; // Used to only send the ACL data to the wiimote + /* HCI Commands */ void HCI_Command(uint8_t* data, uint16_t nbytes); void hci_reset(); @@ -177,9 +198,13 @@ public: void hci_pin_code_request_reply(const char* key); void hci_pin_code_negative_request_reply(); void hci_link_key_request_negative_reply(); + void hci_inquiry(); + void hci_inquiry_cancel(); + void hci_connect(); /* L2CAP Commands */ void L2CAP_Command(uint16_t handle, uint8_t* data, uint8_t nbytes, uint8_t channelLow = 0x01, uint8_t channelHigh = 0x00); // Standard L2CAP header: Channel ID (0x01) for ACL-U + void l2cap_connection_request(uint16_t handle, uint8_t rxid, uint8_t* scid, uint16_t psm); void l2cap_connection_response(uint16_t handle, uint8_t rxid, uint8_t* dcid, uint8_t* scid, uint8_t result); void l2cap_config_request(uint16_t handle, uint8_t rxid, uint8_t* dcid); void l2cap_config_response(uint16_t handle, uint8_t rxid, uint8_t* scid); @@ -214,7 +239,7 @@ private: uint8_t hci_state; //current state of bluetooth hci connection uint16_t hci_counter; // counter used for bluetooth hci reset loops uint8_t hci_num_reset_loops; // this value indicate how many times it should read before trying to reset - uint16_t hci_event_flag; // hci flags of received bluetooth events + uint16_t hci_event_flag; // hci flags of received bluetooth events uint8_t hcibuf[BULK_MAXPKTSIZE];//General purpose buffer for hci data uint8_t l2capinbuf[BULK_MAXPKTSIZE];//General purpose buffer for l2cap in data diff --git a/EXTRAREADME b/EXTRAREADME index 7fff05ea..619e4d5b 100644 --- a/EXTRAREADME +++ b/EXTRAREADME @@ -1,4 +1,4 @@ -The BTD.cpp, BTD.h, SPP.cpp, SPP.h, PS3BT.cpp, PS3BT.h, PS3USB.cpp, PS3USB.h, XBOXUSB.cpp, and XBOXUSB.h is developed by Kristian Lauszus +The BTD.cpp, BTD.h, SPP.cpp, SPP.h, PS3BT.cpp, PS3BT.h, Wii.cpp, Wii.h PS3USB.cpp, PS3USB.h, XBOXUSB.cpp, and XBOXUSB.h is developed by Kristian Lauszus For more information regarding the PS3 protocol etc. visit my blog at: http://blog.tkjelectronics.dk/ or send me an email at kristianl at tkjelectronics dot dk. You could also visit the official wiki: https://github.com/TKJElectronics/USB_Host_Shield_2.0/wiki for information. @@ -26,4 +26,7 @@ http://pingus.seul.org/~grumbel/xboxdrv/ To implement the RFCOMM protocol I used a bluetooth sniffing tool called PacketLogger developed by Apple. It enables me to see the bluetooth communication between my Mac and any device. +All the information about the Wii controller is from this site: http://wiibrew.org/wiki/Wiimote +And the old Wii library created by Moyuchin: https://github.com/moyuchin/WiiRemote_on_Arduino + And at last I would like to thank Oleg from http://www.circuitsathome.com/ for making such an awesome shield! \ No newline at end of file diff --git a/PS3BT.h b/PS3BT.h index 9db07636..806193df 100644 --- a/PS3BT.h +++ b/PS3BT.h @@ -56,10 +56,6 @@ #define l2cap_disconnect_response_control_flag (l2cap_event_flag & L2CAP_FLAG_DISCONNECT_CONTROL_RESPONSE) #define l2cap_disconnect_response_interrupt_flag (l2cap_event_flag & L2CAP_FLAG_DISCONNECT_INTERRUPT_RESPONSE) -/* Bluetooth L2CAP PSM */ -#define HID_CTRL_PSM 0x11 // HID_Control -#define HID_INTR_PSM 0x13 // HID_Interrupt - enum LED { LED1 = 0x01, LED2 = 0x02, diff --git a/SPP.h b/SPP.h index 0ce0a801..f6fc97ec 100644 --- a/SPP.h +++ b/SPP.h @@ -55,10 +55,6 @@ #define l2cap_disconnect_request_rfcomm_flag (l2cap_event_flag & L2CAP_FLAG_DISCONNECT_RFCOMM_REQUEST) #define l2cap_disconnect_response_flag (l2cap_event_flag & L2CAP_FLAG_DISCONNECT_RESPONSE) -/* Bluetooth L2CAP PSM */ -#define SDP_PSM 0x01 // Service Discovery Protocol PSM Value -#define RFCOMM_PSM 0x03 // RFCOMM PSM Value - /* Used for SDP */ #define SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST_PDU 0x06 // See the RFCOMM specs #define SDP_SERVICE_SEARCH_ATTRIBUTE_RESPONSE_PDU 0x07 // See the RFCOMM specs diff --git a/Wii.cpp b/Wii.cpp new file mode 100644 index 00000000..9293ba58 --- /dev/null +++ b/Wii.cpp @@ -0,0 +1,359 @@ +/* Copyright (C) 2012 Kristian Lauszus, TKJ Electronics. 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 Lauszus, TKJ Electronics + Web : http://www.tkjelectronics.com + e-mail : kristianl@tkjelectronics.com + */ + +#include "Wii.h" +#define DEBUG // Uncomment to print data for debugging +//#define EXTRADEBUG // Uncomment to get even more debugging data +//#define PRINTREPORT // Uncomment to print the report send by the Wiimote + +WII::WII(BTD *p, uint8_t btadr5, uint8_t btadr4, uint8_t btadr3, uint8_t btadr2, uint8_t btadr1, uint8_t btadr0): +pBtd(p) // pointer to USB class instance - mandatory +{ + if (pBtd) + pBtd->wiiServiceID = pBtd->registerServiceClass(this); // Register it as a Bluetooth service + + pBtd->disc_bdaddr[5] = btadr5; // Change to your dongle's Bluetooth address instead + pBtd->disc_bdaddr[4] = btadr4; + pBtd->disc_bdaddr[3] = btadr3; + pBtd->disc_bdaddr[2] = btadr2; + pBtd->disc_bdaddr[1] = btadr1; + pBtd->disc_bdaddr[0] = btadr0; + + HIDBuffer[0] = 0xA2;// HID BT DATA_request (0x50) | Report Type (Output 0x02) + + /* Set device cid for the control and intterrupt channelse - LSB */ + control_dcid[0] = 0x60;//0x0060 + control_dcid[1] = 0x00; + interrupt_dcid[0] = 0x61;//0x0061 + interrupt_dcid[1] = 0x00; + + Reset(); +} +void WII::Reset() { + connected = false; + l2cap_event_flag = 0; // Reset flags + l2cap_state = L2CAP_WAIT; +} + +void WII::disconnect() { // Use this void to disconnect any of the controllers + //First the HID interrupt channel has to be disconencted, then the HID control channel and finally the HCI connection + pBtd->l2cap_disconnection_request(hci_handle,0x0A, interrupt_scid, interrupt_dcid); + Reset(); + l2cap_state = L2CAP_INTERRUPT_DISCONNECT; +} + +void WII::ACLData(uint8_t* l2capinbuf) { + if (((l2capinbuf[0] | (l2capinbuf[1] << 8)) == (hci_handle | 0x2000))) { //acl_handle_ok + if ((l2capinbuf[6] | (l2capinbuf[7] << 8)) == 0x0001) { //l2cap_control - Channel ID for ACL-U + if (l2capinbuf[8] == L2CAP_CMD_COMMAND_REJECT) { +#ifdef DEBUG + Notify(PSTR("\r\nL2CAP Command Rejected - Reason: ")); + PrintHex(l2capinbuf[13]); + Notify(PSTR(" ")); + PrintHex(l2capinbuf[12]); + Notify(PSTR(" ")); + PrintHex(l2capinbuf[17]); + Notify(PSTR(" ")); + PrintHex(l2capinbuf[16]); + Notify(PSTR(" ")); + PrintHex(l2capinbuf[15]); + Notify(PSTR(" ")); + PrintHex(l2capinbuf[14]); +#endif + } + 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] == control_dcid[0] && l2capinbuf[15] == control_dcid[1]) { // Success + //Serial.print("\r\nHID Control Connection Complete"); + identifier = l2capinbuf[9]; + control_scid[0] = l2capinbuf[12]; + control_scid[1] = l2capinbuf[13]; + l2cap_event_flag |= L2CAP_FLAG_CONTROL_CONNECTED; + } + else if (l2capinbuf[14] == interrupt_dcid[0] && l2capinbuf[15] == interrupt_dcid[1]) { + //Serial.print("\r\nHID Interrupt Connection Complete"); + identifier = l2capinbuf[9]; + interrupt_scid[0] = l2capinbuf[12]; + interrupt_scid[1] = l2capinbuf[13]; + l2cap_event_flag |= L2CAP_FLAG_INTERRUPT_CONNECTED; + } + } + } + else if (l2capinbuf[8] == L2CAP_CMD_CONFIG_RESPONSE) { + if ((l2capinbuf[16] | (l2capinbuf[17] << 8)) == 0x0000) { // Success + if (l2capinbuf[12] == control_dcid[0] && l2capinbuf[13] == control_dcid[1]) { + //Serial.print("\r\nHID Control Configuration Complete"); + identifier = l2capinbuf[9]; + l2cap_event_flag |= L2CAP_FLAG_CONFIG_CONTROL_SUCCESS; + } + else if (l2capinbuf[12] == interrupt_dcid[0] && l2capinbuf[13] == interrupt_dcid[1]) { + //Serial.print("\r\nHID Interrupt Configuration Complete"); + identifier = l2capinbuf[9]; + l2cap_event_flag |= L2CAP_FLAG_CONFIG_INTERRUPT_SUCCESS; + } + } + } + else if (l2capinbuf[8] == L2CAP_CMD_CONFIG_REQUEST) { + if (l2capinbuf[12] == control_dcid[0] && l2capinbuf[13] == control_dcid[1]) { + //Serial.print("\r\nHID Control Configuration Request"); + pBtd->l2cap_config_response(hci_handle, l2capinbuf[9], control_scid); + } + else if (l2capinbuf[12] == interrupt_dcid[0] && l2capinbuf[13] == interrupt_dcid[1]) { + //Serial.print("\r\nHID Interrupt Configuration Request"); + pBtd->l2cap_config_response(hci_handle, l2capinbuf[9], interrupt_scid); + } + } + else if (l2capinbuf[8] == L2CAP_CMD_DISCONNECT_REQUEST) { + if (l2capinbuf[12] == control_dcid[0] && l2capinbuf[13] == control_dcid[1]) { +#ifdef DEBUG + Notify(PSTR("\r\nDisconnect Request: Control Channel")); +#endif + connected = false; + identifier = l2capinbuf[9]; + pBtd->l2cap_disconnection_response(hci_handle,identifier,control_dcid,control_scid); + Reset(); + } + else if (l2capinbuf[12] == interrupt_dcid[0] && l2capinbuf[13] == interrupt_dcid[1]) { +#ifdef DEBUG + Notify(PSTR("\r\nDisconnect Request: Interrupt Channel")); +#endif + connected = false; + identifier = l2capinbuf[9]; + pBtd->l2cap_disconnection_response(hci_handle,identifier,interrupt_dcid,interrupt_scid); + Reset(); + } + } + else if (l2capinbuf[8] == L2CAP_CMD_DISCONNECT_RESPONSE) { + if (l2capinbuf[12] == control_scid[0] && l2capinbuf[13] == control_scid[1]) { + //Serial.print("\r\nDisconnect Response: Control Channel"); + identifier = l2capinbuf[9]; + l2cap_event_flag |= L2CAP_FLAG_DISCONNECT_CONTROL_RESPONSE; + } + else if (l2capinbuf[12] == interrupt_scid[0] && l2capinbuf[13] == interrupt_scid[1]) { + //Serial.print("\r\nDisconnect Response: Interrupt Channel"); + identifier = l2capinbuf[9]; + l2cap_event_flag |= L2CAP_FLAG_DISCONNECT_INTERRUPT_RESPONSE; + } + } +#ifdef EXTRADEBUG + else { + identifier = l2capinbuf[9]; + Notify(PSTR("\r\nL2CAP Unknown Signaling Command: ")); + PrintHex(l2capinbuf[8]); + } +#endif + } else if (l2capinbuf[6] == interrupt_dcid[0] && l2capinbuf[7] == interrupt_dcid[1]) { // l2cap_interrupt + //Serial.print("\r\nL2CAP Interrupt"); + if(connected) { + /* Read Report */ + if(l2capinbuf[8] == 0xA1) { // HID_THDR_DATA_INPUT + switch (l2capinbuf[9]) { + case 0x30: // Core buttons + ButtonState = (uint16_t)((l2capinbuf[10] & 0x1F) | ((uint16_t)(l2capinbuf[11] & 0x9F) << 8)); + ButtonClickState = ButtonState; // Update click state variable + +#ifdef PRINTREPORT + Notify(PSTR("ButtonState: ")); + PrintHex(ButtonState); + Notify(PSTR("\r\n")); +#endif + + if(ButtonState != OldButtonState) { + buttonChanged = true; + if(ButtonState != 0x0000) { + buttonPressed = true; + buttonReleased = false; + } else { + buttonPressed = false; + buttonReleased = true; + } + } + else { + buttonChanged = false; + buttonPressed = false; + buttonReleased = false; + } + OldButtonState = ButtonState; + break; +#ifdef DEBUG + default: + Notify(PSTR("\r\nUnknown Report type: ")); + Serial.print(l2capinbuf[9],HEX); + break; +#endif + } + } + } + } + L2CAP_task(); + } +} +void WII::L2CAP_task() { + switch (l2cap_state) { + case L2CAP_CONTROL_CONNECT_REQUEST: + if (l2cap_connected_control_flag) { +#ifdef DEBUG + Notify(PSTR("\r\nSend HID Control Config Request")); +#endif + identifier++; + pBtd->l2cap_config_request(hci_handle, identifier, control_scid); + l2cap_state = L2CAP_CONTROL_CONFIG_REQUEST; + } + break; + + case L2CAP_CONTROL_CONFIG_REQUEST: + if(l2cap_config_success_control_flag) { +#ifdef DEBUG + Notify(PSTR("\r\nSend HID Interrupt Connection Request")); +#endif + identifier++; + pBtd->l2cap_connection_request(hci_handle,identifier,interrupt_dcid,HID_INTR_PSM); + l2cap_state = L2CAP_INTERRUPT_CONNECT_REQUEST; + } + break; + + case L2CAP_INTERRUPT_CONNECT_REQUEST: + if(l2cap_connected_interrupt_flag) { +#ifdef DEBUG + Notify(PSTR("\r\nSend HID Interrupt Config Request")); +#endif + identifier++; + pBtd->l2cap_config_request(hci_handle, identifier, interrupt_scid); + l2cap_state = L2CAP_INTERRUPT_CONFIG_REQUEST; + } + break; + + + case L2CAP_INTERRUPT_CONFIG_REQUEST: + if(l2cap_config_success_interrupt_flag) { +#ifdef DEBUG + Notify(PSTR("\r\nHID Channels Established")); +#endif + connected = true; + pBtd->connectToWii = false; + ButtonState = 0; + OldButtonState = 0; + ButtonClickState = 0; + setLedOn(LED1); + l2cap_state = L2CAP_DONE; + } + break; +/* + case L2CAP_WIIREMOTE_CAL_STATE: + //Todo enable support for Motion Plus + break; +*/ + case L2CAP_DONE: + break; + + case L2CAP_INTERRUPT_DISCONNECT: + if (l2cap_disconnect_response_interrupt_flag) { +#ifdef DEBUG + Notify(PSTR("\r\nDisconnected Interrupt Channel")); +#endif + identifier++; + pBtd->l2cap_disconnection_request(hci_handle, identifier, control_scid, control_dcid); + l2cap_state = L2CAP_CONTROL_DISCONNECT; + } + break; + + case L2CAP_CONTROL_DISCONNECT: + if (l2cap_disconnect_response_control_flag) { +#ifdef DEBUG + Notify(PSTR("\r\nDisconnected Control Channel")); +#endif + pBtd->hci_disconnect(hci_handle); + l2cap_event_flag = 0; // Reset flags + l2cap_state = L2CAP_WAIT; + } + break; + } +} +void WII::Run() { + switch (l2cap_state) { + case L2CAP_WAIT: + if(pBtd->connectToWii) { +#ifdef DEBUG + Notify(PSTR("\r\nSend HID Control Connection Request")); +#endif + hci_handle = pBtd->hci_handle; // Store the HCI Handle for the connection + l2cap_event_flag = 0; // Reset flags + identifier = 0; + pBtd->l2cap_connection_request(hci_handle,identifier,control_dcid,HID_CTRL_PSM); + l2cap_state = L2CAP_CONTROL_CONNECT_REQUEST; + } + break; + } +} + +/************************************************************/ +/* HID Commands */ +/************************************************************/ +void WII::HID_Command(uint8_t* data, uint8_t nbytes) { + pBtd->L2CAP_Command(hci_handle,data,nbytes,control_scid[0],control_scid[1]); // Both the Navigation and Dualshock controller sends data via the control channel +} +void WII::setAllOff() { + HIDBuffer[1] = 0x11; + HIDBuffer[2] = 0x00; + HID_Command(HIDBuffer, 3); +} +void WII::setRumbleOff() { + HIDBuffer[1] = 0x11; + HIDBuffer[2] &= ~0x01; // Bit 0 control the rumble + HID_Command(HIDBuffer, 3); +} +void WII::setRumbleOn() { + HIDBuffer[1] = 0x11; + HIDBuffer[2] |= 0x01; // Bit 0 control the rumble + HID_Command(HIDBuffer, 3); +} +void WII::setRumbleToggle() { + HIDBuffer[1] = 0x11; + HIDBuffer[2] ^= 0x01; // Bit 0 control the rumble + HID_Command(HIDBuffer, 3); +} +void WII::setLedOff(LED a) { + HIDBuffer[1] = 0x11; + HIDBuffer[2] &= ~((uint8_t)a); + HID_Command(HIDBuffer, 3); +} +void WII::setLedOn(LED a) { + HIDBuffer[1] = 0x11; + HIDBuffer[2] |= (uint8_t)a; + HID_Command(HIDBuffer, 3); +} +void WII::setLedToggle(LED a) { + HIDBuffer[1] = 0x11; + HIDBuffer[2] ^= (uint8_t)a; + HID_Command(HIDBuffer, 3); +} + +/************************************************************/ +/* WII Commands */ +/************************************************************/ + +bool WII::getButtonPress(Button b) { + if(ButtonState & (uint16_t)b) + return true; + else + return false; +} +bool WII::getButtonClick(Button b) { + bool click = ((ButtonClickState & (uint16_t)b) != 0); + ButtonClickState &= ~((uint16_t)b); // clear "click" event + return click; +} \ No newline at end of file diff --git a/Wii.h b/Wii.h new file mode 100644 index 00000000..6298da9f --- /dev/null +++ b/Wii.h @@ -0,0 +1,138 @@ +/* Copyright (C) 2012 Kristian Lauszus, TKJ Electronics. 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 Lauszus, TKJ Electronics + Web : http://www.tkjelectronics.com + e-mail : kristianl@tkjelectronics.com + */ + +#ifndef _wii_h_ +#define _wii_h_ + +#include "BTD.h" + +/* Bluetooth L2CAP states for L2CAP_task() */ +#define L2CAP_WAIT 0 +#define L2CAP_CONTROL_CONNECT_REQUEST 1 +#define L2CAP_CONTROL_CONFIG_REQUEST 2 +#define L2CAP_INTERRUPT_CONNECT_REQUEST 3 +#define L2CAP_INTERRUPT_CONFIG_REQUEST 4 +//#define L2CAP_WIIREMOTE_CAL_STATE 9 /* TODO: Enable support for Motion Plus */ +#define L2CAP_DONE 5 +#define L2CAP_INTERRUPT_DISCONNECT 6 +#define L2CAP_CONTROL_DISCONNECT 7 + +/* L2CAP event flags */ +#define L2CAP_FLAG_CONTROL_CONNECTED 0x01 +#define L2CAP_FLAG_INTERRUPT_CONNECTED 0x02 +#define L2CAP_FLAG_CONFIG_CONTROL_SUCCESS 0x04 +#define L2CAP_FLAG_CONFIG_INTERRUPT_SUCCESS 0x08 +#define L2CAP_FLAG_DISCONNECT_CONTROL_RESPONSE 0x40 +#define L2CAP_FLAG_DISCONNECT_INTERRUPT_RESPONSE 0x80 + +/*Macros for L2CAP event flag tests */ +#define l2cap_connected_control_flag (l2cap_event_flag & L2CAP_FLAG_CONTROL_CONNECTED) +#define l2cap_connected_interrupt_flag (l2cap_event_flag & L2CAP_FLAG_INTERRUPT_CONNECTED) +#define l2cap_config_success_control_flag (l2cap_event_flag & L2CAP_FLAG_CONFIG_CONTROL_SUCCESS) +#define l2cap_config_success_interrupt_flag (l2cap_event_flag & L2CAP_FLAG_CONFIG_INTERRUPT_SUCCESS) +#define l2cap_disconnect_response_control_flag (l2cap_event_flag & L2CAP_FLAG_DISCONNECT_CONTROL_RESPONSE) +#define l2cap_disconnect_response_interrupt_flag (l2cap_event_flag & L2CAP_FLAG_DISCONNECT_INTERRUPT_RESPONSE) + +enum LED { + LED1 = 0x10, + LED2 = 0x20, + LED3 = 0x40, + LED4 = 0x80, + + LED5 = 0x90, + LED6 = 0xA0, + LED7 = 0xC0, + LED8 = 0xD0, + LED9 = 0xE0, + LED10 = 0xF0, +}; + +enum Button { + LEFT = 0x0001, + RIGHT = 0x0002, + DOWN = 0x0004, + UP = 0x0008, + PLUS = 0x0010, + + TWO = 0x0100, + ONE = 0x0200, + B = 0x0400, + A = 0x0800, + MINUS = 0x1000, + HOME = 0x8000, +}; + +class WII : public BluetoothService { +public: + WII(BTD *pBtd, uint8_t btadr5=0, uint8_t btadr4=0, uint8_t btadr3=0, uint8_t btadr2=0, uint8_t btadr1=0, uint8_t btadr0=0); + + // BluetoothService implementation + virtual void ACLData(uint8_t* ACLData); // Used to pass acldata to the services + virtual void Run(); // Used to run part of the state maschine + virtual void Reset(); // Use this to reset the service + virtual void disconnect(); // Use this void to disconnect any of the controllers + + bool getButtonPress(Button b); // This will read true as long as the button is held down + bool getButtonClick(Button b); // This will only be true when the button is clicked the first time +/* + TODO: Enable support for Motion Plus + int16_t getSensor(Sensor a); + double getAngle(Angle a); +*/ + void setAllOff(); // Turn both rumble and all LEDs off + void setRumbleOff(); + void setRumbleOn(); + void setRumbleToggle(); + void setLedOff(LED a); + void setLedOn(LED a); + void setLedToggle(LED a); + + bool connected;// Variable used to indicate if a Wiimote is connected + bool buttonChanged;//Indicate if a button has been changed + bool buttonPressed;//Indicate if a button has been pressed + bool buttonReleased;//Indicate if a button has been released + +private: + /* Mandatory members */ + BTD *pBtd; + + void L2CAP_task(); // L2CAP state machine + + /* Variables filled from HCI event management */ + uint16_t hci_handle; + + /* variables used by high level L2CAP task */ + uint8_t l2cap_state; + uint16_t l2cap_event_flag;// l2cap flags of received bluetooth events + + uint16_t ButtonState; + uint16_t OldButtonState; + uint16_t ButtonClickState; + + uint8_t HIDBuffer[3];// Used to store HID commands + + /* L2CAP Channels */ + uint8_t control_scid[2];// L2CAP source CID for HID_Control + uint8_t control_dcid[2];//0x0060 + uint8_t interrupt_scid[2];// L2CAP source CID for HID_Interrupt + uint8_t interrupt_dcid[2];//0x0061 + uint8_t identifier;//Identifier for connection + + /* HID Commands */ + void HID_Command(uint8_t* data, uint8_t nbytes); +}; +#endif \ No newline at end of file diff --git a/examples/Bluetooth/Wiimote/Wiimote.ino b/examples/Bluetooth/Wiimote/Wiimote.ino new file mode 100644 index 00000000..065ab2b1 --- /dev/null +++ b/examples/Bluetooth/Wiimote/Wiimote.ino @@ -0,0 +1,77 @@ +/* + Example sketch for the Wiimote Bluetooth library - developed by Kristian Lauszus + For more information visit my blog: http://blog.tkjelectronics.dk/ or + send me an e-mail: kristianl@tkjelectronics.com + */ + +#include +USB Usb; +BTD Btd(&Usb); // You have to create the Bluetooth Dongle instance like so +/* You can create the instance of the class in two ways */ +WII Wii(&Btd); // This will start inquiry which will connect to any Wiimote +//WII Wii(&Btd,0x00,0x26,0x59,0x48,0xFF,0xFB); // This will connect to the Wiimote with that specific Bluetooth Address + +void setup() { + Serial.begin(115200); + if (Usb.Init() == -1) { + Serial.print(F("\r\nOSC did not start")); + while(1); //halt + } + Serial.print(F("\r\nWiimote Bluetooth Library Started")); +} +void loop() { + Usb.Task(); + if(Wii.connected) { + if(Wii.buttonPressed) { + if(Wii.getButtonClick(HOME)) { // You can use getButtonPress to see if the button is held down + Serial.print(F("\r\nHOME")); + Wii.disconnect(); // If you disconnect you have to reset the Arduino to establish the connection again + } + else { + if(Wii.getButtonClick(LEFT)) { + Wii.setAllOff(); + Wii.setLedOn(LED1); + Serial.print(F("\r\nLeft")); + } + if(Wii.getButtonClick(RIGHT)) { + Wii.setAllOff(); + Wii.setLedOn(LED3); + Serial.print(F("\r\nRight")); + } + if(Wii.getButtonClick(DOWN)) { + Wii.setAllOff(); + Wii.setLedOn(LED4); + Serial.print(F("\r\nDown")); + } + if(Wii.getButtonClick(UP)) { + Wii.setAllOff(); + Wii.setLedOn(LED2); + Serial.print(F("\r\nUp")); + } + + if(Wii.getButtonClick(PLUS)) { + Serial.print(F("\r\nPlus")); + } + if(Wii.getButtonClick(MINUS)) { + Serial.print(F("\r\nMinus")); + } + + if(Wii.getButtonClick(ONE)) { + Serial.print(F("\r\nOne")); + } + if(Wii.getButtonClick(TWO)) { + Serial.print(F("\r\nTwo")); + } + + if(Wii.getButtonClick(A)) { + Serial.print(F("\r\nA")); + } + if(Wii.getButtonClick(B)) { + Wii.setRumbleToggle(); + Serial.print(F("\r\nB")); + } + } + } + } +} + diff --git a/keywords.txt b/keywords.txt index 17d9015a..57fe5b46 100644 --- a/keywords.txt +++ b/keywords.txt @@ -201,4 +201,22 @@ SPP KEYWORD1 connected KEYWORD2 printNumber KEYWORD2 -printNumberln KEYWORD2 \ No newline at end of file +printNumberln KEYWORD2 + +#################################################### +# Syntax Coloring Map For Wiimote Library +#################################################### + +#################################################### +# Datatypes (KEYWORD1) +#################################################### + +WII KEYWORD1 + +#################################################### +# Methods and Functions (KEYWORD2) +#################################################### + +getButtonPress KEYWORD2 +getButtonClick KEYWORD2 +setRumbleToggle KEYWORD2 \ No newline at end of file