From ee90afde31006e6686e8dc9e3df714a83ef6ee5a Mon Sep 17 00:00:00 2001 From: Kristian Lauszus Date: Mon, 21 Apr 2014 18:27:04 +0200 Subject: [PATCH] Merged SPPi class in from @magictaler - see: #79 I have made several improvements. Including making a new SPPBase class. --- BTD.cpp | 86 ++- BTD.h | 13 + README.md | 1 + SPP.cpp | 201 +---- SPP.h | 175 +---- SPPBase.cpp | 185 +++++ SPPBase.h | 229 ++++++ SPPi.cpp | 687 ++++++++++++++++++ SPPi.h | 89 +++ UsbCore.h | 2 + .../Bluetooth/SPP/SPPClient/SPPClient.ino | 48 ++ .../SPP/{SPP.ino => SPPServer/SPPServer.ino} | 17 +- .../SPPServerMulti/SPPServerMulti.ino} | 0 13 files changed, 1356 insertions(+), 377 deletions(-) mode change 100644 => 100755 SPP.cpp mode change 100644 => 100755 SPP.h create mode 100644 SPPBase.cpp create mode 100644 SPPBase.h create mode 100755 SPPi.cpp create mode 100755 SPPi.h mode change 100644 => 100755 UsbCore.h create mode 100644 examples/Bluetooth/SPP/SPPClient/SPPClient.ino rename examples/Bluetooth/SPP/{SPP.ino => SPPServer/SPPServer.ino} (80%) rename examples/Bluetooth/{SPPMulti/SPPMulti.ino => SPP/SPPServerMulti/SPPServerMulti.ino} (100%) diff --git a/BTD.cpp b/BTD.cpp index f373c607..8f5d8e30 100755 --- a/BTD.cpp +++ b/BTD.cpp @@ -29,6 +29,7 @@ connectToWii(false), pairWithWii(false), connectToHIDDevice(false), pairWithHIDDevice(false), +pairWithOtherDevice(false), pUsb(p), // Pointer to USB class instance - mandatory bAddress(0), // Device address - mandatory bNumEP(1), // If config descriptor needs to be parsed @@ -301,6 +302,7 @@ void BTD::Initialize() { connectToWii = false; incomingWii = false; connectToHIDDevice = false; + connectToOtherDevice = false; incomingHIDDevice = false; incomingPS4 = false; bAddress = 0; // Clear device address @@ -410,11 +412,13 @@ void BTD::HCI_event_task() { break; case EV_INQUIRY_COMPLETE: - if(inquiry_counter >= 5 && (pairWithWii || pairWithHIDDevice)) { + if (inquiry_counter >= 5 && (pairWithWii || pairWithHIDDevice || pairWithOtherDevice)) { inquiry_counter = 0; #ifdef DEBUG_USB_HOST if(pairWithWii) Notify(PSTR("\r\nCouldn't find Wiimote"), 0x80); + else if (pairWithOtherDevice) + Notify(PSTR("\r\nCouldn't find 'Other' device"), 0x80); else Notify(PSTR("\r\nCouldn't find HID device"), 0x80); #endif @@ -422,6 +426,8 @@ void BTD::HCI_event_task() { pairWithWii = false; connectToHIDDevice = false; pairWithHIDDevice = false; + connectToOtherDevice = false; + pairWithOtherDevice = false; hci_state = HCI_SCANNING_STATE; } inquiry_counter++; @@ -464,17 +470,37 @@ void BTD::HCI_event_task() { disc_bdaddr[j] = hcibuf[j + 3 + 6 * i]; hci_set_flag(HCI_FLAG_DEVICE_FOUND); - } + } else { #ifdef EXTRADEBUG - else { Notify(PSTR("\r\nClass of device: "), 0x80); D_PrintHex (classOfDevice[2], 0x80); Notify(PSTR(" "), 0x80); D_PrintHex (classOfDevice[1], 0x80); Notify(PSTR(" "), 0x80); D_PrintHex (classOfDevice[0], 0x80); - } #endif + uint8_t discovered = true; + for (uint8_t j = 0; j < 6; j++) { + if (hcibuf[j + 3 + 6 * i] != remote_bdaddr[j]) + discovered = false; + disc_bdaddr[j] = hcibuf[j + 3 + 6 * i]; + } + if (discovered) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nDevice: "), 0x80); + for (int8_t j = 5; j > 0; j--) { + D_PrintHex (disc_bdaddr[j], 0x80); + Notify(PSTR(":"), 0x80); + } + D_PrintHex (disc_bdaddr[0], 0x80); + + Notify(PSTR(" has been found"), 0x80); +#endif + hci_set_flag(HCI_FLAG_DEVICE_FOUND); + } + + } + } } break; @@ -582,6 +608,11 @@ void BTD::HCI_event_task() { Notify(PSTR("\r\nPairing successful with HID device"), 0x80); #endif connectToHIDDevice = true; // Used to indicate to the BTHID service, that it should connect to this device + } else if (pairWithOtherDevice && !connectToOtherDevice) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nPairing successful with 'Other' device"), 0x80); +#endif + connectToOtherDevice = true; } break; /* We will just ignore the following events */ @@ -694,12 +725,14 @@ void BTD::HCI_task() { break; case HCI_CHECK_DEVICE_SERVICE: - if(pairWithHIDDevice || pairWithWii) { // Check if it should try to connect to a Wiimote + if(pairWithHIDDevice || pairWithWii || pairWithOtherDevice) { // Check if it should try to connect to a HID device, Wiimote or 'Other device' #ifdef DEBUG_USB_HOST if(pairWithWii) Notify(PSTR("\r\nStarting inquiry\r\nPress 1 & 2 on the Wiimote\r\nOr press sync if you are using a Wii U Pro Controller"), 0x80); - else + else if (pairWithHIDDevice) Notify(PSTR("\r\nPlease enable discovery of your device"), 0x80); + else + Notify(PSTR("\r\nPairing with 'Other' device with predefined address"), 0x80); #endif hci_inquiry(); hci_state = HCI_INQUIRY_STATE; @@ -713,20 +746,28 @@ void BTD::HCI_task() { #ifdef DEBUG_USB_HOST if(pairWithWii) Notify(PSTR("\r\nWiimote found"), 0x80); + else if (pairWithOtherDevice) + Notify(PSTR("\r\n'Other' device found"), 0x80); else Notify(PSTR("\r\nHID device found"), 0x80); - Notify(PSTR("\r\nNow just create the instance like so:"), 0x80); - if(pairWithWii) - Notify(PSTR("\r\nWII Wii(&Btd);"), 0x80); - else - Notify(PSTR("\r\nBTHID bthid(&Btd);"), 0x80); + if (!pairWithOtherDevice) { + Notify(PSTR("\r\nNow just create the instance like so:"), 0x80); + if(pairWithWii) + Notify(PSTR("\r\nWII Wii(&Btd);"), 0x80); + else +#ifdef _ps4bt_h_ // Check if the PS4 driver is being used + Notify(PSTR("\r\nPS4BT PS4(&Btd);"), 0x80); +#else + Notify(PSTR("\r\nBTHID bthid(&Btd);"), 0x80); +#endif - Notify(PSTR("\r\nAnd then press any button on the "), 0x80); - if(pairWithWii) - Notify(PSTR("Wiimote"), 0x80); - else - Notify(PSTR("device"), 0x80); + Notify(PSTR("\r\nAnd then press any button on the "), 0x80); + if(pairWithWii) + Notify(PSTR("Wiimote"), 0x80); + else + Notify(PSTR("device"), 0x80); + } #endif if(motionPlusInside) { hci_remote_name(); // We need to know the name to distinguish between a Wiimote and a Wii U Pro Controller @@ -741,6 +782,8 @@ void BTD::HCI_task() { #ifdef DEBUG_USB_HOST if(pairWithWii) Notify(PSTR("\r\nConnecting to Wiimote"), 0x80); + else if (pairWithOtherDevice) + Notify(PSTR("\r\nConnecting to 'Other' device"), 0x80); else Notify(PSTR("\r\nConnecting to HID device"), 0x80); #endif @@ -755,6 +798,8 @@ void BTD::HCI_task() { #ifdef DEBUG_USB_HOST if(pairWithWii) Notify(PSTR("\r\nConnected to Wiimote"), 0x80); + else if (pairWithOtherDevice) + Notify(PSTR("\r\nConnected to 'Other' device"), 0x80); else Notify(PSTR("\r\nConnected to HID device"), 0x80); #endif @@ -770,7 +815,8 @@ void BTD::HCI_task() { break; case HCI_SCANNING_STATE: - if(!connectToWii && !pairWithWii && !connectToHIDDevice && !pairWithHIDDevice) { + if (!connectToWii && !pairWithWii && !connectToHIDDevice && !pairWithHIDDevice && !connectToOtherDevice && !pairWithOtherDevice) { + #ifdef DEBUG_USB_HOST Notify(PSTR("\r\nWait For Incoming Connection Request"), 0x80); #endif @@ -879,6 +925,7 @@ void BTD::HCI_task() { connectToWii = incomingWii = pairWithWii = false; connectToHIDDevice = incomingHIDDevice = pairWithHIDDevice = false; + pairWithOtherDevice = connectToOtherDevice = false; incomingPS4 = false; hci_state = HCI_SCANNING_STATE; @@ -1040,7 +1087,10 @@ void BTD::hci_inquiry_cancel() { } void BTD::hci_connect() { - hci_connect(disc_bdaddr); // Use last discovered device + if (pairWithOtherDevice) + hci_connect(remote_bdaddr); + else + hci_connect(disc_bdaddr); // Use last discovered device } void BTD::hci_connect(uint8_t *bdaddr) { diff --git a/BTD.h b/BTD.h index 3a639abb..b56591c6 100755 --- a/BTD.h +++ b/BTD.h @@ -524,6 +524,19 @@ public: /** True when it should pair with a device like a mouse or keyboard. */ bool pairWithHIDDevice; + /** True when it should pair with a device with a specific Bluetooth address. */ + bool pairWithOtherDevice; + /* True when it should connect to a device with a specific Bluetooth address. */ + bool connectToOtherDevice; + /** Call this to pair with a device with a specific Bluetooth address. */ + void pairWithOther() { + pairWithOtherDevice = true; + hci_state = HCI_CONNECT_DEVICE_STATE; + }; + + /** Remote address of Bluetooth device to connect to. */ + uint8_t remote_bdaddr[6]; + /** * Read the poll interval taken from the endpoint descriptors. * @return The poll interval in ms. diff --git a/README.md b/README.md index 43fde867..b9e8db89 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ Currently the following boards are supported by the library: * Balanduino * Sanguino * Black Widdow +* Luminardo The following boards need to be activated manually in [settings.h](settings.h): diff --git a/SPP.cpp b/SPP.cpp old mode 100644 new mode 100755 index ef0cf216..f8bdf423 --- a/SPP.cpp +++ b/SPP.cpp @@ -20,30 +20,8 @@ //#define EXTRADEBUG // Uncomment to get even more debugging data //#define PRINTREPORT // Uncomment to print the report sent to the Arduino -/* - * CRC (reversed crc) lookup table as calculated by the table generator in ETSI TS 101 369 V6.3.0. - */ -const uint8_t rfcomm_crc_table[256] PROGMEM = {/* reversed, 8-bit, poly=0x07 */ - 0x00, 0x91, 0xE3, 0x72, 0x07, 0x96, 0xE4, 0x75, 0x0E, 0x9F, 0xED, 0x7C, 0x09, 0x98, 0xEA, 0x7B, - 0x1C, 0x8D, 0xFF, 0x6E, 0x1B, 0x8A, 0xF8, 0x69, 0x12, 0x83, 0xF1, 0x60, 0x15, 0x84, 0xF6, 0x67, - 0x38, 0xA9, 0xDB, 0x4A, 0x3F, 0xAE, 0xDC, 0x4D, 0x36, 0xA7, 0xD5, 0x44, 0x31, 0xA0, 0xD2, 0x43, - 0x24, 0xB5, 0xC7, 0x56, 0x23, 0xB2, 0xC0, 0x51, 0x2A, 0xBB, 0xC9, 0x58, 0x2D, 0xBC, 0xCE, 0x5F, - 0x70, 0xE1, 0x93, 0x02, 0x77, 0xE6, 0x94, 0x05, 0x7E, 0xEF, 0x9D, 0x0C, 0x79, 0xE8, 0x9A, 0x0B, - 0x6C, 0xFD, 0x8F, 0x1E, 0x6B, 0xFA, 0x88, 0x19, 0x62, 0xF3, 0x81, 0x10, 0x65, 0xF4, 0x86, 0x17, - 0x48, 0xD9, 0xAB, 0x3A, 0x4F, 0xDE, 0xAC, 0x3D, 0x46, 0xD7, 0xA5, 0x34, 0x41, 0xD0, 0xA2, 0x33, - 0x54, 0xC5, 0xB7, 0x26, 0x53, 0xC2, 0xB0, 0x21, 0x5A, 0xCB, 0xB9, 0x28, 0x5D, 0xCC, 0xBE, 0x2F, - 0xE0, 0x71, 0x03, 0x92, 0xE7, 0x76, 0x04, 0x95, 0xEE, 0x7F, 0x0D, 0x9C, 0xE9, 0x78, 0x0A, 0x9B, - 0xFC, 0x6D, 0x1F, 0x8E, 0xFB, 0x6A, 0x18, 0x89, 0xF2, 0x63, 0x11, 0x80, 0xF5, 0x64, 0x16, 0x87, - 0xD8, 0x49, 0x3B, 0xAA, 0xDF, 0x4E, 0x3C, 0xAD, 0xD6, 0x47, 0x35, 0xA4, 0xD1, 0x40, 0x32, 0xA3, - 0xC4, 0x55, 0x27, 0xB6, 0xC3, 0x52, 0x20, 0xB1, 0xCA, 0x5B, 0x29, 0xB8, 0xCD, 0x5C, 0x2E, 0xBF, - 0x90, 0x01, 0x73, 0xE2, 0x97, 0x06, 0x74, 0xE5, 0x9E, 0x0F, 0x7D, 0xEC, 0x99, 0x08, 0x7A, 0xEB, - 0x8C, 0x1D, 0x6F, 0xFE, 0x8B, 0x1A, 0x68, 0xF9, 0x82, 0x13, 0x61, 0xF0, 0x85, 0x14, 0x66, 0xF7, - 0xA8, 0x39, 0x4B, 0xDA, 0xAF, 0x3E, 0x4C, 0xDD, 0xA6, 0x37, 0x45, 0xD4, 0xA1, 0x30, 0x42, 0xD3, - 0xB4, 0x25, 0x57, 0xC6, 0xB3, 0x22, 0x50, 0xC1, 0xBA, 0x2B, 0x59, 0xC8, 0xBD, 0x2C, 0x5E, 0xCF -}; - -SPP::SPP(BTD *p, const char* name, const char* pin) : -pBtd(p) // Pointer to BTD class instance - mandatory +SPP::SPP(BTD *p, const char *name, const char *pin, bool pair, uint8_t *addr) : +SPPBase(p) { if(pBtd) pBtd->registerServiceClass(this); // Register it as a Bluetooth service @@ -51,9 +29,16 @@ pBtd(p) // Pointer to BTD class instance - mandatory pBtd->btdName = name; pBtd->btdPin = pin; + if (addr) // Make sure address is set + pBtd->pairWithOtherDevice = pair; + + for (uint8_t i = 0; i < 6; i++) + pBtd->remote_bdaddr[i] = addr[i]; + /* Set device cid for the SDP and RFCOMM channelse */ sdp_dcid[0] = 0x50; // 0x0050 sdp_dcid[1] = 0x00; + rfcomm_dcid[0] = 0x51; // 0x0051 rfcomm_dcid[1] = 0x00; @@ -71,19 +56,7 @@ void SPP::Reset() { sppIndex = 0; } -void SPP::disconnect() { - connected = false; - // First the two L2CAP channels has to be disconnected and then the HCI connection - if(RFCOMMConnected) - pBtd->l2cap_disconnection_request(hci_handle, ++identifier, rfcomm_scid, rfcomm_dcid); - if(RFCOMMConnected && SDPConnected) - delay(1); // Add delay between commands - if(SDPConnected) - pBtd->l2cap_disconnection_request(hci_handle, ++identifier, sdp_scid, sdp_dcid); - l2cap_sdp_state = L2CAP_DISCONNECT_RESPONSE; -} - -void SPP::ACLData(uint8_t* l2capinbuf) { +void SPP::ACLData(uint8_t *l2capinbuf) { if(!connected) { if(l2capinbuf[8] == L2CAP_CMD_CONNECTION_REQUEST) { if((l2capinbuf[12] | (l2capinbuf[13] << 8)) == SDP_PSM && !pBtd->sdpConnectionClaimed) { @@ -99,7 +72,7 @@ void SPP::ACLData(uint8_t* l2capinbuf) { } //if((l2capinbuf[0] | (uint16_t)l2capinbuf[1] << 8) == (hci_handle | 0x2000U)) { // acl_handle_ok if(UHS_ACL_HANDLE_OK(l2capinbuf, hci_handle)) { // acl_handle_ok - if((l2capinbuf[6] | (l2capinbuf[7] << 8)) == 0x0001U) { //l2cap_control - Channel ID for ACL-U + if((l2capinbuf[6] | (l2capinbuf[7] << 8)) == 0x0001U) { // l2cap_control - Channel ID for ACL-U if(l2capinbuf[8] == L2CAP_CMD_COMMAND_REJECT) { #ifdef DEBUG_USB_HOST Notify(PSTR("\r\nL2CAP Command Rejected - Reason: "), 0x80); @@ -528,9 +501,8 @@ void SPP::RFCOMM_task() { } /************************************************************/ /* SDP Commands */ - /************************************************************/ -void SPP::SDP_Command(uint8_t* data, uint8_t nbytes) { // See page 223 in the Bluetooth specs +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]); } @@ -660,156 +632,7 @@ void SPP::l2capResponse2(uint8_t transactionIDHigh, uint8_t transactionIDLow) { } /************************************************************/ /* RFCOMM Commands */ - /************************************************************/ void SPP::RFCOMM_Command(uint8_t* data, uint8_t nbytes) { pBtd->L2CAP_Command(hci_handle, data, nbytes, rfcomm_scid[0], rfcomm_scid[1]); } - -void SPP::sendRfcomm(uint8_t channel, uint8_t direction, uint8_t CR, uint8_t channelType, uint8_t pfBit, uint8_t* data, uint8_t length) { - l2capoutbuf[0] = channel | direction | CR | extendAddress; // RFCOMM Address - l2capoutbuf[1] = channelType | pfBit; // RFCOMM Control - l2capoutbuf[2] = length << 1 | 0x01; // Length and format (always 0x01 bytes format) - uint8_t i = 0; - for(; i < length; i++) - l2capoutbuf[i + 3] = data[i]; - l2capoutbuf[i + 3] = calcFcs(l2capoutbuf); -#ifdef EXTRADEBUG - Notify(PSTR(" - RFCOMM Data: "), 0x80); - for(i = 0; i < length + 4; i++) { - D_PrintHex (l2capoutbuf[i], 0x80); - Notify(PSTR(" "), 0x80); - } -#endif - RFCOMM_Command(l2capoutbuf, length + 4); -} - -void SPP::sendRfcommCredit(uint8_t channel, uint8_t direction, uint8_t CR, uint8_t channelType, uint8_t pfBit, uint8_t credit) { - l2capoutbuf[0] = channel | direction | CR | extendAddress; // RFCOMM Address - l2capoutbuf[1] = channelType | pfBit; // RFCOMM Control - l2capoutbuf[2] = 0x01; // Length = 0 - l2capoutbuf[3] = credit; // Credit - l2capoutbuf[4] = calcFcs(l2capoutbuf); -#ifdef EXTRADEBUG - Notify(PSTR(" - RFCOMM Credit Data: "), 0x80); - for(uint8_t i = 0; i < 5; i++) { - D_PrintHex (l2capoutbuf[i], 0x80); - Notify(PSTR(" "), 0x80); - } -#endif - RFCOMM_Command(l2capoutbuf, 5); -} - -/* CRC on 2 bytes */ -uint8_t SPP::crc(uint8_t *data) { - return (pgm_read_byte(&rfcomm_crc_table[pgm_read_byte(&rfcomm_crc_table[0xFF ^ data[0]]) ^ data[1]])); -} - -/* Calculate FCS */ -uint8_t SPP::calcFcs(uint8_t *data) { - uint8_t temp = crc(data); - if((data[1] & 0xEF) == RFCOMM_UIH) - return (0xFF - temp); // FCS on 2 bytes - else - return (0xFF - pgm_read_byte(&rfcomm_crc_table[temp ^ data[2]])); // FCS on 3 bytes -} - -/* Check FCS */ -bool SPP::checkFcs(uint8_t *data, uint8_t fcs) { - uint8_t temp = crc(data); - if((data[1] & 0xEF) != RFCOMM_UIH) - temp = pgm_read_byte(&rfcomm_crc_table[temp ^ data[2]]); // FCS on 3 bytes - return (pgm_read_byte(&rfcomm_crc_table[temp ^ fcs]) == 0xCF); -} - -/* Serial commands */ -#if defined(ARDUINO) && ARDUINO >=100 - -size_t SPP::write(uint8_t data) { - return write(&data, 1); -} -#else - -void SPP::write(uint8_t data) { - write(&data, 1); -} -#endif - -#if defined(ARDUINO) && ARDUINO >=100 - -size_t SPP::write(const uint8_t *data, size_t size) { -#else - -void SPP::write(const uint8_t *data, size_t size) { -#endif - for(uint8_t i = 0; i < size; i++) { - if(sppIndex >= sizeof (sppOutputBuffer) / sizeof (sppOutputBuffer[0])) - send(); // Send the current data in the buffer - sppOutputBuffer[sppIndex++] = data[i]; // All the bytes are put into a buffer and then send using the send() function - } -#if defined(ARDUINO) && ARDUINO >=100 - return size; -#endif -} - -void SPP::send() { - if(!connected || !sppIndex) - return; - uint8_t length; // This is the length of the string we are sending - uint8_t offset = 0; // This is used to keep track of where we are in the string - - l2capoutbuf[0] = rfcommChannelConnection | 0 | 0 | extendAddress; // RFCOMM Address - l2capoutbuf[1] = RFCOMM_UIH; // RFCOMM Control - - while(sppIndex) { // We will run this while loop until this variable is 0 - if(sppIndex > (sizeof (l2capoutbuf) - 4)) // Check if the string is larger than the outgoing buffer - length = sizeof (l2capoutbuf) - 4; - else - length = sppIndex; - - l2capoutbuf[2] = length << 1 | 1; // Length - uint8_t i = 0; - for(; i < length; i++) - l2capoutbuf[i + 3] = sppOutputBuffer[i + offset]; - l2capoutbuf[i + 3] = calcFcs(l2capoutbuf); // Calculate checksum - - RFCOMM_Command(l2capoutbuf, length + 4); - - sppIndex -= length; - offset += length; // Increment the offset - } -} - -int SPP::available(void) { - return rfcommAvailable; -}; - -void SPP::discard(void) { - rfcommAvailable = 0; -} - -int SPP::peek(void) { - if(rfcommAvailable == 0) // Don't read if there is nothing in the buffer - return -1; - return rfcommDataBuffer[0]; -} - -int SPP::read(void) { - if(rfcommAvailable == 0) // Don't read if there is nothing in the buffer - return -1; - uint8_t output = rfcommDataBuffer[0]; - for(uint8_t i = 1; i < rfcommAvailable; i++) - rfcommDataBuffer[i - 1] = rfcommDataBuffer[i]; // Shift the buffer one left - rfcommAvailable--; - bytesRead++; - if(bytesRead > (sizeof (rfcommDataBuffer) - 5)) { // We will send the command just before it runs out of credit - bytesRead = 0; - sendRfcommCredit(rfcommChannelConnection, rfcommDirection, 0, RFCOMM_UIH, 0x10, sizeof (rfcommDataBuffer)); // Send more credit -#ifdef EXTRADEBUG - Notify(PSTR("\r\nSent "), 0x80); - Notify((uint8_t)sizeof (rfcommDataBuffer), 0x80); - Notify(PSTR(" more credit"), 0x80); -#endif - } - return output; -} diff --git a/SPP.h b/SPP.h old mode 100644 new mode 100755 index d9f6761c..c18394a5 --- a/SPP.h +++ b/SPP.h @@ -18,205 +18,56 @@ #ifndef _spp_h_ #define _spp_h_ -#include "BTD.h" - -/* 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 -#define SERIALPORT_UUID 0x1101 // See http://www.bluetooth.org/Technical/AssignedNumbers/service_discovery.htm -#define L2CAP_UUID 0x0100 - -/* Used for RFCOMM */ -#define RFCOMM_SABM 0x2F -#define RFCOMM_UA 0x63 -#define RFCOMM_UIH 0xEF -//#define RFCOMM_DM 0x0F -#define RFCOMM_DISC 0x43 - -#define extendAddress 0x01 // Always 1 - -// Multiplexer message types -#define BT_RFCOMM_PN_CMD 0x83 -#define BT_RFCOMM_PN_RSP 0x81 -#define BT_RFCOMM_MSC_CMD 0xE3 -#define BT_RFCOMM_MSC_RSP 0xE1 -#define BT_RFCOMM_RPN_CMD 0x93 -#define BT_RFCOMM_RPN_RSP 0x91 -/* -#define BT_RFCOMM_TEST_CMD 0x23 -#define BT_RFCOMM_TEST_RSP 0x21 -#define BT_RFCOMM_FCON_CMD 0xA3 -#define BT_RFCOMM_FCON_RSP 0xA1 -#define BT_RFCOMM_FCOFF_CMD 0x63 -#define BT_RFCOMM_FCOFF_RSP 0x61 -#define BT_RFCOMM_RLS_CMD 0x53 -#define BT_RFCOMM_RLS_RSP 0x51 -#define BT_RFCOMM_NSC_RSP 0x11 - */ +#include "SPPBase.h" /** - * This BluetoothService class implements the Serial Port Protocol (SPP). - * It inherits the Arduino Stream class. This allows it to use all the standard Arduino print and stream functions. + * This BluetoothService class a Serial Port Protocol (SPP) server. + * It inherits the Arduino Stream class. This allows it to use all the standard Arduino print functions. */ -class SPP : public BluetoothService, public Stream { +class SPP : public SPPBase { public: /** * Constructor for the SPP class. - * @param p Pointer to BTD class instance. - * @param name Set the name to BTD#btdName. If argument is omitted, then "Arduino" will be used. + * @param p Pointer to BTD class instance. + * @param name Set the name to BTD#btdName. If argument is omitted, then "Arduino" will be used. * @param pin Write the pin to BTD#btdPin. If argument is omitted, then "0000" will be used. + * @param pair Set this to true if you want to pair with a device. + * @param addr Set this to the address you want to connect to. */ - SPP(BTD *p, const char *name = "Arduino", const char *pin = "0000"); + SPP(BTD *p, const char *name = "Arduino", const char *pin = "0000", bool pair = false, uint8_t *addr = NULL); - /** - * Used to provide Boolean tests for the class. - * @return Return true if SPP communication is connected. - */ - operator bool() { - return connected; - } - /** Variable used to indicate if the connection is established. */ - bool connected; - - /** @name BluetoothService implementation */ + /** @name SPPBase implementation */ /** * Used to pass acldata to the services. * @param ACLData Incoming acldata. */ - virtual void ACLData(uint8_t* ACLData); + virtual void ACLData(uint8_t *ACLData); /** Used to establish the connection automatically. */ virtual void Run(); /** Use this to reset the service. */ virtual void Reset(); - /** Used this to disconnect the virtual serial port. */ - virtual void disconnect(); - /**@}*/ - - /** @name Serial port profile (SPP) Print functions */ - /** - * Get number of bytes waiting to be read. - * @return Return the number of bytes ready to be read. - */ - virtual int available(void); - - /** Send out all bytes in the buffer. */ - virtual void flush(void) { - send(); - }; - /** - * Used to read the next value in the buffer without advancing to the next one. - * @return Return the byte. Will return -1 if no bytes are available. - */ - virtual int peek(void); - /** - * Used to read the buffer. - * @return Return the byte. Will return -1 if no bytes are available. - */ - virtual int read(void); - -#if defined(ARDUINO) && ARDUINO >=100 - /** - * Writes the byte to send to a buffer. The message is send when either send() or after Usb.Task() is called. - * @param data The byte to write. - * @return Return the number of bytes written. - */ - virtual size_t write(uint8_t data); - /** - * Writes the bytes to send to a buffer. The message is send when either send() or after Usb.Task() is called. - * @param data The data array to send. - * @param size Size of the data. - * @return Return the number of bytes written. - */ - virtual size_t write(const uint8_t* data, size_t size); - /** Pull in write(const char *str) from Print */ - using Print::write; -#else - /** - * Writes the byte to send to a buffer. The message is send when either send() or after Usb.Task() is called. - * @param data The byte to write. - */ - virtual void write(uint8_t data); - /** - * Writes the bytes to send to a buffer. The message is send when either send() or after Usb.Task() is called. - * @param data The data array to send. - * @param size Size of the data. - */ - virtual void write(const uint8_t* data, size_t size); -#endif - - /** Discard all the bytes in the buffer. */ - void discard(void); - /** - * This will send all the bytes in the buffer. - * This is called whenever Usb.Task() is called, - * but can also be called via this function. - */ - void send(void); /**@}*/ private: - /* Bluetooth dongle library pointer */ - BTD *pBtd; - - /* Set true when a channel is created */ - bool SDPConnected; - bool RFCOMMConnected; - - uint16_t hci_handle; // The HCI Handle for the connection - - /* Variables used by L2CAP state machines */ - uint8_t l2cap_sdp_state; - uint8_t l2cap_rfcomm_state; uint32_t l2cap_event_flag; // l2cap flags of received Bluetooth events - uint8_t l2capoutbuf[BULK_MAXPKTSIZE]; // General purpose buffer for l2cap out data - uint8_t rfcommbuf[10]; // Buffer for RFCOMM Commands - - /* L2CAP Channels */ - uint8_t sdp_scid[2]; // L2CAP source CID for SDP - uint8_t sdp_dcid[2]; // 0x0050 - uint8_t rfcomm_scid[2]; // L2CAP source CID for RFCOMM - uint8_t rfcomm_dcid[2]; // 0x0051 - uint8_t identifier; // Identifier for command - - /* RFCOMM Variables */ - uint8_t rfcommChannel; - uint8_t rfcommChannelConnection; // This is the channel the SPP channel will be running at - uint8_t rfcommDirection; - uint8_t rfcommCommandResponse; - uint8_t rfcommChannelType; - uint8_t rfcommPfBit; - unsigned long timer; bool waitForLastCommand; - bool creditSent; - - uint8_t rfcommDataBuffer[100]; // Create a 100 sized buffer for incoming data - uint8_t sppOutputBuffer[100]; // Create a 100 sized buffer for outgoing SPP data - uint8_t sppIndex; - uint8_t rfcommAvailable; bool firstMessage; // Used to see if it's the first SDP request received - uint8_t bytesRead; // Counter to see when it's time to send more credit /* State machines */ void SDP_task(); // SDP state machine void RFCOMM_task(); // RFCOMM state machine /* SDP Commands */ - void SDP_Command(uint8_t *data, uint8_t nbytes); + virtual void SDP_Command(uint8_t *data, uint8_t nbytes); void serviceNotSupported(uint8_t transactionIDHigh, uint8_t transactionIDLow); void serialPortResponse1(uint8_t transactionIDHigh, uint8_t transactionIDLow); void serialPortResponse2(uint8_t transactionIDHigh, uint8_t transactionIDLow); void l2capResponse1(uint8_t transactionIDHigh, uint8_t transactionIDLow); void l2capResponse2(uint8_t transactionIDHigh, uint8_t transactionIDLow); - /* RFCOMM Commands */ - void RFCOMM_Command(uint8_t *data, uint8_t nbytes); - void sendRfcomm(uint8_t channel, uint8_t direction, uint8_t CR, uint8_t channelType, uint8_t pfBit, uint8_t *data, uint8_t length); - void sendRfcommCredit(uint8_t channel, uint8_t direction, uint8_t CR, uint8_t channelType, uint8_t pfBit, uint8_t credit); - uint8_t calcFcs(uint8_t *data); - bool checkFcs(uint8_t *data, uint8_t fcs); - uint8_t crc(uint8_t *data); + virtual void RFCOMM_Command(uint8_t *data, uint8_t nbytes); // Used for RFCOMM commands }; #endif diff --git a/SPPBase.cpp b/SPPBase.cpp new file mode 100644 index 00000000..7e25669f --- /dev/null +++ b/SPPBase.cpp @@ -0,0 +1,185 @@ +/* Copyright (C) 2014 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 "SPPBase.h" + +SPPBase::SPPBase(BTD *p) : pBtd(p) {}; + +void SPPBase::disconnect() { + connected = false; + // First the two L2CAP channels has to be disconnected and then the HCI connection + if (RFCOMMConnected) + pBtd->l2cap_disconnection_request(hci_handle, ++identifier, rfcomm_scid, rfcomm_dcid); + if (RFCOMMConnected && SDPConnected) + delay(1); // Add delay between commands + if (SDPConnected) + pBtd->l2cap_disconnection_request(hci_handle, ++identifier, sdp_scid, sdp_dcid); + l2cap_sdp_state = L2CAP_DISCONNECT_RESPONSE; +} + +void SPPBase::sendRfcomm(uint8_t channel, uint8_t direction, uint8_t CR, uint8_t channelType, uint8_t pfBit, uint8_t* data, uint8_t length) { + l2capoutbuf[0] = channel | direction | CR | extendAddress; // RFCOMM Address + l2capoutbuf[1] = channelType | pfBit; // RFCOMM Control + l2capoutbuf[2] = length << 1 | 0x01; // Length and format (always 0x01 bytes format) + uint8_t i = 0; + for (; i < length; i++) + l2capoutbuf[i + 3] = data[i]; + l2capoutbuf[i + 3] = calcFcs(l2capoutbuf); +#ifdef EXTRADEBUG + Notify(PSTR(" - RFCOMM Data: "), 0x80); + for (i = 0; i < length + 4; i++) { + D_PrintHex (l2capoutbuf[i], 0x80); + Notify(PSTR(" "), 0x80); + } +#endif + RFCOMM_Command(l2capoutbuf, length + 4); +} + +void SPPBase::sendRfcommCredit(uint8_t channel, uint8_t direction, uint8_t CR, uint8_t channelType, uint8_t pfBit, uint8_t credit) { + l2capoutbuf[0] = channel | direction | CR | extendAddress; // RFCOMM Address + l2capoutbuf[1] = channelType | pfBit; // RFCOMM Control + l2capoutbuf[2] = 0x01; // Length = 0 + l2capoutbuf[3] = credit; // Credit + l2capoutbuf[4] = calcFcs(l2capoutbuf); +#ifdef EXTRADEBUG + Notify(PSTR(" - RFCOMM Credit Data: "), 0x80); + for (uint8_t i = 0; i < 5; i++) { + D_PrintHex (l2capoutbuf[i], 0x80); + Notify(PSTR(" "), 0x80); + } +#endif + RFCOMM_Command(l2capoutbuf, 5); +} + + +/* CRC on 2 bytes */ +uint8_t SPPBase::crc(uint8_t *data) { + return (pgm_read_byte(&rfcomm_crc_table[pgm_read_byte(&rfcomm_crc_table[0xFF ^ data[0]]) ^ data[1]])); +} + +/* Calculate FCS */ +uint8_t SPPBase::calcFcs(uint8_t *data) { + uint8_t temp = crc(data); + if ((data[1] & 0xEF) == RFCOMM_UIH) + return (0xFF - temp); // FCS on 2 bytes + else + return (0xFF - pgm_read_byte(&rfcomm_crc_table[temp ^ data[2]])); // FCS on 3 bytes +} + +/* Check FCS */ +bool SPPBase::checkFcs(uint8_t *data, uint8_t fcs) { + uint8_t temp = crc(data); + if ((data[1] & 0xEF) != RFCOMM_UIH) + temp = pgm_read_byte(&rfcomm_crc_table[temp ^ data[2]]); // FCS on 3 bytes + return (pgm_read_byte(&rfcomm_crc_table[temp ^ fcs]) == 0xCF); +} + +/* Serial commands */ +#if defined(ARDUINO) && ARDUINO >=100 + +size_t SPPBase::write(uint8_t data) { + return write(&data, 1); +} +#else + +void SPPBase::write(uint8_t data) { + write(&data, 1); +} +#endif + +#if defined(ARDUINO) && ARDUINO >=100 + +size_t SPPBase::write(const uint8_t *data, size_t size) { +#else + +void SPPBase::write(const uint8_t *data, size_t size) { +#endif + for(uint8_t i = 0; i < size; i++) { + if(sppIndex >= sizeof (sppOutputBuffer) / sizeof (sppOutputBuffer[0])) + send(); // Send the current data in the buffer + sppOutputBuffer[sppIndex++] = data[i]; // All the bytes are put into a buffer and then send using the send() function + } +#if defined(ARDUINO) && ARDUINO >=100 + return size; +#endif +} + +void SPPBase::send() { + if(!connected || !sppIndex) + return; + uint8_t length; // This is the length of the string we are sending + uint8_t offset = 0; // This is used to keep track of where we are in the string + + l2capoutbuf[0] = rfcommChannelConnection | 0 | 0 | extendAddress; // RFCOMM Address + l2capoutbuf[1] = RFCOMM_UIH; // RFCOMM Control + + while(sppIndex) { // We will run this while loop until this variable is 0 + if(sppIndex > (sizeof (l2capoutbuf) - 4)) // Check if the string is larger than the outgoing buffer + length = sizeof (l2capoutbuf) - 4; + else + length = sppIndex; + + l2capoutbuf[2] = length << 1 | 1; // Length + uint8_t i = 0; + for(; i < length; i++) + l2capoutbuf[i + 3] = sppOutputBuffer[i + offset]; + l2capoutbuf[i + 3] = calcFcs(l2capoutbuf); // Calculate checksum + + RFCOMM_Command(l2capoutbuf, length + 4); + + sppIndex -= length; + offset += length; // Increment the offset + } +} + +int SPPBase::available(void) { + return rfcommAvailable; +}; + +void SPPBase::flush(void) { + send(); +}; + +void SPPBase::discard(void) { + rfcommAvailable = 0; +} + +int SPPBase::peek(void) { + if(rfcommAvailable == 0) // Don't read if there is nothing in the buffer + return -1; + return rfcommDataBuffer[0]; +} + +int SPPBase::read(void) { + if(rfcommAvailable == 0) // Don't read if there is nothing in the buffer + return -1; + uint8_t output = rfcommDataBuffer[0]; + for(uint8_t i = 1; i < rfcommAvailable; i++) + rfcommDataBuffer[i - 1] = rfcommDataBuffer[i]; // Shift the buffer one left + rfcommAvailable--; + bytesRead++; + if(bytesRead > (sizeof (rfcommDataBuffer) - 5)) { // We will send the command just before it runs out of credit + bytesRead = 0; + sendRfcommCredit(rfcommChannelConnection, rfcommDirection, 0, RFCOMM_UIH, 0x10, sizeof (rfcommDataBuffer)); // Send more credit +#ifdef EXTRADEBUG + Notify(PSTR("\r\nSent "), 0x80); + Notify((uint8_t)sizeof (rfcommDataBuffer), 0x80); + Notify(PSTR(" more credit"), 0x80); +#endif + } + return output; +} \ No newline at end of file diff --git a/SPPBase.h b/SPPBase.h new file mode 100644 index 00000000..6cfc1501 --- /dev/null +++ b/SPPBase.h @@ -0,0 +1,229 @@ +/* Copyright (C) 2014 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 _sppbase_h_ +#define _sppbase_h_ + +#include "BTD.h" + +/* 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 +#define SDP_SERVICE_SEARCH_REQUEST_PDU 0x02 +#define SDP_SERVICE_SEARCH_RESPONSE_PDU 0x03 + +#define SERIALPORT_UUID 0x1101 // See http://www.bluetooth.org/Technical/AssignedNumbers/service_discovery.htm +#define L2CAP_UUID 0x0100 + +/* Used for RFCOMM */ +#define RFCOMM_SABM 0x2F +#define RFCOMM_UA 0x63 +#define RFCOMM_UIH 0xEF +#define RFCOMM_DM 0x0F +#define RFCOMM_DISC 0x43 + +#define extendAddress 0x01 // Always 1 + +// Multiplexer message types +#define BT_RFCOMM_PN_CMD 0x83 +#define BT_RFCOMM_PN_RSP 0x81 +#define BT_RFCOMM_MSC_CMD 0xE3 +#define BT_RFCOMM_MSC_RSP 0xE1 +#define BT_RFCOMM_RPN_CMD 0x93 +#define BT_RFCOMM_RPN_RSP 0x91 +/* +#define BT_RFCOMM_TEST_CMD 0x23 +#define BT_RFCOMM_TEST_RSP 0x21 +#define BT_RFCOMM_FCON_CMD 0xA3 +#define BT_RFCOMM_FCON_RSP 0xA1 +#define BT_RFCOMM_FCOFF_CMD 0x63 +#define BT_RFCOMM_FCOFF_RSP 0x61 +#define BT_RFCOMM_RLS_CMD 0x53 +#define BT_RFCOMM_RLS_RSP 0x51 +#define BT_RFCOMM_NSC_RSP 0x11 +*/ + +/* + * CRC (reversed crc) lookup table as calculated by the table generator in ETSI TS 101 369 V6.3.0. + */ +const uint8_t rfcomm_crc_table[256] PROGMEM = {/* reversed, 8-bit, poly=0x07 */ + 0x00, 0x91, 0xE3, 0x72, 0x07, 0x96, 0xE4, 0x75, 0x0E, 0x9F, 0xED, 0x7C, 0x09, 0x98, 0xEA, 0x7B, + 0x1C, 0x8D, 0xFF, 0x6E, 0x1B, 0x8A, 0xF8, 0x69, 0x12, 0x83, 0xF1, 0x60, 0x15, 0x84, 0xF6, 0x67, + 0x38, 0xA9, 0xDB, 0x4A, 0x3F, 0xAE, 0xDC, 0x4D, 0x36, 0xA7, 0xD5, 0x44, 0x31, 0xA0, 0xD2, 0x43, + 0x24, 0xB5, 0xC7, 0x56, 0x23, 0xB2, 0xC0, 0x51, 0x2A, 0xBB, 0xC9, 0x58, 0x2D, 0xBC, 0xCE, 0x5F, + 0x70, 0xE1, 0x93, 0x02, 0x77, 0xE6, 0x94, 0x05, 0x7E, 0xEF, 0x9D, 0x0C, 0x79, 0xE8, 0x9A, 0x0B, + 0x6C, 0xFD, 0x8F, 0x1E, 0x6B, 0xFA, 0x88, 0x19, 0x62, 0xF3, 0x81, 0x10, 0x65, 0xF4, 0x86, 0x17, + 0x48, 0xD9, 0xAB, 0x3A, 0x4F, 0xDE, 0xAC, 0x3D, 0x46, 0xD7, 0xA5, 0x34, 0x41, 0xD0, 0xA2, 0x33, + 0x54, 0xC5, 0xB7, 0x26, 0x53, 0xC2, 0xB0, 0x21, 0x5A, 0xCB, 0xB9, 0x28, 0x5D, 0xCC, 0xBE, 0x2F, + 0xE0, 0x71, 0x03, 0x92, 0xE7, 0x76, 0x04, 0x95, 0xEE, 0x7F, 0x0D, 0x9C, 0xE9, 0x78, 0x0A, 0x9B, + 0xFC, 0x6D, 0x1F, 0x8E, 0xFB, 0x6A, 0x18, 0x89, 0xF2, 0x63, 0x11, 0x80, 0xF5, 0x64, 0x16, 0x87, + 0xD8, 0x49, 0x3B, 0xAA, 0xDF, 0x4E, 0x3C, 0xAD, 0xD6, 0x47, 0x35, 0xA4, 0xD1, 0x40, 0x32, 0xA3, + 0xC4, 0x55, 0x27, 0xB6, 0xC3, 0x52, 0x20, 0xB1, 0xCA, 0x5B, 0x29, 0xB8, 0xCD, 0x5C, 0x2E, 0xBF, + 0x90, 0x01, 0x73, 0xE2, 0x97, 0x06, 0x74, 0xE5, 0x9E, 0x0F, 0x7D, 0xEC, 0x99, 0x08, 0x7A, 0xEB, + 0x8C, 0x1D, 0x6F, 0xFE, 0x8B, 0x1A, 0x68, 0xF9, 0x82, 0x13, 0x61, 0xF0, 0x85, 0x14, 0x66, 0xF7, + 0xA8, 0x39, 0x4B, 0xDA, 0xAF, 0x3E, 0x4C, 0xDD, 0xA6, 0x37, 0x45, 0xD4, 0xA1, 0x30, 0x42, 0xD3, + 0xB4, 0x25, 0x57, 0xC6, 0xB3, 0x22, 0x50, 0xC1, 0xBA, 0x2B, 0x59, 0xC8, 0xBD, 0x2C, 0x5E, 0xCF +}; + +/** + * This BluetoothService class implements the Serial Port Protocol (SPP). + * It inherits the Arduino Stream class. This allows it to use all the standard Arduino print and stream functions. + */ +class SPPBase : public BluetoothService, public Stream { +public: + /** + * Constructor for the SPPBase class. + * @param p Pointer to BTD class instance. + */ + SPPBase(BTD *p); + + /** + * Used to provide Boolean tests for the class. + * @return Return true if SPP communication is connected. + */ + operator bool() { + return connected; + }; + + /** Variable used to indicate if the connection is established. */ + bool connected; + + /** @name Serial port profile (SPP) Print functions */ + /** + * Get number of bytes waiting to be read. + * @return Return the number of bytes ready to be read. + */ + virtual int available(void); + /** Send out all bytes in the buffer. */ + virtual void flush(void); + /** + * Used to read the next value in the buffer without advancing to the next one. + * @return Return the byte. Will return -1 if no bytes are available. + */ + virtual int peek(void); + /** + * Used to read the buffer. + * @return Return the byte. Will return -1 if no bytes are available. + */ + virtual int read(void); + +#if defined(ARDUINO) && ARDUINO >=100 + /** + * Writes the byte to send to a buffer. The message is send when either send() or after Usb.Task() is called. + * @param data The byte to write. + * @return Return the number of bytes written. + */ + virtual size_t write(uint8_t data); + /** + * Writes the bytes to send to a buffer. The message is send when either send() or after Usb.Task() is called. + * @param data The data array to send. + * @param size Size of the data. + * @return Return the number of bytes written. + */ + virtual size_t write(const uint8_t* data, size_t size); + /** Pull in write(const char *str) from Print */ + using Print::write; +#else + /** + * Writes the byte to send to a buffer. The message is send when either send() or after Usb.Task() is called. + * @param data The byte to write. + */ + virtual void write(uint8_t data); + /** + * Writes the bytes to send to a buffer. The message is send when either send() or after Usb.Task() is called. + * @param data The data array to send. + * @param size Size of the data. + */ + virtual void write(const uint8_t* data, size_t size); +#endif + + /** Discard all the bytes in the buffer. */ + void discard(void); + /** + * This will send all the bytes in the buffer. + * This is called whenever Usb.Task() is called, + * but can also be called via this function. + */ + void send(void); + /**@}*/ +protected: + /** @name BluetoothService implementation */ + /** + * Used to pass acldata to the services. + * @param ACLData Incoming acldata. + */ + virtual void ACLData(uint8_t *ACLData) = 0; + /** Used to establish the connection automatically. */ + virtual void Run() = 0; + /** Use this to reset the service. */ + virtual void Reset() = 0; + /** Used this to disconnect the virtual serial port. */ + virtual void disconnect(); + /**@}*/ + + /* Pointer to Bluetooth dongle library instance */ + BTD *pBtd; + + /* Set true when a channel is created */ + bool SDPConnected; + bool RFCOMMConnected; + + uint16_t hci_handle; // The HCI Handle for the connection + + /* Variables used by L2CAP state machines */ + uint8_t l2cap_sdp_state; + uint8_t l2cap_rfcomm_state; + + uint8_t l2capoutbuf[BULK_MAXPKTSIZE]; // General purpose buffer for l2cap out data + uint8_t rfcommbuf[10]; // Buffer for RFCOMM Commands + + /* L2CAP Channels */ + uint8_t sdp_scid[2]; // L2CAP source CID for SDP + uint8_t sdp_dcid[2]; // 0x0050 + uint8_t rfcomm_scid[2]; // L2CAP source CID for RFCOMM + uint8_t rfcomm_dcid[2]; // 0x0051 + uint8_t identifier; // Identifier for command + + /* RFCOMM Variables */ + uint8_t rfcommChannel; + uint8_t rfcommChannelConnection; // This is the channel the SPP channel will be running at + uint8_t rfcommDirection; + uint8_t rfcommCommandResponse; + uint8_t rfcommChannelType; + uint8_t rfcommPfBit; + + bool creditSent; + + uint8_t rfcommDataBuffer[100]; // Create a 100 sized buffer for incoming data + uint8_t sppOutputBuffer[100]; // Create a 100 sized buffer for outgoing SPP data + uint8_t sppIndex; + uint8_t rfcommAvailable; + + uint8_t bytesRead; // Counter to see when it's time to send more credit + + virtual void SDP_Command(uint8_t *data, uint8_t nbytes) = 0; // Used for SDP commands + + /* RFCOMM Commands */ + virtual void RFCOMM_Command(uint8_t *data, uint8_t nbytes) = 0; // Used for RFCOMM commands + void sendRfcomm(uint8_t channel, uint8_t direction, uint8_t CR, uint8_t channelType, uint8_t pfBit, uint8_t *data, uint8_t length); + void sendRfcommCredit(uint8_t channel, uint8_t direction, uint8_t CR, uint8_t channelType, uint8_t pfBit, uint8_t credit); + uint8_t calcFcs(uint8_t *data); + bool checkFcs(uint8_t *data, uint8_t fcs); + uint8_t crc(uint8_t *data); +}; + +#endif diff --git a/SPPi.cpp b/SPPi.cpp new file mode 100755 index 00000000..d79aa176 --- /dev/null +++ b/SPPi.cpp @@ -0,0 +1,687 @@ +/* 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 + + Enhanced by Dmitry Pakhomenko to initiate connection with remote SPP-aware device + 04.04.2014, Magictale Electronics + */ + + +#include "SPPi.h" +// To enable serial debugging see "settings.h" +//#define EXTRADEBUG // Uncomment to get even more debugging data +//#define PRINTREPORT // Uncomment to print the report sent to the Arduino + +/* + * "RFCOMM UUID" signature, a channel number comes immediately after it + */ +const uint8_t rfcomm_uuid_sign[6] PROGMEM = { 0x35, 0x05, 0x19, 0x00, 0x03, 0x08 }; + +SPPi::SPPi(BTD *p, const char* name, const char* pin, bool pair, uint8_t *addr) : +SPPBase(p) +{ + if (pBtd) + pBtd->registerServiceClass(this); // Register it as a Bluetooth service + + pBtd->btdName = name; + pBtd->btdPin = pin; + + if (addr) // Make sure address is set + pBtd->pairWithOtherDevice = pair; + + for (uint8_t i = 0; i < 6; i++) + pBtd->remote_bdaddr[i] = addr[i]; + + /* Set device cid for the SDP and RFCOMM channels */ + sdp_scid[0] = 0x50; // 0x0050 + sdp_scid[1] = 0x00; + + rfcomm_scid[0] = 0x51; // 0x0051 + rfcomm_scid[1] = 0x00; + + Reset(); +} + +void SPPi::Reset() { + connected = false; + RFCOMMConnected = false; + SDPConnected = false; + l2cap_sdp_state = L2CAP_SDP_WAIT; + l2cap_rfcomm_state = L2CAP_RFCOMM_WAIT; + sppIndex = 0; + + rfcomm_uuid_sign_idx = 0; + rfcomm_found = false; +} + +void SPPi::ACLData(uint8_t *l2capinbuf) { + +#ifdef EXTRADEBUG + Notify(PSTR("\r\nIncoming Packet: "), 0x80); + + for (uint8_t i = 0; i < (l2capinbuf[2] + 4); i++) { + D_PrintHex (l2capinbuf[i], 0x80); + Notify(PSTR(" "), 0x80); + } + Notify(PSTR("\r\n"), 0x80); +#endif + + //if((l2capinbuf[0] | (uint16_t)l2capinbuf[1] << 8) == (hci_handle | 0x2000U)) { // acl_handle_ok + if(UHS_ACL_HANDLE_OK(l2capinbuf, hci_handle)) { // acl_handle_ok + if((l2capinbuf[6] | (l2capinbuf[7] << 8)) == 0x0001U) { // l2cap_control - Channel ID for ACL-U + if (l2capinbuf[8] == L2CAP_CMD_COMMAND_REJECT) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nL2CAP Command Rejected - Reason: "), 0x80); + D_PrintHex (l2capinbuf[13], 0x80); + Notify(PSTR(" "), 0x80); + D_PrintHex (l2capinbuf[12], 0x80); + Notify(PSTR(" Data: "), 0x80); + D_PrintHex (l2capinbuf[17], 0x80); + Notify(PSTR(" "), 0x80); + D_PrintHex (l2capinbuf[16], 0x80); + Notify(PSTR(" "), 0x80); + D_PrintHex (l2capinbuf[15], 0x80); + Notify(PSTR(" "), 0x80); + D_PrintHex (l2capinbuf[14], 0x80); +#endif + }else if (l2capinbuf[8] == L2CAP_CMD_CONNECTION_RESPONSE) { + if (((l2capinbuf[16] | (l2capinbuf[17] << 8)) == 0x0000) && ((l2capinbuf[18] | (l2capinbuf[19] << 8)) == SUCCESSFUL)) { // Success +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nL2CAP Connection Response"), 0x80); +#endif + if (l2capinbuf[14] == sdp_scid[0] && l2capinbuf[15] == sdp_scid[1]) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nSDP Connection Response"), 0x80); +#endif + sdp_dcid[0] = l2capinbuf[12]; + sdp_dcid[1] = l2capinbuf[13]; + + identifier++; + l2cap_sdp_state = L2CAP_SDP_CONN_RESPONSE; + } else if (l2capinbuf[14] == rfcomm_scid[0] && l2capinbuf[15] == rfcomm_scid[1]) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nRFComm Connection Response"), 0x80); +#endif + rfcomm_dcid[0] = l2capinbuf[12]; + rfcomm_dcid[1] = l2capinbuf[13]; + + identifier++; + l2cap_rfcomm_state = L2CAP_RFCOMM_CONN_RESPONSE; + } + } + } else if(l2capinbuf[8] == L2CAP_CMD_CONFIG_RESPONSE) { + if((l2capinbuf[16] | (l2capinbuf[17] << 8)) == 0x0000) { // Success + if(l2capinbuf[12] == sdp_scid[0] && l2capinbuf[13] == sdp_scid[1]) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nSDP Configuration Response Received"), 0x80); + Notify(PSTR("\r\nSDP Successfully Configured"), 0x80); +#endif + l2cap_sdp_state = L2CAP_SDP_SERVICE_SEARCH_ATTR1; + identifier++; + SDP_Service_Search_Attr(0, identifier); +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nSDP Service Search Attribute Request 1 Sent"), 0x80); +#endif + } else if(l2capinbuf[12] == rfcomm_scid[0] && l2capinbuf[13] == rfcomm_scid[1]) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nRFComm Configuration Response Received"), 0x80); + Notify(PSTR("\r\nRFComm Successfully Configured"), 0x80); +#endif + l2cap_sdp_state = L2CAP_RFCOMM_DONE; + identifier++; +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nRFComm SABM Sent"), 0x80); +#endif + rfcommAvailable = 0; // Reset number of bytes available + bytesRead = 0; // Reset number of bytes received + RFCOMMConnected = true; + + // channel direction, CR,channelType, pfBit, data, length + sendRfcomm(0, 0, (1 << 1), RFCOMM_SABM, (1 << 4), rfcommbuf, 0); + } + } + } else if (l2capinbuf[8] == L2CAP_CMD_CONFIG_REQUEST) { + if (l2capinbuf[12] == sdp_scid[0] && l2capinbuf[13] == sdp_scid[1]) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nSDP Configuration Request Received"), 0x80); +#endif + identifier = l2capinbuf[9]; + pBtd->l2cap_config_response(hci_handle, identifier, sdp_dcid); +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nSDP Configuration Response Sent"), 0x80); +#endif + l2cap_sdp_state = L2CAP_SDP_CONFIG_REQUEST; + identifier++; + delay(1); +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nSDP Configuration Request Sent"), 0x80); +#endif + pBtd->l2cap_config_request(hci_handle, identifier, sdp_dcid); + + }else if (l2capinbuf[12] == rfcomm_scid[0] && l2capinbuf[13] == rfcomm_scid[1]) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nRFComm Configuration Request Received"), 0x80); +#endif + identifier = l2capinbuf[9]; + + pBtd->l2cap_config_response(hci_handle, identifier, rfcomm_dcid); +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nRFComm Configuration Response Sent"), 0x80); +#endif + l2cap_rfcomm_state = L2CAP_RFCOMM_CONFIG_REQUEST; + identifier++; + delay(1); +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nRFComm Configuration Request Sent"), 0x80); +#endif + pBtd->l2cap_config_request(hci_handle, identifier, rfcomm_dcid); + } + + } else if (l2capinbuf[8] == L2CAP_CMD_DISCONNECT_REQUEST) { + if (l2capinbuf[12] == sdp_scid[0] && l2capinbuf[13] == sdp_scid[1]) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nDisconnect Request: SDP Channel"), 0x80); +#endif + identifier = l2capinbuf[9]; + + SDPConnected = false; +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nDisconnected SDP Channel"), 0x80); +#endif + pBtd->l2cap_disconnection_response(hci_handle, identifier, sdp_scid, sdp_dcid); + l2cap_sdp_state = L2CAP_SDP_WAIT; + + }else if (l2capinbuf[12] == rfcomm_scid[0] && l2capinbuf[13] == rfcomm_scid[1]) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nDisconnect Request: RFComm Channel"), 0x80); +#endif + identifier = l2capinbuf[9]; + + RFCOMMConnected = false; + connected = false; +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nDisconnected RFComm Channel"), 0x80); +#endif + pBtd->l2cap_disconnection_response(hci_handle, identifier, rfcomm_dcid, rfcomm_scid); + l2cap_rfcomm_state = L2CAP_RFCOMM_WAIT; + + } + } else if (l2capinbuf[8] == L2CAP_CMD_DISCONNECT_RESPONSE) { + if (l2capinbuf[12] == sdp_dcid[0] && l2capinbuf[13] == sdp_dcid[1]) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nDisconnect Response: SDP Channel"), 0x80); +#endif + SDPConnected = false; + l2cap_sdp_state = L2CAP_SDP_WAIT; + + }else if (l2capinbuf[12] == rfcomm_dcid[0] && l2capinbuf[13] == rfcomm_dcid[1]) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nDisconnect Response: RFComm Channel"), 0x80); + Notify(PSTR("\r\nDisconnected L2CAP Connection"), 0x80); +#endif + RFCOMMConnected = false; + + pBtd->hci_disconnect(hci_handle); + hci_handle = -1; // Reset handle + + l2cap_rfcomm_state = L2CAP_RFCOMM_WAIT; + } + + } else if (l2capinbuf[8] == L2CAP_CMD_INFORMATION_REQUEST) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nInformation request"), 0x80); +#endif + identifier = l2capinbuf[9]; + pBtd->l2cap_information_response(hci_handle, identifier, l2capinbuf[12], l2capinbuf[13]); + } + else { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nL2CAP Unknown Signaling Command: "), 0x80); + D_PrintHex (l2capinbuf[8], 0x80); +#endif + } + } else if (l2capinbuf[6] == sdp_scid[0] && l2capinbuf[7] == sdp_scid[1]) { // SDP +#ifdef EXTRADEBUG + Notify(PSTR("\r\nSDP data:"), 0x80); +#endif + if (l2capinbuf[8] == SDP_SERVICE_SEARCH_ATTRIBUTE_RESPONSE_PDU) { + + if (l2cap_sdp_state == L2CAP_SDP_SERVICE_SEARCH_ATTR1){ +#ifdef EXTRADEBUG + Notify(PSTR(" - SDP Service Search Attribute Response 1: "), 0x80); +#endif + remainingBytes = l2capinbuf[l2capinbuf[2] + 3]; + + parseAttrReply(l2capinbuf); + + l2cap_sdp_state = L2CAP_SDP_SERVICE_SEARCH_ATTR2; + identifier++; + if (remainingBytes) { + SDP_Service_Search_Attr(0, identifier, remainingBytes); +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nSDP Service Search Attribute Request 2 Sent"), 0x80); +#endif + } else { + l2cap_sdp_state = L2CAP_SDP_DONE; + + if (rfcomm_found) { + pBtd->l2cap_connection_request(hci_handle, identifier, rfcomm_scid, RFCOMM_PSM); +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nRFComm Connection Request Sent"), 0x80); +#endif + l2cap_rfcomm_state = L2CAP_RFCOMM_REQUEST; + } else{ +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nRFComm Channel Number not found"), 0x80); +#endif + } + } + + } else if (l2cap_sdp_state == L2CAP_SDP_SERVICE_SEARCH_ATTR2){ +#ifdef EXTRADEBUG + Notify(PSTR(" - SDP Service Search Attribute Response 2: "), 0x80); +#endif + + parseAttrReply(l2capinbuf); + + l2cap_sdp_state = L2CAP_SDP_DONE; + identifier++; + + if (rfcomm_found) { + pBtd->l2cap_connection_request(hci_handle, identifier, rfcomm_scid, RFCOMM_PSM); +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nRFComm Connection Request Sent"), 0x80); +#endif + l2cap_rfcomm_state = L2CAP_RFCOMM_REQUEST; + }else{ +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nRFComm Channel Number not found"), 0x80); +#endif + } + } + } + else { +#ifdef EXTRADEBUG + Notify(PSTR("\r\nUnknown PDU: "), 0x80); + D_PrintHex (l2capinbuf[8], 0x80); +#endif + } + + } else if (l2capinbuf[6] == rfcomm_scid[0] && l2capinbuf[7] == rfcomm_scid[1]) { // RFCOMM + + rfcommChannel = l2capinbuf[8] & 0xF8; + rfcommDirection = l2capinbuf[8] & 0x04; + rfcommCommandResponse = l2capinbuf[8] & 0x02; + rfcommChannelType = l2capinbuf[9] & 0xEF; + rfcommPfBit = l2capinbuf[9] & 0x10; + +#ifdef EXTRADEBUG + Notify(PSTR("\r\nRFComm Channel: "), 0x80); + D_PrintHex (rfcommChannel >> 3, 0x80); + Notify(PSTR(" Direction: "), 0x80); + D_PrintHex (rfcommDirection >> 2, 0x80); + Notify(PSTR(" CommandResponse: "), 0x80); + D_PrintHex (rfcommCommandResponse >> 1, 0x80); + Notify(PSTR(" ChannelType: "), 0x80); + D_PrintHex (rfcommChannelType, 0x80); + Notify(PSTR(" PF_BIT: "), 0x80); + D_PrintHex (rfcommPfBit >> 4, 0x80); +#endif + if (rfcommChannelType == RFCOMM_DISC) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nReceived Disconnect RFComm Command on channel: "), 0x80); + D_PrintHex (rfcommChannel, 0x80); +#endif + connected = false; +// sendRfcomm(rfcommChannel, rfcommDirection, rfcommCommandResponse, RFCOMM_UA, rfcommPfBit, rfcommbuf, 0x00); // UA Command + } + + if (connected) + { + /* Read the incoming message */ + if (rfcommChannelType == RFCOMM_UIH && rfcommChannel == (rfcommChannelConnection << 3)) { + uint8_t length = l2capinbuf[10] >> 1; // Get length + uint8_t offset = l2capinbuf[4] - length - 4; // Check if there is credit + if (checkFcs(&l2capinbuf[8], l2capinbuf[11 + length + offset])) { + uint8_t i = 0; + for (; i < length; i++) { + if (rfcommAvailable + i >= sizeof (rfcommDataBuffer)) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nWarning: Buffer is full!"), 0x80); +#endif + break; + } + rfcommDataBuffer[rfcommAvailable + i] = l2capinbuf[11 + i + offset]; + } + rfcommAvailable += i; +#ifdef EXTRADEBUG + Notify(PSTR("\r\nRFCOMM Data Available: "), 0x80); + Notify(rfcommAvailable, 0x80); + if (offset) { + Notify(PSTR(" - Credit: 0x"), 0x80); + D_PrintHex (l2capinbuf[11], 0x80); + } +#endif + } +#ifdef DEBUG_USB_HOST + else + Notify(PSTR("\r\nError in FCS checksum!"), 0x80); +#endif +#ifdef PRINTREPORT // Uncomment "#define PRINTREPORT" to print the report send to the Arduino via Bluetooth + for (uint8_t i = 0; i < length; i++) + Notifyc(l2capinbuf[i + 11 + offset], 0x80); +#endif + } else if (rfcommChannelType == RFCOMM_UIH && l2capinbuf[11] == BT_RFCOMM_RPN_CMD) { // UIH Remote Port Negotiation Command +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nReceived UIH Remote Port Negotiation Command"), 0x80); +#endif +/* rfcommbuf[0] = BT_RFCOMM_RPN_RSP; // Command + rfcommbuf[1] = l2capinbuf[12]; // Length and shiftet like so: length << 1 | 1 + rfcommbuf[2] = l2capinbuf[13]; // Channel: channel << 1 | 1 + rfcommbuf[3] = l2capinbuf[14]; // Pre difined for Bluetooth, see 5.5.3 of TS 07.10 Adaption for RFCOMM + rfcommbuf[4] = l2capinbuf[15]; // Priority + rfcommbuf[5] = l2capinbuf[16]; // Timer + rfcommbuf[6] = l2capinbuf[17]; // Max Fram Size LSB + rfcommbuf[7] = l2capinbuf[18]; // Max Fram Size MSB + rfcommbuf[8] = l2capinbuf[19]; // MaxRatransm. + rfcommbuf[9] = l2capinbuf[20]; // Number of Frames + sendRfcomm(rfcommChannel, rfcommDirection, 0, RFCOMM_UIH, rfcommPfBit, rfcommbuf, 0x0A); // UIH Remote Port Negotiation Response*/ + } else if (rfcommChannelType == RFCOMM_UIH && l2capinbuf[11] == BT_RFCOMM_MSC_CMD) { // UIH Modem Status Command +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nSend UIH Modem Status Response"), 0x80); +#endif + /*rfcommbuf[0] = BT_RFCOMM_MSC_RSP; // UIH Modem Status Response + rfcommbuf[1] = 2 << 1 | 1; // Length and shiftet like so: length << 1 | 1 + rfcommbuf[2] = l2capinbuf[13]; // Channel: (1 << 0) | (1 << 1) | (0 << 2) | (channel << 3) + rfcommbuf[3] = l2capinbuf[14]; + sendRfcomm(rfcommChannel, rfcommDirection, 0, RFCOMM_UIH, rfcommPfBit, rfcommbuf, 0x04);*/ + } + }else{ + + if (rfcommChannelType == RFCOMM_SABM) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nReceived SAMB RFComm Packet on channel: "), 0x80); + D_PrintHex (rfcommChannel >> 3, 0x80); +#endif + } else if (rfcommChannelType == RFCOMM_UA) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nReceived UA RFComm Packet on channel: "), 0x80); + D_PrintHex (rfcommChannel >> 3, 0x80); +#endif + if ((rfcommChannel >> 3) == 0) + { + //Reply on 1-st SAMB command on channel 0 + rfcommbuf[0] = BT_RFCOMM_PN_CMD; // UIH Parameter Negotiation Request + rfcommbuf[1] = ((8 << 1) | 1); // Length and shiftet like so: length << 1 | 1 + rfcommbuf[2] = ((0x10<< 1)); // Channel: channel << 1 | 1 TODO: replace channel with variable + rfcommbuf[3] = 0xE0; // Pre defined for Bluetooth, see 5.5.3 of TS 07.10 Adaption for RFCOMM + rfcommbuf[4] = 0x00; // Priority + rfcommbuf[5] = 0x00; // Timer + rfcommbuf[6] = BULK_MAXPKTSIZE - 14; // Max Fram Size LSB - set to the size of received data (50) + rfcommbuf[7] = 0x00; // Max Fram Size MSB + rfcommbuf[8] = 0x00; // MaxRetransm. + rfcommbuf[9] = 0x00; // Number of Frames +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nSent UAH RFComm Cmd BT_RFCOMM_PN_CMD (Parameter Negotiation Request) on channel: "), 0x80); + D_PrintHex (rfcommChannel >> 3, 0x80); +#endif + // channel direction, CR,channelType,pfBit, data, length + sendRfcomm(0, 0, (1 << 1), RFCOMM_UIH, 0, rfcommbuf, 0x0A); + }else{ + //Reply on 2-nd SAMB command on channel 0 +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nSend UIH RFComm Cmd BT_RFCOMM_MSC_CMD (Modem Status Command)"), 0x80); +#endif + rfcommbuf[0] = BT_RFCOMM_MSC_CMD; // UIH Modem Status Command + rfcommbuf[1] = 2 << 1 | 1; // Length and shiftet like so: length << 1 | 1 + rfcommbuf[2] = (1 << 0) | (1 << 1) | (0 << 2) | (rfcommChannelConnection << 3); //0x83 // Channel: (1 << 0) | (1 << 1) | (0 << 2) | (channel << 3) + rfcommbuf[3] = 0x8D; // Can receive frames (YES), Ready to Communicate (YES), Ready to Receive (YES), Incomig Call (NO), Data is Value (YES) + + // channel direction, CR,channelType,pfBit, data, length + sendRfcomm(0, 0, (1 << 1), RFCOMM_UIH, 0, rfcommbuf, 0x04); + } + } else if (rfcommChannelType == RFCOMM_UIH) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nReceived UIH RFComm Packet on channel: "), 0x80); + D_PrintHex (rfcommChannel >> 3, 0x80); +#endif + if (l2capinbuf[11] == BT_RFCOMM_PN_RSP) + { +#ifdef DEBUG_USB_HOST + Notify(PSTR(" - BT_RFCOMM_PN_RSP (Parameter Negotiation Response)"), 0x80); + Notify(PSTR("\r\nRFComm 2-nd SABM Sent"), 0x80); +#endif + // channel direction, CR,channelType, pfBit, data, length + sendRfcomm((rfcommChannelConnection << 3), 0, (1 << 1), RFCOMM_SABM, (1 << 4), rfcommbuf, 0); + }else if (l2capinbuf[11] == BT_RFCOMM_MSC_CMD) + { +#ifdef DEBUG_USB_HOST + Notify(PSTR(" - BT_RFCOMM_MSC_CMD (Modem Status Cmd)"), 0x80); +#endif + rfcommbuf[0] = BT_RFCOMM_MSC_RSP; // UIH Modem Status Command + rfcommbuf[1] = 2 << 1 | 1; // Length and shiftet like so: length << 1 | 1 + rfcommbuf[2] = (1 << 0) | (1 << 1) | (0 << 2) | (rfcommChannelConnection << 3); //0x83 // Channel: (1 << 0) | (1 << 1) | (0 << 2) | (channel << 3) + rfcommbuf[3] = 0x8D; // Can receive frames (YES), Ready to Communicate (YES), Ready to Receive (YES), Incomig Call (NO), Data is Value (YES) +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nSend UIH RFComm Cmd BT_RFCOMM_MSC_RSP (Modem Status Response)"), 0x80); +#endif + // channel direction, CR,channelType,pfBit, data, length + sendRfcomm(0, 0, (1 << 1), RFCOMM_UIH, 0, rfcommbuf, 0x04); + }else if (l2capinbuf[11] == BT_RFCOMM_MSC_RSP) + { +#ifdef DEBUG_USB_HOST + Notify(PSTR(" - BT_RFCOMM_MSC_RSP (Modem Status Response)"), 0x80); + Notify(PSTR("\r\nRFComm Cmd with Credit Sent"), 0x80); +#endif + sendRfcommCredit((0x10 << 3), 0, (1 << 1), RFCOMM_UIH, 0x10, + sizeof (rfcommDataBuffer)); // Send credit + + connected = true; // The RFCOMM channel is now established + sppIndex = 0; + }else if (l2capinbuf[11] == BT_RFCOMM_RPN_CMD) + { +#ifdef DEBUG_USB_HOST + Notify(PSTR(" - BT_RFCOMM_RPN_CMD (Remote Port Negotiation Cmd)"), 0x80); +#endif + //Just a stub. This command is optional according to the spec + }else if (l2capinbuf[11] == BT_RFCOMM_RPN_RSP) + { +#ifdef DEBUG_USB_HOST + Notify(PSTR(" - BT_RFCOMM_RPN_RSP (Remote Port Negotiation Response)"), 0x80); +#endif + //Just a stub. This command is optional according to the spec + }else + { +#ifdef DEBUG_USB_HOST + Notify(PSTR(" - Unknown Response: "), 0x80); + D_PrintHex (l2capinbuf[11], 0x80); +#endif + } + } else if (rfcommChannelType == RFCOMM_DM) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nReceived DM RFComm Command on channel: "), 0x80); + D_PrintHex (rfcommChannel >> 3, 0x80); +#endif + } else { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nUnknown Response, rfCommChannelType: "), 0x80); + D_PrintHex (rfcommChannelType, 0x80); +#endif + } + } + } else { +#ifdef EXTRADEBUG + Notify(PSTR("\r\nUnsupported L2CAP Data - Channel ID: "), 0x80); + D_PrintHex (l2capinbuf[7], 0x80); + Notify(PSTR(" "), 0x80); + D_PrintHex (l2capinbuf[6], 0x80); + Notify(PSTR("\r\n"), 0x80); + + for (uint8_t i = 0; i < BULK_MAXPKTSIZE; i++) { + D_PrintHex (l2capinbuf[i], 0x80); + Notify(PSTR(" "), 0x80); + } +#endif + } + }else{ +#ifdef EXTRADEBUG + Notify(PSTR("\r\nBad ACL handle: "), 0x80); + D_PrintHex (l2capinbuf[0], 0x80); + Notify(PSTR(" "), 0x80); + D_PrintHex (l2capinbuf[1], 0x80); + Notify(PSTR(" "), 0x80); +#endif + } +} + +void SPPi::Run() { + if (pBtd->pairWithOtherDevice){ + if (l2cap_sdp_state == L2CAP_SDP_WAIT) { + if (pBtd->connectToOtherDevice && !pBtd->l2capConnectionClaimed && !connected) { + pBtd->l2capConnectionClaimed = true; +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nSDP Connection Request Sent"), 0x80); +#endif + hci_handle = pBtd->hci_handle; // Store the HCI Handle for the connection + identifier = 0; + + pBtd->l2cap_connection_request(hci_handle, identifier, sdp_scid, SDP_PSM); + l2cap_sdp_state = L2CAP_SDP_REQUEST; + } + } + } +} + +/************************************************************/ +/* SDP Commands */ +/************************************************************/ +void SPPi::SDP_Command(uint8_t *data, uint8_t nbytes) { // See page 223 in the Bluetooth specs + pBtd->L2CAP_Command(hci_handle, data, nbytes, sdp_dcid[0], sdp_dcid[1]); +} + +void SPPi::SDP_Service_Search_Attr(uint8_t transactionIDHigh, uint8_t transactionIDLow, uint8_t remainingLen) { + l2capoutbuf[0] = SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST_PDU; + l2capoutbuf[1] = transactionIDHigh; + l2capoutbuf[2] = transactionIDLow; + + l2capoutbuf[3] = 0x00; // Parameter Length + if (remainingLen == 0) + l2capoutbuf[4] = 0x0F; // Parameter Length + else + l2capoutbuf[4] = 0x11; // Parameter Length + + l2capoutbuf[5] = 0x35; // Data Element Sequence, data size in next 8 bits + l2capoutbuf[6] = 0x03; // Data size + + l2capoutbuf[7] = 0x19; // 00011 001 UUID, 2 bytes + l2capoutbuf[8] = 0x01; + l2capoutbuf[9] = 0x00; // 0x0100 - L2CAP_UUID + + l2capoutbuf[10] = 0x00; + l2capoutbuf[11] = 0x26;// 0x0026 Maximum attribute byte count + + l2capoutbuf[12] = 0x35;// Data Element Sequence, data size in next 8 bits + l2capoutbuf[13] = 0x05;// Data size + + l2capoutbuf[14] = 0x0A;// 00001 010 unsigned int, 4 bytes - Attribute ID range + + l2capoutbuf[15] = 0x00; + l2capoutbuf[16] = 0x00;// range from 0x0000... + + l2capoutbuf[17] = 0xFF; + l2capoutbuf[18] = 0xFF;// ... to 0xFFFF + + if (remainingLen == 0) { + l2capoutbuf[19] = 0x00;// No more data + SDP_Command(l2capoutbuf, 20); + } else{ + l2capoutbuf[19] = 0x02; + l2capoutbuf[20] = 0x0; //will be 0 anyway + l2capoutbuf[21] = remainingLen; + SDP_Command(l2capoutbuf, 22); + } +} + +/************************************************************/ +/* RFCOMM Commands */ +/************************************************************/ +void SPPi::RFCOMM_Command(uint8_t* data, uint8_t nbytes) { + pBtd->L2CAP_Command(hci_handle, data, nbytes, rfcomm_dcid[0], rfcomm_dcid[1]); +} + +void SPPi::parseAttrReply(uint8_t *l2capinbuf) { + if ((l2capinbuf[2] + 4) < 15) return; // Sanity check + + if (rfcomm_found) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nChannel is already found"), 0x80); +#endif + return; + } + + if (rfcomm_uuid_sign_idx == sizeof(rfcomm_uuid_sign)) { + // Signature has been already found but channel is in next packet + rfcommChannelConnection = l2capinbuf[15]; + rfcomm_found = true; + +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nChannel found in second packet: "), 0x80); + Notify((uint8_t)rfcommChannelConnection, 0x80); +#endif + return; + } + + uint8_t nextSignBt; + for (uint8_t i = 15; i < (l2capinbuf[2] + 4); i++) { // Searching through packet payload only + // Keep searching for signature + if (l2capinbuf[i] == pgm_read_byte(&rfcomm_uuid_sign[rfcomm_uuid_sign_idx])) { +#ifdef EXTRADEBUG + Notify(PSTR("\r\nFound match @"), 0x80); + Notify((uint8_t)i, 0x80); +#endif + rfcomm_uuid_sign_idx++; + if (rfcomm_uuid_sign_idx == sizeof(rfcomm_uuid_sign)) { + // Signature found, trying to get channel number + if (l2capinbuf[i + 1] == 0x2) { + // Is the byte we are looking at the second last in the packet? + if ((l2capinbuf[2] + 1) == (i + 1)) { + // Oh well, channel number didn't fit in this packet, waiting for next +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nChannel will be in the next packet"), 0x80); +#endif + return; + } + } + rfcommChannelConnection = l2capinbuf[i + 1]; + rfcomm_found = true; +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nChannel found in first packet: "), 0x80); + D_PrintHex (rfcommChannelConnection, 0x80); +#endif + return; + } + } else if ((l2capinbuf[i] == 0x2) && (i == l2capinbuf[2] + 1)) { + // This is an indication of packet end with more data to come in next packet - do not reset rfcomm_uuid_sign_idx, we will continue when next packet arrives +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nEnd of packet reached, no full signature yet"), 0x80); +#endif + return; + } else { + // Otherwise the signature is not found - start over again + rfcomm_uuid_sign_idx = 0; + } + } + return; +} diff --git a/SPPi.h b/SPPi.h new file mode 100755 index 00000000..8cca6956 --- /dev/null +++ b/SPPi.h @@ -0,0 +1,89 @@ +/* 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 + + Enhanced by Dmitry Pakhomenko to initiate connection with remote SPP-aware device + 04.04.2014, Magictale Electronics + */ + +#ifndef _sppi_h_ +#define _sppi_h_ + +#include "SPPBase.h" + +/* Bluetooth L2CAP states for SDP_task() */ +#define L2CAP_SDP_WAIT 0 +#define L2CAP_SDP_REQUEST 1 +#define L2CAP_SDP_DONE 2 +#define L2CAP_DISCONNECT_RESPONSE 3 +#define L2CAP_SDP_CONN_RESPONSE 4 +#define L2CAP_SDP_CONFIG_REQUEST 5 +#define L2CAP_SDP_SERVICE_SEARCH_ATTR1 6 +#define L2CAP_SDP_SERVICE_SEARCH_ATTR2 7 + +/* Bluetooth L2CAP states for RFCOMM_task() */ +#define L2CAP_RFCOMM_WAIT 0 +#define L2CAP_RFCOMM_REQUEST 1 +#define L2CAP_RFCOMM_DONE 3 +#define L2CAP_RFCOMM_CONN_RESPONSE 4 +#define L2CAP_RFCOMM_CONFIG_REQUEST 6 +#define L2CAP_RFCOMM_CONFIG_RESPONSE 7 + +/** + * This BluetoothService class a Serial Port Protocol (SPP) client. + * It inherits the Arduino Stream class. This allows it to use all the standard Arduino print functions. + */ +class SPPi : public SPPBase { +public: + /** + * Constructor for the SPPi class. + * @param p Pointer to BTD class instance. + * @param name Set the name to BTD#btdName. If argument is omitted, then "Arduino" will be used. + * @param pin Write the pin to BTD#btdPin. If argument is omitted, then "0000" will be used. + * @param pair Set this to true if you want to pair with a device. + * @param addr Set this to the address you want to connect to. + */ + SPPi(BTD *p, const char *name = "Arduino", const char *pin = "0000", bool pair = false, uint8_t *addr = NULL); + +#if GCC_VERSION > 40700 // Test for GCC > 4.7.0 + SPPi(BTD *p, bool pair = false, uint8_t *addr = NULL) : SPPi(p, "Arduino", "0000", pair, addr) {}; // Use a delegating constructor +#endif + + /** @name SPPBase implementation */ + /** + * Used to pass acldata to the services. + * @param ACLData Incoming acldata. + */ + virtual void ACLData(uint8_t *ACLData); + /** Used to establish the connection automatically. */ + virtual void Run(); + /** Use this to reset the service. */ + virtual void Reset(); + /**@}*/ + +private: + uint8_t remainingBytes; + uint8_t rfcomm_uuid_sign_idx; // A progressing index while searching for "RFCOMM UUID" signature, starts from 0 (nothing found) and ends with 5 (found full sequence) + uint8_t rfcomm_found; + + /* SDP Commands */ + virtual void SDP_Command(uint8_t *data, uint8_t nbytes); + void SDP_Service_Search_Attr(uint8_t transactionIDHigh, uint8_t transactionIDLow, uint8_t remainingLen = 0); + + /* RFCOMM Commands */ + virtual void RFCOMM_Command(uint8_t *data, uint8_t nbytes); // Used for RFCOMM commands + void parseAttrReply(uint8_t *l2capinbuf); +}; +#endif \ No newline at end of file diff --git a/UsbCore.h b/UsbCore.h old mode 100644 new mode 100755 index 76d9d5ee..3abe1ed9 --- a/UsbCore.h +++ b/UsbCore.h @@ -37,6 +37,8 @@ typedef MAX3421e MAX3421E; // Teensy++ 1.0 and 2.0 typedef MAX3421e MAX3421E; // Arduino Mega ADK #elif defined(ARDUINO_AVR_BALANDUINO) typedef MAX3421e MAX3421E; // Balanduino +#elif defined(LUMINARDO) +typedef MAX3421e MAX3421E; // Luminardo #else typedef MAX3421e MAX3421E; // Official Arduinos (UNO, Duemilanove, Mega, 2560, Leonardo, Due etc.) or Teensy 2.0 and 3.0 #endif diff --git a/examples/Bluetooth/SPP/SPPClient/SPPClient.ino b/examples/Bluetooth/SPP/SPPClient/SPPClient.ino new file mode 100644 index 00000000..412af57d --- /dev/null +++ b/examples/Bluetooth/SPP/SPPClient/SPPClient.ino @@ -0,0 +1,48 @@ +/* + Example sketch for the RFCOMM/SPP Client 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 +#include +// Satisfy IDE, which only needs to see the include statment in the ino. +#ifdef dobogusinclude +#include +#endif + +USB Usb; +//USBHub Hub1(&Usb); // Some dongles have a hub inside + +BTD Btd(&Usb); // You have to create the Bluetooth Dongle instance like so + +uint8_t addr[6] = { 0x71, 0xB4, 0xB0, 0xC8, 0xBC, 0xC8 }; // Set this to the Bluetooth address you want to connect to +SPPi SerialBT(&Btd, true, addr); + +boolean firstMessage = true; + +void setup() { + Serial.begin(115200); + while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection + if (Usb.Init() == -1) { + Serial.println(F("OSC did not start")); + while (1); // Halt + } + Serial.print(F("\r\nSPP Client Started")); +} + +void loop() { + Usb.Task(); // The SPP data is actually not send until this is called, one could call SerialBT.send() directly as well + + if (SerialBT.connected) { + if (firstMessage) { + firstMessage = false; + SerialBT.println(F("Hello from Arduino SPP client")); // Send welcome message + } + while (Serial.available()) + SerialBT.write(Serial.read()); + while (SerialBT.available()) + Serial.write(SerialBT.read()); + } else + firstMessage = true; +} diff --git a/examples/Bluetooth/SPP/SPP.ino b/examples/Bluetooth/SPP/SPPServer/SPPServer.ino similarity index 80% rename from examples/Bluetooth/SPP/SPP.ino rename to examples/Bluetooth/SPP/SPPServer/SPPServer.ino index d8276b7b..9fcfa533 100644 --- a/examples/Bluetooth/SPP/SPP.ino +++ b/examples/Bluetooth/SPP/SPPServer/SPPServer.ino @@ -1,5 +1,5 @@ /* - Example sketch for the RFCOMM/SPP Bluetooth library - developed by Kristian Lauszus + Example sketch for the RFCOMM/SPP Server 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 */ @@ -15,6 +15,7 @@ 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 class in two ways */ SPP SerialBT(&Btd); // This will set the name to the defaults: "Arduino" and the pin to "0000" //SPP SerialBT(&Btd, "Lauszus's Arduino", "1234"); // You can also set the name and pin like so @@ -26,23 +27,23 @@ void setup() { while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection if (Usb.Init() == -1) { Serial.print(F("\r\nOSC did not start")); - while (1); //halt + while (1); // Halt } - Serial.print(F("\r\nSPP Bluetooth Library Started")); + Serial.print(F("\r\nSPP Server Started")); } + void loop() { Usb.Task(); // The SPP data is actually not send until this is called, one could call SerialBT.send() directly as well if (SerialBT.connected) { if (firstMessage) { firstMessage = false; - SerialBT.println(F("Hello from Arduino")); // Send welcome message + SerialBT.println(F("Hello from Arduino SPP server")); // Send welcome message } - if (Serial.available()) + while (Serial.available()) SerialBT.write(Serial.read()); - if (SerialBT.available()) + while (SerialBT.available()) Serial.write(SerialBT.read()); - } - else + } else firstMessage = true; } diff --git a/examples/Bluetooth/SPPMulti/SPPMulti.ino b/examples/Bluetooth/SPP/SPPServerMulti/SPPServerMulti.ino similarity index 100% rename from examples/Bluetooth/SPPMulti/SPPMulti.ino rename to examples/Bluetooth/SPP/SPPServerMulti/SPPServerMulti.ino