diff --git a/.travis.yml b/.travis.yml index bf8d9007..4311efbf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,10 +22,16 @@ before_install: arduino --install-boards arduino:mbed; fi - buildExamplePeripheralSketch() { arduino --verbose-build --verify --board $BOARD $PWD/examples/Peripheral/$1/$1.ino; } + - buildExampleCentralSketch() { arduino --verbose-build --verify --board $BOARD $PWD/examples/Central/$1/$1.ino; } install: - mkdir -p $HOME/Arduino/libraries - ln -s $PWD $HOME/Arduino/libraries/. script: + - buildExampleCentralSketch LedControl + - buildExampleCentralSketch PeripheralExplorer + - buildExampleCentralSketch Scan + - buildExampleCentralSketch ScanCallback + - buildExampleCentralSketch SensorTagButton - buildExamplePeripheralSketch BatteryMonitor - buildExamplePeripheralSketch ButtonLED - buildExamplePeripheralSketch CallbackLED diff --git a/README.md b/README.md index 5d8391f7..45e9f933 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Enables BLE connectivity on the Arduino MKR WiFi 1010, Arduino UNO WiFi Rev.2, Arduino Nano 33 IoT, and Arduino Nano 33 BLE. -This library currently supports creating a BLE peripheral. +This library supports creating a BLE peripheral and BLE central mode. For the Arduino MKR WiFi 1010, Arduino UNO WiFi Rev.2, and Arduino Nano 33 IoT boards, it requires the NINA module to be running [Arduino NINA-W102 firmware](https://github.com/arduino/nina-fw) v1.2.0 or later. diff --git a/examples/Central/LedControl/LedControl.ino b/examples/Central/LedControl/LedControl.ino new file mode 100644 index 00000000..8301a311 --- /dev/null +++ b/examples/Central/LedControl/LedControl.ino @@ -0,0 +1,128 @@ +/* + LED Control + + This example scans for BLE peripherals until one with the advertised service + "19b10000-e8f2-537e-4f6c-d104768a1214" UUID is found. Once discovered and connected, + it will remotely control the BLE Peripheral's LED, when the button is pressed or released. + + The circuit: + - Arduino MKR WiFi 1010, Arduino Uno WiFi Rev2 board, Arduino Nano 33 IoT, + Arduino Nano 33 BLE, or Arduino Nano 33 BLE Sense board. + - Button with pull-up resistor connected to pin 2. + + You can use it with another board that is compatible with this library and the + Peripherals -> LED example. + + This example code is in the public domain. +*/ + +#include + +// variables for button +const int buttonPin = 2; +int oldButtonState = LOW; + +void setup() { + Serial.begin(9600); + while (!Serial); + + // configure the button pin as input + pinMode(buttonPin, INPUT); + + // initialize the BLE hardware + BLE.begin(); + + Serial.println("BLE Central - LED control"); + + // start scanning for peripherals + BLE.scanForUuid("19b10000-e8f2-537e-4f6c-d104768a1214"); +} + +void loop() { + // check if a peripheral has been discovered + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // discovered a peripheral, print out address, local name, and advertised service + Serial.print("Found "); + Serial.print(peripheral.address()); + Serial.print(" '"); + Serial.print(peripheral.localName()); + Serial.print("' "); + Serial.print(peripheral.advertisedServiceUuid()); + Serial.println(); + + if (peripheral.localName() != "LED") { + return; + } + + // stop scanning + BLE.stopScan(); + + controlLed(peripheral); + + // peripheral disconnected, start scanning again + BLE.scanForUuid("19b10000-e8f2-537e-4f6c-d104768a1214"); + } +} + +void controlLed(BLEDevice peripheral) { + // connect to the peripheral + Serial.println("Connecting ..."); + + if (peripheral.connect()) { + Serial.println("Connected"); + } else { + Serial.println("Failed to connect!"); + return; + } + + // discover peripheral attributes + Serial.println("Discovering attributes ..."); + if (peripheral.discoverAttributes()) { + Serial.println("Attributes discovered"); + } else { + Serial.println("Attribute discovery failed!"); + peripheral.disconnect(); + return; + } + + // retrieve the LED characteristic + BLECharacteristic ledCharacteristic = peripheral.characteristic("19b10001-e8f2-537e-4f6c-d104768a1214"); + + if (!ledCharacteristic) { + Serial.println("Peripheral does not have LED characteristic!"); + peripheral.disconnect(); + return; + } else if (!ledCharacteristic.canWrite()) { + Serial.println("Peripheral does not have a writable LED characteristic!"); + peripheral.disconnect(); + return; + } + + while (peripheral.connected()) { + // while the peripheral is connected + + // read the button pin + int buttonState = digitalRead(buttonPin); + + if (oldButtonState != buttonState) { + // button changed + oldButtonState = buttonState; + + if (buttonState) { + Serial.println("button pressed"); + + // button is pressed, write 0x01 to turn the LED on + ledCharacteristic.writeValue((byte)0x01); + } else { + Serial.println("button released"); + + // button is released, write 0x00 to turn the LED off + ledCharacteristic.writeValue((byte)0x00); + } + } + } + + Serial.println("Peripheral disconnected"); +} diff --git a/examples/Central/PeripheralExplorer/PeripheralExplorer.ino b/examples/Central/PeripheralExplorer/PeripheralExplorer.ino new file mode 100644 index 00000000..b0360f57 --- /dev/null +++ b/examples/Central/PeripheralExplorer/PeripheralExplorer.ino @@ -0,0 +1,175 @@ +/* + Peripheral Explorer + + This example scans for BLE peripherals until one with a particular name ("LED") + is found. Then connects, and discovers + prints all the peripheral's attributes. + + The circuit: + - Arduino MKR WiFi 1010, Arduino Uno WiFi Rev2 board, Arduino Nano 33 IoT, + Arduino Nano 33 BLE, or Arduino Nano 33 BLE Sense board. + + You can use it with another board that is compatible with this library and the + Peripherals -> LED example. + + This example code is in the public domain. +*/ + +#include + +void setup() { + Serial.begin(9600); + while (!Serial); + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting BLE failed!"); + + while (1); + } + + Serial.println("BLE Central - Peripheral Explorer"); + + // start scanning for peripherals + BLE.scan(); +} + +void loop() { + // check if a peripheral has been discovered + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // discovered a peripheral, print out address, local name, and advertised service + Serial.print("Found "); + Serial.print(peripheral.address()); + Serial.print(" '"); + Serial.print(peripheral.localName()); + Serial.print("' "); + Serial.print(peripheral.advertisedServiceUuid()); + Serial.println(); + + // see if peripheral is a LED + if (peripheral.localName() == "LED") { + // stop scanning + BLE.stopScan(); + + explorerPeripheral(peripheral); + + // peripheral disconnected, we are done + while (1) { + // do nothing + } + } + } +} + +void explorerPeripheral(BLEDevice peripheral) { + // connect to the peripheral + Serial.println("Connecting ..."); + + if (peripheral.connect()) { + Serial.println("Connected"); + } else { + Serial.println("Failed to connect!"); + return; + } + + // discover peripheral attributes + Serial.println("Discovering attributes ..."); + if (peripheral.discoverAttributes()) { + Serial.println("Attributes discovered"); + } else { + Serial.println("Attribute discovery failed!"); + peripheral.disconnect(); + return; + } + + // read and print device name of peripheral + Serial.println(); + Serial.print("Device name: "); + Serial.println(peripheral.deviceName()); + Serial.print("Appearance: 0x"); + Serial.println(peripheral.appearance(), HEX); + Serial.println(); + + // loop the services of the peripheral and explore each + for (int i = 0; i < peripheral.serviceCount(); i++) { + BLEService service = peripheral.service(i); + + exploreService(service); + } + + Serial.println(); + + // we are done exploring, disconnect + Serial.println("Disconnecting ..."); + peripheral.disconnect(); + Serial.println("Disconnected"); +} + +void exploreService(BLEService service) { + // print the UUID of the service + Serial.print("Service "); + Serial.println(service.uuid()); + + // loop the characteristics of the service and explore each + for (int i = 0; i < service.characteristicCount(); i++) { + BLECharacteristic characteristic = service.characteristic(i); + + exploreCharacteristic(characteristic); + } +} + +void exploreCharacteristic(BLECharacteristic characteristic) { + // print the UUID and properies of the characteristic + Serial.print("\tCharacteristic "); + Serial.print(characteristic.uuid()); + Serial.print(", properties 0x"); + Serial.print(characteristic.properties(), HEX); + + // check if the characteristic is readable + if (characteristic.canRead()) { + // read the characteristic value + characteristic.read(); + + if (characteristic.valueLength() > 0) { + // print out the value of the characteristic + Serial.print(", value 0x"); + printData(characteristic.value(), characteristic.valueLength()); + } + } + Serial.println(); + + // loop the descriptors of the characteristic and explore each + for (int i = 0; i < characteristic.descriptorCount(); i++) { + BLEDescriptor descriptor = characteristic.descriptor(i); + + exploreDescriptor(descriptor); + } +} + +void exploreDescriptor(BLEDescriptor descriptor) { + // print the UUID of the descriptor + Serial.print("\t\tDescriptor "); + Serial.print(descriptor.uuid()); + + // read the descriptor value + descriptor.read(); + + // print out the value of the descriptor + Serial.print(", value 0x"); + printData(descriptor.value(), descriptor.valueLength()); + + Serial.println(); +} + +void printData(const unsigned char data[], int length) { + for (int i = 0; i < length; i++) { + unsigned char b = data[i]; + + if (b < 16) { + Serial.print("0"); + } + + Serial.print(b, HEX); + } +} diff --git a/examples/Central/Scan/Scan.ino b/examples/Central/Scan/Scan.ino new file mode 100644 index 00000000..784f3058 --- /dev/null +++ b/examples/Central/Scan/Scan.ino @@ -0,0 +1,68 @@ +/* + Scan + + This example scans for BLE peripherals and prints out their advertising details: + address, local name, adverised service UUID's. + + The circuit: + - Arduino MKR WiFi 1010, Arduino Uno WiFi Rev2 board, Arduino Nano 33 IoT, + Arduino Nano 33 BLE, or Arduino Nano 33 BLE Sense board. + + This example code is in the public domain. +*/ + +#include + +void setup() { + Serial.begin(9600); + while (!Serial); + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting BLE failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); +} + +void loop() { + // check if a peripheral has been discovered + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // discovered a peripheral + Serial.println("Discovered a peripheral"); + Serial.println("-----------------------"); + + // print address + Serial.print("Address: "); + Serial.println(peripheral.address()); + + // print the local name, if present + if (peripheral.hasLocalName()) { + Serial.print("Local Name: "); + Serial.println(peripheral.localName()); + } + + // print the advertised service UUIDs, if present + if (peripheral.hasAdvertisedServiceUuid()) { + Serial.print("Service UUIDs: "); + for (int i = 0; i < peripheral.advertisedServiceUuidCount(); i++) { + Serial.print(peripheral.advertisedServiceUuid(i)); + Serial.print(" "); + } + Serial.println(); + } + + // print the RSSI + Serial.print("RSSI: "); + Serial.println(peripheral.rssi()); + + Serial.println(); + } +} diff --git a/examples/Central/ScanCallback/ScanCallback.ino b/examples/Central/ScanCallback/ScanCallback.ino new file mode 100644 index 00000000..9583079c --- /dev/null +++ b/examples/Central/ScanCallback/ScanCallback.ino @@ -0,0 +1,73 @@ +/* + Scan Callback + + This example scans for BLE peripherals and prints out their advertising details: + address, local name, adverised service UUIDs. Unlike the Scan example, it uses + the callback style APIs and disables filtering so the peripheral discovery is + reported for every single advertisement it makes. + + The circuit: + - Arduino MKR WiFi 1010, Arduino Uno WiFi Rev2 board, Arduino Nano 33 IoT, + Arduino Nano 33 BLE, or Arduino Nano 33 BLE Sense board. + + This example code is in the public domain. +*/ + +#include + +void setup() { + Serial.begin(9600); + while (!Serial); + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting BLE failed!"); + + while (1); + } + + Serial.println("BLE Central scan callback"); + + // set the discovered event handle + BLE.setEventHandler(BLEDiscovered, bleCentralDiscoverHandler); + + // start scanning for peripherals with duplicates + BLE.scan(true); +} + +void loop() { + // poll the central for events + BLE.poll(); +} + +void bleCentralDiscoverHandler(BLEDevice peripheral) { + // discovered a peripheral + Serial.println("Discovered a peripheral"); + Serial.println("-----------------------"); + + // print address + Serial.print("Address: "); + Serial.println(peripheral.address()); + + // print the local name, if present + if (peripheral.hasLocalName()) { + Serial.print("Local Name: "); + Serial.println(peripheral.localName()); + } + + // print the advertised service UUIDs, if present + if (peripheral.hasAdvertisedServiceUuid()) { + Serial.print("Service UUIDs: "); + for (int i = 0; i < peripheral.advertisedServiceUuidCount(); i++) { + Serial.print(peripheral.advertisedServiceUuid(i)); + Serial.print(" "); + } + Serial.println(); + } + + // print the RSSI + Serial.print("RSSI: "); + Serial.println(peripheral.rssi()); + + Serial.println(); +} diff --git a/examples/Central/SensorTagButton/SensorTagButton.ino b/examples/Central/SensorTagButton/SensorTagButton.ino new file mode 100644 index 00000000..72dad90b --- /dev/null +++ b/examples/Central/SensorTagButton/SensorTagButton.ino @@ -0,0 +1,133 @@ +/* + SensorTag Button + + This example scans for BLE peripherals until a TI SensorTag is discovered. + It then connects to it, discovers the attributes of the 0xffe0 service, + subscribes to the Simple Key Characteristic (UUID 0xffe1). When a button is + pressed on the SensorTag a notification is received and the button state is + outputted to the Serial Monitor when one is pressed. + + The circuit: + - Arduino MKR WiFi 1010, Arduino Uno WiFi Rev2 board, Arduino Nano 33 IoT, + Arduino Nano 33 BLE, or Arduino Nano 33 BLE Sense board. + - TI SensorTag + + This example code is in the public domain. +*/ + +#include + +void setup() { + Serial.begin(9600); + while (!Serial); + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting BLE failed!"); + + while (1); + } + + Serial.println("BLE Central - SensorTag button"); + Serial.println("Make sure to turn on the device."); + + // start scanning for peripheral + BLE.scan(); +} + +void loop() { + // check if a peripheral has been discovered + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // discovered a peripheral, print out address, local name, and advertised service + Serial.print("Found "); + Serial.print(peripheral.address()); + Serial.print(" '"); + Serial.print(peripheral.localName()); + Serial.print("' "); + Serial.print(peripheral.advertisedServiceUuid()); + Serial.println(); + + // Check if the peripheral is a SensorTag, the local name will be: + // "CC2650 SensorTag" + if (peripheral.localName() == "CC2650 SensorTag") { + // stop scanning + BLE.stopScan(); + + monitorSensorTagButtons(peripheral); + + // peripheral disconnected, start scanning again + BLE.scan(); + } + } +} + +void monitorSensorTagButtons(BLEDevice peripheral) { + // connect to the peripheral + Serial.println("Connecting ..."); + if (peripheral.connect()) { + Serial.println("Connected"); + } else { + Serial.println("Failed to connect!"); + return; + } + + // discover peripheral attributes + Serial.println("Discovering service 0xffe0 ..."); + if (peripheral.discoverService("ffe0")) { + Serial.println("Service discovered"); + } else { + Serial.println("Attribute discovery failed."); + peripheral.disconnect(); + + while (1); + return; + } + + // retrieve the simple key characteristic + BLECharacteristic simpleKeyCharacteristic = peripheral.characteristic("ffe1"); + + // subscribe to the simple key characteristic + Serial.println("Subscribing to simple key characteristic ..."); + if (!simpleKeyCharacteristic) { + Serial.println("no simple key characteristic found!"); + peripheral.disconnect(); + return; + } else if (!simpleKeyCharacteristic.canSubscribe()) { + Serial.println("simple key characteristic is not subscribable!"); + peripheral.disconnect(); + return; + } else if (!simpleKeyCharacteristic.subscribe()) { + Serial.println("subscription failed!"); + peripheral.disconnect(); + return; + } else { + Serial.println("Subscribed"); + Serial.println("Press the right and left buttons on your SensorTag."); + } + + while (peripheral.connected()) { + // while the peripheral is connected + + // check if the value of the simple key characteristic has been updated + if (simpleKeyCharacteristic.valueUpdated()) { + // yes, get the value, characteristic is 1 byte so use byte value + byte value = 0; + + simpleKeyCharacteristic.readValue(value); + + if (value & 0x01) { + // first bit corresponds to the right button + Serial.println("Right button pressed"); + } + + if (value & 0x02) { + // second bit corresponds to the left button + Serial.println("Left button pressed"); + } + } + } + + Serial.println("SensorTag disconnected!"); +} diff --git a/keywords.txt b/keywords.txt index cd0149c1..464cac72 100644 --- a/keywords.txt +++ b/keywords.txt @@ -9,7 +9,7 @@ ArduinoBLE KEYWORD1 BLE KEYWORD1 -BLE KEYWORD1 +BLEDevice KEYWORD1 BLECharacteristic KEYWORD1 BLEDescriptor KEYWORD1 BLEService KEYWORD1 @@ -41,7 +41,22 @@ end KEYWORD2 connected KEYWORD2 disconnect KEYWORD2 address KEYWORD2 +hasLocalName KEYWORD2 +hasAdvertisedServiceUuid KEYWORD2 +advertisedServiceUuidCount KEYWORD2 +localName KEYWORD2 +advertisedServiceUuid KEYWORD2 rssi KEYWORD2 +connect KEYWORD2 +discoverAttributes KEYWORD2 +discoverService KEYWORD2 +deviceName KEYWORD2 +appearance KEYWORD2 +serviceCount KEYWORD2 +hasService KEYWORD2 +service KEYWORD2 +characteristicCount KEYWORD2 +characteristic KEYWORD2 setAdvertisedServiceUuid KEYWORD2 setManufacturerData KEYWORD2 @@ -51,11 +66,18 @@ setAppearance KEYWORD2 addService KEYWORD2 advertise KEYWORD2 stopAdvertise KEYWORD2 +scan KEYWORD2 +scanForName KEYWORD2 +scanForUuid KEYWORD2 +scanForAddress KEYWORD2 +stopScan KEYWORD2 central KEYWORD2 +available KEYWORD2 setEventHandler KEYWORD2 setAdvertisingInterval KEYWORD2 setConnectionInterval KEYWORD2 setConnectable KEYWORD2 +setTimeout KEYWORD2 debug KEYWORD2 noDebug KEYWORD2 @@ -63,12 +85,24 @@ properties KEYWORD2 valueSize KEYWORD2 value KEYWORD2 valueLength KEYWORD2 +readValue KEYWORD2 writeValue KEYWORD2 setValue KEYWORD2 broadcast KEYWORD2 written KEYWORD2 subscribed KEYWORD2 +valueUpdated KEYWORD2 addDescriptor KEYWORD2 +descriptorCount KEYWORD2 +hasDescriptor KEYWORD2 +descriptor KEYWORD2 +canRead KEYWORD2 +read KEYWORD2 +canWrite KEYWORD2 +canSubscribe KEYWORD2 +subscribe KEYWORD2 +canUnsubscribe KEYWORD2 +unsubscribe KEYWORD2 writeValueLE KEYWORD2 setValueLE KEYWORD2 valueLE KEYWORD2 @@ -83,6 +117,10 @@ addCharacteristic KEYWORD # Constants (LITERAL1) ####################################### +BLEConnected LITERAL1 +BLEDisconnected LITERAL1 +BLEDiscovered LITERAL1 + BLEBroadcast LITERAL1 BLERead LITERAL1 BLEWriteWithoutResponse LITERAL1 @@ -93,4 +131,5 @@ BLEIndicate LITERAL1 BLESubscribed LITERAL1 BLEUnsubscribed LITERAL1 BLEWritten LITERAL1 +BLEUpdated LITERAL1 diff --git a/library.properties b/library.properties index ff0b6718..f40fccd4 100644 --- a/library.properties +++ b/library.properties @@ -3,7 +3,7 @@ version=1.0.0 author=Arduino maintainer=Arduino sentence=Enables BLE connectivity on the Arduino MKR WiFi 1010, Arduino UNO WiFi Rev.2, Arduino Nano 33 IoT, and Arduino Nano 33 BLE. -paragraph=This library currently supports creating a BLE peripheral. +paragraph=This library supports creating a BLE peripheral and BLE central mode. category=Communication url=https://github.com/arduino-libraries/ArduinoBLE architectures=samd,megaavr,mbed diff --git a/src/BLECharacteristic.cpp b/src/BLECharacteristic.cpp index 7ff92430..6db7b72e 100644 --- a/src/BLECharacteristic.cpp +++ b/src/BLECharacteristic.cpp @@ -17,23 +17,36 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "BLEProperty.h" + #include "local/BLELocalCharacteristic.h" +#include "remote/BLERemoteCharacteristic.h" #include "BLECharacteristic.h" BLECharacteristic::BLECharacteristic() : - BLECharacteristic(NULL) + BLECharacteristic((BLELocalCharacteristic*)NULL) { } BLECharacteristic::BLECharacteristic(BLELocalCharacteristic* local) : - _local(local) + _local(local), + _remote(NULL) { if (_local) { _local->retain(); } } +BLECharacteristic::BLECharacteristic(BLERemoteCharacteristic* remote) : + _local(NULL), + _remote(remote) +{ + if (_remote) { + _remote->retain(); + } +} + BLECharacteristic::BLECharacteristic(const char* uuid, uint8_t properties, int valueSize, bool fixedLength) : BLECharacteristic(new BLELocalCharacteristic(uuid, properties, valueSize, fixedLength)) { @@ -44,11 +57,41 @@ BLECharacteristic::BLECharacteristic(const char* uuid, uint8_t properties, const { } +BLECharacteristic::BLECharacteristic(const BLECharacteristic& other) +{ + _local = other._local; + if (_local) { + _local->retain(); + } + + _remote = other._remote; + if (_remote) { + _remote->retain(); + } +} + BLECharacteristic::~BLECharacteristic() { if (_local && _local->release() <= 0) { delete _local; } + + if (_remote && _remote->release() <= 0) { + delete _remote; + } +} + +const char* BLECharacteristic::uuid() const +{ + if (_local) { + return _local->uuid(); + } + + if (_remote) { + return _remote->uuid(); + } + + return ""; } uint8_t BLECharacteristic::properties() const @@ -57,6 +100,10 @@ uint8_t BLECharacteristic::properties() const return _local->properties(); } + if (_remote) { + return _remote->properties(); + } + return 0; } @@ -66,6 +113,10 @@ int BLECharacteristic::valueSize() const return _local->valueSize(); } + if (_remote) { + return _remote->valueLength(); + } + return 0; } @@ -75,6 +126,10 @@ const uint8_t* BLECharacteristic::value() const return _local->value(); } + if (_remote) { + return _remote->value(); + } + return NULL; } @@ -84,6 +139,10 @@ int BLECharacteristic::valueLength() const return _local->valueLength(); } + if (_remote) { + return _remote->valueLength(); + } + return 0; } @@ -93,27 +152,150 @@ uint8_t BLECharacteristic::operator[] (int offset) const return (*_local)[offset]; } + if (_remote) { + return (*_remote)[offset]; + } + return 0; } +int BLECharacteristic::readValue(uint8_t value[], int length) +{ + int bytesRead = 0; + + if (_local) { + bytesRead = min(length, _local->valueLength()); + + memcpy(value, _local->value(), bytesRead); + } + + if (_remote) { + // trigger a read if the value hasn't been updated via + // indication or notification, and the characteristic is + // readable + if (!valueUpdated() && canRead()) { + if (!read()) { + // read failed + return 0; + } + } + + bytesRead = min(length, _remote->valueLength()); + + memcpy(value, _remote->value(), bytesRead); + } + + return bytesRead; +} + +int BLECharacteristic::readValue(void* value, int length) +{ + return readValue((uint8_t*)value, length); +} + +int BLECharacteristic::readValue(uint8_t& value) +{ + value = 0; + + return readValue((uint8_t*)&value, sizeof(value)); +} + +int BLECharacteristic::readValue(int8_t& value) +{ + value = 0; + + return readValue((uint8_t*)&value, sizeof(value)); +} + +int BLECharacteristic::readValue(uint16_t& value) +{ + value = 0; + + return readValue((uint8_t*)&value, sizeof(value)); +} + +int BLECharacteristic::readValue(int16_t& value) +{ + value = 0; + + return readValue((uint8_t*)&value, sizeof(value)); +} + +int BLECharacteristic::readValue(uint32_t& value) +{ + value = 0; + + return readValue((uint8_t*)&value, sizeof(value)); +} + +int BLECharacteristic::readValue(int32_t& value) +{ + value = 0; + + return readValue((uint8_t*)&value, sizeof(value)); +} + int BLECharacteristic::writeValue(const uint8_t value[], int length) { if (_local) { return _local->writeValue(value, length); } + if (_remote) { + return _remote->writeValue(value, length); + } + return 0; } +int BLECharacteristic::writeValue(const void* value, int length) +{ + return writeValue((const uint8_t*)value, length); +} + int BLECharacteristic::writeValue(const char* value) { if (_local) { return _local->writeValue(value); } + if (_remote) { + return _remote->writeValue(value); + } + return 0; } +int BLECharacteristic::writeValue(uint8_t value) +{ + return writeValue((uint8_t*)&value, sizeof(value)); +} + +int BLECharacteristic::writeValue(int8_t value) +{ + return writeValue((uint8_t*)&value, sizeof(value)); +} + +int BLECharacteristic::writeValue(uint16_t value) +{ + return writeValue((uint8_t*)&value, sizeof(value)); +} + +int BLECharacteristic::writeValue(int16_t value) +{ + return writeValue((uint8_t*)&value, sizeof(value)); +} + +int BLECharacteristic::writeValue(uint32_t value) +{ + return writeValue((uint8_t*)&value, sizeof(value)); +} + +int BLECharacteristic::writeValue(int32_t value) +{ + return writeValue((uint8_t*)&value, sizeof(value)); +} + int BLECharacteristic::broadcast() { if (_local) { @@ -141,6 +323,15 @@ bool BLECharacteristic::subscribed() return false; } +bool BLECharacteristic::valueUpdated() +{ + if (_remote) { + return _remote->valueUpdated(); + } + + return false; +} + void BLECharacteristic::addDescriptor(BLEDescriptor& descriptor) { if (_local) { @@ -150,7 +341,7 @@ void BLECharacteristic::addDescriptor(BLEDescriptor& descriptor) BLECharacteristic::operator bool() const { - return (_local != NULL); + return (_local != NULL) || (_remote != NULL); } BLELocalCharacteristic* BLECharacteristic::local() @@ -163,4 +354,139 @@ void BLECharacteristic::setEventHandler(int event, BLECharacteristicEventHandler if (_local) { _local->setEventHandler((BLECharacteristicEvent)event, eventHandler); } + + if (_remote) { + _remote->setEventHandler((BLECharacteristicEvent)event, eventHandler); + } +} + +int BLECharacteristic::descriptorCount() const +{ + if (_remote) { + return _remote->descriptorCount(); + } + + return 0; +} + +bool BLECharacteristic::hasDescriptor(const char* uuid) const +{ + return hasDescriptor(uuid, 0); +} + +bool BLECharacteristic::hasDescriptor(const char* uuid, int index) const +{ + if (_remote) { + int count = 0; + int numDescriptors = _remote->descriptorCount(); + + for (int i = 0; i < numDescriptors; i++) { + BLERemoteDescriptor* d = _remote->descriptor(i); + + if (strcmp(uuid, d->uuid()) == 0) { + if (count == index) { + return true; + } + + count++; + } + } + } + + return false; +} + +BLEDescriptor BLECharacteristic::descriptor(int index) const +{ + if (_remote) { + return BLEDescriptor(_remote->descriptor(index)); + } + + return BLEDescriptor(); +} + +BLEDescriptor BLECharacteristic::descriptor(const char * uuid) const +{ + return descriptor(uuid, 0); +} + +BLEDescriptor BLECharacteristic::descriptor(const char * uuid, int index) const +{ + if (_remote) { + int count = 0; + int numDescriptors = _remote->descriptorCount(); + + for (int i = 0; i < numDescriptors; i++) { + BLERemoteDescriptor* d = _remote->descriptor(i); + + if (strcmp(uuid, d->uuid()) == 0) { + if (count == index) { + return BLEDescriptor(d); + } + + count++; + } + } + } + + return BLEDescriptor(); +} + +bool BLECharacteristic::canRead() +{ + if (_remote) { + return (properties() & BLERead) != 0; + } + + return false; +} + +bool BLECharacteristic::read() +{ + if (_remote) { + return _remote->read(); + } + + return false; +} + +bool BLECharacteristic::canWrite() +{ + if (_remote) { + return (properties() & (BLEWrite | BLEWriteWithoutResponse)) != 0; + } + + return false; +} + +bool BLECharacteristic::canSubscribe() +{ + if (_remote) { + return (properties() & (BLENotify | BLEIndicate)) != 0; + } + + return false; +} + +bool BLECharacteristic::subscribe() +{ + if (_remote) { + return _remote->writeCccd((properties() & BLEIndicate) ? 0x0002 : 0x0001); + } + + return false; +} + +bool BLECharacteristic::canUnsubscribe() +{ + return canSubscribe(); +} + +bool BLECharacteristic::unsubscribe() +{ + if (_remote) { + return _remote->writeCccd(0x0000); + } + + return false; } diff --git a/src/BLECharacteristic.h b/src/BLECharacteristic.h index 9513c1d0..03e1b61e 100644 --- a/src/BLECharacteristic.h +++ b/src/BLECharacteristic.h @@ -29,6 +29,7 @@ enum BLECharacteristicEvent { BLEUnsubscribed = 1, //BLERead = 2, // defined in BLEProperties.h BLEWritten = 3, + BLEUpdated = BLEWritten, // alias BLECharacteristicEventLast }; @@ -39,14 +40,18 @@ class BLEDevice; typedef void (*BLECharacteristicEventHandler)(BLEDevice device, BLECharacteristic characteristic); class BLELocalCharacteristic; +class BLERemoteCharacteristic; class BLECharacteristic { public: BLECharacteristic(); BLECharacteristic(const char* uuid, uint8_t properties, int valueSize, bool fixedLength = false); BLECharacteristic(const char* uuid, uint8_t properties, const char* value); + BLECharacteristic(const BLECharacteristic& other); virtual ~BLECharacteristic(); + const char* uuid() const; + uint8_t properties() const; int valueSize() const; @@ -54,9 +59,26 @@ class BLECharacteristic { int valueLength() const; uint8_t operator[] (int offset) const; + int readValue(uint8_t value[], int length); + int readValue(void* value, int length); + int readValue(uint8_t& value); + int readValue(int8_t& value); + int readValue(uint16_t& value); + int readValue(int16_t& value); + int readValue(uint32_t& value); + int readValue(int32_t& value); + int writeValue(const uint8_t value[], int length); + int writeValue(const void* value, int length); int writeValue(const char* value); - + int writeValue(uint8_t value); + int writeValue(int8_t value); + int writeValue(uint16_t value); + int writeValue(int16_t value); + int writeValue(uint32_t value); + int writeValue(int32_t value); + + // deprecated, use writeValue(...) int setValue(const uint8_t value[], int length) { return writeValue(value, length); } int setValue(const char* value) { return writeValue(value); } @@ -64,6 +86,7 @@ class BLECharacteristic { bool written(); bool subscribed(); + bool valueUpdated(); void addDescriptor(BLEDescriptor& descriptor); @@ -71,6 +94,21 @@ class BLECharacteristic { void setEventHandler(int event, BLECharacteristicEventHandler eventHandler); + int descriptorCount() const; + bool hasDescriptor(const char* uuid) const; + bool hasDescriptor(const char* uuid, int index) const; + BLEDescriptor descriptor(int index) const; + BLEDescriptor descriptor(const char * uuid) const; + BLEDescriptor descriptor(const char * uuid, int index) const; + + bool canRead(); + bool read(); + bool canWrite(); + bool canSubscribe(); + bool subscribe(); + bool canUnsubscribe(); + bool unsubscribe(); + protected: friend class BLELocalCharacteristic; friend class BLELocalService; @@ -79,8 +117,16 @@ class BLECharacteristic { BLELocalCharacteristic* local(); +protected: + friend class BLEDevice; + friend class BLEService; + friend class BLERemoteCharacteristic; + + BLECharacteristic(BLERemoteCharacteristic* remote); + private: BLELocalCharacteristic* _local; + BLERemoteCharacteristic* _remote; }; #endif diff --git a/src/BLEDescriptor.cpp b/src/BLEDescriptor.cpp index 3f12fd0f..7a6736b0 100644 --- a/src/BLEDescriptor.cpp +++ b/src/BLEDescriptor.cpp @@ -20,22 +20,33 @@ #include #include "local/BLELocalDescriptor.h" +#include "remote/BLERemoteDescriptor.h" #include "BLEDescriptor.h" BLEDescriptor::BLEDescriptor() : - BLEDescriptor(NULL) + BLEDescriptor((BLELocalDescriptor*)NULL) { } BLEDescriptor::BLEDescriptor(BLELocalDescriptor* local) : - _local(local) + _local(local), + _remote(NULL) { if (_local) { _local->retain(); } } +BLEDescriptor::BLEDescriptor(BLERemoteDescriptor* remote) : + _local(NULL), + _remote(remote) +{ + if (_remote) { + _remote->retain(); + } +} + BLEDescriptor::BLEDescriptor(const char* uuid, const uint8_t value[], int valueSize) : BLEDescriptor(new BLELocalDescriptor(uuid, value, valueSize)) { @@ -46,11 +57,41 @@ BLEDescriptor::BLEDescriptor(const char* uuid, const char* value) : { } +BLEDescriptor::BLEDescriptor(const BLEDescriptor& other) +{ + _local = other._local; + if (_local) { + _local->retain(); + } + + _remote = other._remote; + if (_remote) { + _remote->retain(); + } +} + BLEDescriptor::~BLEDescriptor() { if (_local && _local->release() <= 0) { delete _local; } + + if (_remote && _remote->release() <= 0) { + delete _remote; + } +} + +const char* BLEDescriptor::uuid() const +{ + if (_local) { + return _local->uuid(); + } + + if (_remote) { + return _remote->uuid(); + } + + return ""; } int BLEDescriptor::valueSize() const @@ -59,6 +100,10 @@ int BLEDescriptor::valueSize() const return _local->valueSize(); } + if (_remote) { + return _remote->valueLength(); + } + return 0; } @@ -68,18 +113,116 @@ const uint8_t* BLEDescriptor::value() const return _local->value(); } + if (_remote) { + return _remote->value(); + } + return NULL; } +int BLEDescriptor::valueLength() const +{ + return valueSize(); +} + uint8_t BLEDescriptor::operator[] (int offset) const { if (_local) { return (*_local)[offset]; } + if (_remote) { + return (*_remote)[offset]; + } + return 0; } +int BLEDescriptor::readValue(uint8_t value[], int length) +{ + int bytesRead = 0; + + if (_local) { + bytesRead = min(length, _local->valueSize()); + + memcpy(value, _local->value(), bytesRead); + } + + if (_remote) { + if (!read()) { + // read failed + return 0; + } + + bytesRead = min(length, _remote->valueLength()); + + memcpy(value, _remote->value(), bytesRead); + } + + return bytesRead; +} + +int BLEDescriptor::readValue(void* value, int length) +{ + return readValue((uint8_t*)value, length); +} + +int BLEDescriptor::readValue(uint8_t& value) +{ + value = 0; + + return readValue((uint8_t*)&value, sizeof(value)); +} + +int BLEDescriptor::readValue(int8_t& value) +{ + value = 0; + + return readValue((uint8_t*)&value, sizeof(value)); +} + +int BLEDescriptor::readValue(uint16_t& value) +{ + value = 0; + + return readValue((uint8_t*)&value, sizeof(value)); +} + +int BLEDescriptor::readValue(int16_t& value) +{ + value = 0; + + return readValue((uint8_t*)&value, sizeof(value)); +} + +int BLEDescriptor::readValue(uint32_t& value) +{ + value = 0; + + return readValue((uint8_t*)&value, sizeof(value)); +} + +int BLEDescriptor::readValue(int32_t& value) +{ + value = 0; + + return readValue((uint8_t*)&value, sizeof(value)); +} + +BLEDescriptor::operator bool() const +{ + return (_local != NULL) || (_remote != NULL); +} + +bool BLEDescriptor::read() +{ + if (_remote) { + return _remote->read(); + } + + return false; +} + BLELocalDescriptor* BLEDescriptor::local() { return _local; diff --git a/src/BLEDescriptor.h b/src/BLEDescriptor.h index 01ae55b6..800da122 100644 --- a/src/BLEDescriptor.h +++ b/src/BLEDescriptor.h @@ -23,20 +23,36 @@ #include class BLELocalDescriptor; +class BLERemoteDescriptor; class BLEDescriptor { public: BLEDescriptor(); + BLEDescriptor(const BLEDescriptor& other); BLEDescriptor(const char* uuid, const uint8_t value[], int valueSize); BLEDescriptor(const char* uuid, const char* value); virtual ~BLEDescriptor(); + const char* uuid() const; + int valueSize() const; const uint8_t* value() const; + int valueLength() const; uint8_t operator[] (int offset) const; + int readValue(uint8_t value[], int length); + int readValue(void* value, int length); + int readValue(uint8_t& value); + int readValue(int8_t& value); + int readValue(uint16_t& value); + int readValue(int16_t& value); + int readValue(uint32_t& value); + int readValue(int32_t& value); + operator bool() const; + bool read(); + protected: friend class BLELocalCharacteristic; @@ -44,8 +60,14 @@ class BLEDescriptor { BLELocalDescriptor* local(); +protected: + friend class BLECharacteristic; + + BLEDescriptor(BLERemoteDescriptor* remote); + private: BLELocalDescriptor* _local; + BLERemoteDescriptor* _remote; }; #endif diff --git a/src/BLEDevice.cpp b/src/BLEDevice.cpp index 01118060..a74c1d9c 100644 --- a/src/BLEDevice.cpp +++ b/src/BLEDevice.cpp @@ -18,18 +18,26 @@ */ #include "utility/ATT.h" +#include "utility/BLEUuid.h" #include "utility/HCI.h" +#include "remote/BLERemoteDevice.h" + #include "BLEDevice.h" BLEDevice::BLEDevice() : - _handle(0xffff) + _advertisementTypeMask(0), + _eirDataLength(0), + _rssi(127) { memset(_address, 0x00, sizeof(_address)); } -BLEDevice::BLEDevice(uint16_t handle, uint8_t address[6]) : - _handle(handle) +BLEDevice::BLEDevice(uint8_t addressType, uint8_t address[6]) : + _addressType(addressType), + _advertisementTypeMask(0), + _eirDataLength(0), + _rssi(127) { memcpy(_address, address, sizeof(_address)); } @@ -56,16 +64,12 @@ bool BLEDevice::connected() const return false; } - return ATT.connected(_handle, _address); + return ATT.connected(_addressType, _address); } bool BLEDevice::disconnect() { - if (_handle != 0xffff) { - return HCI.disconnect(_handle); - } - - return false; + return ATT.disconnect(_addressType, _address); } String BLEDevice::address() const @@ -76,28 +80,433 @@ String BLEDevice::address() const return result; } +bool BLEDevice::hasLocalName() const +{ + return (localName().length() > 0); +} + +bool BLEDevice::hasAdvertisedServiceUuid() const +{ + return hasAdvertisedServiceUuid(0); +} + +bool BLEDevice::hasAdvertisedServiceUuid(int index) const +{ + return (advertisedServiceUuid(index).length() > 0); +} + +int BLEDevice::advertisedServiceUuidCount() const +{ + int advertisedServiceCount = 0; + + for (unsigned char i = 0; i < _eirDataLength;) { + int eirLength = _eirData[i++]; + int eirType = _eirData[i++]; + + if (eirType == 0x02 || eirType == 0x03 || eirType == 0x06 || eirType == 0x07) { + int uuidLength; + + if (eirType == 0x02 || eirType == 0x03) { + uuidLength = 2; + } else /*if (eirType == 0x06 || eirType == 0x07)*/ { + uuidLength = 16; + } + + for (int j = 0; j < (eirLength - 1); j += uuidLength) { + advertisedServiceCount++; + } + } + + i += (eirLength - 1); + } + + return advertisedServiceCount; +} + +String BLEDevice::localName() const +{ + String localName = ""; + + for (int i = 0; i < _eirDataLength;) { + int eirLength = _eirData[i++]; + int eirType = _eirData[i++]; + + if (eirType == 0x08 || eirType == 0x09) { + localName.reserve(eirLength - 1); + + for (int j = 0; j < (eirLength - 1); j++) { + localName += (char)_eirData[i + j]; + } + break; + } + + i += (eirLength - 1); + } + + return localName; +} + +String BLEDevice::advertisedServiceUuid() const +{ + return advertisedServiceUuid(0); +} + +String BLEDevice::advertisedServiceUuid(int index) const +{ + String serviceUuid; + int uuidIndex = 0; + + for (unsigned char i = 0; i < _eirDataLength;) { + int eirLength = _eirData[i++]; + int eirType = _eirData[i++]; + + if (eirType == 0x02 || eirType == 0x03 || eirType == 0x06 || eirType == 0x07) { + int uuidLength; + + if (eirType == 0x02 || eirType == 0x03) { + uuidLength = 2; + } else /*if (eirType == 0x06 || eirType == 0x07)*/ { + uuidLength = 16; + } + + for (int j = 0; j < (eirLength - 1); j += uuidLength) { + if (uuidIndex == index) { + serviceUuid = BLEUuid::uuidToString(&_eirData[i + j * uuidLength], uuidLength); + } + + uuidIndex++; + } + } + + i += (eirLength - 1); + } + + return serviceUuid; +} + int BLEDevice::rssi() { - if (_handle != 0xffff) { - return HCI.readRssi(_handle); + uint16_t handle = ATT.connectionHandle(_addressType, _address); + + if (handle != 0xffff) { + return HCI.readRssi(handle); } - return 127; + return _rssi; +} + +bool BLEDevice::connect() +{ + return ATT.connect(_addressType, _address); +} + +bool BLEDevice::discoverAttributes() +{ + return ATT.discoverAttributes(_addressType, _address, NULL); +} + +bool BLEDevice::discoverService(const char* serviceUuid) +{ + return ATT.discoverAttributes(_addressType, _address, serviceUuid); } BLEDevice::operator bool() const { uint8_t zeros[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,}; - return ((_handle != 0xffff) && memcmp(_address, zeros, sizeof(zeros)) != 0); + return (memcmp(_address, zeros, sizeof(zeros)) != 0); } bool BLEDevice::operator==(const BLEDevice& rhs) const { - return ((_handle == rhs._handle) && memcmp(_address, rhs._address, sizeof(_address)) == 0); + return ((_addressType == rhs._addressType) && memcmp(_address, rhs._address, sizeof(_address)) == 0); } bool BLEDevice::operator!=(const BLEDevice& rhs) const { - return ((_handle != rhs._handle) || memcmp(_address, rhs._address, sizeof(_address)) != 0); + return ((_addressType != rhs._addressType) || memcmp(_address, rhs._address, sizeof(_address)) != 0); +} + +String BLEDevice::deviceName() +{ + BLERemoteDevice* device = ATT.device(_addressType, _address); + + if (device) { + BLEService genericAccessService = service("1800"); + + if (genericAccessService) { + BLECharacteristic deviceNameCharacteristic = genericAccessService.characteristic("2a00"); + + if (deviceNameCharacteristic) { + deviceNameCharacteristic.read(); + + String result; + int valueLength = deviceNameCharacteristic.valueLength(); + const char* value = (const char*)deviceNameCharacteristic.value(); + + result.reserve(valueLength); + + for (int i = 0; i < valueLength; i++) { + result += value[i]; + } + + return result; + } + } + } + + return ""; } + +int BLEDevice::appearance() +{ + BLERemoteDevice* device = ATT.device(_addressType, _address); + + if (device) { + BLEService genericAccessService = service("1801"); + + if (genericAccessService) { + BLECharacteristic appearanceCharacteristic = genericAccessService.characteristic("2a01"); + + if (appearanceCharacteristic) { + appearanceCharacteristic.read(); + + uint16_t result = 0; + + memcpy (&result, appearanceCharacteristic.value(), min((int)sizeof(result), appearanceCharacteristic.valueLength())); + + return result; + } + } + } + + return 0; +} + +int BLEDevice::serviceCount() const +{ + BLERemoteDevice* device = ATT.device(_addressType, _address); + + if (device) { + return device->serviceCount(); + } + + return 0; +} + +bool BLEDevice::hasService(const char* uuid) const +{ + return hasService(uuid, 0); +} + +bool BLEDevice::hasService(const char* uuid, int index) const +{ + BLERemoteDevice* device = ATT.device(_addressType, _address); + + if (device) { + int count = 0; + int numServices = device->serviceCount(); + + for (int i = 0; i < numServices; i++) { + BLERemoteService* s = device->service(i); + + if (strcmp(uuid, s->uuid()) == 0) { + if (count == index) { + return true; + } + + count++; + } + } + } + + return false; +} + +BLEService BLEDevice::service(int index) const +{ + BLERemoteDevice* device = ATT.device(_addressType, _address); + + if (device) { + if (index < (int)device->serviceCount()) { + return BLEService(device->service(index)); + } + } + + return BLEService(); +} + +BLEService BLEDevice::service(const char * uuid) const +{ + return service(uuid, 0); +} + +BLEService BLEDevice::service(const char * uuid, int index) const +{ + BLERemoteDevice* device = ATT.device(_addressType, _address); + + if (device) { + int count = 0; + int numServices = device->serviceCount(); + + for (int i = 0; i < numServices; i++) { + BLERemoteService* s = device->service(i); + + if (strcmp(uuid, s->uuid()) == 0) { + if (count == index) { + return BLEService(s); + } + + count++; + } + } + } + + return BLEService(); +} + +int BLEDevice::characteristicCount() const +{ + BLERemoteDevice* device = ATT.device(_addressType, _address); + + if (device) { + int result = 0; + int numServices = device->serviceCount(); + + for (int i = 0; i < numServices; i++) { + result += device->service(i)->characteristicCount(); + } + + return result; + } + + return 0; +} + +bool BLEDevice::hasCharacteristic(const char* uuid) const +{ + return hasCharacteristic(uuid, 0); +} + +bool BLEDevice::hasCharacteristic(const char* uuid, int index) const +{ + BLERemoteDevice* device = ATT.device(_addressType, _address); + + if (device) { + int count = 0; + int numServices = device->serviceCount(); + + for (int i = 0; i < numServices; i++) { + BLERemoteService* s = device->service(i); + + int numCharacteristics = s->characteristicCount(); + + for (int j = 0; j < numCharacteristics; j++) { + BLERemoteCharacteristic* c = s->characteristic(j); + + + if (strcmp(c->uuid(), uuid) == 0) { + if (count == index) { + return true; + } + } + + count++; + } + } + } + + return false; +} + +BLECharacteristic BLEDevice::characteristic(int index) const +{ + BLERemoteDevice* device = ATT.device(_addressType, _address); + + if (device) { + int count = 0; + int numServices = device->serviceCount(); + + for (int i = 0; i < numServices; i++) { + BLERemoteService* s = device->service(i); + + int numCharacteristics = s->characteristicCount(); + + for (int j = 0; j < numCharacteristics; j++) { + if (count == index) { + BLERemoteCharacteristic* c = s->characteristic(j); + + return BLECharacteristic(c); + } + + count++; + } + } + } + + return BLECharacteristic(); +} + +BLECharacteristic BLEDevice::characteristic(const char * uuid) const +{ + return characteristic(uuid, 0); +} + +BLECharacteristic BLEDevice::characteristic(const char * uuid, int index) const +{ + BLERemoteDevice* device = ATT.device(_addressType, _address); + + if (device) { + int count = 0; + int numServices = device->serviceCount(); + + for (int i = 0; i < numServices; i++) { + BLERemoteService* s = device->service(i); + + int numCharacteristics = s->characteristicCount(); + + for (int j = 0; j < numCharacteristics; j++) { + BLERemoteCharacteristic* c = s->characteristic(j); + + if (strcmp(c->uuid(), uuid) == 0) { + if (count == index) { + + return BLECharacteristic(c); + } + + count++; + } + } + } + } + + return BLECharacteristic(); +} + +bool BLEDevice::hasAddress(uint8_t addressType, uint8_t address[6]) +{ + return (_addressType == addressType) && (memcmp(_address, address, sizeof(_address)) == 0); +} + +void BLEDevice::setAdvertisementData(uint8_t type, uint8_t eirDataLength, uint8_t eirData[], int8_t rssi) +{ + _advertisementTypeMask = (1 << type); + _eirDataLength = eirDataLength; + memcpy(_eirData, eirData, eirDataLength); + _rssi = rssi; +} + +void BLEDevice::setScanResponseData(uint8_t eirDataLength, uint8_t eirData[], int8_t rssi) +{ + _advertisementTypeMask |= (1 << 0x04); + memcpy(&_eirData[_eirDataLength], eirData, eirDataLength); + _eirDataLength += eirDataLength; + _rssi = rssi; +} + +bool BLEDevice::discovered() +{ + // expect, 0x03 or 0x04 flag to be set + return (_advertisementTypeMask & 0x18) != 0; +} + diff --git a/src/BLEDevice.h b/src/BLEDevice.h index 7675b195..cbe79c71 100644 --- a/src/BLEDevice.h +++ b/src/BLEDevice.h @@ -22,9 +22,12 @@ #include +#include "BLEService.h" + enum BLEDeviceEvent { BLEConnected = 0, BLEDisconnected = 1, + BLEDiscovered = 2, BLEDeviceLastEvent }; @@ -46,20 +49,65 @@ class BLEDevice { virtual String address() const; + bool hasLocalName() const; + + bool hasAdvertisedServiceUuid() const; + bool hasAdvertisedServiceUuid(int index) const; + int advertisedServiceUuidCount() const; + + String localName() const; + String advertisedServiceUuid() const; + String advertisedServiceUuid(int index) const; + virtual int rssi(); + bool connect(); + bool discoverAttributes(); + bool discoverService(const char* serviceUuid); + virtual operator bool() const; virtual bool operator==(const BLEDevice& rhs) const; virtual bool operator!=(const BLEDevice& rhs) const; + String deviceName(); + int appearance(); + + int serviceCount() const; + bool hasService(const char* uuid) const; + bool hasService(const char* uuid, int index) const; + BLEService service(int index) const; + BLEService service(const char * uuid) const; + BLEService service(const char * uuid, int index) const; + int characteristicCount() const; + bool hasCharacteristic(const char* uuid) const; + bool hasCharacteristic(const char* uuid, int index) const; + BLECharacteristic characteristic(int index) const; + BLECharacteristic characteristic(const char * uuid) const; + BLECharacteristic characteristic(const char * uuid, int index) const; + protected: friend class ATTClass; + friend class GAPClass; + + BLEDevice(uint8_t addressType, uint8_t address[6]); + +protected: + friend class GAPClass; + + bool hasAddress(uint8_t addressType, uint8_t address[6]); + + void setAdvertisementData(uint8_t type, uint8_t eirDataLength, uint8_t eirData[], int8_t rssi); + void setScanResponseData(uint8_t eirDataLength, uint8_t eirData[], int8_t rssi); - BLEDevice(uint16_t handle, uint8_t address[6]); + bool discovered(); private: - uint16_t _handle; - uint8_t _address[6]; + uint8_t _addressType; + uint8_t _address[6]; + uint8_t _advertisementTypeMask; + uint8_t _eirDataLength; + uint8_t _eirData[31 * 2]; + int8_t _rssi; }; #endif diff --git a/src/BLEService.cpp b/src/BLEService.cpp index 67d0d1af..7f80663a 100644 --- a/src/BLEService.cpp +++ b/src/BLEService.cpp @@ -18,6 +18,7 @@ */ #include "local/BLELocalService.h" +#include "remote/BLERemoteService.h" #include "BLEService.h" @@ -27,23 +28,50 @@ BLEService::BLEService() : } BLEService::BLEService(BLELocalService* local) : - _local(local) + _local(local), + _remote(NULL) { if (_local) { _local->retain(); } } +BLEService::BLEService(BLERemoteService* remote) : + _local(NULL), + _remote(remote) +{ + if (_remote) { + _remote->retain(); + } +} + BLEService::BLEService(const char* uuid) : BLEService(new BLELocalService(uuid)) { } +BLEService::BLEService(const BLEService& other) +{ + _local = other._local; + if (_local) { + _local->retain(); + } + + _remote = other._remote; + if (_remote) { + _remote->retain(); + } +} + BLEService::~BLEService() { if (_local && _local->release() <= 0) { delete _local; } + + if (_remote && _remote->release() <= 0) { + delete _remote; + } } const char* BLEService::uuid() const @@ -52,6 +80,10 @@ const char* BLEService::uuid() const return _local->uuid(); } + if (_remote) { + return _remote->uuid(); + } + return ""; } @@ -62,6 +94,83 @@ void BLEService::addCharacteristic(BLECharacteristic& characteristic) } } +BLEService::operator bool() const +{ + return (_local != NULL) || (_remote != NULL); +} + +int BLEService::characteristicCount() const +{ + if (_remote) { + return _remote->characteristicCount(); + } + + return 0; +} + +bool BLEService::hasCharacteristic(const char* uuid) const +{ + return hasCharacteristic(uuid, 0); +} + +bool BLEService::hasCharacteristic(const char* uuid, int index) const +{ + if (_remote) { + int count = 0; + int numCharacteristics = _remote->characteristicCount(); + + for (int i = 0; i < numCharacteristics; i++) { + BLERemoteCharacteristic* c = _remote->characteristic(i); + + if (strcmp(uuid, c->uuid()) == 0) { + if (count == index) { + return true; + } + + count++; + } + } + } + + return false; +} + +BLECharacteristic BLEService::characteristic(int index) const +{ + if (_remote) { + return BLECharacteristic(_remote->characteristic(index)); + } + + return BLECharacteristic(); +} + +BLECharacteristic BLEService::characteristic(const char * uuid) const +{ + return characteristic(uuid, 0); +} + +BLECharacteristic BLEService::characteristic(const char * uuid, int index) const +{ + if (_remote) { + int count = 0; + int numCharacteristics = _remote->characteristicCount(); + + for (int i = 0; i < numCharacteristics; i++) { + BLERemoteCharacteristic* c = _remote->characteristic(i); + + if (strcmp(uuid, c->uuid()) == 0) { + if (count == index) { + return BLECharacteristic(c); + } + + count++; + } + } + } + + return BLECharacteristic(); +} + BLELocalService* BLEService::local() { return _local; diff --git a/src/BLEService.h b/src/BLEService.h index ef8e4234..a8d8b010 100644 --- a/src/BLEService.h +++ b/src/BLEService.h @@ -23,11 +23,13 @@ #include "BLECharacteristic.h" class BLELocalService; +class BLERemoteService; class BLEService { public: BLEService(); BLEService(const char* uuid); + BLEService(const BLEService& other); virtual ~BLEService(); const char* uuid() const; @@ -36,6 +38,13 @@ class BLEService { operator bool() const; + int characteristicCount() const; + bool hasCharacteristic(const char* uuid) const; + bool hasCharacteristic(const char* uuid, int index) const; + BLECharacteristic characteristic(int index) const; + BLECharacteristic characteristic(const char * uuid) const; + BLECharacteristic characteristic(const char * uuid, int index) const; + protected: friend class GATTClass; @@ -45,8 +54,14 @@ class BLEService { void addCharacteristic(BLELocalCharacteristic* characteristic); +protected: + friend class BLEDevice; + + BLEService(BLERemoteService* remote); + private: BLELocalService* _local; + BLERemoteService* _remote; }; #endif diff --git a/src/BLEAttribute.cpp b/src/local/BLELocalAttribute.cpp similarity index 73% rename from src/BLEAttribute.cpp rename to src/local/BLELocalAttribute.cpp index efe4d49b..ce3010a0 100644 --- a/src/BLEAttribute.cpp +++ b/src/local/BLELocalAttribute.cpp @@ -17,46 +17,46 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "BLEAttribute.h" +#include "BLELocalAttribute.h" -BLEAttribute::BLEAttribute(const char* uuid) : +BLELocalAttribute::BLELocalAttribute(const char* uuid) : _uuid(uuid), _refCount(0) { } -BLEAttribute::~BLEAttribute() +BLELocalAttribute::~BLELocalAttribute() { } -const char* BLEAttribute::uuid() const +const char* BLELocalAttribute::uuid() const { return _uuid.str(); } -const uint8_t* BLEAttribute::uuidData() const +const uint8_t* BLELocalAttribute::uuidData() const { return _uuid.data(); } -uint8_t BLEAttribute::uuidLength() const +uint8_t BLELocalAttribute::uuidLength() const { return _uuid.length(); } -enum BLEAttributeType BLEAttribute::type() const +enum BLEAttributeType BLELocalAttribute::type() const { return BLETypeUnknown; } -int BLEAttribute::retain() +int BLELocalAttribute::retain() { _refCount++; return _refCount; } -int BLEAttribute::release() +int BLELocalAttribute::release() { _refCount--; diff --git a/src/BLEAttribute.h b/src/local/BLELocalAttribute.h similarity index 81% rename from src/BLEAttribute.h rename to src/local/BLELocalAttribute.h index 670c3a06..f68f256f 100644 --- a/src/BLEAttribute.h +++ b/src/local/BLELocalAttribute.h @@ -17,10 +17,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef _BLE_ATTRIBUTE_H_ -#define _BLE_ATTRIBUTE_H_ +#ifndef _BLE_LOCAL_ATTRIBUTE_H_ +#define _BLE_LOCAL_ATTRIBUTE_H_ -#include "BLEUuid.h" +#include "utility/BLEUuid.h" enum BLEAttributeType { BLETypeUnknown = 0x0000, @@ -30,23 +30,28 @@ enum BLEAttributeType { BLETypeDescriptor = 0x2900 }; -class BLEAttribute +class BLELocalAttribute { public: - BLEAttribute(const char* uuid); - virtual ~BLEAttribute(); + BLELocalAttribute(const char* uuid); + virtual ~BLELocalAttribute(); const char* uuid() const; - const uint8_t* uuidData() const; - uint8_t uuidLength() const; virtual enum BLEAttributeType type() const; int retain(); int release(); +protected: + friend class ATTClass; + friend class GATTClass; + + const uint8_t* uuidData() const; + uint8_t uuidLength() const; + private: - BLEUuid _uuid; + BLEUuid _uuid; int _refCount; }; diff --git a/src/local/BLELocalCharacteristic.cpp b/src/local/BLELocalCharacteristic.cpp index 18430b03..23b5f275 100644 --- a/src/local/BLELocalCharacteristic.cpp +++ b/src/local/BLELocalCharacteristic.cpp @@ -29,7 +29,7 @@ #include "BLELocalCharacteristic.h" BLELocalCharacteristic::BLELocalCharacteristic(const char* uuid, uint8_t properties, int valueSize, bool fixedLength) : - BLEAttribute(uuid), + BLELocalAttribute(uuid), _properties(properties), _valueSize(min(valueSize, 512)), _valueLength(0), @@ -156,7 +156,6 @@ bool BLELocalCharacteristic::written() return written; } - bool BLELocalCharacteristic::subscribed() { return (_cccdValue != 0x0000); diff --git a/src/local/BLELocalCharacteristic.h b/src/local/BLELocalCharacteristic.h index 7d9cb0a6..ee42390a 100644 --- a/src/local/BLELocalCharacteristic.h +++ b/src/local/BLELocalCharacteristic.h @@ -22,15 +22,16 @@ #include -#include "BLEAttribute.h" #include "BLECharacteristic.h" #include "BLEDescriptor.h" +#include "BLELocalAttribute.h" + #include "utility/BLELinkedList.h" class BLELocalDescriptor; -class BLELocalCharacteristic : public BLEAttribute { +class BLELocalCharacteristic : public BLELocalAttribute { public: BLELocalCharacteristic(const char* uuid, uint8_t properties, int valueSize, bool fixedLength = false); BLELocalCharacteristic(const char* uuid, uint8_t properties, const char* value); diff --git a/src/local/BLELocalDescriptor.cpp b/src/local/BLELocalDescriptor.cpp index 601a8afe..c466897f 100644 --- a/src/local/BLELocalDescriptor.cpp +++ b/src/local/BLELocalDescriptor.cpp @@ -22,7 +22,7 @@ #include "BLELocalDescriptor.h" BLELocalDescriptor::BLELocalDescriptor(const char* uuid, const uint8_t value[], int valueSize) : - BLEAttribute(uuid), + BLELocalAttribute(uuid), _value(value), _valueSize(min(valueSize, 512)), _handle(0x0000) diff --git a/src/local/BLELocalDescriptor.h b/src/local/BLELocalDescriptor.h index 915a1cd9..cf2fd672 100644 --- a/src/local/BLELocalDescriptor.h +++ b/src/local/BLELocalDescriptor.h @@ -22,9 +22,9 @@ #include -#include "BLEAttribute.h" +#include "BLELocalAttribute.h" -class BLELocalDescriptor : public BLEAttribute { +class BLELocalDescriptor : public BLELocalAttribute { public: BLELocalDescriptor(const char* uuid, const uint8_t value[], int valueSize); BLELocalDescriptor(const char* uuid, const char* value); diff --git a/src/local/BLELocalDevice.cpp b/src/local/BLELocalDevice.cpp index c3cc87dd..341f5aa7 100644 --- a/src/local/BLELocalDevice.cpp +++ b/src/local/BLELocalDevice.cpp @@ -126,6 +126,16 @@ void BLELocalDevice::end() #endif } +void BLELocalDevice::poll() +{ + HCI.poll(); +} + +void BLELocalDevice::poll(unsigned long timeout) +{ + HCI.poll(timeout); +} + bool BLELocalDevice::connected() const { HCI.poll(); @@ -205,6 +215,31 @@ void BLELocalDevice::stopAdvertise() GAP.stopAdvertise(); } +int BLELocalDevice::scan(bool withDuplicates) +{ + return GAP.scan(withDuplicates); +} + +int BLELocalDevice::scanForName(String name, bool withDuplicates) +{ + return GAP.scanForName(name, withDuplicates); +} + +int BLELocalDevice::scanForUuid(String uuid, bool withDuplicates) +{ + return GAP.scanForUuid(uuid, withDuplicates); +} + +int BLELocalDevice::scanForAddress(String address, bool withDuplicates) +{ + return GAP.scanForAddress(address, withDuplicates); +} + +void BLELocalDevice::stopScan() +{ + GAP.stopScan(); +} + BLEDevice BLELocalDevice::central() { HCI.poll(); @@ -212,9 +247,20 @@ BLEDevice BLELocalDevice::central() return ATT.central(); } +BLEDevice BLELocalDevice::available() +{ + HCI.poll(); + + return GAP.available(); +} + void BLELocalDevice::setEventHandler(BLEDeviceEvent event, BLEDeviceEventHandler eventHandler) { - ATT.setEventHandler(event, eventHandler); + if (event == BLEDiscovered) { + GAP.setEventHandler(event, eventHandler); + } else { + ATT.setEventHandler(event, eventHandler); + } } void BLELocalDevice::setAdvertisingInterval(uint16_t advertisingInterval) @@ -232,19 +278,9 @@ void BLELocalDevice::setConnectable(bool connectable) GAP.setConnectable(connectable); } -BLELocalDevice::operator bool() const -{ - return true; -} - -bool BLELocalDevice::operator==(const BLEDevice& rhs) const -{ - return (this == &rhs); -} - -bool BLELocalDevice::operator!=(const BLEDevice& rhs) const +void BLELocalDevice::setTimeout(unsigned long timeout) { - return (this != &rhs); + ATT.setTimeout(timeout); } void BLELocalDevice::debug(Stream& stream) diff --git a/src/local/BLELocalDevice.h b/src/local/BLELocalDevice.h index f7d8684e..6e970c71 100644 --- a/src/local/BLELocalDevice.h +++ b/src/local/BLELocalDevice.h @@ -23,7 +23,7 @@ #include "BLEDevice.h" #include "BLEService.h" -class BLELocalDevice : public BLEDevice { +class BLELocalDevice { public: BLELocalDevice(); virtual ~BLELocalDevice(); @@ -31,12 +31,15 @@ class BLELocalDevice : public BLEDevice { int begin(); void end(); - virtual bool connected() const; - virtual bool disconnect(); + void poll(); + void poll(unsigned long timeout); - virtual String address() const; + bool connected() const; + bool disconnect(); - virtual int rssi(); + String address() const; + + int rssi(); void setAdvertisedServiceUuid(const char* advertisedServiceUuid); void setAdvertisedService(const BLEService& service); @@ -51,7 +54,14 @@ class BLELocalDevice : public BLEDevice { int advertise(); void stopAdvertise(); + int scan(bool withDuplicates = false); + int scanForName(String name, bool withDuplicates = false); + int scanForUuid(String uuid, bool withDuplicates = false); + int scanForAddress(String address, bool withDuplicates = false); + void stopScan(); + BLEDevice central(); + BLEDevice available(); void setEventHandler(BLEDeviceEvent event, BLEDeviceEventHandler eventHandler); @@ -59,15 +69,12 @@ class BLELocalDevice : public BLEDevice { void setConnectionInterval(uint16_t minimumConnectionInterval, uint16_t maximumConnectionInterval); void setConnectable(bool connectable); - virtual operator bool() const; - virtual bool operator==(const BLEDevice& rhs) const; - virtual bool operator!=(const BLEDevice& rhs) const; + void setTimeout(unsigned long timeout); void debug(Stream& stream); void noDebug(); private: - }; extern BLELocalDevice BLE; diff --git a/src/local/BLELocalService.cpp b/src/local/BLELocalService.cpp index a856c73d..442c5422 100644 --- a/src/local/BLELocalService.cpp +++ b/src/local/BLELocalService.cpp @@ -22,7 +22,7 @@ #include "BLELocalService.h" BLELocalService::BLELocalService(const char* uuid) : - BLEAttribute(uuid), + BLELocalAttribute(uuid), _startHandle(0x0000), _endHandle(0x0000) { diff --git a/src/local/BLELocalService.h b/src/local/BLELocalService.h index 184a1e01..e0179a93 100644 --- a/src/local/BLELocalService.h +++ b/src/local/BLELocalService.h @@ -20,14 +20,15 @@ #ifndef _BLE_LOCAL_SERVICE_H_ #define _BLE_LOCAL_SERVICE_H_ -#include "BLEAttribute.h" #include "BLECharacteristic.h" +#include "BLELocalAttribute.h" + #include "utility/BLELinkedList.h" class BLELocalCharacteristic; -class BLELocalService : public BLEAttribute { +class BLELocalService : public BLELocalAttribute { public: BLELocalService(const char* uuid); virtual ~BLELocalService(); diff --git a/src/remote/BLERemoteAttribute.cpp b/src/remote/BLERemoteAttribute.cpp new file mode 100644 index 00000000..3018f161 --- /dev/null +++ b/src/remote/BLERemoteAttribute.cpp @@ -0,0 +1,51 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2019 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "utility/BLEUuid.h" + +#include "BLERemoteAttribute.h" + +BLERemoteAttribute::BLERemoteAttribute(const uint8_t uuid[], uint8_t uuidLen) : + _uuid(BLEUuid::uuidToString(uuid, uuidLen)), + _refCount(0) +{ +} + +BLERemoteAttribute::~BLERemoteAttribute() +{ +} + +const char* BLERemoteAttribute::uuid() const +{ + return _uuid.c_str(); +} + +int BLERemoteAttribute::retain() +{ + _refCount++; + + return _refCount; +} + +int BLERemoteAttribute::release() +{ + _refCount--; + + return _refCount; +} diff --git a/src/remote/BLERemoteAttribute.h b/src/remote/BLERemoteAttribute.h new file mode 100644 index 00000000..2d10e5ba --- /dev/null +++ b/src/remote/BLERemoteAttribute.h @@ -0,0 +1,41 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2019 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _BLE_REMOTE_ATTRIBUTE_H_ +#define _BLE_REMOTE_ATTRIBUTE_H_ + +#include + +class BLERemoteAttribute +{ +public: + BLERemoteAttribute(const uint8_t uuid[], uint8_t uuidLen); + virtual ~BLERemoteAttribute(); + + const char* uuid() const; + + int retain(); + int release(); + +private: + String _uuid; + int _refCount; +}; + +#endif diff --git a/src/remote/BLERemoteCharacteristic.cpp b/src/remote/BLERemoteCharacteristic.cpp new file mode 100644 index 00000000..bb3ffe54 --- /dev/null +++ b/src/remote/BLERemoteCharacteristic.cpp @@ -0,0 +1,253 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2019 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "BLEProperty.h" + +#include "utility/ATT.h" + +#include "BLERemoteCharacteristic.h" + +BLERemoteCharacteristic::BLERemoteCharacteristic(const uint8_t uuid[], uint8_t uuidLen, uint16_t connectionHandle, + uint16_t startHandle, uint8_t properties, uint16_t valueHandle) : + BLERemoteAttribute(uuid, uuidLen), + _connectionHandle(connectionHandle), + _startHandle(startHandle), + _properties(properties), + _valueHandle(valueHandle), + _value(NULL), + _valueLength(0), + _valueUpdated(false), + _valueUpdatedEventHandler(NULL) +{ +} + +BLERemoteCharacteristic::~BLERemoteCharacteristic() +{ + for (unsigned int i = 0; i < descriptorCount(); i++) { + BLERemoteDescriptor* d = descriptor(i); + + if (d->release() <= 0) { + delete d; + } + } + + _descriptors.clear(); + + if (_value) { + free(_value); + _value = NULL; + } +} + +uint16_t BLERemoteCharacteristic::startHandle() const +{ + return _startHandle; +} + +uint8_t BLERemoteCharacteristic::properties() const +{ + return _properties; +} + +const uint8_t* BLERemoteCharacteristic::value() const +{ + return _value; +} + +int BLERemoteCharacteristic::valueLength() const +{ + return _valueLength; +} + +uint8_t BLERemoteCharacteristic::operator[] (int offset) const +{ + if (_value) { + return _value[offset]; + } + + return 0; +} + +int BLERemoteCharacteristic::writeValue(const uint8_t value[], int length) +{ + if (!ATT.connected(_connectionHandle)) { + return false; + } + + uint16_t maxLength = ATT.mtu(_connectionHandle) - 3; + + if (length > (int)maxLength) { + // cap to MTU max length + length = maxLength; + } + + _value = (uint8_t*)realloc(_value, length); + if (_value == NULL) { + // realloc failed + return 0; + } + + if (_properties & BLEWrite) { + uint8_t resp[4]; + int respLength = ATT.writeReq(_connectionHandle, _valueHandle, value, length, resp); + + if (!respLength) { + return 0; + } + + if (resp[0] == 0x01) { + // error + return 0; + } + + memcpy(_value, value, length); + _valueLength = length; + + return 1; + } else if (_properties & BLEWriteWithoutResponse) { + ATT.writeCmd(_connectionHandle, _valueHandle, value, length); + + memcpy(_value, value, length); + _valueLength = length; + + return 1; + } + + return 0; +} + +int BLERemoteCharacteristic::writeValue(const char* value) +{ + return writeValue((uint8_t*)value, strlen(value)); +} + +bool BLERemoteCharacteristic::valueUpdated() +{ + ATT.connected(_connectionHandle); // to force a poll + + bool result = _valueUpdated; + + _valueUpdated = false; + + return result; +} + +bool BLERemoteCharacteristic::read() +{ + if (!ATT.connected(_connectionHandle)) { + return false; + } + + uint8_t resp[256]; + + int respLength = ATT.readReq(_connectionHandle, _valueHandle, resp); + + if (!respLength) { + _valueLength = 0; + return false; + } + + if (resp[0] == 0x01) { + // error + _valueLength = 0; + return false; + } + + _valueLength = respLength - 1; + _value = (uint8_t*)realloc(_value, _valueLength); + + if (_value == NULL) { + _valueLength = 0; + return false; + } + + _valueUpdated = true; + memcpy(_value, &resp[1], _valueLength); + + return true; +} + +bool BLERemoteCharacteristic::writeCccd(uint16_t value) +{ + int numDescriptors = descriptorCount(); + + for (int i = 0; i < numDescriptors; i++) { + BLERemoteDescriptor* d = descriptor(i); + + if (strcmp(d->uuid(), "2902") == 0) { + return d->writeValue((uint8_t*)&value, sizeof(value)); + } + } + + if (_properties & (BLENotify | BLEIndicate)) { + // no CCCD descriptor found, fallback to _valueHandle + 1 + BLERemoteDescriptor cccd(NULL, 0, _connectionHandle, _valueHandle + 1); + + return cccd.writeValue((uint8_t*)&value, sizeof(value)); + } + + return false; +} + +uint16_t BLERemoteCharacteristic::valueHandle() const +{ + return _valueHandle; +} + +unsigned int BLERemoteCharacteristic::descriptorCount() const +{ + return _descriptors.size(); +} + +BLERemoteDescriptor* BLERemoteCharacteristic::descriptor(unsigned int index) const +{ + return _descriptors.get(index); +} + +void BLERemoteCharacteristic::setEventHandler(BLECharacteristicEvent event, BLECharacteristicEventHandler eventHandler) +{ + if (event == BLEUpdated) { + _valueUpdatedEventHandler = eventHandler; + } +} + +void BLERemoteCharacteristic::addDescriptor(BLERemoteDescriptor* descriptor) +{ + descriptor->retain(); + + _descriptors.add(descriptor); +} + +void BLERemoteCharacteristic::writeValue(BLEDevice device, const uint8_t value[], int length) +{ + _valueLength = length; + _value = (uint8_t*)realloc(_value, _valueLength); + + if (_value == NULL) { + _valueLength = 0; + return; + } + + _valueUpdated = true; + memcpy(_value, value, _valueLength); + + if (_valueUpdatedEventHandler) { + _valueUpdatedEventHandler(device, BLECharacteristic(this)); + } +} diff --git a/src/remote/BLERemoteCharacteristic.h b/src/remote/BLERemoteCharacteristic.h new file mode 100644 index 00000000..277f2575 --- /dev/null +++ b/src/remote/BLERemoteCharacteristic.h @@ -0,0 +1,80 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2019 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _BLE_REMOTE_CHARACTERISTIC_H_ +#define _BLE_REMOTE_CHARACTERISTIC_H_ + +#include "BLECharacteristic.h" + +#include "BLERemoteAttribute.h" +#include "BLERemoteDescriptor.h" + +#include "utility/BLELinkedList.h" + +class BLERemoteCharacteristic : public BLERemoteAttribute { +public: + BLERemoteCharacteristic(const uint8_t uuid[], uint8_t uuidLen, uint16_t connectionHandle, uint16_t startHandle, uint8_t properties, uint16_t valueHandle); + virtual ~BLERemoteCharacteristic(); + + uint8_t properties() const; + + const uint8_t* value() const; + int valueLength() const; + uint8_t operator[] (int offset) const; + + int writeValue(const uint8_t value[], int length); + int writeValue(const char* value); + + bool valueUpdated(); + + bool read(); + bool writeCccd(uint16_t value); + + unsigned int descriptorCount() const; + BLERemoteDescriptor* descriptor(unsigned int index) const; + + void setEventHandler(BLECharacteristicEvent event, BLECharacteristicEventHandler eventHandler); + +protected: + friend class ATTClass; + + uint16_t startHandle() const; + uint16_t valueHandle() const; + + void addDescriptor(BLERemoteDescriptor* descriptor); + + void writeValue(BLEDevice device, const uint8_t value[], int length); + +private: + uint16_t _connectionHandle; + uint16_t _startHandle; + uint8_t _properties; + uint16_t _valueHandle; + + uint8_t* _value; + int _valueLength; + + bool _valueUpdated; + + BLELinkedList _descriptors; + + BLECharacteristicEventHandler _valueUpdatedEventHandler; +}; + +#endif diff --git a/src/remote/BLERemoteDescriptor.cpp b/src/remote/BLERemoteDescriptor.cpp new file mode 100644 index 00000000..bf68675b --- /dev/null +++ b/src/remote/BLERemoteDescriptor.cpp @@ -0,0 +1,134 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2019 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "utility/ATT.h" + +#include "BLERemoteDescriptor.h" + +BLERemoteDescriptor::BLERemoteDescriptor(const uint8_t uuid[], uint8_t uuidLen, uint16_t connectionHandle, uint16_t handle) : + BLERemoteAttribute(uuid, uuidLen), + _connectionHandle(connectionHandle), + _handle(handle), + _value(NULL), + _valueLength(0) +{ +} + +BLERemoteDescriptor::~BLERemoteDescriptor() +{ + if (_value) { + free(_value); + _value = NULL; + } +} + +const uint8_t* BLERemoteDescriptor::value() const +{ + return _value; +} + +int BLERemoteDescriptor::valueLength() const +{ + return _valueLength; +} + +uint8_t BLERemoteDescriptor::operator[] (int offset) const +{ + if (_value) { + return _value[offset]; + } + + return 0; +} + +int BLERemoteDescriptor::writeValue(const uint8_t value[], int length) +{ + if (!ATT.connected(_connectionHandle)) { + return false; + } + + uint16_t maxLength = ATT.mtu(_connectionHandle) - 3; + + if (length > (int)maxLength) { + // cap to MTU max length + length = maxLength; + } + + _value = (uint8_t*)realloc(_value, length); + if (_value == NULL) { + // realloc failed + return 0; + } + + uint8_t resp[4]; + int respLength = ATT.writeReq(_connectionHandle, _handle, value, length, resp); + + if (!respLength) { + return 0; + } + + if (resp[0] == 0x01) { + // error + return 0; + } + + memcpy(_value, value, length); + _valueLength = length; + + return 1; +} + +bool BLERemoteDescriptor::read() +{ + if (!ATT.connected(_connectionHandle)) { + return false; + } + + uint8_t resp[256]; + + int respLength = ATT.readReq(_connectionHandle, _handle, resp); + + if (!respLength) { + _valueLength = 0; + return false; + } + + if (resp[0] == 0x01) { + // error + _valueLength = 0; + return false; + } + + _valueLength = respLength - 1; + _value = (uint8_t*)realloc(_value, _valueLength); + + if (_value == NULL) { + _valueLength = 0; + return false; + } + + memcpy(_value, &resp[1], _valueLength); + + return true; +} + +uint16_t BLERemoteDescriptor::handle() const +{ + return _handle; +} diff --git a/src/remote/BLERemoteDescriptor.h b/src/remote/BLERemoteDescriptor.h new file mode 100644 index 00000000..68b428e9 --- /dev/null +++ b/src/remote/BLERemoteDescriptor.h @@ -0,0 +1,50 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2019 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _BLE_REMOTE_DESCRIPTOR_H_ +#define _BLE_REMOTE_DESCRIPTOR_H_ + +#include "BLERemoteAttribute.h" + +class BLERemoteDescriptor : public BLERemoteAttribute { +public: + BLERemoteDescriptor(const uint8_t uuid[], uint8_t uuidLen, uint16_t connectionHandle, uint16_t handle); + virtual ~BLERemoteDescriptor(); + + const uint8_t* value() const; + int valueLength() const; + uint8_t operator[] (int offset) const; + + int writeValue(const uint8_t value[], int length); + + bool read(); + +protected: + friend class ATTClass; + uint16_t handle() const; + +private: + uint16_t _connectionHandle; + uint16_t _handle; + + uint8_t* _value; + int _valueLength; +}; + +#endif diff --git a/src/remote/BLERemoteDevice.cpp b/src/remote/BLERemoteDevice.cpp new file mode 100644 index 00000000..5a49f26f --- /dev/null +++ b/src/remote/BLERemoteDevice.cpp @@ -0,0 +1,59 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2019 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "BLERemoteDevice.h" + +BLERemoteDevice::BLERemoteDevice() +{ +} + +BLERemoteDevice::~BLERemoteDevice() +{ + clearServices(); +} + +void BLERemoteDevice::addService(BLERemoteService* service) +{ + service->retain(); + + _services.add(service); +} + +unsigned int BLERemoteDevice::serviceCount() const +{ + return _services.size(); +} + +BLERemoteService* BLERemoteDevice::service(unsigned int index) const +{ + return _services.get(index); +} + +void BLERemoteDevice::clearServices() +{ + for (unsigned int i = 0; i < serviceCount(); i++) { + BLERemoteService* s = service(i); + + if (s->release() <= 0) { + delete s; + } + } + + _services.clear(); +} diff --git a/src/remote/BLERemoteDevice.h b/src/remote/BLERemoteDevice.h new file mode 100644 index 00000000..146a241b --- /dev/null +++ b/src/remote/BLERemoteDevice.h @@ -0,0 +1,43 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2019 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _BLE_REMOTE_DEVICE_H_ +#define _BLE_REMOTE_DEVICE_H_ + +#include "utility/BLELinkedList.h" + +#include "BLERemoteService.h" + +class BLERemoteDevice /*: public BLEDevice*/ { +public: + BLERemoteDevice(); + virtual ~BLERemoteDevice(); + + void addService(BLERemoteService* service); + + unsigned int serviceCount() const; + BLERemoteService* service(unsigned int index) const; + + void clearServices(); + +private: + BLELinkedList _services; +}; + +#endif diff --git a/src/remote/BLERemoteService.cpp b/src/remote/BLERemoteService.cpp new file mode 100644 index 00000000..fd5c0ba6 --- /dev/null +++ b/src/remote/BLERemoteService.cpp @@ -0,0 +1,67 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2019 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "BLERemoteService.h" + +BLERemoteService::BLERemoteService(const uint8_t uuid[], uint8_t uuidLen, uint16_t startHandle, uint16_t endHandle) : + BLERemoteAttribute(uuid, uuidLen), + _startHandle(startHandle), + _endHandle(endHandle) +{ +} + +BLERemoteService::~BLERemoteService() +{ + for (unsigned int i = 0; i < characteristicCount(); i++) { + BLERemoteCharacteristic* c = characteristic(i); + + if (c->release() <= 0) { + delete c; + } + } + + _characteristics.clear(); +} + +uint16_t BLERemoteService::startHandle() const +{ + return _startHandle; +} + +uint16_t BLERemoteService::endHandle() const +{ + return _endHandle; +} + +unsigned int BLERemoteService::characteristicCount() const +{ + return _characteristics.size(); +} + +BLERemoteCharacteristic* BLERemoteService::characteristic(unsigned int index) const +{ + return _characteristics.get(index); +} + +void BLERemoteService::addCharacteristic(BLERemoteCharacteristic* characteristic) +{ + characteristic-> retain(); + + _characteristics.add(characteristic); +} diff --git a/src/remote/BLERemoteService.h b/src/remote/BLERemoteService.h new file mode 100644 index 00000000..c7987ee1 --- /dev/null +++ b/src/remote/BLERemoteService.h @@ -0,0 +1,53 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2019 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _BLE_REMOTE_SERVICE_H_ +#define _BLE_REMOTE_SERVICE_H_ + +#include "BLERemoteAttribute.h" +#include "BLERemoteCharacteristic.h" + +#include "utility/BLELinkedList.h" + +class BLERemoteService : public BLERemoteAttribute { +public: + BLERemoteService(const uint8_t uuid[], uint8_t uuidLen, uint16_t startHandle, uint16_t endHandle); + virtual ~BLERemoteService(); + + unsigned int characteristicCount() const; + BLERemoteCharacteristic* characteristic(unsigned int index) const; + +protected: + friend class ATTClass; + + uint16_t startHandle() const; + uint16_t endHandle() const; + + void addCharacteristic(BLERemoteCharacteristic* characteristic); + +private: + uint16_t _startHandle; + uint16_t _endHandle; + + String _uuid; + + BLELinkedList _characteristics; +}; + +#endif diff --git a/src/utility/ATT.cpp b/src/utility/ATT.cpp index bddd9c69..d46b2e2f 100644 --- a/src/utility/ATT.cpp +++ b/src/utility/ATT.cpp @@ -22,10 +22,14 @@ #include "HCI.h" #include "GATT.h" +#include "local/BLELocalAttribute.h" #include "local/BLELocalCharacteristic.h" #include "local/BLELocalDescriptor.h" #include "local/BLELocalService.h" +#include "remote/BLERemoteDevice.h" +#include "remote/BLERemoteService.h" + #include "BLEProperty.h" #include "ATT.h" @@ -79,13 +83,20 @@ ATTClass::ATTClass() : _maxMtu(23), - _connectionHandle(0xffff), - _mtu(23), + _timeout(5000), _longWriteHandle(0x0000), _longWriteValue(NULL), _longWriteValueLength(0) { - memset(_peerAddress, 0x00, sizeof(_peerAddress)); + for (int i = 0; i < ATT_MAX_PEERS; i++) { + _peers[i].connectionHandle = 0xffff; + _peers[i].role = 0x00; + _peers[i].addressType = 0x00; + memset(_peers[i].address, 0x00, sizeof(_peers[i].address)); + _peers[i].mtu = 23; + _peers[i].device = NULL; + } + memset(_eventHandlers, 0x00, sizeof(_eventHandlers)); } @@ -96,25 +107,155 @@ ATTClass::~ATTClass() } } +bool ATTClass::connect(uint8_t peerBdaddrType, uint8_t peerBdaddr[6]) +{ + if (HCI.leCreateConn(0x0060, 0x0030, 0x00, peerBdaddrType, peerBdaddr, 0x00, + 0x0006, 0x000c, 0x0000, 0x00c8, 0x0004, 0x0006) != 0) { + return false; + } + + bool isConnected = false; + + for (unsigned long start = millis(); (millis() - start) < _timeout;) { + HCI.poll(); + + isConnected = connected(peerBdaddrType, peerBdaddr); + + if (isConnected) { + break; + } + } + + if (!isConnected) { + HCI.leCancelConn(); + } + + return isConnected; +} + +bool ATTClass::disconnect(uint8_t peerBdaddrType, uint8_t peerBdaddr[6]) +{ + uint16_t connHandle = connectionHandle(peerBdaddrType, peerBdaddr); + if (connHandle == 0xffff) { + return false; + } + + HCI.disconnect(connHandle); + + for (unsigned long start = millis(); (millis() - start) < _timeout;) { + HCI.poll(); + + if (!connected(connHandle)) { + return true; + } + } + + return false; +} + +bool ATTClass::discoverAttributes(uint8_t peerBdaddrType, uint8_t peerBdaddr[6], const char* serviceUuidFilter) +{ + uint16_t connHandle = connectionHandle(peerBdaddrType, peerBdaddr); + if (connHandle == 0xffff) { + return false; + } + + // send MTU request + if (!exchangeMtu(connHandle)) { + return false; + } + + // find the device entry for the peeer + BLERemoteDevice* device = NULL; + + for (int i = 0; i < ATT_MAX_PEERS; i++) { + if (_peers[i].connectionHandle == connHandle) { + if (_peers[i].device == NULL) { + _peers[i].device = new BLERemoteDevice(); + } + + device = _peers[i].device; + + break; + } + } + + if (device == NULL) { + return false; + } + + if (serviceUuidFilter == NULL) { + // clear existing services + device->clearServices(); + } else { + int serviceCount = device->serviceCount(); + + for (int i = 0; i < serviceCount; i++) { + BLERemoteService* service = device->service(i); + + if (strcmp(service->uuid(), serviceUuidFilter) == 0) { + // found an existing service with same UUID + return true; + } + } + } + + // discover services + if (!discoverServices(connHandle, device, serviceUuidFilter)) { + return false; + } + + // discover characteristics + if (!discoverCharacteristics(connHandle, device)) { + return false; + } + + // discover descriptors + if (!discoverDescriptors(connHandle, device)) { + return false; + } + + return true; +} + void ATTClass::setMaxMtu(uint16_t maxMtu) { _maxMtu = maxMtu; } -void ATTClass::addConnection(uint16_t handle, uint8_t role, uint8_t /*peerBdaddrType*/, +void ATTClass::setTimeout(unsigned long timeout) +{ + _timeout = timeout; +} + +void ATTClass::addConnection(uint16_t handle, uint8_t role, uint8_t peerBdaddrType, uint8_t peerBdaddr[6], uint16_t /*interval*/, uint16_t /*latency*/, uint16_t /*supervisionTimeout*/, uint8_t /*masterClockAccuracy*/) { - if (role == 1) { - _connectionHandle = handle; - _mtu = 23; - memcpy(_peerAddress, peerBdaddr, sizeof(_peerAddress)); + int peerIndex = -1; - if (_eventHandlers[BLEConnected]) { - _eventHandlers[BLEConnected](BLEDevice(_connectionHandle, _peerAddress)); + for (int i = 0; i < ATT_MAX_PEERS; i++) { + if (_peers[i].connectionHandle == 0xffff) { + peerIndex = i; + break; } } + + if (peerIndex == -1) { + // bail, no space + return; + } + + _peers[peerIndex].connectionHandle = handle; + _peers[peerIndex].role = role; + _peers[peerIndex].mtu = 23; + _peers[peerIndex].addressType = peerBdaddrType; + memcpy(_peers[peerIndex].address, peerBdaddr, sizeof(_peers[peerIndex].address)); + + if (_eventHandlers[BLEConnected]) { + _eventHandlers[BLEConnected](BLEDevice(peerBdaddrType, peerBdaddr)); + } } void ATTClass::handleData(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) @@ -124,43 +265,78 @@ void ATTClass::handleData(uint16_t connectionHandle, uint8_t dlen, uint8_t data[ dlen--; data++; + uint16_t mtu = this->mtu(connectionHandle); + switch (opcode) { + case ATT_OP_ERROR: + error(connectionHandle, dlen, data); + break; + case ATT_OP_MTU_REQ: mtuReq(connectionHandle, dlen, data); break; + case ATT_OP_MTU_RESP: + mtuResp(connectionHandle, dlen, data); + break; + case ATT_OP_FIND_INFO_REQ: - findInfoReq(connectionHandle, dlen, data); + findInfoReq(connectionHandle, mtu, dlen, data); + break; + + case ATT_OP_FIND_INFO_RESP: + findInfoResp(connectionHandle, dlen, data); break; case ATT_OP_FIND_BY_TYPE_REQ: - findByTypeReq(connectionHandle, dlen, data); + findByTypeReq(connectionHandle, mtu, dlen, data); break; case ATT_OP_READ_BY_TYPE_REQ: - readByTypeReq(connectionHandle, dlen, data); + readByTypeReq(connectionHandle, mtu, dlen, data); + break; + + case ATT_OP_READ_BY_TYPE_RESP: + readByTypeResp(connectionHandle, dlen, data); break; case ATT_OP_READ_BY_GROUP_REQ: - readByGroupReq(connectionHandle, dlen, data); + readByGroupReq(connectionHandle, mtu, dlen, data); + break; + + case ATT_OP_READ_BY_GROUP_RESP: + readByGroupResp(connectionHandle, dlen, data); break; case ATT_OP_READ_REQ: case ATT_OP_READ_BLOB_REQ: - readOrReadBlobReq(connectionHandle, opcode, dlen, data); + readOrReadBlobReq(connectionHandle, mtu, opcode, dlen, data); + break; + + case ATT_OP_READ_RESP: + readResp(connectionHandle, dlen, data); break; case ATT_OP_WRITE_REQ: case ATT_OP_WRITE_CMD: - writeReqOrCmd(connectionHandle, opcode, dlen, data); + writeReqOrCmd(connectionHandle, mtu, opcode, dlen, data); + break; + + case ATT_OP_WRITE_RESP: + writeResp(connectionHandle, dlen, data); break; case ATT_OP_PREP_WRITE_REQ: - prepWriteReq(connectionHandle, dlen, data); + prepWriteReq(connectionHandle, mtu, dlen, data); break; case ATT_OP_EXEC_WRITE_REQ: - execWriteReq(connectionHandle, dlen, data); + execWriteReq(connectionHandle, mtu, dlen, data); + break; + + case ATT_OP_HANDLE_NOTIFY: + case ATT_OP_HANDLE_IND: + handleNotifyOrInd(connectionHandle, opcode, dlen, data); break; case ATT_OP_HANDLE_CNF: @@ -177,12 +353,30 @@ void ATTClass::handleData(uint16_t connectionHandle, uint8_t dlen, uint8_t data[ void ATTClass::removeConnection(uint8_t handle, uint16_t /*reason*/) { - if (_connectionHandle == handle) { - BLEDevice bleDevice(_connectionHandle, _peerAddress); + int peerIndex = -1; + int peerCount = 0; + for (int i = 0; i < ATT_MAX_PEERS; i++) { + if (_peers[i].connectionHandle == handle) { + peerIndex = i; + } + + if (_peers[i].connectionHandle != 0xffff) { + peerCount++; + } + } + + if (peerIndex == -1) { + // bail not found + return; + } + + BLEDevice bleDevice(_peers[peerIndex].addressType, _peers[peerIndex].address); + + if (peerCount == 1) { // clear CCCD values on disconnect for (uint16_t i = 0; i < GATT.attributeCount(); i++) { - BLEAttribute* attribute = GATT.attribute(i); + BLELocalAttribute* attribute = GATT.attribute(i); if (attribute->type() == BLETypeCharacteristic) { BLELocalCharacteristic* characteristic = (BLELocalCharacteristic*)attribute; @@ -191,46 +385,126 @@ void ATTClass::removeConnection(uint8_t handle, uint16_t /*reason*/) } } - if (_eventHandlers[BLEDisconnected]) { - _eventHandlers[BLEDisconnected](bleDevice); - } - - _connectionHandle = 0xffff; - memset(_peerAddress, 0x00, sizeof(_peerAddress)); _longWriteHandle = 0x0000; _longWriteValueLength = 0; } + + if (_eventHandlers[BLEDisconnected]) { + _eventHandlers[BLEDisconnected](bleDevice); + } + + _peers[peerIndex].connectionHandle = 0xffff; + _peers[peerIndex].role = 0x00; + _peers[peerIndex].addressType = 0x00; + memset(_peers[peerIndex].address, 0x00, sizeof(_peers[peerIndex].address)); + _peers[peerIndex].mtu = 23; + + if (_peers[peerIndex].device) { + delete _peers[peerIndex].device; + } + _peers[peerIndex].device = NULL; +} + +uint16_t ATTClass::connectionHandle(uint8_t addressType, const uint8_t address[6]) const +{ + for (int i = 0; i < ATT_MAX_PEERS; i++) { + if (_peers[i].addressType == addressType && memcmp(_peers[i].address, address, 6) == 0) { + return _peers[i].connectionHandle; + } + } + + return 0xffff; +} + +BLERemoteDevice* ATTClass::device(uint8_t addressType, const uint8_t address[6]) const +{ + for (int i = 0; i < ATT_MAX_PEERS; i++) { + if (_peers[i].addressType == addressType && memcmp(_peers[i].address, address, 6) == 0) { + return _peers[i].device; + } + } + + return NULL; } bool ATTClass::connected() const { - return (_connectionHandle != 0xffff); + for (int i = 0; i < ATT_MAX_PEERS; i++) { + if (_peers[i].connectionHandle != 0xffff) { + return true; + } + } + + return false; +} + +bool ATTClass::connected(uint8_t addressType, const uint8_t address[6]) const +{ + return (connectionHandle(addressType, address) != 0xffff); +} + +bool ATTClass::connected(uint16_t handle) const +{ + HCI.poll(); + + for (int i = 0; i < ATT_MAX_PEERS; i++) { + if (_peers[i].connectionHandle == handle) { + return true; + } + } + + return false; } -bool ATTClass::connected(uint16_t handle, const uint8_t address[6]) const +uint16_t ATTClass::mtu(uint16_t handle) const { - return ((_connectionHandle == handle) && memcmp(_peerAddress, address, 6) == 0); + for (int i = 0; i < ATT_MAX_PEERS; i++) { + if (_peers[i].connectionHandle == handle) { + return _peers[i].mtu; + } + } + + return 23; } bool ATTClass::disconnect() { - if (_connectionHandle != 0xffff) { - if (HCI.disconnect(_connectionHandle) != 0) { - return false; + int numDisconnects = 0; + + for (int i = 0; i < ATT_MAX_PEERS; i++) { + if (_peers[i].connectionHandle == 0xffff) { + continue; + } + + if (HCI.disconnect(_peers[i].connectionHandle) != 0) { + continue; } - _connectionHandle = 0xffff; - memset(_peerAddress, 0x00, sizeof(_peerAddress)); - return true; + numDisconnects++; + + _peers[i].connectionHandle = 0xffff; + _peers[i].role = 0x00; + _peers[i].addressType = 0x00; + memset(_peers[i].address, 0x00, sizeof(_peers[i].address)); + _peers[i].mtu = 23; + + if (_peers[i].device) { + delete _peers[i].device; + } + _peers[i].device = NULL; } - return false; + return (numDisconnects > 0); } BLEDevice ATTClass::central() { - if (connected()) { - return BLEDevice(_connectionHandle, _peerAddress); + for (int i = 0; i < ATT_MAX_PEERS; i++) { + if (_peers[i].connectionHandle == 0xffff || _peers[i].role != 0x01) { + continue; + } + + return BLEDevice(_peers[i].addressType, _peers[i].address); } return BLEDevice(); @@ -238,8 +512,14 @@ BLEDevice ATTClass::central() bool ATTClass::handleNotify(uint16_t handle, const uint8_t* value, int length) { - if (_connectionHandle != 0xffff) { - uint8_t notication[_mtu]; + int numNotifications = 0; + + for (int i = 0; i < ATT_MAX_PEERS; i++) { + if (_peers[i].connectionHandle == 0xffff) { + continue; + } + + uint8_t notication[_peers[i].mtu]; uint16_t noticationLength = 0; notication[0] = ATT_OP_HANDLE_NOTIFY; @@ -248,22 +528,28 @@ bool ATTClass::handleNotify(uint16_t handle, const uint8_t* value, int length) memcpy(¬ication[1], &handle, sizeof(handle)); noticationLength += sizeof(handle); - length = min((uint16_t)(_mtu - noticationLength), (uint16_t)length); + length = min((uint16_t)(_peers[i].mtu - noticationLength), (uint16_t)length); memcpy(¬ication[noticationLength], value, length); noticationLength += length; - HCI.sendAclPkt(_connectionHandle, ATT_CID, noticationLength, notication); + HCI.sendAclPkt(_peers[i].connectionHandle, ATT_CID, noticationLength, notication); - return true; + numNotifications++; } - return false; + return (numNotifications > 0); } bool ATTClass::handleInd(uint16_t handle, const uint8_t* value, int length) { - if (_connectionHandle != 0xffff) { - uint8_t indication[_mtu]; + int numIndications = 0; + + for (int i = 0; i < ATT_MAX_PEERS; i++) { + if (_peers[i].connectionHandle == 0xffff) { + continue; + } + + uint8_t indication[_peers[i].mtu]; uint16_t indicationLength = 0; indication[0] = ATT_OP_HANDLE_IND; @@ -272,26 +558,46 @@ bool ATTClass::handleInd(uint16_t handle, const uint8_t* value, int length) memcpy(&indication[1], &handle, sizeof(handle)); indicationLength += sizeof(handle); - length = min((uint16_t)(_mtu - indicationLength), (uint16_t)length); + length = min((uint16_t)(_peers[i].mtu - indicationLength), (uint16_t)length); memcpy(&indication[indicationLength], value, length); indicationLength += length; _cnf = false; - HCI.sendAclPkt(_connectionHandle, ATT_CID, indicationLength, indication); + HCI.sendAclPkt(_peers[i].connectionHandle, ATT_CID, indicationLength, indication); while (!_cnf) { HCI.poll(); - if (!connected()) { - return false; + if (!connected(_peers[i].addressType, _peers[i].address)) { + break; } } - return true; + numIndications++; } - return false; + return (numIndications > 0); +} + +void ATTClass::error(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) +{ + if (dlen != 4) { + // drop + return; + } + + struct __attribute__ ((packed)) AttError { + uint8_t opcode; + uint16_t handle; + uint8_t code; + } *attError = (AttError*)data; + + if (_pendingResp.connectionHandle == connectionHandle && (_pendingResp.op - 1) == attError->opcode) { + _pendingResp.buffer[0] = ATT_OP_ERROR; + memcpy(&_pendingResp.buffer[1], data, dlen); + _pendingResp.length = dlen + 1; + } } void ATTClass::mtuReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) @@ -307,7 +613,12 @@ void ATTClass::mtuReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) mtu = _maxMtu; } - _mtu = mtu; + for (int i = 0; i < ATT_MAX_PEERS; i++) { + if (_peers[i].connectionHandle == connectionHandle) { + _peers[i].mtu = mtu; + break; + } + } struct __attribute__ ((packed)) { uint8_t op; @@ -317,7 +628,39 @@ void ATTClass::mtuReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) HCI.sendAclPkt(connectionHandle, ATT_CID, sizeof(mtuResp), &mtuResp); } -void ATTClass::findInfoReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) +int ATTClass::mtuReq(uint16_t connectionHandle, uint16_t mtu, uint8_t responseBuffer[]) +{ + struct __attribute__ ((packed)) { + uint8_t op; + uint16_t mtu; + } mtuReq = { ATT_OP_MTU_REQ, mtu }; + + return sendReq(connectionHandle, &mtuReq, sizeof(mtuReq), responseBuffer); +} + +void ATTClass::mtuResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) +{ + uint16_t mtu = *(uint16_t*)data; + + if (dlen != 2) { + return; + } + + for (int i = 0; i < ATT_MAX_PEERS; i++) { + if (_peers[i].connectionHandle == connectionHandle) { + _peers[i].mtu = mtu; + break; + } + } + + if (connectionHandle == _pendingResp.connectionHandle && _pendingResp.op == ATT_OP_MTU_RESP) { + _pendingResp.buffer[0] = ATT_OP_MTU_RESP; + memcpy(&_pendingResp.buffer[1], data, dlen); + _pendingResp.length = dlen + 1; + } +} + +void ATTClass::findInfoReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]) { struct __attribute__ ((packed)) FindInfoReq { uint16_t startHandle; @@ -329,7 +672,7 @@ void ATTClass::findInfoReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data return; } - uint8_t response[_mtu]; + uint8_t response[mtu]; uint16_t responseLength; response[0] = ATT_OP_FIND_INFO_RESP; @@ -337,7 +680,7 @@ void ATTClass::findInfoReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data responseLength = 2; for (uint16_t i = (findInfoReq->startHandle - 1); i < GATT.attributeCount() && i <= (findInfoReq->endHandle - 1); i++) { - BLEAttribute* attribute = GATT.attribute(i); + BLELocalAttribute* attribute = GATT.attribute(i); uint16_t handle = (i + 1); bool isValueHandle = (attribute->type() == BLETypeCharacteristic) && (((BLELocalCharacteristic*)attribute)->valueHandle() == handle); int uuidLen = isValueHandle ? 2 : attribute->uuidLength(); @@ -368,7 +711,7 @@ void ATTClass::findInfoReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data responseLength += sizeof(type); } - if ((responseLength + (2 + uuidLen)) > _mtu) { + if ((responseLength + (2 + uuidLen)) > mtu) { break; } } @@ -380,7 +723,31 @@ void ATTClass::findInfoReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data } } -void ATTClass::findByTypeReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) +int ATTClass::findInfoReq(uint16_t connectionHandle, uint16_t startHandle, uint16_t endHandle, uint8_t responseBuffer[]) +{ + struct __attribute__ ((packed)) { + uint8_t op; + uint16_t startHandle; + uint16_t endHandle; + } findInfoReq = { ATT_OP_FIND_INFO_REQ, startHandle, endHandle }; + + return sendReq(connectionHandle, &findInfoReq, sizeof(findInfoReq), responseBuffer); +} + +void ATTClass::findInfoResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) +{ + if (dlen < 2) { + return; // invalid, drop + } + + if (connectionHandle == _pendingResp.connectionHandle && _pendingResp.op == ATT_OP_FIND_INFO_RESP) { + _pendingResp.buffer[0] = ATT_OP_FIND_INFO_RESP; + memcpy(&_pendingResp.buffer[1], data, dlen); + _pendingResp.length = dlen + 1; + } +} + +void ATTClass::findByTypeReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]) { struct __attribute__ ((packed)) FindByTypeReq { uint16_t startHandle; @@ -396,7 +763,7 @@ void ATTClass::findByTypeReq(uint16_t connectionHandle, uint8_t dlen, uint8_t da uint16_t valueLength = dlen - sizeof(*findByTypeReq); uint8_t* value = &data[sizeof(*findByTypeReq)]; - uint8_t response[_mtu]; + uint8_t response[mtu]; uint16_t responseLength; response[0] = ATT_OP_FIND_BY_TYPE_RESP; @@ -404,7 +771,7 @@ void ATTClass::findByTypeReq(uint16_t connectionHandle, uint8_t dlen, uint8_t da if (findByTypeReq->type == BLETypeService) { for (uint16_t i = (findByTypeReq->startHandle - 1); i < GATT.attributeCount() && i <= (findByTypeReq->endHandle - 1); i++) { - BLEAttribute* attribute = GATT.attribute(i); + BLELocalAttribute* attribute = GATT.attribute(i); if ((attribute->type() == findByTypeReq->type) && (attribute->uuidLength() == valueLength) && memcmp(attribute->uuidData(), value, valueLength) == 0) { BLELocalService* service = (BLELocalService*)attribute; @@ -420,7 +787,7 @@ void ATTClass::findByTypeReq(uint16_t connectionHandle, uint8_t dlen, uint8_t da responseLength += sizeof(endHandle); } - if ((responseLength + 4) > _mtu) { + if ((responseLength + 4) > mtu) { break; } } @@ -433,7 +800,7 @@ void ATTClass::findByTypeReq(uint16_t connectionHandle, uint8_t dlen, uint8_t da } } -void ATTClass::readByGroupReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) +void ATTClass::readByGroupReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]) { struct __attribute__ ((packed)) ReadByGroupReq { uint16_t startHandle; @@ -446,7 +813,7 @@ void ATTClass::readByGroupReq(uint16_t connectionHandle, uint8_t dlen, uint8_t d return; } - uint8_t response[_mtu]; + uint8_t response[mtu]; uint16_t responseLength; response[0] = ATT_OP_READ_BY_GROUP_RESP; @@ -454,7 +821,7 @@ void ATTClass::readByGroupReq(uint16_t connectionHandle, uint8_t dlen, uint8_t d responseLength = 2; for (uint16_t i = (readByGroupReq->startHandle - 1); i < GATT.attributeCount() && i <= (readByGroupReq->endHandle - 1); i++) { - BLEAttribute* attribute = GATT.attribute(i); + BLELocalAttribute* attribute = GATT.attribute(i); if (readByGroupReq->uuid != attribute->type()) { // not the type @@ -489,7 +856,7 @@ void ATTClass::readByGroupReq(uint16_t connectionHandle, uint8_t dlen, uint8_t d memcpy(&response[responseLength], service->uuidData(), uuidLen); responseLength += uuidLen; - if ((responseLength + infoSize) > _mtu) { + if ((responseLength + infoSize) > mtu) { break; } } @@ -501,7 +868,32 @@ void ATTClass::readByGroupReq(uint16_t connectionHandle, uint8_t dlen, uint8_t d } } -void ATTClass::readOrReadBlobReq(uint16_t connectionHandle, uint8_t opcode, uint8_t dlen, uint8_t data[]) +int ATTClass::readByGroupReq(uint16_t connectionHandle, uint16_t startHandle, uint16_t endHandle, uint16_t uuid, uint8_t responseBuffer[]) +{ + struct __attribute__ ((packed)) { + uint8_t op; + uint16_t startHandle; + uint16_t endHandle; + uint16_t uuid; + } readByGroupReq = { ATT_OP_READ_BY_GROUP_REQ, startHandle, endHandle, uuid }; + + return sendReq(connectionHandle, &readByGroupReq, sizeof(readByGroupReq), responseBuffer); +} + +void ATTClass::readByGroupResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) +{ + if (dlen < 2) { + return; // invalid, drop + } + + if (connectionHandle == _pendingResp.connectionHandle && _pendingResp.op == ATT_OP_READ_BY_GROUP_RESP) { + _pendingResp.buffer[0] = ATT_OP_READ_BY_GROUP_RESP; + memcpy(&_pendingResp.buffer[1], data, dlen); + _pendingResp.length = dlen + 1; + } +} + +void ATTClass::readOrReadBlobReq(uint16_t connectionHandle, uint16_t mtu, uint8_t opcode, uint8_t dlen, uint8_t data[]) { if (opcode == ATT_OP_READ_REQ) { if (dlen != sizeof(uint16_t)) { @@ -523,13 +915,13 @@ void ATTClass::readOrReadBlobReq(uint16_t connectionHandle, uint8_t opcode, uint return; } - uint8_t response[_mtu]; + uint8_t response[mtu]; uint16_t responseLength; response[0] = (opcode == ATT_OP_READ_REQ) ? ATT_OP_READ_RESP : ATT_OP_READ_BLOB_RESP; responseLength = 1; - BLEAttribute* attribute = GATT.attribute(handle - 1); + BLELocalAttribute* attribute = GATT.attribute(handle - 1); enum BLEAttributeType attributeType = attribute->type(); if (attributeType == BLETypeService) { @@ -578,10 +970,14 @@ void ATTClass::readOrReadBlobReq(uint16_t connectionHandle, uint8_t opcode, uint return; } - valueLength = min(_mtu - responseLength, valueLength - offset); + valueLength = min(mtu - responseLength, valueLength - offset); - characteristic->readValue(BLEDevice(connectionHandle, _peerAddress), offset, &response[responseLength], valueLength); - responseLength += valueLength; + for (int i = 0; i < ATT_MAX_PEERS; i++) { + if (_peers[i].connectionHandle == connectionHandle) { + characteristic->readValue(BLEDevice(_peers[i].addressType, _peers[i].address), offset, &response[responseLength], valueLength); + responseLength += valueLength; + } + } } } else if (attributeType == BLETypeDescriptor) { BLELocalDescriptor* descriptor = (BLELocalDescriptor*)attribute; @@ -593,7 +989,7 @@ void ATTClass::readOrReadBlobReq(uint16_t connectionHandle, uint8_t opcode, uint return; } - valueLength = min(_mtu - responseLength, valueLength - offset); + valueLength = min(mtu - responseLength, valueLength - offset); memcpy(&response[responseLength], descriptor->value() + offset, valueLength); responseLength += valueLength; @@ -602,7 +998,16 @@ void ATTClass::readOrReadBlobReq(uint16_t connectionHandle, uint8_t opcode, uint HCI.sendAclPkt(connectionHandle, ATT_CID, responseLength, response); } -void ATTClass::readByTypeReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) +void ATTClass::readResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) +{ + if (connectionHandle == _pendingResp.connectionHandle && _pendingResp.op == ATT_OP_READ_RESP) { + _pendingResp.buffer[0] = ATT_OP_READ_RESP; + memcpy(&_pendingResp.buffer[1], data, dlen); + _pendingResp.length = dlen + 1; + } +} + +void ATTClass::readByTypeReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]) { struct __attribute__ ((packed)) ReadByTypeReq { uint16_t startHandle; @@ -615,7 +1020,7 @@ void ATTClass::readByTypeReq(uint16_t connectionHandle, uint8_t dlen, uint8_t da return; } - uint8_t response[_mtu]; + uint8_t response[mtu]; uint16_t responseLength; response[0] = ATT_OP_READ_BY_TYPE_RESP; @@ -623,7 +1028,7 @@ void ATTClass::readByTypeReq(uint16_t connectionHandle, uint8_t dlen, uint8_t da responseLength = 2; for (uint16_t i = (readByTypeReq->startHandle - 1); i < GATT.attributeCount() && i <= (readByTypeReq->endHandle - 1); i++) { - BLEAttribute* attribute = GATT.attribute(i); + BLELocalAttribute* attribute = GATT.attribute(i); uint16_t handle = (i + 1); if (attribute->type() == readByTypeReq->uuid) { @@ -666,7 +1071,7 @@ void ATTClass::readByTypeReq(uint16_t connectionHandle, uint8_t dlen, uint8_t da // skip the next handle, it's a value handle i++; - if ((responseLength + typeSize) > _mtu) { + if ((responseLength + typeSize) > mtu) { break; } } else if (attribute->type() == 0x2902) { @@ -677,7 +1082,7 @@ void ATTClass::readByTypeReq(uint16_t connectionHandle, uint8_t dlen, uint8_t da responseLength += sizeof(handle); // add the value - int valueSize = min((uint16_t)(_mtu - responseLength), (uint16_t)descriptor->valueSize()); + int valueSize = min((uint16_t)(mtu - responseLength), (uint16_t)descriptor->valueSize()); memcpy(&response[responseLength], descriptor->value(), valueSize); responseLength += valueSize; @@ -693,7 +1098,7 @@ void ATTClass::readByTypeReq(uint16_t connectionHandle, uint8_t dlen, uint8_t da responseLength += sizeof(handle); // add the value - int valueLength = min((uint16_t)(_mtu - responseLength), (uint16_t)characteristic->valueLength()); + int valueLength = min((uint16_t)(mtu - responseLength), (uint16_t)characteristic->valueLength()); memcpy(&response[responseLength], characteristic->value(), valueLength); responseLength += valueLength; @@ -710,7 +1115,32 @@ void ATTClass::readByTypeReq(uint16_t connectionHandle, uint8_t dlen, uint8_t da } } -void ATTClass::writeReqOrCmd(uint16_t connectionHandle, uint8_t op, uint8_t dlen, uint8_t data[]) +int ATTClass::readByTypeReq(uint16_t connectionHandle, uint16_t startHandle, uint16_t endHandle, uint16_t type, uint8_t responseBuffer[]) +{ + struct __attribute__ ((packed)) { + uint8_t op; + uint16_t startHandle; + uint16_t endHandle; + uint16_t type; + } readByTypeReq = { ATT_OP_READ_BY_TYPE_REQ, startHandle, endHandle, type }; + + return sendReq(connectionHandle, &readByTypeReq, sizeof(readByTypeReq), responseBuffer); +} + +void ATTClass::readByTypeResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) +{ + if (dlen < 1) { + return; // invalid, drop + } + + if (connectionHandle == _pendingResp.connectionHandle && _pendingResp.op == ATT_OP_READ_BY_TYPE_RESP) { + _pendingResp.buffer[0] = ATT_OP_READ_BY_TYPE_RESP; + memcpy(&_pendingResp.buffer[1], data, dlen); + _pendingResp.length = dlen + 1; + } +} + +void ATTClass::writeReqOrCmd(uint16_t connectionHandle, uint16_t mtu, uint8_t op, uint8_t dlen, uint8_t data[]) { boolean withResponse = (op == ATT_OP_WRITE_REQ); @@ -733,7 +1163,7 @@ void ATTClass::writeReqOrCmd(uint16_t connectionHandle, uint8_t op, uint8_t dlen uint8_t valueLength = dlen - sizeof(handle); uint8_t* value = &data[sizeof(handle)]; - BLEAttribute* attribute = GATT.attribute(handle - 1); + BLELocalAttribute* attribute = GATT.attribute(handle - 1); if (attribute->type() == BLETypeCharacteristic) { BLELocalCharacteristic* characteristic = (BLELocalCharacteristic*)attribute; @@ -747,8 +1177,11 @@ void ATTClass::writeReqOrCmd(uint16_t connectionHandle, uint8_t op, uint8_t dlen return; } - if (connectionHandle == _connectionHandle) { - characteristic->writeValue(BLEDevice(connectionHandle, _peerAddress), value, valueLength); + for (int i = 0; i < ATT_MAX_PEERS; i++) { + if (_peers[i].connectionHandle == connectionHandle) { + characteristic->writeValue(BLEDevice(_peers[i].addressType, _peers[i].address), value, valueLength); + break; + } } } else if (attribute->type() == BLETypeDescriptor) { BLELocalDescriptor* descriptor = (BLELocalDescriptor*)attribute; @@ -773,8 +1206,11 @@ void ATTClass::writeReqOrCmd(uint16_t connectionHandle, uint8_t op, uint8_t dlen BLELocalCharacteristic* characteristic = (BLELocalCharacteristic*)attribute; - if (connectionHandle == _connectionHandle) { - characteristic->writeCccdValue(BLEDevice(connectionHandle, _peerAddress), *((uint16_t*)value)); + for (int i = 0; i < ATT_MAX_PEERS; i++) { + if (_peers[i].connectionHandle == connectionHandle) { + characteristic->writeCccdValue(BLEDevice(_peers[i].addressType, _peers[i].address), *((uint16_t*)value)); + break; + } } } else { if (withResponse) { @@ -784,7 +1220,7 @@ void ATTClass::writeReqOrCmd(uint16_t connectionHandle, uint8_t op, uint8_t dlen } if (withResponse) { - uint8_t response[_mtu]; + uint8_t response[mtu]; uint16_t responseLength; response[0] = ATT_OP_WRITE_RESP; @@ -794,7 +1230,20 @@ void ATTClass::writeReqOrCmd(uint16_t connectionHandle, uint8_t op, uint8_t dlen } } -void ATTClass::prepWriteReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) +void ATTClass::writeResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) +{ + if (dlen != 0) { + return; // drop + } + + if (connectionHandle == _pendingResp.connectionHandle && _pendingResp.op == ATT_OP_WRITE_RESP) { + _pendingResp.buffer[0] = ATT_OP_WRITE_RESP; + memcpy(&_pendingResp.buffer[1], data, dlen); + _pendingResp.length = dlen + 1; + } +} + +void ATTClass::prepWriteReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]) { struct __attribute__ ((packed)) PrepWriteReq { uint16_t handle; @@ -814,7 +1263,7 @@ void ATTClass::prepWriteReq(uint16_t connectionHandle, uint8_t dlen, uint8_t dat return; } - BLEAttribute* attribute = GATT.attribute(handle - 1); + BLELocalAttribute* attribute = GATT.attribute(handle - 1); if (attribute->type() != BLETypeCharacteristic) { sendError(connectionHandle, ATT_OP_PREP_WRITE_REQ, handle, ATT_ECODE_ATTR_NOT_LONG); @@ -857,7 +1306,7 @@ void ATTClass::prepWriteReq(uint16_t connectionHandle, uint8_t dlen, uint8_t dat memcpy(_longWriteValue + offset, value, valueLength); _longWriteValueLength += valueLength; - uint8_t response[_mtu]; + uint8_t response[mtu]; uint16_t responseLength; response[0] = ATT_OP_PREP_WRITE_RESP; @@ -867,7 +1316,7 @@ void ATTClass::prepWriteReq(uint16_t connectionHandle, uint8_t dlen, uint8_t dat HCI.sendAclPkt(connectionHandle, ATT_CID, responseLength, response); } -void ATTClass::execWriteReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) +void ATTClass::execWriteReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]) { if (dlen != sizeof(uint8_t)) { sendError(connectionHandle, ATT_OP_EXEC_WRITE_REQ, 0x0000, ATT_ECODE_INVALID_PDU); @@ -879,15 +1328,18 @@ void ATTClass::execWriteReq(uint16_t connectionHandle, uint8_t dlen, uint8_t dat if (_longWriteHandle && (flag & 0x01)) { BLELocalCharacteristic* characteristic = (BLELocalCharacteristic*)GATT.attribute(_longWriteHandle - 1); - if (connectionHandle == _connectionHandle) { - characteristic->writeValue(BLEDevice(connectionHandle, _peerAddress), _longWriteValue, _longWriteValueLength); + for (int i = 0; i < ATT_MAX_PEERS; i++) { + if (_peers[i].connectionHandle == connectionHandle) { + characteristic->writeValue(BLEDevice(_peers[i].addressType, _peers[i].address), _longWriteValue, _longWriteValueLength); + break; + } } } _longWriteHandle = 0x0000; _longWriteValueLength = 0; - uint8_t response[_mtu]; + uint8_t response[mtu]; uint16_t responseLength; response[0] = ATT_OP_EXEC_WRITE_RESP; @@ -896,6 +1348,59 @@ void ATTClass::execWriteReq(uint16_t connectionHandle, uint8_t dlen, uint8_t dat HCI.sendAclPkt(connectionHandle, ATT_CID, responseLength, response); } +void ATTClass::handleNotifyOrInd(uint16_t connectionHandle, uint8_t opcode, uint8_t dlen, uint8_t data[]) +{ + if (dlen < 2) { + return; // drop + } + + struct __attribute__ ((packed)) HandleNotifyOrInd { + uint16_t handle; + } *handleNotifyOrInd = (HandleNotifyOrInd*)data; + + uint8_t handle = handleNotifyOrInd->handle; + + for (int i = 0; i < ATT_MAX_PEERS; i++) { + if (_peers[i].connectionHandle != connectionHandle) { + continue; + } + + BLERemoteDevice* device = _peers[i].device; + + if (!device) { + break; + } + + int serviceCount = device->serviceCount(); + + for (int i = 0; i < serviceCount; i++) { + BLERemoteService* s = device->service(i); + + if (s->startHandle() < handle && s->endHandle() >= handle) { + int characteristicCount = s->characteristicCount(); + + for (int j = 0; j < characteristicCount; j++) { + BLERemoteCharacteristic* c = s->characteristic(j); + + if (c->valueHandle() == handle) { + c->writeValue(BLEDevice(_peers[i].addressType, _peers[i].address), &data[2], dlen - 2); + } + } + + break; + } + } + } + + if (opcode == ATT_OP_HANDLE_IND) { + // send CNF for IND + + uint8_t cnf = ATT_OP_HANDLE_CNF; + + HCI.sendAclPkt(connectionHandle, ATT_CID, sizeof(cnf), &cnf); + } +} + void ATTClass::handleCnf(uint16_t /*connectionHandle*/, uint8_t /*dlen*/, uint8_t /*data*/[]) { _cnf = true; @@ -913,6 +1418,228 @@ void ATTClass::sendError(uint16_t connectionHandle, uint8_t opcode, uint16_t han HCI.sendAclPkt(connectionHandle, ATT_CID, sizeof(attError), &attError); } + +bool ATTClass::exchangeMtu(uint16_t connectionHandle) +{ + uint8_t responseBuffer[_maxMtu]; + + if (!mtuReq(connectionHandle, _maxMtu, responseBuffer)) { + return false; + } + + return true; +} + +bool ATTClass::discoverServices(uint16_t connectionHandle, BLERemoteDevice* device, const char* serviceUuidFilter) +{ + uint16_t reqStartHandle = 0x0001; + uint16_t reqEndHandle = 0xffff; + + uint8_t responseBuffer[_maxMtu]; + + BLEUuid serviceUuid(serviceUuidFilter); + + while (reqEndHandle == 0xffff) { + int respLength = readByGroupReq(connectionHandle, reqStartHandle, reqEndHandle, BLETypeService, responseBuffer); + + if (respLength == 0) { + return false; + } + + if (responseBuffer[0] == ATT_OP_READ_BY_GROUP_RESP) { + uint16_t lengthPerService = responseBuffer[1]; + uint8_t uuidLen = lengthPerService - 4; + + for (int i = 2; i < respLength; i += lengthPerService) { + struct __attribute__ ((packed)) RawService { + uint16_t startHandle; + uint16_t endHandle; + uint8_t uuid[16]; + } *rawService = (RawService*)&responseBuffer[i]; + + if (serviceUuidFilter == NULL || + (uuidLen == serviceUuid.length() && memcmp(rawService->uuid, serviceUuid.data(), uuidLen) == 0)) { + + BLERemoteService* service = new BLERemoteService(rawService->uuid, uuidLen, + rawService->startHandle, + rawService->endHandle); + + if (service == NULL) { + return false; + } + + device->addService(service); + + } + + reqStartHandle = rawService->endHandle + 1; + + if (reqStartHandle == 0x0000) { + reqEndHandle = 0x0000; + } + } + } else { + reqEndHandle = 0x0000; + } + } + + return true; +} + +bool ATTClass::discoverCharacteristics(uint16_t connectionHandle, BLERemoteDevice* device) +{ + uint16_t reqStartHandle = 0x0001; + uint16_t reqEndHandle = 0xffff; + + uint8_t responseBuffer[_maxMtu]; + + int serviceCount = device->serviceCount(); + + for (int i = 0; i < serviceCount; i++) { + BLERemoteService* service = device->service(i); + + reqStartHandle = service->startHandle(); + reqEndHandle = service->endHandle(); + + while (1) { + int respLength = readByTypeReq(connectionHandle, reqStartHandle, reqEndHandle, BLETypeCharacteristic, responseBuffer); + + if (respLength == 0) { + return false; + } + + if (responseBuffer[0] == ATT_OP_READ_BY_TYPE_RESP) { + uint16_t lengthPerCharacteristic = responseBuffer[1]; + uint8_t uuidLen = lengthPerCharacteristic - 5; + + for (int i = 2; i < respLength; i += lengthPerCharacteristic) { + struct __attribute__ ((packed)) RawCharacteristic { + uint16_t startHandle; + uint8_t properties; + uint16_t valueHandle; + uint8_t uuid[16]; + } *rawCharacteristic = (RawCharacteristic*)&responseBuffer[i]; + + BLERemoteCharacteristic* characteristic = new BLERemoteCharacteristic(rawCharacteristic->uuid, uuidLen, + connectionHandle, + rawCharacteristic->startHandle, + rawCharacteristic->properties, + rawCharacteristic->valueHandle); + + if (characteristic == NULL) { + return false; + } + + service->addCharacteristic(characteristic); + + reqStartHandle = rawCharacteristic->valueHandle + 1; + } + } else { + break; + } + } + } + + return true; +} + +bool ATTClass::discoverDescriptors(uint16_t connectionHandle, BLERemoteDevice* device) +{ + uint16_t reqStartHandle = 0x0001; + uint16_t reqEndHandle = 0xffff; + + uint8_t responseBuffer[_maxMtu]; + + int serviceCount = device->serviceCount(); + + for (int i = 0; i < serviceCount; i++) { + BLERemoteService* service = device->service(i); + + uint16_t serviceEndHandle = service->endHandle(); + + int characteristicCount = service->characteristicCount(); + + for (int j = 0; j < characteristicCount; j++) { + BLERemoteCharacteristic* characteristic = service->characteristic(j); + BLERemoteCharacteristic* nextCharacteristic = (j == (characteristicCount - 1)) ? NULL : service->characteristic(j); + + reqStartHandle = characteristic->valueHandle() + 1; + reqEndHandle = nextCharacteristic ? nextCharacteristic->valueHandle() : serviceEndHandle; + + if (reqStartHandle > reqEndHandle) { + continue; + } + + while (1) { + int respLength = findInfoReq(connectionHandle, reqStartHandle, reqEndHandle, responseBuffer); + + if (respLength == 0) { + return false; + } + + if (responseBuffer[0] == ATT_OP_FIND_INFO_RESP) { + uint16_t lengthPerDescriptor = responseBuffer[1] * 4; + uint8_t uuidLen = 2; + + for (int i = 2; i < respLength; i += lengthPerDescriptor) { + struct __attribute__ ((packed)) RawDescriptor { + uint16_t handle; + uint8_t uuid[16]; + } *rawDescriptor = (RawDescriptor*)&responseBuffer[i]; + + BLERemoteDescriptor* descriptor = new BLERemoteDescriptor(rawDescriptor->uuid, uuidLen, + connectionHandle, + rawDescriptor->handle); + + if (descriptor == NULL) { + return false; + } + + characteristic->addDescriptor(descriptor); + + reqStartHandle = rawDescriptor->handle + 1; + } + } else { + break; + } + } + } + } + + return true; +} + +int ATTClass::sendReq(uint16_t connectionHandle, void* requestBuffer, int requestLength, uint8_t responseBuffer[]) +{ + _pendingResp.connectionHandle = connectionHandle; + _pendingResp.op = ((uint8_t*)requestBuffer)[0] + 1; + _pendingResp.buffer = responseBuffer; + _pendingResp.length = 0; + + HCI.sendAclPkt(connectionHandle, ATT_CID, requestLength, requestBuffer); + + if (responseBuffer == NULL) { + // not waiting response + return 0; + } + + for (unsigned long start = millis(); (millis() - start) < _timeout;) { + HCI.poll(); + + if (!connected(connectionHandle)) { + break; + } + + if (_pendingResp.length != 0) { + _pendingResp.connectionHandle = 0xffff; + return _pendingResp.length; + } + } + + _pendingResp.connectionHandle = 0xffff; + return 0; +} + void ATTClass::setEventHandler(BLEDeviceEvent event, BLEDeviceEventHandler eventHandler) { if (event < (sizeof(_eventHandlers) / (sizeof(_eventHandlers[0])))) { @@ -920,4 +1647,44 @@ void ATTClass::setEventHandler(BLEDeviceEvent event, BLEDeviceEventHandler event } } +int ATTClass::readReq(uint16_t connectionHandle, uint16_t handle, uint8_t responseBuffer[]) +{ + struct __attribute__ ((packed)) { + uint8_t op; + uint16_t handle; + } readReq = { ATT_OP_READ_REQ, handle }; + + return sendReq(connectionHandle, &readReq, sizeof(readReq), responseBuffer); +} + +int ATTClass::writeReq(uint16_t connectionHandle, uint16_t handle, const uint8_t* data, uint8_t dataLen, uint8_t responseBuffer[]) +{ + struct __attribute__ ((packed)) { + uint8_t op; + uint16_t handle; + uint8_t data[255]; + } writeReq; + + writeReq.op = ATT_OP_WRITE_REQ; + writeReq.handle = handle; + memcpy(writeReq.data, data, dataLen); + + return sendReq(connectionHandle, &writeReq, 3 + dataLen, responseBuffer); +} + +void ATTClass::writeCmd(uint16_t connectionHandle, uint16_t handle, const uint8_t* data, uint8_t dataLen) +{ + struct __attribute__ ((packed)) { + uint8_t op; + uint16_t handle; + uint8_t data[255]; + } writeReq; + + writeReq.op = ATT_OP_WRITE_CMD; + writeReq.handle = handle; + memcpy(writeReq.data, data, dataLen); + + sendReq(connectionHandle, &writeReq, 3 + dataLen, NULL); +} + ATTClass ATT; diff --git a/src/utility/ATT.h b/src/utility/ATT.h index 7f6e1c57..45802da5 100644 --- a/src/utility/ATT.h +++ b/src/utility/ATT.h @@ -24,7 +24,15 @@ #include "BLEDevice.h" -#define ATT_CID 0x0004 +#define ATT_CID 0x0004 + +#ifdef DM_CONN_MAX +#define ATT_MAX_PEERS DM_CONN_MAX // Mbed + Cordio +#else +#define ATT_MAX_PEERS 3 +#endif + +class BLERemoteDevice; class ATTClass { public: @@ -32,6 +40,11 @@ class ATTClass { virtual ~ATTClass(); void setMaxMtu(uint16_t maxMtu); + void setTimeout(unsigned long timeout); + + bool connect(uint8_t peerBdaddrType, uint8_t peerBdaddr[6]); + bool disconnect(uint8_t peerBdaddrType, uint8_t peerBdaddr[6]); + bool discoverAttributes(uint8_t peerBdaddrType, uint8_t peerBdaddr[6], const char* serviceUuidFilter); void addConnection(uint16_t handle, uint8_t role, uint8_t peerBdaddrType, uint8_t peerBdaddr[6], uint16_t interval, @@ -42,8 +55,12 @@ class ATTClass { void removeConnection(uint8_t handle, uint16_t reason); + uint16_t connectionHandle(uint8_t addressType, const uint8_t address[6]) const; + BLERemoteDevice* device(uint8_t addressType, const uint8_t address[6]) const; bool connected() const; - bool connected(uint16_t handle, const uint8_t address[6]) const; + bool connected(uint8_t addressType, const uint8_t address[6]) const; + bool connected(uint16_t handle) const; + uint16_t mtu(uint16_t handle) const; bool disconnect(); @@ -54,31 +71,68 @@ class ATTClass { void setEventHandler(BLEDeviceEvent event, BLEDeviceEventHandler eventHandler); + int readReq(uint16_t connectionHandle, uint16_t handle, uint8_t responseBuffer[]); + int writeReq(uint16_t connectionHandle, uint16_t handle, const uint8_t* data, uint8_t dataLen, uint8_t responseBuffer[]); + void writeCmd(uint16_t connectionHandle, uint16_t handle, const uint8_t* data, uint8_t dataLen); + private: + void error(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); void mtuReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); - void findInfoReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); - void findByTypeReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); - void readByTypeReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); - void readOrReadBlobReq(uint16_t connectionHandle, uint8_t opcode, uint8_t dlen, uint8_t data[]); - void readByGroupReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); - void writeReqOrCmd(uint16_t connectionHandle, uint8_t op, uint8_t dlen, uint8_t data[]); - void prepWriteReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); - void execWriteReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); + int mtuReq(uint16_t connectionHandle, uint16_t mtu, uint8_t responseBuffer[]); + void mtuResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); + void findInfoReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]); + int findInfoReq(uint16_t connectionHandle, uint16_t startHandle, uint16_t endHandle, uint8_t responseBuffer[]); + void findInfoResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); + void findByTypeReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]); + void readByTypeReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]); + int readByTypeReq(uint16_t connectionHandle, uint16_t startHandle, uint16_t endHandle, uint16_t type, uint8_t responseBuffer[]); + void readByTypeResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); + void readOrReadBlobReq(uint16_t connectionHandle, uint16_t mtu, uint8_t opcode, uint8_t dlen, uint8_t data[]); + void readResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); + void readByGroupReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]); + int readByGroupReq(uint16_t connectionHandle, uint16_t startHandle, uint16_t endHandle, uint16_t uuid, uint8_t responseBuffer[]); + void readByGroupResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); + void writeReqOrCmd(uint16_t connectionHandle, uint16_t mtu, uint8_t op, uint8_t dlen, uint8_t data[]); + void writeResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); + void prepWriteReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]); + void execWriteReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]); + void handleNotifyOrInd(uint16_t connectionHandle, uint8_t opcode, uint8_t dlen, uint8_t data[]); void handleCnf(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); void sendError(uint16_t connectionHandle, uint8_t opcode, uint16_t handle, uint8_t code); + bool exchangeMtu(uint16_t connectionHandle); + bool discoverServices(uint16_t connectionHandle, BLERemoteDevice* device, const char* serviceUuidFilter); + bool discoverCharacteristics(uint16_t connectionHandle, BLERemoteDevice* device); + bool discoverDescriptors(uint16_t connectionHandle, BLERemoteDevice* device); + + int sendReq(uint16_t connectionHandle, void* requestBuffer, int requestLength, uint8_t responseBuffer[]); + private: uint16_t _maxMtu; - uint16_t _connectionHandle; - uint8_t _peerAddress[6]; - uint16_t _mtu; + unsigned long _timeout; + struct { + uint16_t connectionHandle; + uint8_t role; + uint8_t addressType; + uint8_t address[6]; + uint16_t mtu; + BLERemoteDevice* device; + } _peers[ATT_MAX_PEERS]; + volatile bool _cnf; uint16_t _longWriteHandle; uint8_t* _longWriteValue; uint16_t _longWriteValueLength; - BLEDeviceEventHandler _eventHandlers[BLEDeviceLastEvent]; + struct { + uint16_t connectionHandle; + uint8_t op; + uint8_t* buffer; + uint8_t length; + } _pendingResp; + + BLEDeviceEventHandler _eventHandlers[2]; }; extern ATTClass ATT; diff --git a/src/utility/BLELinkedList.h b/src/utility/BLELinkedList.h index fce2765d..15838502 100644 --- a/src/utility/BLELinkedList.h +++ b/src/utility/BLELinkedList.h @@ -36,6 +36,7 @@ template class BLELinkedList { void add(T); T get(unsigned int index) const; void clear(); + T remove(unsigned int index); unsigned int size() const; @@ -111,4 +112,36 @@ template unsigned int BLELinkedList::size() const return _size; } +template T BLELinkedList::remove(unsigned int index) +{ + if (index >= _size) { + return T(); + } + + BLELinkedListNode* previousItemNode = NULL; + BLELinkedListNode* itemNode = _root; + + for (unsigned int i = 0; i < index; i++) { + previousItemNode = itemNode; + itemNode = itemNode->next; + } + + T result = itemNode->data; + + if (previousItemNode == NULL) { + _root = itemNode->next; + } else { + previousItemNode->next = itemNode->next; + } + + if (_last == itemNode) { + _last = previousItemNode; + } + + delete itemNode; + _size--; + + return result; +} + #endif diff --git a/src/BLEUuid.cpp b/src/utility/BLEUuid.cpp similarity index 81% rename from src/BLEUuid.cpp rename to src/utility/BLEUuid.cpp index d1bcfd62..fba6244a 100644 --- a/src/BLEUuid.cpp +++ b/src/utility/BLEUuid.cpp @@ -65,3 +65,24 @@ uint8_t BLEUuid::length() const { return _length; } + +const char* BLEUuid::uuidToString(const uint8_t* data, uint8_t length) +{ + static char uuid[36 + 1]; + char* c = uuid; + + for (int i = length - 1; i >= 0; i--) { + uint8_t b = data[i]; + + utoa(b >> 4, c++, 16); + utoa(b & 0x0f, c++, 16); + + if (i == 6 || i == 8 || i == 10 || i == 12) { + *c++ = '-'; + } + } + + *c = '\0'; + + return uuid; +} diff --git a/src/BLEUuid.h b/src/utility/BLEUuid.h similarity index 92% rename from src/BLEUuid.h rename to src/utility/BLEUuid.h index d8315711..f6836bf6 100644 --- a/src/BLEUuid.h +++ b/src/utility/BLEUuid.h @@ -20,7 +20,7 @@ #ifndef _BLE_UUID_H_ #define _BLE_UUID_H_ -#include +#include #define BLE_UUID_MAX_LENGTH 16 @@ -33,6 +33,8 @@ class BLEUuid const uint8_t * data() const; uint8_t length() const; + static const char* uuidToString(const uint8_t* data, uint8_t length); + private: const char* _str; uint8_t _data[BLE_UUID_MAX_LENGTH]; diff --git a/src/utility/GAP.cpp b/src/utility/GAP.cpp index 07b2d436..78b1556e 100644 --- a/src/utility/GAP.cpp +++ b/src/utility/GAP.cpp @@ -22,9 +22,11 @@ #include "GAP.h" +#define GAP_MAX_DISCOVERED_QUEUE_SIZE 5 GAPClass::GAPClass() : _advertising(false), + _scanning(false), _advertisedServiceUuid(NULL), _manufacturerData(NULL), _manufacturerDataLength(0), @@ -32,7 +34,8 @@ GAPClass::GAPClass() : _advertisingInterval(160), _connectable(true), _serviceData(NULL), - _serviceDataLength(0) + _serviceDataLength(0), + _discoverEventHandler(NULL) { } @@ -153,6 +156,88 @@ void GAPClass::stopAdvertise() HCI.leSetAdvertiseEnable(0x00); } +int GAPClass::scan(bool withDuplicates) +{ + HCI.leSetScanEnable(false, true); + + // active scan, 10 ms scan interval (N * 0.625), 10 ms scan window (N * 0.625), public own address type, no filter + if (HCI.leSetScanParameters(0x01, 0x0010, 0x0010, 0x00, 0x00) != 0) { + return false; + } + + _scanning = true; + + if (HCI.leSetScanEnable(true, !withDuplicates) != 0) { + return 0; + } + + return 1; +} + +int GAPClass::scanForName(String name, bool withDuplicates) +{ + _scanNameFilter = name; + _scanUuidFilter = ""; + _scanAddressFilter = ""; + + return scan(withDuplicates); +} + +int GAPClass::scanForUuid(String uuid, bool withDuplicates) +{ + _scanNameFilter = ""; + _scanUuidFilter = uuid; + _scanAddressFilter = ""; + + return scan(withDuplicates); +} + +int GAPClass::scanForAddress(String address, bool withDuplicates) +{ + _scanNameFilter = ""; + _scanUuidFilter = ""; + _scanAddressFilter = address; + + return scan(withDuplicates); +} + +void GAPClass::stopScan() +{ + HCI.leSetScanEnable(false, false); + + _scanning = false; + + for (unsigned int i = 0; i < _discoveredDevices.size(); i++) { + BLEDevice* device = _discoveredDevices.get(i); + + delete device; + } + + _discoveredDevices.clear(); +} + +BLEDevice GAPClass::available() +{ + for (unsigned int i = 0; i < _discoveredDevices.size(); i++) { + BLEDevice* device = _discoveredDevices.get(i); + + if (device->discovered()) { + BLEDevice result = *device; + + _discoveredDevices.remove(i); + delete device; + + if (matchesScanFilter(result)) { + return result; + } else { + continue; + } + } + } + + return BLEDevice(); +} + void GAPClass::setAdvertisingInterval(uint16_t advertisingInterval) { _advertisingInterval = advertisingInterval; @@ -163,6 +248,13 @@ void GAPClass::setConnectable(bool connectable) _connectable = connectable; } +void GAPClass::setEventHandler(BLEDeviceEvent event, BLEDeviceEventHandler eventHandler) +{ + if (event == BLEDiscovered) { + _discoverEventHandler = eventHandler; + } +} + void GAPClass::setAdvertisedServiceData(uint16_t uuid, const uint8_t data[], int length) { _serviceDataUuid = uuid; @@ -170,4 +262,81 @@ void GAPClass::setAdvertisedServiceData(uint16_t uuid, const uint8_t data[], int _serviceDataLength = length; } +void GAPClass::handleLeAdvertisingReport(uint8_t type, uint8_t addressType, uint8_t address[6], + uint8_t eirLength, uint8_t eirData[], int8_t rssi) +{ + if (!_scanning) { + return; + } + + if (_discoverEventHandler && type == 0x03) { + // call event handler and skip adding to discover list + BLEDevice device(addressType, address); + + device.setAdvertisementData(type, eirLength, eirData, rssi); + + if (matchesScanFilter(device)) { + _discoverEventHandler(device); + } + return; + } + + BLEDevice* discoveredDevice = NULL; + int discoveredIndex = -1; + + for (unsigned int i = 0; i < _discoveredDevices.size(); i++) { + BLEDevice* device = _discoveredDevices.get(i); + + if (device->hasAddress(addressType, address)) { + discoveredDevice = device; + discoveredIndex = i; + + break; + } + } + + if (discoveredDevice == NULL) { + if (_discoveredDevices.size() >= GAP_MAX_DISCOVERED_QUEUE_SIZE) { + // drop + return; + } + + discoveredDevice = new BLEDevice(addressType, address); + + _discoveredDevices.add(discoveredDevice); + discoveredIndex = _discoveredDevices.size() - 1; + } + + if (type != 0x04) { + discoveredDevice->setAdvertisementData(type, eirLength, eirData, rssi); + } else { + discoveredDevice->setScanResponseData(eirLength, eirData, rssi); + } + + if (discoveredDevice->discovered() && _discoverEventHandler) { + // remove from list and report as discovered + BLEDevice device = *discoveredDevice; + + _discoveredDevices.remove(discoveredIndex); + delete discoveredDevice; + + if (matchesScanFilter(device)) { + _discoverEventHandler(device); + } + } +} + +bool GAPClass::matchesScanFilter(const BLEDevice& device) +{ + if (_scanAddressFilter.length() > 0 && _scanAddressFilter != device.address()) { + return false; // drop doesn't match + } else if (_scanNameFilter.length() > 0 && _scanNameFilter != device.localName()) { + return false; // drop doesn't match + } else if (_scanUuidFilter.length() > 0 && _scanUuidFilter != device.advertisedServiceUuid()) { + return false; // drop doesn't match + } + + return true; +} + GAPClass GAP; diff --git a/src/utility/GAP.h b/src/utility/GAP.h index dd8a73fb..a75b9386 100644 --- a/src/utility/GAP.h +++ b/src/utility/GAP.h @@ -20,6 +20,10 @@ #ifndef _GAP_H_ #define _GAP_H_ +#include "utility/BLELinkedList.h" + +#include "BLEDevice.h" + class GAPClass { public: GAPClass(); @@ -33,16 +37,35 @@ class GAPClass { int advertise(); void stopAdvertise(); + int scan(bool withDuplicates); + int scanForName(String name, bool withDuplicates); + int scanForUuid(String uuid, bool withDuplicates); + int scanForAddress(String address, bool withDuplicates); + void stopScan(); + BLEDevice available(); + void setAdvertisingInterval(uint16_t advertisingInterval); - void setConnectable(bool connectable); + void setConnectable(bool connectable); + + void setEventHandler(BLEDeviceEvent event, BLEDeviceEventHandler eventHandler); protected: friend class BLELocalCharacteristic; void setAdvertisedServiceData(uint16_t uuid, const uint8_t data[], int length); +protected: + friend class HCIClass; + + void handleLeAdvertisingReport(uint8_t type, uint8_t addressType, uint8_t address[6], + uint8_t eirLength, uint8_t eirData[], int8_t rssi); + +private: + bool matchesScanFilter(const BLEDevice& device); + private: bool _advertising; + bool _scanning; const char* _advertisedServiceUuid; const uint8_t* _manufacturerData; @@ -54,6 +77,13 @@ class GAPClass { uint16_t _serviceDataUuid; const uint8_t* _serviceData; int _serviceDataLength; + + BLEDeviceEventHandler _discoverEventHandler; + BLELinkedList _discoveredDevices; + + String _scanNameFilter; + String _scanUuidFilter; + String _scanAddressFilter; }; extern GAPClass GAP; diff --git a/src/utility/GATT.cpp b/src/utility/GATT.cpp index 906f4f2c..e1ee2c58 100644 --- a/src/utility/GATT.cpp +++ b/src/utility/GATT.cpp @@ -88,7 +88,7 @@ unsigned int GATTClass::attributeCount() const return _attributes.size(); } -BLEAttribute* GATTClass::attribute(unsigned int index) const +BLELocalAttribute* GATTClass::attribute(unsigned int index) const { return _attributes.get(index); } @@ -100,7 +100,7 @@ uint16_t GATTClass::serviceUuidForCharacteristic(BLELocalCharacteristic* charact BLELocalService* lastService = NULL; for (unsigned int i = 0; i < attributeCount(); i++) { - BLEAttribute* a = attribute(i); + BLELocalAttribute* a = attribute(i); uint16_t attributeType = a->type(); if (attributeType == BLETypeService) { @@ -153,7 +153,7 @@ void GATTClass::addService(BLELocalService* service) void GATTClass::clearAttributes() { for (unsigned int i = 0; i < attributeCount(); i++) { - BLEAttribute* a = attribute(i); + BLELocalAttribute* a = attribute(i); if (a->release() <= 0) { delete a; diff --git a/src/utility/GATT.h b/src/utility/GATT.h index ae53f62b..5bc426d7 100644 --- a/src/utility/GATT.h +++ b/src/utility/GATT.h @@ -22,10 +22,10 @@ #include "utility/BLELinkedList.h" -#include "local/BLELocalService.h" +#include "local/BLELocalAttribute.h" #include "local/BLELocalCharacteristic.h" +#include "local/BLELocalService.h" -#include "BLEAttribute.h" #include "BLEService.h" class GATTClass { @@ -45,7 +45,7 @@ class GATTClass { friend class ATTClass; unsigned int attributeCount() const; - BLEAttribute* attribute(unsigned int index) const; + BLELocalAttribute* attribute(unsigned int index) const; protected: friend class BLELocalCharacteristic; @@ -58,7 +58,7 @@ class GATTClass { void clearAttributes(); private: - BLELinkedList _attributes; + BLELinkedList _attributes; BLELocalService _genericAccessService; BLELocalCharacteristic _deviceNameCharacteristic; diff --git a/src/utility/HCI.cpp b/src/utility/HCI.cpp index 16ad61c8..21dc369a 100644 --- a/src/utility/HCI.cpp +++ b/src/utility/HCI.cpp @@ -18,6 +18,7 @@ */ #include "ATT.h" +#include "GAP.h" #include "HCITransport.h" #include "L2CAPSignaling.h" @@ -27,12 +28,14 @@ #define HCI_ACLDATA_PKT 0x02 #define HCI_EVENT_PKT 0x04 -#define EVT_CMD_COMPLETE 0xe #define EVT_DISCONN_COMPLETE 0x05 -#define EVT_NUM_COMP_PKTS 0x13 -#define EVT_LE_META_EVENT 0x3e +#define EVT_CMD_COMPLETE 0xe +#define EVT_CMD_STATUS 0x0f +#define EVT_NUM_COMP_PKTS 0x13 +#define EVT_LE_META_EVENT 0x3e -#define EVT_LE_CONN_COMPLETE 0x01 +#define EVT_LE_CONN_COMPLETE 0x01 +#define EVT_LE_ADVERTISING_REPORT 0x02 #define OGF_LINK_CTL 0x01 #define OGF_HOST_CTL 0x03 @@ -61,6 +64,10 @@ #define OCF_LE_SET_ADVERTISING_DATA 0x0008 #define OCF_LE_SET_SCAN_RESPONSE_DATA 0x0009 #define OCF_LE_SET_ADVERTISE_ENABLE 0x000a +#define OCF_LE_SET_SCAN_PARAMETERS 0x000b +#define OCF_LE_SET_SCAN_ENABLE 0x000c +#define OCF_LE_CREATE_CONN 0x000d +#define OCF_LE_CANCEL_CONN 0x000e #define OCF_LE_CONN_UPDATE 0x0013 #define HCI_OE_USER_ENDED_CONNECTION 0x13 @@ -308,6 +315,80 @@ int HCIClass::leSetAdvertiseEnable(uint8_t enable) return sendCommand(OGF_LE_CTL << 10 | OCF_LE_SET_ADVERTISE_ENABLE, sizeof(enable), &enable); } +int HCIClass::leSetScanParameters(uint8_t type, uint16_t interval, uint16_t window, + uint8_t ownBdaddrType, uint8_t filter) +{ + struct __attribute__ ((packed)) HCILeSetScanParameters { + uint8_t type; + uint16_t interval; + uint16_t window; + uint8_t ownBdaddrType; + uint8_t filter; + } leScanParameters; + + leScanParameters.type = type; + leScanParameters.interval = interval; + leScanParameters.window = window; + leScanParameters.ownBdaddrType = ownBdaddrType; + leScanParameters.filter = filter; + + return sendCommand(OGF_LE_CTL << 10 | OCF_LE_SET_SCAN_PARAMETERS, sizeof(leScanParameters), &leScanParameters); +} + +int HCIClass::leSetScanEnable(uint8_t enabled, uint8_t duplicates) +{ + struct __attribute__ ((packed)) HCILeSetScanEnableData { + uint8_t enabled; + uint8_t duplicates; + } leScanEnableData; + + leScanEnableData.enabled = enabled; + leScanEnableData.duplicates = duplicates; + + return sendCommand(OGF_LE_CTL << 10 | OCF_LE_SET_SCAN_ENABLE, sizeof(leScanEnableData), &leScanEnableData); +} + +int HCIClass::leCreateConn(uint16_t interval, uint16_t window, uint8_t initiatorFilter, + uint8_t peerBdaddrType, uint8_t peerBdaddr[6], uint8_t ownBdaddrType, + uint16_t minInterval, uint16_t maxInterval, uint16_t latency, + uint16_t supervisionTimeout, uint16_t minCeLength, uint16_t maxCeLength) +{ + struct __attribute__ ((packed)) HCILeCreateConnData { + uint16_t interval; + uint16_t window; + uint8_t initiatorFilter; + uint8_t peerBdaddrType; + uint8_t peerBdaddr[6]; + uint8_t ownBdaddrType; + uint16_t minInterval; + uint16_t maxInterval; + uint16_t latency; + uint16_t supervisionTimeout; + uint16_t minCeLength; + uint16_t maxCeLength; + } leCreateConnData; + + leCreateConnData.interval = interval; + leCreateConnData.window = window; + leCreateConnData.initiatorFilter = initiatorFilter; + leCreateConnData.peerBdaddrType = peerBdaddrType; + memcpy(leCreateConnData.peerBdaddr, peerBdaddr, sizeof(leCreateConnData.peerBdaddr)); + leCreateConnData.ownBdaddrType = ownBdaddrType; + leCreateConnData.minInterval = minInterval; + leCreateConnData.maxInterval = maxInterval; + leCreateConnData.latency = latency; + leCreateConnData.supervisionTimeout = supervisionTimeout; + leCreateConnData.minCeLength = minCeLength; + leCreateConnData.maxCeLength = maxCeLength; + + return sendCommand(OGF_LE_CTL << 10 | OCF_LE_CREATE_CONN, sizeof(leCreateConnData), &leCreateConnData); +} + +int HCIClass::leCancelConn() +{ + return sendCommand(OGF_LE_CTL << 10 | OCF_LE_CANCEL_CONN, 0, NULL); +} + int HCIClass::leConnUpdate(uint16_t handle, uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t supervisionTimeout) { @@ -473,6 +554,17 @@ void HCIClass::handleEventPkt(uint8_t /*plen*/, uint8_t pdata[]) _cmdCompleteStatus = cmdCompleteHeader->status; _cmdResponseLen = pdata[1] - sizeof(CmdComplete); _cmdResponse = &pdata[sizeof(HCIEventHdr) + sizeof(CmdComplete)]; + + } else if (eventHdr->evt == EVT_CMD_STATUS) { + struct __attribute__ ((packed)) CmdStatus { + uint8_t status; + uint8_t ncmd; + uint16_t opcode; + } *cmdStatusHeader = (CmdStatus*)&pdata[sizeof(HCIEventHdr)]; + + _cmdCompleteOpcode = cmdStatusHeader->opcode; + _cmdCompleteStatus = cmdStatusHeader->status; + _cmdResponseLen = 0; } else if (eventHdr->evt == EVT_NUM_COMP_PKTS) { uint8_t numHandles = pdata[sizeof(HCIEventHdr)]; uint16_t* data = (uint16_t*)&pdata[sizeof(HCIEventHdr) + sizeof(numHandles)]; @@ -519,6 +611,28 @@ void HCIClass::handleEventPkt(uint8_t /*plen*/, uint8_t pdata[]) leConnectionComplete->supervisionTimeout, leConnectionComplete->masterClockAccuracy); } + } else if (leMetaHeader->subevent == EVT_LE_ADVERTISING_REPORT) { + struct __attribute__ ((packed)) EvtLeAdvertisingReport { + uint8_t status; + uint8_t type; + uint8_t peerBdaddrType; + uint8_t peerBdaddr[6]; + uint8_t eirLength; + uint8_t eirData[31]; + } *leAdvertisingReport = (EvtLeAdvertisingReport*)&pdata[sizeof(HCIEventHdr) + sizeof(LeMetaEventHeader)]; + + if (leAdvertisingReport->status == 0x01) { + // last byte is RSSI + int8_t rssi = leAdvertisingReport->eirData[leAdvertisingReport->eirLength]; + + GAP.handleLeAdvertisingReport(leAdvertisingReport->type, + leAdvertisingReport->peerBdaddrType, + leAdvertisingReport->peerBdaddr, + leAdvertisingReport->eirLength, + leAdvertisingReport->eirData, + rssi); + + } } } } diff --git a/src/utility/HCI.h b/src/utility/HCI.h index 8235c1a6..905cef7c 100644 --- a/src/utility/HCI.h +++ b/src/utility/HCI.h @@ -52,8 +52,17 @@ class HCIClass { int leSetAdvertisingData(uint8_t length, uint8_t data[]); int leSetScanResponseData(uint8_t length, uint8_t data[]); int leSetAdvertiseEnable(uint8_t enable); + int leSetScanParameters(uint8_t type, uint16_t interval, uint16_t window, + uint8_t ownBdaddrType, uint8_t filter); + int leSetScanEnable(uint8_t enabled, uint8_t duplicates); + int leCreateConn(uint16_t interval, uint16_t window, uint8_t initiatorFilter, + uint8_t peerBdaddrType, uint8_t peerBdaddr[6], uint8_t ownBdaddrType, + uint16_t minInterval, uint16_t maxInterval, uint16_t latency, + uint16_t supervisionTimeout, uint16_t minCeLength, uint16_t maxCeLength); int leConnUpdate(uint16_t handle, uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t supervisionTimeout); + int leCancelConn(); + int sendAclPkt(uint16_t handle, uint8_t cid, uint8_t plen, void* data);