Merged SPPi class in from @magictaler - see: #79

I have made several improvements. Including making a new SPPBase class.
This commit is contained in:
Kristian Lauszus 2014-04-21 18:27:04 +02:00
parent d9dfa3cf35
commit ee90afde31
13 changed files with 1356 additions and 377 deletions

86
BTD.cpp
View file

@ -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<uint8_t > (classOfDevice[2], 0x80);
Notify(PSTR(" "), 0x80);
D_PrintHex<uint8_t > (classOfDevice[1], 0x80);
Notify(PSTR(" "), 0x80);
D_PrintHex<uint8_t > (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<uint8_t > (disc_bdaddr[j], 0x80);
Notify(PSTR(":"), 0x80);
}
D_PrintHex<uint8_t > (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) {

13
BTD.h
View file

@ -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.

View file

@ -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):

201
SPP.cpp Normal file → Executable file
View file

@ -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<uint8_t > (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<uint8_t > (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;
}

175
SPP.h Normal file → Executable file
View file

@ -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

185
SPPBase.cpp Normal file
View file

@ -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<uint8_t > (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<uint8_t > (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;
}

229
SPPBase.h Normal file
View file

@ -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

687
SPPi.cpp Executable file
View file

@ -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<uint8_t > (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<uint8_t > (l2capinbuf[13], 0x80);
Notify(PSTR(" "), 0x80);
D_PrintHex<uint8_t > (l2capinbuf[12], 0x80);
Notify(PSTR(" Data: "), 0x80);
D_PrintHex<uint8_t > (l2capinbuf[17], 0x80);
Notify(PSTR(" "), 0x80);
D_PrintHex<uint8_t > (l2capinbuf[16], 0x80);
Notify(PSTR(" "), 0x80);
D_PrintHex<uint8_t > (l2capinbuf[15], 0x80);
Notify(PSTR(" "), 0x80);
D_PrintHex<uint8_t > (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<uint8_t > (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<uint8_t > (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<uint8_t > (rfcommChannel >> 3, 0x80);
Notify(PSTR(" Direction: "), 0x80);
D_PrintHex<uint8_t > (rfcommDirection >> 2, 0x80);
Notify(PSTR(" CommandResponse: "), 0x80);
D_PrintHex<uint8_t > (rfcommCommandResponse >> 1, 0x80);
Notify(PSTR(" ChannelType: "), 0x80);
D_PrintHex<uint8_t > (rfcommChannelType, 0x80);
Notify(PSTR(" PF_BIT: "), 0x80);
D_PrintHex<uint8_t > (rfcommPfBit >> 4, 0x80);
#endif
if (rfcommChannelType == RFCOMM_DISC) {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nReceived Disconnect RFComm Command on channel: "), 0x80);
D_PrintHex<uint8_t > (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<uint8_t > (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<uint8_t > (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<uint8_t > (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<uint8_t > (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<uint8_t > (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<uint8_t > (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<uint8_t > (rfcommChannel >> 3, 0x80);
#endif
} else {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nUnknown Response, rfCommChannelType: "), 0x80);
D_PrintHex<uint8_t > (rfcommChannelType, 0x80);
#endif
}
}
} else {
#ifdef EXTRADEBUG
Notify(PSTR("\r\nUnsupported L2CAP Data - Channel ID: "), 0x80);
D_PrintHex<uint8_t > (l2capinbuf[7], 0x80);
Notify(PSTR(" "), 0x80);
D_PrintHex<uint8_t > (l2capinbuf[6], 0x80);
Notify(PSTR("\r\n"), 0x80);
for (uint8_t i = 0; i < BULK_MAXPKTSIZE; i++) {
D_PrintHex<uint8_t > (l2capinbuf[i], 0x80);
Notify(PSTR(" "), 0x80);
}
#endif
}
}else{
#ifdef EXTRADEBUG
Notify(PSTR("\r\nBad ACL handle: "), 0x80);
D_PrintHex<uint8_t > (l2capinbuf[0], 0x80);
Notify(PSTR(" "), 0x80);
D_PrintHex<uint8_t > (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<uint8_t > (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;
}

89
SPPi.h Executable file
View file

@ -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

2
UsbCore.h Normal file → Executable file
View file

@ -37,6 +37,8 @@ typedef MAX3421e<P9, P8> MAX3421E; // Teensy++ 1.0 and 2.0
typedef MAX3421e<P53, P54> MAX3421E; // Arduino Mega ADK
#elif defined(ARDUINO_AVR_BALANDUINO)
typedef MAX3421e<P20, P19> MAX3421E; // Balanduino
#elif defined(LUMINARDO)
typedef MAX3421e<P4, P18> MAX3421E; // Luminardo
#else
typedef MAX3421e<P10, P9> MAX3421E; // Official Arduinos (UNO, Duemilanove, Mega, 2560, Leonardo, Due etc.) or Teensy 2.0 and 3.0
#endif

View file

@ -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 <SPPi.h>
#include <usbhub.h>
// Satisfy IDE, which only needs to see the include statment in the ino.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#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;
}

View file

@ -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;
}