From 75069b93eabd7944302ea42c730e0a7d597abeef Mon Sep 17 00:00:00 2001 From: Roberto Date: Mon, 31 May 2021 18:46:43 +0200 Subject: [PATCH 01/17] First stable client connection. TODO: autoReconnect and ConnectTo --- README.md | 2 + src/hardware/BLEMIDI_Client_ESP32.h | 355 ++++++++++++++++++++++++++++ 2 files changed, 357 insertions(+) create mode 100644 src/hardware/BLEMIDI_Client_ESP32.h diff --git a/README.md b/README.md index c93bc1a..07bac14 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Arduino BLE-MIDI Transport [![arduino-library-badge](https://www.ardu-badge.com/badge/BLE-MIDI.svg?)](https://www.ardu-badge.com/BLE-MIDI) +This fork is for Client nimBLE transport layer. + This library implements the BLE-MIDI transport layer for the [FortySevenEffects Arduino MIDI Library](https://github.com/FortySevenEffects/arduino_midi_library) ## Installation diff --git a/src/hardware/BLEMIDI_Client_ESP32.h b/src/hardware/BLEMIDI_Client_ESP32.h new file mode 100644 index 0000000..bbe9f1d --- /dev/null +++ b/src/hardware/BLEMIDI_Client_ESP32.h @@ -0,0 +1,355 @@ +#pragma once + +// Headers for ESP32 nimBLE +#include + +static NimBLEAdvertisedDevice *advDevice; +static bool doConnect = false; +static bool scanDone = false; +/** Define a class to handle the callbacks when advertisments are received */ +class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks +{ + + void onResult(NimBLEAdvertisedDevice *advertisedDevice) + { + Serial.print("Advertised Device found: "); + Serial.println(advertisedDevice->toString().c_str()); + if (advertisedDevice->isAdvertisingService(NimBLEUUID(SERVICE_UUID))) + { + Serial.println("Found Our Service"); + /** stop scan before connecting */ + NimBLEDevice::getScan()->stop(); + /** Save the device reference in a global for the client to use*/ + advDevice = advertisedDevice; + /** Ready to connect now */ + doConnect = true; + } + else + { + doConnect = false; + } + }; +}; + +void scanEndedCB(NimBLEScanResults results); + +BEGIN_BLEMIDI_NAMESPACE + +class BLEMIDI_Client_ESP32 +{ +private: + BLEClient *_client = nullptr; + BLEAdvertising *_advertising = nullptr; + BLECharacteristic *_characteristic = nullptr; + + BLEMIDI_Transport *_bleMidiTransport = nullptr; + + friend class AdvertisedDeviceCallbacks; + +protected: + QueueHandle_t mRxQueue; + +public: + BLEMIDI_Client_ESP32() + { + } + + bool begin(const char *, BLEMIDI_Transport *); + + void write(uint8_t *data, uint8_t length) + { + _characteristic->setValue(data, length); + //_characteristic->notify(); + } + + bool available(byte *pvBuffer) + { + // return 1 byte from the Queue + return xQueueReceive(mRxQueue, (void *)pvBuffer, 0); // return immediately when the queue is empty + } + + void add(byte value) + { + // called from BLE-MIDI, to add it to a buffer here + xQueueSend(mRxQueue, &value, portMAX_DELAY); + } + + void receive(uint8_t *buffer, size_t length) + { + // forward the buffer so it can be parsed + _bleMidiTransport->receive(buffer, length); + } + + void connected() + { + if (_bleMidiTransport->_connectedCallback) + _bleMidiTransport->_connectedCallback(); + } + + void disconnected() + { + if (_bleMidiTransport->_disconnectedCallback) + _bleMidiTransport->_disconnectedCallback(); + } + + void notifyCB(NimBLERemoteCharacteristic *pRemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify); +}; + +class MyClientCallbacks : public BLEClientCallbacks +{ +public: + MyClientCallbacks(BLEMIDI_Client_ESP32 *bluetoothEsp32) + : _bluetoothEsp32(bluetoothEsp32) + { + } + +protected: + BLEMIDI_Client_ESP32 *_bluetoothEsp32 = nullptr; + + void onConnect(BLEClient *pClient) + { + Serial.println("Connected"); + /** After connection we should change the parameters if we don't need fast response times. + * These settings are 150ms interval, 0 latency, 450ms timout. + * Timeout should be a multiple of the interval, minimum is 100ms. + * I find a multiple of 3-5 * the interval works best for quick response/reconnect. + * Min interval: 120 * 1.25ms = 150, Max interval: 120 * 1.25ms = 150, 0 latency, 60 * 10ms = 600ms timeout + */ + pClient->updateConnParams(6, 32, 0, 40); + if (_bluetoothEsp32) + _bluetoothEsp32->connected(); + }; + + void onDisconnect(BLEClient *pClient) + { + Serial.print(pClient->getPeerAddress().toString().c_str()); + Serial.println(" Disconnected - Starting scan"); + + + + if (_bluetoothEsp32){ + _bluetoothEsp32->disconnected(); + } + + NimBLEDevice::getScan()->start(3,scanEndedCB); + } + bool onConnParamsUpdateRequest(NimBLEClient *pClient, const ble_gap_upd_params *params) + { + if (params->itvl_min < 24) + { /** 1.25ms units */ + return false; + } + else if (params->itvl_max > 40) + { /** 1.25ms units */ + return false; + } + else if (params->latency > 2) + { /** Number of intervals allowed to skip */ + return false; + } + else if (params->supervision_timeout > 100) + { /** 10ms units */ + return false; + } + + return true; + }; +}; + +/** Notification / Indication receiving handler callback */ +void BLEMIDI_Client_ESP32::notifyCB(NimBLERemoteCharacteristic *pRemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) +{ + // std::string str = (isNotify == true) ? "Notification" : "Indication"; + // str += " from "; + // /** NimBLEAddress and NimBLEUUID have std::string operators */ + // str += std::string(pRemoteCharacteristic->getRemoteService()->getClient()->getPeerAddress()); + // str += ": Service = " + std::string(pRemoteCharacteristic->getRemoteService()->getUUID()); + // str += ", Characteristic = " + std::string(pRemoteCharacteristic->getUUID()); + // str += ", Value = " + std::string((char *)pData, length); + // Serial.println(str.c_str()); + + receive(pData, length); +} + +bool BLEMIDI_Client_ESP32::begin(const char *deviceName, BLEMIDI_Transport *bleMidiTransport) +{ + + using namespace std::placeholders; + _bleMidiTransport = bleMidiTransport; + Serial.println("CreateBLE"); + std::string stringDeviceName(deviceName); + NimBLEDevice::init(stringDeviceName); + Serial.println("CREATED"); + + // To communicate between the 2 cores. + // Core_0 runs here, core_1 runs the BLE stack + mRxQueue = xQueueCreate(64, sizeof(uint8_t)); // TODO Settings::MaxBufferSize + Serial.println("QUEUE"); + /** Set the IO capabilities of the device, each option will trigger a different pairing method. + * BLE_HS_IO_KEYBOARD_ONLY - Passkey pairing + * BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing + * BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing + */ + //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_KEYBOARD_ONLY); // use passkey + //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_YESNO); //use numeric comparison + + /** 2 different ways to set security - both calls achieve the same result. + * no bonding, no man in the middle protection, secure connections. + * + * These are the default values, only shown here for demonstration. + */ + //NimBLEDevice::setSecurityAuth(false, false, true); + NimBLEDevice::setSecurityAuth(/*BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM | */ BLE_SM_PAIR_AUTHREQ_SC); + Serial.println("Security"); + /** Optional: set the transmit power, default is 3db */ + NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ + Serial.println("Power"); + /** Optional: set any devices you don't want to get advertisments from */ + // NimBLEDevice::addIgnored(NimBLEAddress ("aa:bb:cc:dd:ee:ff")); + + // Retrieve a Scanner and set the callback we want to use to be informed when we + // have detected a new device. Specify that we want active scanning and start the + // scan to run for 5 seconds. + + NimBLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks()); + pBLEScan->setInterval(150); + pBLEScan->setWindow(50); + pBLEScan->setActiveScan(true); + //doScan = true; + Serial.println("Scan"); + pBLEScan->start(10, scanEndedCB); + Serial.println("Scan2"); + + while (!doConnect) + { + + if (!pBLEScan->isScanning()) + { + Serial.println("RESCAN"); + pBLEScan->start(10, scanEndedCB); + } + vTaskDelay(100); + } + Serial.println("LOOP OUT"); + + _client = BLEDevice::createClient(); + + _client->setClientCallbacks(new MyClientCallbacks(this), false); + /** Set initial connection parameters: These settings are 15ms interval, 0 latency, 120ms timout. + * These settings are safe for 3 clients to connect reliably, can go faster if you have less + * connections. Timeout should be a multiple of the interval, minimum is 100ms. + * Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 51 * 10ms = 510ms timeout + */ + _client->setConnectionParams(12, 12, 0, 51); + /** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */ + _client->setConnectTimeout(5); + Serial.println("#Connection"); + if (!_client->connect(advDevice)) + { + /** Created a client but failed to connect, don't need to keep it as it has no data */ + NimBLEDevice::deleteClient(_client); + Serial.println("Failed to connect, deleted client"); + return false; + } + vTaskDelay(100); + + if (!_client->isConnected()) + { + if (!_client->connect(advDevice)) + { + Serial.println("Failed to connect"); + return false; + } + } + else + { + + Serial.print("Connected to: "); + Serial.println(_client->getPeerAddress().toString().c_str()); + Serial.print("RSSI: "); + Serial.println(_client->getRssi()); + } + /** Now we can read/write/subscribe the charateristics of the services we are interested in */ + NimBLERemoteService *pSvc = nullptr; + NimBLERemoteCharacteristic *pChr = nullptr; + //NimBLERemoteDescriptor *pDsc = nullptr; + + pSvc = _client->getService(SERVICE_UUID); + if (pSvc) + { /** make sure it's not null */ + pChr = pSvc->getCharacteristic(CHARACTERISTIC_UUID); + + if (pChr) + { /** make sure it's not null */ + if (pChr->canRead()) + { + Serial.print(pChr->getUUID().toString().c_str()); + Serial.print(" Value: "); + Serial.println(pChr->readValue().c_str()); + } + + /** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe(). + * Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false. + * Unsubscribe parameter defaults are: response=false. + */ + if (pChr->canNotify()) + { + Serial.println("CAN NOTIFY"); + if (!pChr->subscribe(true, std::bind(&BLEMIDI_Client_ESP32::notifyCB, this, _1, _2, _3, _4))) + { + Serial.println("error"); + /** Disconnect if subscribe failed */ + _client->disconnect(); + return false; + } + } + else if (pChr->canIndicate()) + { + /** Send false as first argument to subscribe to indications instead of notifications */ + //if(!pChr->registerForNotify(notifyCB, false)) { + if (!pChr->subscribe(false, std::bind(&BLEMIDI_Client_ESP32::notifyCB, this, _1, _2, _3, _4))) + { + /** Disconnect if subscribe failed */ + _client->disconnect(); + return false; + } + } + } + } + else + { + Serial.println("MIDI service not found."); + return false; + } + + return true; +} + +END_BLEMIDI_NAMESPACE + +/** Callback to process the results of the last scan or restart it */ +void scanEndedCB(NimBLEScanResults results) +{ + Serial.println("Scan Ended"); + if (!doConnect) + { + scanDone = true; + } + else + { + scanDone = false; + NimBLEDevice::getScan()->start(3,scanEndedCB); + } +} + +/*! \brief Create an instance for ESP32 named + */ +#define BLEMIDI_CREATE_INSTANCE(DeviceName, Name) \ + BLEMIDI_NAMESPACE::BLEMIDI_Transport BLE##Name(DeviceName); \ + MIDI_NAMESPACE::MidiInterface, BLEMIDI_NAMESPACE::MySettings> Name((BLEMIDI_NAMESPACE::BLEMIDI_Transport &)BLE##Name); + +/*! \brief Create a default instance for ESP32 named BLE-MIDI + */ +#define BLEMIDI_CREATE_DEFAULT_INSTANCE() \ + BLEMIDI_CREATE_INSTANCE("Esp32-NimBLE-MIDI", MIDI) From b8831e4d3efb9d02cf1c80b38d94fd93d55c6bad Mon Sep 17 00:00:00 2001 From: Roberto Date: Sat, 5 Jun 2021 14:19:52 +0200 Subject: [PATCH 02/17] Reconnect mechanism done in avaliable funcion. Avaliable is called in MIDI.read() in funtional code. FIXED: Write method now works. TODO: Clear debug comments and structure the code. Change scanDone and doConnect global variables inside AdviceCB class. BUG: If input buffer is completly full it is not possible send or recive anything --- src/hardware/BLEMIDI_Client_ESP32.h | 358 ++++++++++++++++++---------- 1 file changed, 233 insertions(+), 125 deletions(-) diff --git a/src/hardware/BLEMIDI_Client_ESP32.h b/src/hardware/BLEMIDI_Client_ESP32.h index bbe9f1d..2ad8741 100644 --- a/src/hardware/BLEMIDI_Client_ESP32.h +++ b/src/hardware/BLEMIDI_Client_ESP32.h @@ -9,9 +9,14 @@ static bool scanDone = false; /** Define a class to handle the callbacks when advertisments are received */ class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { +public: + int test = 0; +protected: void onResult(NimBLEAdvertisedDevice *advertisedDevice) { + test++; + Serial.println(test); Serial.print("Advertised Device found: "); Serial.println(advertisedDevice->toString().c_str()); if (advertisedDevice->isAdvertisingService(NimBLEUUID(SERVICE_UUID))) @@ -40,11 +45,17 @@ class BLEMIDI_Client_ESP32 private: BLEClient *_client = nullptr; BLEAdvertising *_advertising = nullptr; - BLECharacteristic *_characteristic = nullptr; + BLERemoteCharacteristic *_characteristic = nullptr; + BLERemoteService *pSvc = nullptr; + //NimBLERemoteDescriptor *pDsc = nullptr; BLEMIDI_Transport *_bleMidiTransport = nullptr; friend class AdvertisedDeviceCallbacks; + friend class MyClientCallbacks; + friend class MIDI_NAMESPACE::MidiInterface, MySettings>; + + AdvertisedDeviceCallbacks myAdvCB; protected: QueueHandle_t mRxQueue; @@ -58,17 +69,44 @@ public: void write(uint8_t *data, uint8_t length) { - _characteristic->setValue(data, length); - //_characteristic->notify(); + _characteristic->writeValue(data, length, true); + myAdvCB.test++; } bool available(byte *pvBuffer) { + if (_client == nullptr || !_client->isConnected()) + { + //Serial.println("No Conectado"); + if (doConnect) + { + doConnect = false; + Serial.println("intentar conexion"); + if (connect()) + { + Serial.println("reconnected"); + } + else + { + Serial.println("Rescan"); + scanDone=false; + NimBLEDevice::getScan()->start(3, scanEndedCB); + } + } + else if (scanDone) + { + scanDone=false; + Serial.println("Rescan 2"); + NimBLEDevice::getScan()->start(3, scanEndedCB); + } + } + // return 1 byte from the Queue return xQueueReceive(mRxQueue, (void *)pvBuffer, 0); // return immediately when the queue is empty } - void add(byte value) + void + add(byte value) { // called from BLE-MIDI, to add it to a buffer here xQueueSend(mRxQueue, &value, portMAX_DELAY); @@ -80,10 +118,16 @@ public: _bleMidiTransport->receive(buffer, length); } + void connectCallbacks(MIDI_NAMESPACE::MidiInterface, MySettings> *MIDIcallback); + void connected() { + Serial.println("!!"); if (_bleMidiTransport->_connectedCallback) + { + Serial.println("@"); _bleMidiTransport->_connectedCallback(); + } } void disconnected() @@ -93,6 +137,9 @@ public: } void notifyCB(NimBLERemoteCharacteristic *pRemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify); + + void scan(); + bool connect(); }; class MyClientCallbacks : public BLEClientCallbacks @@ -108,30 +155,33 @@ protected: void onConnect(BLEClient *pClient) { - Serial.println("Connected"); + Serial.println("##Connected##"); /** After connection we should change the parameters if we don't need fast response times. * These settings are 150ms interval, 0 latency, 450ms timout. * Timeout should be a multiple of the interval, minimum is 100ms. * I find a multiple of 3-5 * the interval works best for quick response/reconnect. * Min interval: 120 * 1.25ms = 150, Max interval: 120 * 1.25ms = 150, 0 latency, 60 * 10ms = 600ms timeout */ + //pClient->updateConnParams pClient->updateConnParams(6, 32, 0, 40); if (_bluetoothEsp32) + { + Serial.println("??"); _bluetoothEsp32->connected(); + } }; void onDisconnect(BLEClient *pClient) { Serial.print(pClient->getPeerAddress().toString().c_str()); Serial.println(" Disconnected - Starting scan"); - - - - if (_bluetoothEsp32){ + + if (_bluetoothEsp32) + { _bluetoothEsp32->disconnected(); } - NimBLEDevice::getScan()->start(3,scanEndedCB); + NimBLEDevice::getScan()->start(3, scanEndedCB); } bool onConnParamsUpdateRequest(NimBLEClient *pClient, const ble_gap_upd_params *params) { @@ -171,11 +221,168 @@ void BLEMIDI_Client_ESP32::notifyCB(NimBLERemoteCharacteristic *pRemoteCharacter receive(pData, length); } +void BLEMIDI_Client_ESP32::scan() +{ + // Retrieve a Scanner and set the callback we want to use to be informed when we + // have detected a new device. Specify that we want active scanning and start the + // scan to run for 5 seconds. + NimBLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(&myAdvCB); + pBLEScan->setInterval(1500); + pBLEScan->setWindow(500); + pBLEScan->setActiveScan(true); + //doScan = true; + Serial.println("Scanning..."); + pBLEScan->start(10, scanEndedCB); +}; + +bool BLEMIDI_Client_ESP32::connect() +{ + Serial.println("TryConnection..."); + using namespace std::placeholders; + /** Check if we have a client we should reuse first **/ + if (NimBLEDevice::getClientListSize()) + { + /** Special case when we already know this device, we send false as the + * second argument in connect() to prevent refreshing the service database. + * This saves considerable time and power. + */ + _client = NimBLEDevice::getClientByPeerAddress(advDevice->getAddress()); + if (_client) + { + if (!_client->connect(advDevice, false)) + { + Serial.println("Reconnect failed"); + return false; + } + Serial.println("Reconnected client"); + _client->setConnectionParams(12, 12, 0, 51); + if (_characteristic->canNotify()) + { + Serial.println("CAN NOTIFY"); + if (!_characteristic->subscribe(true, std::bind(&BLEMIDI_Client_ESP32::notifyCB, this, _1, _2, _3, _4))) + { + Serial.println("error"); + /** Disconnect if subscribe failed */ + _client->disconnect(); + return false; + } + } + } + /** We don't already have a client that knows this device, + * we will check for a client that is disconnected that we can use. + */ + else + { + _client = NimBLEDevice::getDisconnectedClient(); + } + } + + /** No client to reuse? Create a new one. */ + if (!_client) + { + if (NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) + { + Serial.println("Max clients reached - no more connections available"); + return false; + } + _client = BLEDevice::createClient(); + + _client->setClientCallbacks(new MyClientCallbacks(this), false); + /** Set initial connection parameters: These settings are 15ms interval, 0 latency, 120ms timout. + * These settings are safe for 3 clients to connect reliably, can go faster if you have less + * connections. Timeout should be a multiple of the interval, minimum is 100ms. + * Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 51 * 10ms = 510ms timeout + */ + _client->setConnectionParams(12, 12, 0, 51); + /** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */ + _client->setConnectTimeout(5); + Serial.println("#Connection"); + if (!_client->connect(advDevice)) + { + /** Created a client but failed to connect, don't need to keep it as it has no data */ + + NimBLEDevice::deleteClient(_client); + _client=nullptr; + Serial.println("Failed to connect, deleted client"); + return false; + } + vTaskDelay(100); + + if (!_client->isConnected()) + { + if (!_client->connect(advDevice)) + { + Serial.println("Failed to connect"); + return false; + } + } + else + { + + Serial.print("Connected to: "); + Serial.println(_client->getPeerAddress().toString().c_str()); + Serial.print("RSSI: "); + Serial.println(_client->getRssi()); + } + /** Now we can read/write/subscribe the charateristics of the services we are interested in */ + + pSvc = _client->getService(SERVICE_UUID); + if (pSvc) + { /** make sure it's not null */ + _characteristic = pSvc->getCharacteristic(CHARACTERISTIC_UUID); + + if (_characteristic) + { /** make sure it's not null */ + // if (_characteristic->canRead()) + // { + // Serial.print(_characteristic->getUUID().toString().c_str()); + // Serial.print(" Value: "); + // Serial.println(_characteristic->readValue().c_str()); + // } + + /** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe(). + * Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false. + * Unsubscribe parameter defaults are: response=false. + */ + if (_characteristic->canNotify()) + { + Serial.println("CAN NOTIFY"); + if (!_characteristic->subscribe(true, std::bind(&BLEMIDI_Client_ESP32::notifyCB, this, _1, _2, _3, _4))) + { + Serial.println("error"); + /** Disconnect if subscribe failed */ + _client->disconnect(); + return false; + } + } + // else if (_characteristic->canIndicate()) + // { + // /** Send false as first argument to subscribe to indications instead of notifications */ + // //if(!pChr->registerForNotify(notifyCB, false)) { + // if (!_characteristic->subscribe(false, std::bind(&BLEMIDI_Client_ESP32::notifyCB, this, _1, _2, _3, _4))) + // { + // /** Disconnect if subscribe failed */ + // _client->disconnect(); + // return false; + // } + // } + } + } + else + { + Serial.println("MIDI service not found."); + return false; + } + } + return true; +}; + bool BLEMIDI_Client_ESP32::begin(const char *deviceName, BLEMIDI_Transport *bleMidiTransport) { - using namespace std::placeholders; _bleMidiTransport = bleMidiTransport; + Serial.println("CreateBLE"); std::string stringDeviceName(deviceName); NimBLEDevice::init(stringDeviceName); @@ -207,121 +414,22 @@ bool BLEMIDI_Client_ESP32::begin(const char *deviceName, BLEMIDI_TransportsetAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks()); - pBLEScan->setInterval(150); - pBLEScan->setWindow(50); - pBLEScan->setActiveScan(true); - //doScan = true; - Serial.println("Scan"); - pBLEScan->start(10, scanEndedCB); - Serial.println("Scan2"); + //connect(); - while (!doConnect) - { + // while (!doConnect) + // { - if (!pBLEScan->isScanning()) - { - Serial.println("RESCAN"); - pBLEScan->start(10, scanEndedCB); - } - vTaskDelay(100); - } - Serial.println("LOOP OUT"); - - _client = BLEDevice::createClient(); - - _client->setClientCallbacks(new MyClientCallbacks(this), false); - /** Set initial connection parameters: These settings are 15ms interval, 0 latency, 120ms timout. - * These settings are safe for 3 clients to connect reliably, can go faster if you have less - * connections. Timeout should be a multiple of the interval, minimum is 100ms. - * Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 51 * 10ms = 510ms timeout - */ - _client->setConnectionParams(12, 12, 0, 51); - /** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */ - _client->setConnectTimeout(5); - Serial.println("#Connection"); - if (!_client->connect(advDevice)) - { - /** Created a client but failed to connect, don't need to keep it as it has no data */ - NimBLEDevice::deleteClient(_client); - Serial.println("Failed to connect, deleted client"); - return false; - } - vTaskDelay(100); - - if (!_client->isConnected()) - { - if (!_client->connect(advDevice)) - { - Serial.println("Failed to connect"); - return false; - } - } - else - { - - Serial.print("Connected to: "); - Serial.println(_client->getPeerAddress().toString().c_str()); - Serial.print("RSSI: "); - Serial.println(_client->getRssi()); - } - /** Now we can read/write/subscribe the charateristics of the services we are interested in */ - NimBLERemoteService *pSvc = nullptr; - NimBLERemoteCharacteristic *pChr = nullptr; - //NimBLERemoteDescriptor *pDsc = nullptr; - - pSvc = _client->getService(SERVICE_UUID); - if (pSvc) - { /** make sure it's not null */ - pChr = pSvc->getCharacteristic(CHARACTERISTIC_UUID); - - if (pChr) - { /** make sure it's not null */ - if (pChr->canRead()) - { - Serial.print(pChr->getUUID().toString().c_str()); - Serial.print(" Value: "); - Serial.println(pChr->readValue().c_str()); - } - - /** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe(). - * Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false. - * Unsubscribe parameter defaults are: response=false. - */ - if (pChr->canNotify()) - { - Serial.println("CAN NOTIFY"); - if (!pChr->subscribe(true, std::bind(&BLEMIDI_Client_ESP32::notifyCB, this, _1, _2, _3, _4))) - { - Serial.println("error"); - /** Disconnect if subscribe failed */ - _client->disconnect(); - return false; - } - } - else if (pChr->canIndicate()) - { - /** Send false as first argument to subscribe to indications instead of notifications */ - //if(!pChr->registerForNotify(notifyCB, false)) { - if (!pChr->subscribe(false, std::bind(&BLEMIDI_Client_ESP32::notifyCB, this, _1, _2, _3, _4))) - { - /** Disconnect if subscribe failed */ - _client->disconnect(); - return false; - } - } - } - } - else - { - Serial.println("MIDI service not found."); - return false; - } + // if (!pBLEScan->isScanning()) + // { + // Serial.println("RESCAN"); + // pBLEScan->start(10, scanEndedCB); + // } + // vTaskDelay(100); + // } + // Serial.println("LOOP OUT"); return true; } @@ -337,9 +445,9 @@ void scanEndedCB(NimBLEScanResults results) scanDone = true; } else - { + { scanDone = false; - NimBLEDevice::getScan()->start(3,scanEndedCB); + NimBLEDevice::getScan()->start(3, scanEndedCB); } } From 65d96380b8c23d98f32b2c43b5a1fe2b42f23fc0 Mon Sep 17 00:00:00 2001 From: RobertoHE Date: Tue, 8 Jun 2021 16:12:04 +0200 Subject: [PATCH 03/17] test --- src/hardware/BLEMIDI_Client_ESP32.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hardware/BLEMIDI_Client_ESP32.h b/src/hardware/BLEMIDI_Client_ESP32.h index 2ad8741..504957f 100644 --- a/src/hardware/BLEMIDI_Client_ESP32.h +++ b/src/hardware/BLEMIDI_Client_ESP32.h @@ -53,7 +53,7 @@ private: friend class AdvertisedDeviceCallbacks; friend class MyClientCallbacks; - friend class MIDI_NAMESPACE::MidiInterface, MySettings>; + friend class MIDI_NAMESPACE::MidiInterface, MySettings>;// AdvertisedDeviceCallbacks myAdvCB; From b830648af43e13d5c937655ac220187ccc187a61 Mon Sep 17 00:00:00 2001 From: Roberto Date: Sat, 3 Jul 2021 11:08:22 +0200 Subject: [PATCH 04/17] =?UTF-8?q?Limpieza=20y=20estructuracion=20del=20cod?= =?UTF-8?q?igo.=20Se=20han=20a=C3=B1adido=20defines=20al=20inicio=20del=20?= =?UTF-8?q?fichero=20para=20que=20modificar=20las=20caracteristicas=20de?= =?UTF-8?q?=20seguridad,=20nombre=20y=20par=C3=A1metros=20de=20comunicacio?= =?UTF-8?q?n.=20TODO:=20No=20depender=20del=20m=C3=A9todo=20read(),=20que?= =?UTF-8?q?=20salten=20los=20callback=20de=20manera=20autom=C3=A1tica?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hardware/BLEMIDI_Client_ESP32.h | 673 ++++++++++++++++------------ 1 file changed, 392 insertions(+), 281 deletions(-) diff --git a/src/hardware/BLEMIDI_Client_ESP32.h b/src/hardware/BLEMIDI_Client_ESP32.h index 2ad8741..ce86493 100644 --- a/src/hardware/BLEMIDI_Client_ESP32.h +++ b/src/hardware/BLEMIDI_Client_ESP32.h @@ -1,45 +1,186 @@ #pragma once +/* +############################################# +########### USER DEFINE BEGINING ############ +####### Only modify these parameters ######## +############################################# +*/ + +/* +##### BLE DEVICE NAME ##### +*/ + +/** + * Set always the same name independiently of name server + */ +//#define BLEMIDI_CLIENT_FIXED_NAME "BleMidiClient" + +#ifndef BLEMIDI_CLIENT_FIXED_NAME //Not modify +/** + * BLE name is composed by the nex way when client try to connect to speccific server: + * BLEMIDI_CLIENT_NAME_PREFIX + + BLEMIDI_CLIENT_NAME_SUBFIX + * + * example: + * BLEMIDI_CLIENT_NAME_PREFIX "Client-" + * "AX-Edge" + * BLEMIDI_CLIENT_NAME_SUBFIX "-Midi1" + * + * Result: "Client-AX-Edge-Midi1" + */ +#define BLEMIDI_CLIENT_NAME_PREFIX "C-" +#define BLEMIDI_CLIENT_NAME_SUBFIX "" + +/** + * BLE name when it tries to connect to first midi server found. + */ +#define BLEMIDI_CLIENT_DEFAULT_NAME "BLEMIDI-CLIENT" +#endif //Not modify + +/* +###### SECURITY ##### +*/ + +/** Set the IO capabilities of the device, each option will trigger a different pairing method. + * BLE_HS_IO_KEYBOARD_ONLY - Passkey pairing + * BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing + * BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing + */ +#define BLEMIDI_CLIENT_SECURITY_CAP BLE_HS_IO_NO_INPUT_OUTPUT + +/** Set security method. + * bonding + * man in the middle protection + * pair. secure connections + * + * More info in nimBLE lib + * + * Uncomment that you need + * These are the default values. + */ +//#define BLEMIDI_CLIENT_BOND +//#define BLEMIDI_CLIENT_MITM +#define BLEMIDI_CLIENT_PAIR + + +/** + * This callback funtion defines what it must to do when server requieres PassKey. + * Add your custom code here. + */ +static uint32_t userOnPassKeyRequest() +{ + //FILL WITH YOUR CUSTOM AUTH METHOD or PASSKEY + //FOR EXAMPLE: + uint32_t passkey = 123456; + + //Serial.println("Client Passkey Request"); + + /** return the passkey to send to the server */ + return passkey; +}; + +/* +###### BLE COMMUNICATION PARAMS ###### +*/ + /** Set connection parameters: + * If only use one connection, put recomended BLE server param communication (you may scan it ussing "nRF Connect" app or other similar). + * + * If you use more than one connection use, for example, settings like 15ms interval, 0 latency, 120ms timout. + * These settings may be safe for 3 clients to connect reliably, can go faster if you have less + * connections. + * + * Min interval (unit: 1.25ms): 12 * 1.25ms = 15 ms, + * Max interval (unit: 1.25ms): 12 * 1.25ms = 15, + * 0 latency (Number of intervals allowed to skip), + * TimeOut (unit: 10ms) 51 * 10ms = 510ms. Timeout should be minimum 100ms. + */ +#define BLEMIDI_CLIENT_COMM_MIN_INTERVAL 6 // 7.5ms +#define BLEMIDI_CLIENT_COMM_MAX_INTERVAL 35 // 40ms +#define BLEMIDI_CLIENT_COMM_LATENCY 0 +#define BLEMIDI_CLIENT_COMM_TIMEOUT 400 //4000ms + + +/* +############################################# +############ USER DEFINES END ############### +############################################# +*/ + // Headers for ESP32 nimBLE #include -static NimBLEAdvertisedDevice *advDevice; -static bool doConnect = false; -static bool scanDone = false; +BEGIN_BLEMIDI_NAMESPACE + +#ifdef BLEMIDI_CLIENT_BOND +#define BLEMIDI_CLIENT_BOND_DUMMY BLE_SM_PAIR_AUTHREQ_BOND +#else +#define BLEMIDI_CLIENT_BOND_DUMMY 0x00 +#endif + +#ifdef BLEMIDI_CLIENT_MITM +#define BLEMIDI_CLIENT_MITM_DUMMY BLE_SM_PAIR_AUTHREQ_MITM +#else +#define BLEMIDI_CLIENT_MITM_DUMMY 0x00 +#endif + +#ifdef BLEMIDI_CLIENT_PAIR +#define BLEMIDI_CLIENT_PAIR_DUMMY BLE_SM_PAIR_AUTHREQ_SC +#else +#define BLEMIDI_CLIENT_PAIR_DUMMY 0x00 +#endif + +/** Set security method. + * bonding + * man in the middle protection + * pair. secure connections + * + * More info in nimBLE lib + */ +#define BLEMIDI_CLIENT_SECURITY_AUTH (BLEMIDI_CLIENT_BOND_DUMMY | BLEMIDI_CLIENT_MITM_DUMMY | BLEMIDI_CLIENT_PAIR_DUMMY) + /** Define a class to handle the callbacks when advertisments are received */ class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { public: - int test = 0; + NimBLEAdvertisedDevice advDevice; + bool doConnect = false; + bool scanDone = false; + bool specificTarget = false; + bool enableConnection = false; + std::string nameTarget; protected: void onResult(NimBLEAdvertisedDevice *advertisedDevice) { - test++; - Serial.println(test); - Serial.print("Advertised Device found: "); - Serial.println(advertisedDevice->toString().c_str()); - if (advertisedDevice->isAdvertisingService(NimBLEUUID(SERVICE_UUID))) + if (enableConnection) //not begin() or end() { - Serial.println("Found Our Service"); - /** stop scan before connecting */ - NimBLEDevice::getScan()->stop(); - /** Save the device reference in a global for the client to use*/ - advDevice = advertisedDevice; - /** Ready to connect now */ - doConnect = true; - } - else - { - doConnect = false; + Serial.print("Advertised Device found: "); + Serial.println(advertisedDevice->toString().c_str()); + if (advertisedDevice->isAdvertisingService(NimBLEUUID(SERVICE_UUID))) + { + Serial.println("Found MIDI Service"); + if (!specificTarget || (advertisedDevice->getName() == nameTarget.c_str() || advertisedDevice->getAddress() == nameTarget)) + { + /** Ready to connect now */ + doConnect = true; + /** Save the device reference in a global for the client to use*/ + advDevice = *advertisedDevice; + /** stop scan before connecting */ + NimBLEDevice::getScan()->stop(); + } + } + else + { + doConnect = false; + } } }; }; +/** Define a funtion to handle the callbacks when scan ends */ void scanEndedCB(NimBLEScanResults results); -BEGIN_BLEMIDI_NAMESPACE - +/** Define the class that perform Client Midi (nimBLE) */ class BLEMIDI_Client_ESP32 { private: @@ -47,10 +188,11 @@ private: BLEAdvertising *_advertising = nullptr; BLERemoteCharacteristic *_characteristic = nullptr; BLERemoteService *pSvc = nullptr; - //NimBLERemoteDescriptor *pDsc = nullptr; BLEMIDI_Transport *_bleMidiTransport = nullptr; + bool specificTarget = false; + friend class AdvertisedDeviceCallbacks; friend class MyClientCallbacks; friend class MIDI_NAMESPACE::MidiInterface, MySettings>; @@ -67,51 +209,31 @@ public: bool begin(const char *, BLEMIDI_Transport *); + bool end() + { + myAdvCB.enableConnection = false; + xQueueReset(mRxQueue); + _client->disconnect(); + _client = nullptr; + + return !_client->isConnected(); + } + void write(uint8_t *data, uint8_t length) { - _characteristic->writeValue(data, length, true); - myAdvCB.test++; + if (myAdvCB.enableConnection) + _characteristic->writeValue(data, length, true); } - bool available(byte *pvBuffer) - { - if (_client == nullptr || !_client->isConnected()) - { - //Serial.println("No Conectado"); - if (doConnect) - { - doConnect = false; - Serial.println("intentar conexion"); - if (connect()) - { - Serial.println("reconnected"); - } - else - { - Serial.println("Rescan"); - scanDone=false; - NimBLEDevice::getScan()->start(3, scanEndedCB); - } - } - else if (scanDone) - { - scanDone=false; - Serial.println("Rescan 2"); - NimBLEDevice::getScan()->start(3, scanEndedCB); - } - } + bool available(byte *pvBuffer); - // return 1 byte from the Queue - return xQueueReceive(mRxQueue, (void *)pvBuffer, 0); // return immediately when the queue is empty - } - - void - add(byte value) + void add(byte value) { // called from BLE-MIDI, to add it to a buffer here xQueueSend(mRxQueue, &value, portMAX_DELAY); } +protected: void receive(uint8_t *buffer, size_t length) { // forward the buffer so it can be parsed @@ -122,10 +244,8 @@ public: void connected() { - Serial.println("!!"); if (_bleMidiTransport->_connectedCallback) { - Serial.println("@"); _bleMidiTransport->_connectedCallback(); } } @@ -142,6 +262,7 @@ public: bool connect(); }; +/** Define the class that perform interrupts callbacks */ class MyClientCallbacks : public BLEClientCallbacks { public: @@ -153,51 +274,51 @@ public: protected: BLEMIDI_Client_ESP32 *_bluetoothEsp32 = nullptr; + uint32_t onPassKeyRequest() + { + return userOnPassKeyRequest(); + }; + void onConnect(BLEClient *pClient) { - Serial.println("##Connected##"); - /** After connection we should change the parameters if we don't need fast response times. - * These settings are 150ms interval, 0 latency, 450ms timout. - * Timeout should be a multiple of the interval, minimum is 100ms. - * I find a multiple of 3-5 * the interval works best for quick response/reconnect. - * Min interval: 120 * 1.25ms = 150, Max interval: 120 * 1.25ms = 150, 0 latency, 60 * 10ms = 600ms timeout - */ - //pClient->updateConnParams - pClient->updateConnParams(6, 32, 0, 40); + //Serial.println("##Connected##"); + pClient->updateConnParams(BLEMIDI_CLIENT_COMM_MIN_INTERVAL, BLEMIDI_CLIENT_COMM_MAX_INTERVAL, BLEMIDI_CLIENT_COMM_LATENCY, BLEMIDI_CLIENT_COMM_TIMEOUT); + vTaskDelay(1); if (_bluetoothEsp32) { - Serial.println("??"); _bluetoothEsp32->connected(); } }; void onDisconnect(BLEClient *pClient) { - Serial.print(pClient->getPeerAddress().toString().c_str()); - Serial.println(" Disconnected - Starting scan"); + //Serial.print(pClient->getPeerAddress().toString().c_str()); + //Serial.println(" Disconnected - Starting scan"); if (_bluetoothEsp32) { _bluetoothEsp32->disconnected(); } + //Try reconnection or look for a new one NimBLEDevice::getScan()->start(3, scanEndedCB); } + bool onConnParamsUpdateRequest(NimBLEClient *pClient, const ble_gap_upd_params *params) { - if (params->itvl_min < 24) + if (params->itvl_min < BLEMIDI_CLIENT_COMM_MIN_INTERVAL) { /** 1.25ms units */ return false; } - else if (params->itvl_max > 40) + else if (params->itvl_max > BLEMIDI_CLIENT_COMM_MAX_INTERVAL) { /** 1.25ms units */ return false; } - else if (params->latency > 2) + else if (params->latency > BLEMIDI_CLIENT_COMM_LATENCY) { /** Number of intervals allowed to skip */ return false; } - else if (params->supervision_timeout > 100) + else if (params->supervision_timeout > BLEMIDI_CLIENT_COMM_TIMEOUT + 10) { /** 10ms units */ return false; } @@ -206,258 +327,248 @@ protected: }; }; -/** Notification / Indication receiving handler callback */ +/* +########################################## +############# IMPLEMENTATION ############# +########################################## +*/ + +bool BLEMIDI_Client_ESP32::begin(const char *deviceName, BLEMIDI_Transport *bleMidiTransport) +{ + _bleMidiTransport = bleMidiTransport; + + std::string strDeviceName(deviceName); + if (strDeviceName == "") // Connect to first midi server found + { + myAdvCB.specificTarget = false; + myAdvCB.nameTarget = ""; + +#ifdef BLEMIDI_CLIENT_FIXED_NAME + strDeviceName = BLEMIDI_CLIENT_FIXED_NAME; +#else + strDeviceName = BLEMIDI_CLIENT_DEFAULT_NAME; +#endif + } + else // Connect to a specific name or addr + { + myAdvCB.specificTarget = true; + myAdvCB.nameTarget = strDeviceName; + +#ifdef BLEMIDI_CLIENT_FIXED_NAME + strDeviceName = BLEMIDI_CLIENT_FIXED_NAME; +#else + strDeviceName = BLEMIDI_CLIENT_NAME_PREFIX + strDeviceName + BLEMIDI_CLIENT_NAME_SUBFIX; +#endif + } + Serial.println(strDeviceName.c_str()); + + NimBLEDevice::init(strDeviceName); + + // To communicate between the 2 cores. + // Core_0 runs here, core_1 runs the BLE stack + mRxQueue = xQueueCreate(256, sizeof(uint8_t)); // TODO Settings::MaxBufferSize + + NimBLEDevice::setSecurityIOCap(BLEMIDI_CLIENT_SECURITY_CAP); // Attenction, may need passkey + NimBLEDevice::setSecurityAuth(BLEMIDI_CLIENT_SECURITY_AUTH); + + /** Optional: set the transmit power, default is 3db */ + NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ + + myAdvCB.enableConnection = true; + scan(); + + return true; +} + +bool BLEMIDI_Client_ESP32::available(byte *pvBuffer) +{ + if (myAdvCB.enableConnection) + { + if (_client == nullptr || !_client->isConnected()) //Try to connect/reconnect + { + if (myAdvCB.doConnect) + { + myAdvCB.doConnect = false; + if (!connect()) + { + scan(); + } + } + else if (myAdvCB.scanDone) + { + scan(); + } + } + // return 1 byte from the Queue + return xQueueReceive(mRxQueue, (void *)pvBuffer, 0); // return immediately when the queue is empty + } + else + { + return false; + } +} + +/** Notification receiving handler callback */ void BLEMIDI_Client_ESP32::notifyCB(NimBLERemoteCharacteristic *pRemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) { - // std::string str = (isNotify == true) ? "Notification" : "Indication"; - // str += " from "; - // /** NimBLEAddress and NimBLEUUID have std::string operators */ - // str += std::string(pRemoteCharacteristic->getRemoteService()->getClient()->getPeerAddress()); - // str += ": Service = " + std::string(pRemoteCharacteristic->getRemoteService()->getUUID()); - // str += ", Characteristic = " + std::string(pRemoteCharacteristic->getUUID()); - // str += ", Value = " + std::string((char *)pData, length); - // Serial.println(str.c_str()); - - receive(pData, length); + if (uxQueueSpacesAvailable(mRxQueue) >= length) //Don't overflow the queue. If the queue is overflowed, comunication breaks out + receive(pData, length); } void BLEMIDI_Client_ESP32::scan() { // Retrieve a Scanner and set the callback we want to use to be informed when we // have detected a new device. Specify that we want active scanning and start the - // scan to run for 5 seconds. + // scan to run for 3 seconds. + myAdvCB.scanDone = true; NimBLEScan *pBLEScan = BLEDevice::getScan(); - pBLEScan->setAdvertisedDeviceCallbacks(&myAdvCB); - pBLEScan->setInterval(1500); - pBLEScan->setWindow(500); - pBLEScan->setActiveScan(true); - //doScan = true; - Serial.println("Scanning..."); - pBLEScan->start(10, scanEndedCB); + if (!pBLEScan->isScanning()) + { + pBLEScan->setAdvertisedDeviceCallbacks(&myAdvCB); + pBLEScan->setInterval(600); + pBLEScan->setWindow(500); + pBLEScan->setActiveScan(true); + + Serial.println("Scanning..."); + pBLEScan->start(3, scanEndedCB); + } }; bool BLEMIDI_Client_ESP32::connect() { - Serial.println("TryConnection..."); - using namespace std::placeholders; - /** Check if we have a client we should reuse first **/ - if (NimBLEDevice::getClientListSize()) - { - /** Special case when we already know this device, we send false as the - * second argument in connect() to prevent refreshing the service database. + Serial.println("Try Connection..."); + using namespace std::placeholders; //<- for bind funtion in callback notification + + /** Check if we have a client we should reuse first / + / Special case when we already know this device * This saves considerable time and power. */ - _client = NimBLEDevice::getClientByPeerAddress(advDevice->getAddress()); - if (_client) - { - if (!_client->connect(advDevice, false)) - { - Serial.println("Reconnect failed"); - return false; - } - Serial.println("Reconnected client"); - _client->setConnectionParams(12, 12, 0, 51); - if (_characteristic->canNotify()) - { - Serial.println("CAN NOTIFY"); - if (!_characteristic->subscribe(true, std::bind(&BLEMIDI_Client_ESP32::notifyCB, this, _1, _2, _3, _4))) - { - Serial.println("error"); - /** Disconnect if subscribe failed */ - _client->disconnect(); - return false; - } - } - } - /** We don't already have a client that knows this device, - * we will check for a client that is disconnected that we can use. - */ - else - { - _client = NimBLEDevice::getDisconnectedClient(); - } - } - /** No client to reuse? Create a new one. */ - if (!_client) + if (_client) { - if (NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) + if (_client == NimBLEDevice::getClientByPeerAddress(myAdvCB.advDevice.getAddress())) { - Serial.println("Max clients reached - no more connections available"); - return false; - } - _client = BLEDevice::createClient(); - - _client->setClientCallbacks(new MyClientCallbacks(this), false); - /** Set initial connection parameters: These settings are 15ms interval, 0 latency, 120ms timout. - * These settings are safe for 3 clients to connect reliably, can go faster if you have less - * connections. Timeout should be a multiple of the interval, minimum is 100ms. - * Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 51 * 10ms = 510ms timeout - */ - _client->setConnectionParams(12, 12, 0, 51); - /** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */ - _client->setConnectTimeout(5); - Serial.println("#Connection"); - if (!_client->connect(advDevice)) - { - /** Created a client but failed to connect, don't need to keep it as it has no data */ - - NimBLEDevice::deleteClient(_client); - _client=nullptr; - Serial.println("Failed to connect, deleted client"); - return false; - } - vTaskDelay(100); - - if (!_client->isConnected()) - { - if (!_client->connect(advDevice)) + if (_client->connect(&myAdvCB.advDevice, false)) { - Serial.println("Failed to connect"); - return false; - } - } - else - { - - Serial.print("Connected to: "); - Serial.println(_client->getPeerAddress().toString().c_str()); - Serial.print("RSSI: "); - Serial.println(_client->getRssi()); - } - /** Now we can read/write/subscribe the charateristics of the services we are interested in */ - - pSvc = _client->getService(SERVICE_UUID); - if (pSvc) - { /** make sure it's not null */ - _characteristic = pSvc->getCharacteristic(CHARACTERISTIC_UUID); - - if (_characteristic) - { /** make sure it's not null */ - // if (_characteristic->canRead()) - // { - // Serial.print(_characteristic->getUUID().toString().c_str()); - // Serial.print(" Value: "); - // Serial.println(_characteristic->readValue().c_str()); - // } - - /** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe(). - * Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false. - * Unsubscribe parameter defaults are: response=false. - */ + //_client->updateConnParams(BLEMIDI_CLIENT_COMM_MIN_INTERVAL, BLEMIDI_CLIENT_COMM_MAX_INTERVAL, BLEMIDI_CLIENT_COMM_LATENCY, BLEMIDI_CLIENT_COMM_TIMEOUT); if (_characteristic->canNotify()) { - Serial.println("CAN NOTIFY"); if (!_characteristic->subscribe(true, std::bind(&BLEMIDI_Client_ESP32::notifyCB, this, _1, _2, _3, _4))) { - Serial.println("error"); + Serial.println("Error - Not subscribe"); /** Disconnect if subscribe failed */ _client->disconnect(); return false; } + else + { + return true; + } } - // else if (_characteristic->canIndicate()) - // { - // /** Send false as first argument to subscribe to indications instead of notifications */ - // //if(!pChr->registerForNotify(notifyCB, false)) { - // if (!_characteristic->subscribe(false, std::bind(&BLEMIDI_Client_ESP32::notifyCB, this, _1, _2, _3, _4))) - // { - // /** Disconnect if subscribe failed */ - // _client->disconnect(); - // return false; - // } - // } + } + else + { + Serial.println("Error. Reconnect failed"); + _client = nullptr; + return false; + } + } + } + + if (NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) + { + Serial.println("Max clients reached - no more connections available"); + return false; + } + + _client = BLEDevice::createClient(); + + _client->setClientCallbacks(new MyClientCallbacks(this), false); + + _client->setConnectionParams(BLEMIDI_CLIENT_COMM_MIN_INTERVAL, BLEMIDI_CLIENT_COMM_MAX_INTERVAL+10, BLEMIDI_CLIENT_COMM_LATENCY+1, BLEMIDI_CLIENT_COMM_TIMEOUT+10); + /** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */ + _client->setConnectTimeout(15); + + if (!_client->connect(&myAdvCB.advDevice)) + { + /** Created a client but failed to connect, don't need to keep it as it has no data */ + NimBLEDevice::deleteClient(_client); + _client = nullptr; + Serial.println("Failed to connect, deleted client"); + return false; + } + vTaskDelay(100); + + if (!_client->isConnected()) + { + Serial.println("Failed to connect"); + _client->disconnect(); + _client = nullptr; + return false; + } + //_client->updateConnParams(BLEMIDI_CLIENT_COMM_MIN_INTERVAL, BLEMIDI_CLIENT_COMM_MAX_INTERVAL, BLEMIDI_CLIENT_COMM_LATENCY, BLEMIDI_CLIENT_COMM_TIMEOUT); + + Serial.print("Connected to: "); + Serial.print(myAdvCB.advDevice.getName().c_str()); + Serial.print(" / "); + Serial.println(_client->getPeerAddress().toString().c_str()); + + Serial.print("RSSI: "); + Serial.println(_client->getRssi()); + + /** Now we can read/write/subscribe the charateristics of the services we are interested in */ + pSvc = _client->getService(SERVICE_UUID); + if (pSvc) + { /** make sure it's not null */ + _characteristic = pSvc->getCharacteristic(CHARACTERISTIC_UUID); + + if (_characteristic) + { /** make sure it's not null */ + if (_characteristic->canNotify()) + { + if (!_characteristic->subscribe(true, std::bind(&BLEMIDI_Client_ESP32::notifyCB, this, _1, _2, _3, _4))) + { + Serial.println("Error - Subcribe error"); + /** Disconnect if subscribe failed */ + _client->disconnect(); + return false; + } + } + else + { + return false; } } else { - Serial.println("MIDI service not found."); return false; } } + else + { + Serial.println("Error. MIDI service not found."); + return false; + } + return true; }; -bool BLEMIDI_Client_ESP32::begin(const char *deviceName, BLEMIDI_Transport *bleMidiTransport) -{ - - _bleMidiTransport = bleMidiTransport; - - Serial.println("CreateBLE"); - std::string stringDeviceName(deviceName); - NimBLEDevice::init(stringDeviceName); - Serial.println("CREATED"); - - // To communicate between the 2 cores. - // Core_0 runs here, core_1 runs the BLE stack - mRxQueue = xQueueCreate(64, sizeof(uint8_t)); // TODO Settings::MaxBufferSize - Serial.println("QUEUE"); - /** Set the IO capabilities of the device, each option will trigger a different pairing method. - * BLE_HS_IO_KEYBOARD_ONLY - Passkey pairing - * BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing - * BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing - */ - //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_KEYBOARD_ONLY); // use passkey - //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_YESNO); //use numeric comparison - - /** 2 different ways to set security - both calls achieve the same result. - * no bonding, no man in the middle protection, secure connections. - * - * These are the default values, only shown here for demonstration. - */ - //NimBLEDevice::setSecurityAuth(false, false, true); - NimBLEDevice::setSecurityAuth(/*BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM | */ BLE_SM_PAIR_AUTHREQ_SC); - Serial.println("Security"); - /** Optional: set the transmit power, default is 3db */ - NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ - Serial.println("Power"); - /** Optional: set any devices you don't want to get advertisments from */ - // NimBLEDevice::addIgnored(NimBLEAddress ("aa:bb:cc:dd:ee:ff")); - - scan(); - vTaskDelay(1000); - - //connect(); - - // while (!doConnect) - // { - - // if (!pBLEScan->isScanning()) - // { - // Serial.println("RESCAN"); - // pBLEScan->start(10, scanEndedCB); - // } - // vTaskDelay(100); - // } - // Serial.println("LOOP OUT"); - - return true; -} - -END_BLEMIDI_NAMESPACE - /** Callback to process the results of the last scan or restart it */ void scanEndedCB(NimBLEScanResults results) { - Serial.println("Scan Ended"); - if (!doConnect) - { - scanDone = true; - } - else - { - scanDone = false; - NimBLEDevice::getScan()->start(3, scanEndedCB); - } + // Serial.println("Scan Ended"); } -/*! \brief Create an instance for ESP32 named +END_BLEMIDI_NAMESPACE +/*! \brief Create an instance for ESP32 named "Prefix + + Subfix" + It will try to connect to a specific server with equal name or addr than */ #define BLEMIDI_CREATE_INSTANCE(DeviceName, Name) \ BLEMIDI_NAMESPACE::BLEMIDI_Transport BLE##Name(DeviceName); \ MIDI_NAMESPACE::MidiInterface, BLEMIDI_NAMESPACE::MySettings> Name((BLEMIDI_NAMESPACE::BLEMIDI_Transport &)BLE##Name); -/*! \brief Create a default instance for ESP32 named BLE-MIDI +/*! \brief Create a default instance for ESP32 named BLEMIDI-CLIENT. + It will try to connect to first midi ble server found. */ #define BLEMIDI_CREATE_DEFAULT_INSTANCE() \ - BLEMIDI_CREATE_INSTANCE("Esp32-NimBLE-MIDI", MIDI) + BLEMIDI_CREATE_INSTANCE("", MIDI) From 665f343d74d815a9e9dcc0db108b02262962ecf6 Mon Sep 17 00:00:00 2001 From: Roberto Date: Fri, 9 Jul 2021 18:33:25 +0200 Subject: [PATCH 05/17] Example added --- examples/MidiBle_Client/MidiBle_Client.ino | 141 +++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 examples/MidiBle_Client/MidiBle_Client.ino diff --git a/examples/MidiBle_Client/MidiBle_Client.ino b/examples/MidiBle_Client/MidiBle_Client.ino new file mode 100644 index 0000000..050fd3d --- /dev/null +++ b/examples/MidiBle_Client/MidiBle_Client.ino @@ -0,0 +1,141 @@ +/** + * -------------------------------------------------------- + * This example shows how to use client MidiBLE + * Client BLEMIDI works im a similar way Server (Common) BLEMIDI, but with some exception. + * + * The most importart exception is read() method. This function works as usual, but + * now it manages machine-states BLE connection too. The + * read() function must be called several times continuously in order to scan BLE device + * and connect with the server. In this example, read() is called in a "multitask function of + * FreeRTOS", but it can be called in loop() function as usual. + * + * Some BLEMIDI_CREATE_INSTANCE() are added in MidiBLE-Client to be able to choose a specific server to connect + * or to connect to the first server which has the MIDI characteristic. You can choose the server by typing in the name field + * the name of the server or the BLE address of the server. If you want to connect + * to the first MIDI server BLE found by the device, you just have to set the name field empty (""). + * + * FOR ADVANCED USERS: Other advanced BLE configurations can be changed in hardware/BLEMIDI_Client_ESP32.h + * #defines in the head of the file (IMPORTANT: Only the first user defines must be modified). These configurations + * are related to security (password, pairing and securityCallback()), communication params, the device name + * and other stuffs. Modify defines at your own risk. + * + * + * + * @auth RobertoHE + * -------------------------------------------------------- + */ + +#include +#include + +#include + +//#include +//#include +//#include +//#include + +BLEMIDI_CREATE_DEFAULT_INSTANCE(); //Connect to first server found + +//BLEMIDI_CREATE_INSTANCE("",MIDI) //Connect to the first server found +//BLEMIDI_CREATE_INSTANCE("f2:c1:d9:36:e7:6b",MIDI) //Connect to a specific BLE address server +//BLEMIDI_CREATE_INSTANCE("MyBLEserver",MIDI) //Connect to a specific name server + +#ifndef LED_BUILTIN +#define LED_BUILTIN 2 //modify for match with yout board +#endif + +void ReadCB(void *parameter); //Continuos Read function (See FreeRTOS multitasks) + +unsigned long t0 = millis(); +bool isConnected = false; + +/** + * ----------------------------------------------------------------------------- + * When BLE is connected, LED will turn on (indicating that connection was successful) + * When receiving a NoteOn, LED will go out, on NoteOff, light comes back on. + * This is an easy and conveniant way to show that the connection is alive and working. + * ----------------------------------------------------------------------------- +*/ +void setup() +{ + Serial.begin(115200); + MIDI.begin(MIDI_CHANNEL_OMNI); + + BLEMIDI.setHandleConnected([]() + { + Serial.println("---------CONNECTED---------"); + isConnected = true; + digitalWrite(LED_BUILTIN, HIGH); + }); + + BLEMIDI.setHandleDisconnected([]() + { + Serial.println("---------NOT CONNECTED---------"); + isConnected = false; + digitalWrite(LED_BUILTIN, LOW); + }); + + MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) + { + Serial.print("NoteON: CH: "); + Serial.print(channel); + Serial.print(" | "); + Serial.print(note); + Serial.print(", "); + Serial.println(velocity); + digitalWrite(LED_BUILTIN, LOW); + }); + MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) + { + digitalWrite(LED_BUILTIN, HIGH); + }); + + xTaskCreatePinnedToCore(ReadCB, //See FreeRTOS for more multitask info + "MIDI-READ", + 3000, + NULL, + 1, + NULL, + 1); //Core0 or Core1 + + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, LOW); +} + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void loop() +{ + //MIDI.read(); // This function is called in the other task + + if (isConnected && (millis() - t0) > 1000) + { + t0 = millis(); + + MIDI.sendNoteOn(60, 100, 1); // note 60, velocity 100 on channel 1 + vTaskDelay(250/portTICK_PERIOD_MS); + MIDI.sendNoteOff(60, 0, 1); + } +} + +/** + * This function is called by xTaskCreatePinnedToCore() to perform a multitask execution. + * In this task, read() is called every millisecond (approx.). + * read() function performs connection, reconnection and scan-BLE functions. + * Call read() method repeatedly to perform a successfull connection with the server + * in case connection is lost. +*/ +void ReadCB(void *parameter) +{ +// Serial.print("READ Task is started on core: "); +// Serial.println(xPortGetCoreID()); + for (;;) + { + MIDI.read(); + vTaskDelay(1 / portTICK_PERIOD_MS); //Feed the watchdog of FreeRTOS. + //Serial.println(uxTaskGetStackHighWaterMark(NULL)); //Only for debug. You can see the watermark of the free resources assigned by the xTaskCreatePinnedToCore() function. + } + vTaskDelay(1); +} \ No newline at end of file From 5f7d493acd078ba8f05f59b8a85108f56c8d4f56 Mon Sep 17 00:00:00 2001 From: Roberto Date: Fri, 9 Jul 2021 18:36:37 +0200 Subject: [PATCH 06/17] Added characterisctic protecction in write(). Clean-up the code --- src/hardware/BLEMIDI_Client_ESP32.h | 101 +++++++++++++++------------- 1 file changed, 54 insertions(+), 47 deletions(-) diff --git a/src/hardware/BLEMIDI_Client_ESP32.h b/src/hardware/BLEMIDI_Client_ESP32.h index e6bd815..9f30309 100644 --- a/src/hardware/BLEMIDI_Client_ESP32.h +++ b/src/hardware/BLEMIDI_Client_ESP32.h @@ -2,8 +2,8 @@ /* ############################################# -########### USER DEFINE BEGINING ############ -####### Only modify these parameters ######## +########## USER DEFINES BEGINNING ########### +####### Modify only these parameters ######## ############################################# */ @@ -12,13 +12,13 @@ */ /** - * Set always the same name independiently of name server + * Set always the same name independently of name server */ //#define BLEMIDI_CLIENT_FIXED_NAME "BleMidiClient" #ifndef BLEMIDI_CLIENT_FIXED_NAME //Not modify /** - * BLE name is composed by the nex way when client try to connect to speccific server: + * When client tries to connect to specific server, BLE name is composed as follows: * BLEMIDI_CLIENT_NAME_PREFIX + + BLEMIDI_CLIENT_NAME_SUBFIX * * example: @@ -32,7 +32,7 @@ #define BLEMIDI_CLIENT_NAME_SUBFIX "" /** - * BLE name when it tries to connect to first midi server found. + * When client tries to connect to the first midi server found: */ #define BLEMIDI_CLIENT_DEFAULT_NAME "BLEMIDI-CLIENT" #endif //Not modify @@ -48,23 +48,22 @@ */ #define BLEMIDI_CLIENT_SECURITY_CAP BLE_HS_IO_NO_INPUT_OUTPUT -/** Set security method. +/** Set the security method. * bonding * man in the middle protection * pair. secure connections * * More info in nimBLE lib * - * Uncomment that you need + * Uncomment what you need * These are the default values. */ //#define BLEMIDI_CLIENT_BOND //#define BLEMIDI_CLIENT_MITM #define BLEMIDI_CLIENT_PAIR - /** - * This callback funtion defines what it must to do when server requieres PassKey. + * This callback function defines what will be done when server requieres PassKey. * Add your custom code here. */ static uint32_t userOnPassKeyRequest() @@ -79,14 +78,15 @@ static uint32_t userOnPassKeyRequest() return passkey; }; -/* + /* ###### BLE COMMUNICATION PARAMS ###### */ /** Set connection parameters: - * If only use one connection, put recomended BLE server param communication (you may scan it ussing "nRF Connect" app or other similar). + * If you only use one connection, put recomended BLE server param communication + * (you may scan it ussing "nRF Connect" app or other similar apps). * - * If you use more than one connection use, for example, settings like 15ms interval, 0 latency, 120ms timout. - * These settings may be safe for 3 clients to connect reliably, can go faster if you have less + * If you use more than one connection adjust, for example, settings like 15ms interval, 0 latency, 120ms timout. + * These settings may be safe for 3 clients to connect reliably, set faster values if you have less * connections. * * Min interval (unit: 1.25ms): 12 * 1.25ms = 15 ms, @@ -94,11 +94,10 @@ static uint32_t userOnPassKeyRequest() * 0 latency (Number of intervals allowed to skip), * TimeOut (unit: 10ms) 51 * 10ms = 510ms. Timeout should be minimum 100ms. */ -#define BLEMIDI_CLIENT_COMM_MIN_INTERVAL 6 // 7.5ms -#define BLEMIDI_CLIENT_COMM_MAX_INTERVAL 35 // 40ms -#define BLEMIDI_CLIENT_COMM_LATENCY 0 -#define BLEMIDI_CLIENT_COMM_TIMEOUT 400 //4000ms - +#define BLEMIDI_CLIENT_COMM_MIN_INTERVAL 6 // 7.5ms +#define BLEMIDI_CLIENT_COMM_MAX_INTERVAL 35 // 40ms +#define BLEMIDI_CLIENT_COMM_LATENCY 0 +#define BLEMIDI_CLIENT_COMM_TIMEOUT 400 //4000ms /* ############################################# @@ -129,7 +128,7 @@ BEGIN_BLEMIDI_NAMESPACE #define BLEMIDI_CLIENT_PAIR_DUMMY 0x00 #endif -/** Set security method. +/** Set the security method. * bonding * man in the middle protection * pair. secure connections @@ -163,11 +162,15 @@ protected: { /** Ready to connect now */ doConnect = true; - /** Save the device reference in a global for the client to use*/ + /** Save the device reference in a public variable the client can use*/ advDevice = *advertisedDevice; /** stop scan before connecting */ NimBLEDevice::getScan()->stop(); } + else + { + Serial.println("Name error"); + } } else { @@ -180,7 +183,7 @@ protected: /** Define a funtion to handle the callbacks when scan ends */ void scanEndedCB(NimBLEScanResults results); -/** Define the class that perform Client Midi (nimBLE) */ +/** Define the class that performs Client Midi (nimBLE) */ class BLEMIDI_Client_ESP32 { private: @@ -195,7 +198,7 @@ private: friend class AdvertisedDeviceCallbacks; friend class MyClientCallbacks; - friend class MIDI_NAMESPACE::MidiInterface, MySettings>;// + friend class MIDI_NAMESPACE::MidiInterface, MySettings>; // AdvertisedDeviceCallbacks myAdvCB; @@ -221,8 +224,11 @@ public: void write(uint8_t *data, uint8_t length) { - if (myAdvCB.enableConnection) - _characteristic->writeValue(data, length, true); + if (!myAdvCB.enableConnection) + return; + if (_characteristic == NULL) + return; + _characteristic->writeValue(data, length, true); } bool available(byte *pvBuffer); @@ -230,13 +236,13 @@ public: void add(byte value) { // called from BLE-MIDI, to add it to a buffer here - xQueueSend(mRxQueue, &value, portMAX_DELAY); + xQueueSend(mRxQueue, &value, portMAX_DELAY/2); } protected: void receive(uint8_t *buffer, size_t length) { - // forward the buffer so it can be parsed + // forward the buffer so that it can be parsed _bleMidiTransport->receive(buffer, length); } @@ -262,7 +268,7 @@ protected: bool connect(); }; -/** Define the class that perform interrupts callbacks */ +/** Define the class that performs interruption callbacks */ class MyClientCallbacks : public BLEClientCallbacks { public: @@ -300,7 +306,7 @@ protected: _bluetoothEsp32->disconnected(); } - //Try reconnection or look for a new one + //Try reconnection or search a new one NimBLEDevice::getScan()->start(3, scanEndedCB); } @@ -338,7 +344,7 @@ bool BLEMIDI_Client_ESP32::begin(const char *deviceName, BLEMIDI_Transport= length) //Don't overflow the queue. If the queue is overflowed, comunication breaks out - receive(pData, length); + if (this->_characteristic == pRemoteCharacteristic) //Redundant protection + { + if (uxQueueSpacesAvailable(mRxQueue) >= length) //Don't overflow the queue. If the queue is overflowed, comunication breaks out + receive(pData, length); + } } void BLEMIDI_Client_ESP32::scan() { - // Retrieve a Scanner and set the callback we want to use to be informed when we - // have detected a new device. Specify that we want active scanning and start the + // Retrieve a Scanner and set the callback you want to use to be informed when a new device is detected. + // Specify that you want active scanning and start the // scan to run for 3 seconds. myAdvCB.scanDone = true; NimBLEScan *pBLEScan = BLEDevice::getScan(); @@ -439,10 +448,10 @@ bool BLEMIDI_Client_ESP32::connect() Serial.println("Try Connection..."); using namespace std::placeholders; //<- for bind funtion in callback notification - /** Check if we have a client we should reuse first / - / Special case when we already know this device - * This saves considerable time and power. - */ + /** Check if we have a client we should reuse first + * Special case when we already know this device + * This saves considerable time and power. + */ if (_client) { @@ -450,7 +459,6 @@ bool BLEMIDI_Client_ESP32::connect() { if (_client->connect(&myAdvCB.advDevice, false)) { - //_client->updateConnParams(BLEMIDI_CLIENT_COMM_MIN_INTERVAL, BLEMIDI_CLIENT_COMM_MAX_INTERVAL, BLEMIDI_CLIENT_COMM_LATENCY, BLEMIDI_CLIENT_COMM_TIMEOUT); if (_characteristic->canNotify()) { if (!_characteristic->subscribe(true, std::bind(&BLEMIDI_Client_ESP32::notifyCB, this, _1, _2, _3, _4))) @@ -484,8 +492,8 @@ bool BLEMIDI_Client_ESP32::connect() _client = BLEDevice::createClient(); _client->setClientCallbacks(new MyClientCallbacks(this), false); - - _client->setConnectionParams(BLEMIDI_CLIENT_COMM_MIN_INTERVAL, BLEMIDI_CLIENT_COMM_MAX_INTERVAL+10, BLEMIDI_CLIENT_COMM_LATENCY+1, BLEMIDI_CLIENT_COMM_TIMEOUT+10); + + _client->setConnectionParams(BLEMIDI_CLIENT_COMM_MIN_INTERVAL, BLEMIDI_CLIENT_COMM_MAX_INTERVAL + 10, BLEMIDI_CLIENT_COMM_LATENCY + 1, BLEMIDI_CLIENT_COMM_TIMEOUT + 10); /** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */ _client->setConnectTimeout(15); @@ -497,7 +505,6 @@ bool BLEMIDI_Client_ESP32::connect() Serial.println("Failed to connect, deleted client"); return false; } - vTaskDelay(100); if (!_client->isConnected()) { @@ -506,7 +513,6 @@ bool BLEMIDI_Client_ESP32::connect() _client = nullptr; return false; } - //_client->updateConnParams(BLEMIDI_CLIENT_COMM_MIN_INTERVAL, BLEMIDI_CLIENT_COMM_MAX_INTERVAL, BLEMIDI_CLIENT_COMM_LATENCY, BLEMIDI_CLIENT_COMM_TIMEOUT); Serial.print("Connected to: "); Serial.print(myAdvCB.advDevice.getName().c_str()); @@ -560,8 +566,9 @@ void scanEndedCB(NimBLEScanResults results) } END_BLEMIDI_NAMESPACE -/*! \brief Create an instance for ESP32 named "Prefix + + Subfix" - It will try to connect to a specific server with equal name or addr than + +/*! \brief Create an instance for ESP32 named , and adviertise it like "Prefix + + Subfix" + It will try to connect to a specific server with equal name or addr than . If is "", it will connect to first midi server */ #define BLEMIDI_CREATE_INSTANCE(DeviceName, Name) \ BLEMIDI_NAMESPACE::BLEMIDI_Transport BLE##Name(DeviceName); \ From 49f7ec93db9fc4e4acccc4d03d324dca31b1036b Mon Sep 17 00:00:00 2001 From: Roberto Date: Fri, 9 Jul 2021 18:40:21 +0200 Subject: [PATCH 07/17] Removed non-functional BLE-MIDI_Client_ESP32.h --- src/hardware/BLE-MIDI_Client_ESP32.h | 95 ---------------------------- 1 file changed, 95 deletions(-) delete mode 100644 src/hardware/BLE-MIDI_Client_ESP32.h diff --git a/src/hardware/BLE-MIDI_Client_ESP32.h b/src/hardware/BLE-MIDI_Client_ESP32.h deleted file mode 100644 index b2a1029..0000000 --- a/src/hardware/BLE-MIDI_Client_ESP32.h +++ /dev/null @@ -1,95 +0,0 @@ -#pragma once - -// Headers for ESP32 BLE -#include -#include -#include -#include - -BEGIN_BLEMIDI_NAMESPACE - -class BLEMIDI_Client_ESP32 -{ -private: - BLEClient* _client = nullptr; - - BLEMIDI* _bleMidiTransport = nullptr; - -public: - BLEMIDI_Client_ESP32() - { - } - - bool begin(const char*, BLEMIDI*); - - void write(uint8_t* data, uint8_t length) - { - _characteristic->setValue(data, length); - _characteristic->notify(); - } - - void receive(uint8_t* buffer, size_t length) - { - // Post the items to the back of the queue - // (drop the first 2 items) - for (size_t i = 2; i < length; i++) - xQueueSend(_bleMidiTransport->mRxQueue, &buffer[i], portMAX_DELAY); - } - - void connected() - { - if (_bleMidiTransport->_connectedCallback) - _bleMidiTransport->_connectedCallback(); - } - - void disconnected() - { - if (_bleMidiTransport->_disconnectedCallback) - _bleMidiTransport->_disconnectedCallback(); - } -}; - -class MyClientCallbacks: public BLEClientCallbacks { -public: - MyClientCallbacks(BLEMIDI_Client_ESP32* bluetoothEsp32) - : _bluetoothEsp32(bluetoothEsp32) { - } - -protected: - BLEMIDI_Client_ESP32* _bluetoothEsp32 = nullptr; - - void onConnect(BLEClient*) { - if (_bluetoothEsp32) - _bluetoothEsp32->connected(); - }; - - void onDisconnect(BLEClient*) { - if (_bluetoothEsp32) - _bluetoothEsp32->disconnected(); - } -}; - -bool BLEMIDI_Client_ESP32::begin(const char* deviceName, BLEMIDI* bleMidiTransport) -{ - _bleMidiTransport = bleMidiTransport; - - BLEDevice::init(deviceName); - - _client = BLEDevice::createClient(); - _client->setCallbacks(new MyClientCallbacks(this)); - - // Retrieve a Scanner and set the callback we want to use to be informed when we - // have detected a new device. Specify that we want active scanning and start the - // scan to run for 5 seconds. - pBLEScan = BLEDevice::getScan(); - pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks(this)); - pBLEScan->setInterval(1349); - pBLEScan->setWindow(449); - pBLEScan->setActiveScan(true); - doScan = true; - pBLEScan->start(10, scanCompleteCB); - - return true; -} - -END_BLEMIDI_NAMESPACE From 43d9cfbc359e45b0ecd6c60eb42d7238556399e6 Mon Sep 17 00:00:00 2001 From: Roberto Date: Fri, 9 Jul 2021 19:11:18 +0200 Subject: [PATCH 08/17] README.md equal like master repo --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 07bac14..c93bc1a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # Arduino BLE-MIDI Transport [![arduino-library-badge](https://www.ardu-badge.com/badge/BLE-MIDI.svg?)](https://www.ardu-badge.com/BLE-MIDI) -This fork is for Client nimBLE transport layer. - This library implements the BLE-MIDI transport layer for the [FortySevenEffects Arduino MIDI Library](https://github.com/FortySevenEffects/arduino_midi_library) ## Installation From 5a0fd77bc6b20c55ae03bea9855a27fa4254a135 Mon Sep 17 00:00:00 2001 From: Roberto Date: Fri, 9 Jul 2021 19:13:46 +0200 Subject: [PATCH 09/17] added a newline at the end of file --- examples/MidiBle_Client/MidiBle_Client.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/MidiBle_Client/MidiBle_Client.ino b/examples/MidiBle_Client/MidiBle_Client.ino index 050fd3d..9a44bdf 100644 --- a/examples/MidiBle_Client/MidiBle_Client.ino +++ b/examples/MidiBle_Client/MidiBle_Client.ino @@ -138,4 +138,4 @@ void ReadCB(void *parameter) //Serial.println(uxTaskGetStackHighWaterMark(NULL)); //Only for debug. You can see the watermark of the free resources assigned by the xTaskCreatePinnedToCore() function. } vTaskDelay(1); -} \ No newline at end of file +} From 4e52f70062ba5c1bccfa0165daa0cd6684216084 Mon Sep 17 00:00:00 2001 From: Roberto Date: Wed, 4 Aug 2021 17:39:48 +0200 Subject: [PATCH 10/17] Modified received(). Running Status and divided SysEx are accepted --- src/BLEMIDI_Transport.h | 256 ++++++++++++++++++++++++++++------------ 1 file changed, 179 insertions(+), 77 deletions(-) diff --git a/src/BLEMIDI_Transport.h b/src/BLEMIDI_Transport.h index 63a61ec..329a265 100644 --- a/src/BLEMIDI_Transport.h +++ b/src/BLEMIDI_Transport.h @@ -12,7 +12,7 @@ BEGIN_BLEMIDI_NAMESPACE -template +template class BLEMIDI_Transport { typedef _Settings Settings; @@ -23,26 +23,26 @@ private: byte mTxBuffer[Settings::MaxBufferSize]; // minimum 5 bytes unsigned mTxIndex = 0; - + char mDeviceName[24]; uint8_t mTimestampLow; private: - T mBleClass; + T mBleClass; -public: - BLEMIDI_Transport(const char* deviceName) - { +public: + BLEMIDI_Transport(const char *deviceName) + { strncpy(mDeviceName, deviceName, sizeof(mDeviceName)); - + mRxIndex = 0; mTxIndex = 0; - } + } -public: +public: static const bool thruActivated = false; - + void begin() { mBleClass.begin(mDeviceName, this); @@ -53,10 +53,10 @@ public: getMidiTimestamp(&mTxBuffer[0], &mTxBuffer[1]); mTxIndex = 2; mTimestampLow = mTxBuffer[1]; // or generate new ? - + return true; } - + void write(byte inData) { if (mTxIndex >= sizeof(mTxBuffer)) @@ -76,8 +76,8 @@ public: { mBleClass.write(mTxBuffer, mTxIndex - 1); - mTxIndex = 1; // keep header - mTxBuffer[mTxIndex++] = mTimestampLow; // or generate new ? + mTxIndex = 1; // keep header + mTxBuffer[mTxIndex++] = mTimestampLow; // or generate new ? } else { @@ -89,23 +89,28 @@ public: mBleClass.write(mTxBuffer, mTxIndex); mTxIndex = 0; } - + byte read() { return mRxBuffer[--mRxIndex]; } + bool end() + { + return mBleClass.end(); + } + unsigned available() { uint8_t byte; auto success = mBleClass.available(&byte); - if (!success) return mRxIndex; + if (!success) + return mRxIndex; mRxBuffer[mRxIndex++] = byte; - return mRxIndex; } - + protected: /* The first byte of all BLE packets must be a header byte. This is followed by timestamp bytes and MIDI messages. @@ -152,31 +157,33 @@ protected: and the MSB of both bytes are set to indicate that this is a header byte. Both bytes are placed into the first two position of an array in preparation for a MIDI message. */ - static void getMidiTimestamp (uint8_t *header, uint8_t *timestamp) + static void getMidiTimestamp(uint8_t *header, uint8_t *timestamp) { auto currentTimeStamp = millis() & 0x01FFF; - - *header = ((currentTimeStamp >> 7) & 0x3F) | 0x80; // 6 bits plus MSB - *timestamp = (currentTimeStamp & 0x7F) | 0x80; // 7 bits plus MSB + + *header = ((currentTimeStamp >> 7) & 0x3F) | 0x80; // 6 bits plus MSB + *timestamp = (currentTimeStamp & 0x7F) | 0x80; // 7 bits plus MSB } - - static void setMidiTimestamp (uint8_t header, uint8_t *timestamp) + + static void setMidiTimestamp(uint8_t header, uint8_t *timestamp) { } - -public: - // callbacks - void(*_connectedCallback)() = nullptr; - void(*_disconnectedCallback)() = nullptr; public: - void setHandleConnected(void(*fptr)()) { - _connectedCallback = fptr; - } + // callbacks + void (*_connectedCallback)() = nullptr; + void (*_disconnectedCallback)() = nullptr; - void setHandleDisconnected(void(*fptr)()) { - _disconnectedCallback = fptr; - } +public: + void setHandleConnected(void (*fptr)()) + { + _connectedCallback = fptr; + } + + void setHandleDisconnected(void (*fptr)()) + { + _disconnectedCallback = fptr; + } /* The general form of a MIDI message follows: @@ -208,83 +215,179 @@ public: MIDI messages. In the MIDI BLE protocol, the System Real-Time messages must be deinterleaved from other messages – except for System Exclusive messages. */ - void receive(byte* buffer, size_t length) - { + void receive(byte *buffer, size_t length) + { // Pointers used to search through payload. - byte lPtr = 0; - byte rPtr = 0; + int lPtr = 0; + int rPtr = 0; + // lastStatus used to capture runningStatus byte lastStatus; - // Decode first packet -- SHALL be "Full MIDI message" - lPtr = 2; //Start at first MIDI status -- SHALL be "MIDI status" - + // previousStatus used to continue a runningStatus interrupted by a timeStamp or a System Message. + byte previousStatus = 0x00; + + byte headerByte = buffer[lPtr++]; + + auto timestampHigh = 0x3f & headerByte; + + byte timestampByte = buffer[lPtr++]; + uint16_t timestamp = 0; + + bool sysExContinuation = false; + bool runningStatusContinuation = false; + + if (timestampByte >= 80) + { + auto timestampLow = 0x7f & timestampByte; + timestamp = timestampLow + (timestampHigh << 7); + } + else + { + sysExContinuation = true; + lPtr--; // the second byte is part of the SysEx + } + //While statement contains incrementing pointers and breaks when buffer size exceeded. while (true) { lastStatus = buffer[lPtr]; - - if( (buffer[lPtr] < 0x80)) - return; // Status message not present, bail + + if (previousStatus == 0x00) + { + if ((lastStatus < 0x80) && !sysExContinuation) + return; // Status message not present and it is not a runningStatus continuation, bail + } + else if (lastStatus < 0x80) + { + lastStatus = previousStatus; + runningStatusContinuation = true; + } // Point to next non-data byte rPtr = lPtr; - while( (buffer[rPtr + 1] < 0x80) && (rPtr < (length - 1)) ) + while ((buffer[rPtr + 1] < 0x80) && (rPtr < (length - 1))) rPtr++; - if (buffer[rPtr + 1] == 0xF7) rPtr++; - // look at l and r pointers and decode by size. - if( rPtr - lPtr < 1 ) { - // Time code or system - mBleClass.add(lastStatus); - } else if( rPtr - lPtr < 2 ) { - mBleClass.add(lastStatus); - mBleClass.add(buffer[lPtr + 1]); - } else if( rPtr - lPtr < 3 ) { - mBleClass.add(lastStatus); - mBleClass.add(buffer[lPtr + 1]); - mBleClass.add(buffer[lPtr + 2]); - } else { - // Too much data - // If not System Common or System Real-Time, send it as running status - switch(buffer[lPtr] & 0xF0) + if (!runningStatusContinuation) + { + // look at l and r pointers and decode by size. + if (rPtr - lPtr < 1) + { + // Time code or system + mBleClass.add(buffer[lPtr]); + } + else if (rPtr - lPtr < 2) + { + mBleClass.add(buffer[lPtr]); + mBleClass.add(buffer[lPtr + 1]); + } + else if (rPtr - lPtr < 3) + { + mBleClass.add(buffer[lPtr]); + mBleClass.add(buffer[lPtr + 1]); + mBleClass.add(buffer[lPtr + 2]); + } + else + { + // Too much data + // If not System Common or System Real-Time, send it as running status + + auto midiType = lastStatus & 0xF0; + if (sysExContinuation) + midiType = 0xF0; + + switch (midiType) + { + case 0x80: + case 0x90: + case 0xA0: + case 0xB0: + case 0xE0: + for (auto i = lPtr; i < rPtr; i = i + 2) + { + mBleClass.add(lastStatus); + mBleClass.add(buffer[i + 1]); + mBleClass.add(buffer[i + 2]); + } + break; + case 0xC0: + case 0xD0: + for (auto i = lPtr; i < rPtr; i = i + 1) + { + mBleClass.add(lastStatus); + mBleClass.add(buffer[i + 1]); + } + break; + case 0xF0: + mBleClass.add(lastStatus); + for (auto i = lPtr; i < rPtr; i++) + mBleClass.add(buffer[i + 1]); + + break; + + default: + break; + } + } + } + else + { + auto midiType = lastStatus & 0xF0; + if (sysExContinuation) + midiType = 0xF0; + + switch (midiType) { case 0x80: case 0x90: case 0xA0: case 0xB0: case 0xE0: - for (auto i = lPtr; i < rPtr; i = i + 2) + //3 bytes full Midi -> 2 bytes runningStatus + for (auto i = lPtr; i <= rPtr; i = i + 2) { mBleClass.add(lastStatus); + mBleClass.add(buffer[i]); mBleClass.add(buffer[i + 1]); - mBleClass.add(buffer[i + 2]); } break; case 0xC0: case 0xD0: - for (auto i = lPtr; i < rPtr; i = i + 1) + //2 bytes full Midi -> 1 byte runningStatus + for (auto i = lPtr; i <= rPtr; i = i + 1) { mBleClass.add(lastStatus); - mBleClass.add(buffer[i + 1]); + mBleClass.add(buffer[i]); } break; - case 0xF0: - mBleClass.add(buffer[lPtr]); - for (auto i = lPtr; i < rPtr; i++) - mBleClass.add(buffer[i + 1]); - break; + default: break; } + runningStatusContinuation = false; } - + + if (++rPtr >= length) + return; // end of packet + + if (lastStatus < 0xf0) //exclude System Message. They must not be RunningStatus + { + previousStatus = lastStatus; + } + + timestampByte = buffer[rPtr++]; + if (timestampByte >= 80) // is bit 7 set? + { + auto timestampLow = 0x7f & timestampByte; + timestamp = timestampLow + (timestampHigh << 7); + } + // Point to next status - lPtr = rPtr + 2; - if(lPtr >= length) + lPtr = rPtr; + if (lPtr >= length) return; //end of packet } - } - + } }; struct MySettings : public MIDI_NAMESPACE::DefaultSettings @@ -293,4 +396,3 @@ struct MySettings : public MIDI_NAMESPACE::DefaultSettings }; END_BLEMIDI_NAMESPACE - From 895089174b64394b009993686efc915d1adb0460 Mon Sep 17 00:00:00 2001 From: RobertoHE Date: Thu, 5 Aug 2021 10:58:02 +0200 Subject: [PATCH 11/17] Update and simplify connect() (UNTESTED) Connect() method is simplified. Only one client connection by object is allowed now. If any connection step fails during connection setup, this client is deleted and reset. This deleted client doesn't ocupe for NIMBLE_MAX_CONNECTIONS count now. Some Serial.print() traces was commented. Now NotifyCB() doesn't check space available in rx buffer. --- src/hardware/BLEMIDI_Client_ESP32.h | 89 +++++++++++++---------------- 1 file changed, 41 insertions(+), 48 deletions(-) diff --git a/src/hardware/BLEMIDI_Client_ESP32.h b/src/hardware/BLEMIDI_Client_ESP32.h index 9f30309..afca3bf 100644 --- a/src/hardware/BLEMIDI_Client_ESP32.h +++ b/src/hardware/BLEMIDI_Client_ESP32.h @@ -78,7 +78,7 @@ static uint32_t userOnPassKeyRequest() return passkey; }; - /* +/* ###### BLE COMMUNICATION PARAMS ###### */ /** Set connection parameters: @@ -162,7 +162,7 @@ protected: { /** Ready to connect now */ doConnect = true; - /** Save the device reference in a public variable the client can use*/ + /** Save the device reference in a public variable that the client can use*/ advDevice = *advertisedDevice; /** stop scan before connecting */ NimBLEDevice::getScan()->stop(); @@ -259,7 +259,9 @@ protected: void disconnected() { if (_bleMidiTransport->_disconnectedCallback) + { _bleMidiTransport->_disconnectedCallback(); + } } void notifyCB(NimBLERemoteCharacteristic *pRemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify); @@ -419,7 +421,7 @@ void BLEMIDI_Client_ESP32::notifyCB(NimBLERemoteCharacteristic *pRemoteCharacter { if (this->_characteristic == pRemoteCharacteristic) //Redundant protection { - if (uxQueueSpacesAvailable(mRxQueue) >= length) //Don't overflow the queue. If the queue is overflowed, comunication breaks out + //if (uxQueueSpacesAvailable(mRxQueue) >= length) //Don't overflow the queue. receive(pData, length); } } @@ -461,26 +463,23 @@ bool BLEMIDI_Client_ESP32::connect() { if (_characteristic->canNotify()) { - if (!_characteristic->subscribe(true, std::bind(&BLEMIDI_Client_ESP32::notifyCB, this, _1, _2, _3, _4))) - { - Serial.println("Error - Not subscribe"); - /** Disconnect if subscribe failed */ - _client->disconnect(); - return false; - } - else + if (_characteristic->subscribe(true, std::bind(&BLEMIDI_Client_ESP32::notifyCB, this, _1, _2, _3, _4))) { + //Re-connection SUCCESS return true; } - } - } - else - { - Serial.println("Error. Reconnect failed"); - _client = nullptr; - return false; + } + /** Disconnect if subscribe failed */ + _client->disconnect(); } + /* If any connection problem exits, delete previous client and try again in the next attemp as new client*/ + NimBLEDevice::deleteClient(_client); + _client = nullptr; + return false; } + /*If client does not match, delete previous client and create a new one*/ + NimBLEDevice::deleteClient(_client); + _client = nullptr; } if (NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) @@ -488,12 +487,14 @@ bool BLEMIDI_Client_ESP32::connect() Serial.println("Max clients reached - no more connections available"); return false; } - + + // Create and setup a new client _client = BLEDevice::createClient(); _client->setClientCallbacks(new MyClientCallbacks(this), false); - _client->setConnectionParams(BLEMIDI_CLIENT_COMM_MIN_INTERVAL, BLEMIDI_CLIENT_COMM_MAX_INTERVAL + 10, BLEMIDI_CLIENT_COMM_LATENCY + 1, BLEMIDI_CLIENT_COMM_TIMEOUT + 10); + _client->setConnectionParams(BLEMIDI_CLIENT_COMM_MIN_INTERVAL, BLEMIDI_CLIENT_COMM_MAX_INTERVAL, BLEMIDI_CLIENT_COMM_LATENCY, BLEMIDI_CLIENT_COMM_TIMEOUT); + /** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */ _client->setConnectTimeout(15); @@ -502,14 +503,15 @@ bool BLEMIDI_Client_ESP32::connect() /** Created a client but failed to connect, don't need to keep it as it has no data */ NimBLEDevice::deleteClient(_client); _client = nullptr; - Serial.println("Failed to connect, deleted client"); + //Serial.println("Failed to connect, deleted client"); return false; } if (!_client->isConnected()) { - Serial.println("Failed to connect"); + //Serial.println("Failed to connect"); _client->disconnect(); + NimBLEDevice::deleteClient(_client); _client = nullptr; return false; } @@ -519,44 +521,35 @@ bool BLEMIDI_Client_ESP32::connect() Serial.print(" / "); Serial.println(_client->getPeerAddress().toString().c_str()); + /* Serial.print("RSSI: "); Serial.println(_client->getRssi()); - + */ + /** Now we can read/write/subscribe the charateristics of the services we are interested in */ pSvc = _client->getService(SERVICE_UUID); - if (pSvc) - { /** make sure it's not null */ + if (pSvc) /** make sure it's not null */ + { _characteristic = pSvc->getCharacteristic(CHARACTERISTIC_UUID); - - if (_characteristic) - { /** make sure it's not null */ + + if (_characteristic) /** make sure it's not null */ + { if (_characteristic->canNotify()) { - if (!_characteristic->subscribe(true, std::bind(&BLEMIDI_Client_ESP32::notifyCB, this, _1, _2, _3, _4))) + if (_characteristic->subscribe(true, std::bind(&BLEMIDI_Client_ESP32::notifyCB, this, _1, _2, _3, _4))) { - Serial.println("Error - Subcribe error"); - /** Disconnect if subscribe failed */ - _client->disconnect(); - return false; + //Connection SUCCESS + return true; } } - else - { - return false; - } - } - else - { - return false; } } - else - { - Serial.println("Error. MIDI service not found."); - return false; - } - - return true; + + //If anything fails, disconnect and delete client + _client->disconnect(); + NimBLEDevice::deleteClient(_client); + _client = nullptr; + return false; }; /** Callback to process the results of the last scan or restart it */ From 586207d747c3fdbc7e80ab56165e41b15d21ad95 Mon Sep 17 00:00:00 2001 From: Roberto Date: Thu, 5 Aug 2021 17:39:33 +0200 Subject: [PATCH 12/17] Some little modification in Scan times. Removed a printf in Connect() Removed avaliableSpace --- src/hardware/BLEMIDI_Client_ESP32.h | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/hardware/BLEMIDI_Client_ESP32.h b/src/hardware/BLEMIDI_Client_ESP32.h index afca3bf..6b066e3 100644 --- a/src/hardware/BLEMIDI_Client_ESP32.h +++ b/src/hardware/BLEMIDI_Client_ESP32.h @@ -94,10 +94,10 @@ static uint32_t userOnPassKeyRequest() * 0 latency (Number of intervals allowed to skip), * TimeOut (unit: 10ms) 51 * 10ms = 510ms. Timeout should be minimum 100ms. */ -#define BLEMIDI_CLIENT_COMM_MIN_INTERVAL 6 // 7.5ms -#define BLEMIDI_CLIENT_COMM_MAX_INTERVAL 35 // 40ms +#define BLEMIDI_CLIENT_COMM_MIN_INTERVAL 6 // 7.5ms +#define BLEMIDI_CLIENT_COMM_MAX_INTERVAL 35 // 40ms #define BLEMIDI_CLIENT_COMM_LATENCY 0 -#define BLEMIDI_CLIENT_COMM_TIMEOUT 400 //4000ms +#define BLEMIDI_CLIENT_COMM_TIMEOUT 200 //2000ms /* ############################################# @@ -309,7 +309,7 @@ protected: } //Try reconnection or search a new one - NimBLEDevice::getScan()->start(3, scanEndedCB); + NimBLEDevice::getScan()->start(1, scanEndedCB); } bool onConnParamsUpdateRequest(NimBLEClient *pClient, const ble_gap_upd_params *params) @@ -421,7 +421,6 @@ void BLEMIDI_Client_ESP32::notifyCB(NimBLERemoteCharacteristic *pRemoteCharacter { if (this->_characteristic == pRemoteCharacteristic) //Redundant protection { - //if (uxQueueSpacesAvailable(mRxQueue) >= length) //Don't overflow the queue. receive(pData, length); } } @@ -441,13 +440,12 @@ void BLEMIDI_Client_ESP32::scan() pBLEScan->setActiveScan(true); Serial.println("Scanning..."); - pBLEScan->start(3, scanEndedCB); + pBLEScan->start(1, scanEndedCB); } }; bool BLEMIDI_Client_ESP32::connect() { - Serial.println("Try Connection..."); using namespace std::placeholders; //<- for bind funtion in callback notification /** Check if we have a client we should reuse first From 31817fba8eed9ba8a2ca0ec0794a9c69140b76dd Mon Sep 17 00:00:00 2001 From: Roberto Date: Thu, 5 Aug 2021 21:31:42 +0200 Subject: [PATCH 13/17] Parser Upgraded --- src/BLEMIDI_Transport.h | 128 +++++++++++++++++++++------------------- 1 file changed, 67 insertions(+), 61 deletions(-) diff --git a/src/BLEMIDI_Transport.h b/src/BLEMIDI_Transport.h index 329a265..6f6c269 100644 --- a/src/BLEMIDI_Transport.h +++ b/src/BLEMIDI_Transport.h @@ -47,6 +47,11 @@ public: { mBleClass.begin(mDeviceName, this); } + + void end() + { + mBleClass.end(); + } bool beginTransmission(MIDI_NAMESPACE::MidiType type) { @@ -95,11 +100,6 @@ public: return mRxBuffer[--mRxIndex]; } - bool end() - { - return mBleClass.end(); - } - unsigned available() { uint8_t byte; @@ -165,8 +165,11 @@ protected: *timestamp = (currentTimeStamp & 0x7F) | 0x80; // 7 bits plus MSB } - static void setMidiTimestamp(uint8_t header, uint8_t *timestamp) + static uint16_t setMidiTimestamp(uint8_t header, uint8_t timestamp) { + auto timestampHigh = 0x3f & header; + auto timestampLow = 0x7f & timestamp; + return (timestampLow + (timestampHigh << 7)); } public: @@ -215,6 +218,14 @@ public: MIDI messages. In the MIDI BLE protocol, the System Real-Time messages must be deinterleaved from other messages – except for System Exclusive messages. */ + + /** + * If #define RUNNING_ENABLE is commented, it will transform all incoming runningStatus messages in full midi messages. + * Else, it will put in the buffer the same info that it had received (runningStatus will be not transformated). + * It recommend not use runningStatus by default. Only use if parser accepts runningStatus and your application has a so high transmission rate. + */ + //#define RUNNING_ENABLE + void receive(byte *buffer, size_t length) { // Pointers used to search through payload. @@ -236,12 +247,12 @@ public: bool sysExContinuation = false; bool runningStatusContinuation = false; - if (timestampByte >= 80) + if (timestampByte >= 80) // if bit 7 is 1, it's a timestampByte { auto timestampLow = 0x7f & timestampByte; timestamp = timestampLow + (timestampHigh << 7); } - else + else // if bit 7 is 0, it's the Continuation of a previous SysEx { sysExContinuation = true; lPtr--; // the second byte is part of the SysEx @@ -270,68 +281,58 @@ public: if (!runningStatusContinuation) { - // look at l and r pointers and decode by size. - if (rPtr - lPtr < 1) - { - // Time code or system - mBleClass.add(buffer[lPtr]); - } - else if (rPtr - lPtr < 2) - { - mBleClass.add(buffer[lPtr]); - mBleClass.add(buffer[lPtr + 1]); - } - else if (rPtr - lPtr < 3) - { - mBleClass.add(buffer[lPtr]); - mBleClass.add(buffer[lPtr + 1]); - mBleClass.add(buffer[lPtr + 2]); - } - else - { - // Too much data - // If not System Common or System Real-Time, send it as running status + // If not System Common or System Real-Time, send it as running status - auto midiType = lastStatus & 0xF0; - if (sysExContinuation) - midiType = 0xF0; + auto midiType = lastStatus & 0xF0; + if (sysExContinuation) + midiType = 0xF0; - switch (midiType) + switch (midiType) + { + case 0x80: + case 0x90: + case 0xA0: + case 0xB0: + case 0xE0: +#ifdef RUNNING_ENABLE + mBleClass.add(lastStatus); +#endif + for (auto i = lPtr; i < rPtr; i = i + 2) { - case 0x80: - case 0x90: - case 0xA0: - case 0xB0: - case 0xE0: - for (auto i = lPtr; i < rPtr; i = i + 2) - { - mBleClass.add(lastStatus); - mBleClass.add(buffer[i + 1]); - mBleClass.add(buffer[i + 2]); - } - break; - case 0xC0: - case 0xD0: - for (auto i = lPtr; i < rPtr; i = i + 1) - { - mBleClass.add(lastStatus); - mBleClass.add(buffer[i + 1]); - } - break; - case 0xF0: +#ifndef RUNNING_ENABLE mBleClass.add(lastStatus); - for (auto i = lPtr; i < rPtr; i++) - mBleClass.add(buffer[i + 1]); - - break; - - default: - break; +#endif + mBleClass.add(buffer[i + 1]); + mBleClass.add(buffer[i + 2]); } + break; + case 0xC0: + case 0xD0: +#ifdef RUNNING_ENABLE + mBleClass.add(lastStatus); +#endif + for (auto i = lPtr; i < rPtr; i = i + 1) + { +#ifndef RUNNING_ENABLE + mBleClass.add(lastStatus); +#endif + mBleClass.add(buffer[i + 1]); + } + break; + case 0xF0: + mBleClass.add(lastStatus); + for (auto i = lPtr; i < rPtr; i++) + mBleClass.add(buffer[i + 1]); + + break; + + default: + break; } } else { +#ifndef RUNNING_ENABLE auto midiType = lastStatus & 0xF0; if (sysExContinuation) midiType = 0xF0; @@ -364,6 +365,11 @@ public: default: break; } +#else + mBleClass.add(lastStatus); + for (auto i = lPtr; i <= rPtr; i++) + mBleClass.add(buffer[i]); +#endif runningStatusContinuation = false; } From ad26e7b4eddf03c5abfcc6f6f39c8c4e517e72bc Mon Sep 17 00:00:00 2001 From: RobertoHE Date: Fri, 6 Aug 2021 00:00:04 +0200 Subject: [PATCH 14/17] Undo Changes for PullRequest Conflict with other opened pull request --- src/BLEMIDI_Transport.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/BLEMIDI_Transport.h b/src/BLEMIDI_Transport.h index 6f6c269..0988035 100644 --- a/src/BLEMIDI_Transport.h +++ b/src/BLEMIDI_Transport.h @@ -122,7 +122,6 @@ protected: The header byte contains the topmost 6 bits of timing information for MIDI events in the BLE packet. The remaining 7 bits of timing information for individual MIDI messages encoded in a packet is expressed by timestamp bytes. - Timestamp Byte bit 7 Set to 1. bits 6-0 timestampLow: Least Significant 7 bits of timestamp information. From ab1dd1567ff921010c49bef638c1fc9f75b857e1 Mon Sep 17 00:00:00 2001 From: RobertoHE Date: Fri, 6 Aug 2021 08:23:03 +0200 Subject: [PATCH 15/17] Create BLE-MIDI_Client_ESP32.h --- .../src/hardware/BLE-MIDI_Client_ESP32.h | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 src/hardware/src/hardware/BLE-MIDI_Client_ESP32.h diff --git a/src/hardware/src/hardware/BLE-MIDI_Client_ESP32.h b/src/hardware/src/hardware/BLE-MIDI_Client_ESP32.h new file mode 100644 index 0000000..b2a1029 --- /dev/null +++ b/src/hardware/src/hardware/BLE-MIDI_Client_ESP32.h @@ -0,0 +1,95 @@ +#pragma once + +// Headers for ESP32 BLE +#include +#include +#include +#include + +BEGIN_BLEMIDI_NAMESPACE + +class BLEMIDI_Client_ESP32 +{ +private: + BLEClient* _client = nullptr; + + BLEMIDI* _bleMidiTransport = nullptr; + +public: + BLEMIDI_Client_ESP32() + { + } + + bool begin(const char*, BLEMIDI*); + + void write(uint8_t* data, uint8_t length) + { + _characteristic->setValue(data, length); + _characteristic->notify(); + } + + void receive(uint8_t* buffer, size_t length) + { + // Post the items to the back of the queue + // (drop the first 2 items) + for (size_t i = 2; i < length; i++) + xQueueSend(_bleMidiTransport->mRxQueue, &buffer[i], portMAX_DELAY); + } + + void connected() + { + if (_bleMidiTransport->_connectedCallback) + _bleMidiTransport->_connectedCallback(); + } + + void disconnected() + { + if (_bleMidiTransport->_disconnectedCallback) + _bleMidiTransport->_disconnectedCallback(); + } +}; + +class MyClientCallbacks: public BLEClientCallbacks { +public: + MyClientCallbacks(BLEMIDI_Client_ESP32* bluetoothEsp32) + : _bluetoothEsp32(bluetoothEsp32) { + } + +protected: + BLEMIDI_Client_ESP32* _bluetoothEsp32 = nullptr; + + void onConnect(BLEClient*) { + if (_bluetoothEsp32) + _bluetoothEsp32->connected(); + }; + + void onDisconnect(BLEClient*) { + if (_bluetoothEsp32) + _bluetoothEsp32->disconnected(); + } +}; + +bool BLEMIDI_Client_ESP32::begin(const char* deviceName, BLEMIDI* bleMidiTransport) +{ + _bleMidiTransport = bleMidiTransport; + + BLEDevice::init(deviceName); + + _client = BLEDevice::createClient(); + _client->setCallbacks(new MyClientCallbacks(this)); + + // Retrieve a Scanner and set the callback we want to use to be informed when we + // have detected a new device. Specify that we want active scanning and start the + // scan to run for 5 seconds. + pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks(this)); + pBLEScan->setInterval(1349); + pBLEScan->setWindow(449); + pBLEScan->setActiveScan(true); + doScan = true; + pBLEScan->start(10, scanCompleteCB); + + return true; +} + +END_BLEMIDI_NAMESPACE From c87819b42e1c316f5ad21de70076ec7261ca7af5 Mon Sep 17 00:00:00 2001 From: RobertoHE Date: Fri, 6 Aug 2021 08:27:37 +0200 Subject: [PATCH 16/17] Delete src/hardware/src directory --- .../src/hardware/BLE-MIDI_Client_ESP32.h | 95 ------------------- 1 file changed, 95 deletions(-) delete mode 100644 src/hardware/src/hardware/BLE-MIDI_Client_ESP32.h diff --git a/src/hardware/src/hardware/BLE-MIDI_Client_ESP32.h b/src/hardware/src/hardware/BLE-MIDI_Client_ESP32.h deleted file mode 100644 index b2a1029..0000000 --- a/src/hardware/src/hardware/BLE-MIDI_Client_ESP32.h +++ /dev/null @@ -1,95 +0,0 @@ -#pragma once - -// Headers for ESP32 BLE -#include -#include -#include -#include - -BEGIN_BLEMIDI_NAMESPACE - -class BLEMIDI_Client_ESP32 -{ -private: - BLEClient* _client = nullptr; - - BLEMIDI* _bleMidiTransport = nullptr; - -public: - BLEMIDI_Client_ESP32() - { - } - - bool begin(const char*, BLEMIDI*); - - void write(uint8_t* data, uint8_t length) - { - _characteristic->setValue(data, length); - _characteristic->notify(); - } - - void receive(uint8_t* buffer, size_t length) - { - // Post the items to the back of the queue - // (drop the first 2 items) - for (size_t i = 2; i < length; i++) - xQueueSend(_bleMidiTransport->mRxQueue, &buffer[i], portMAX_DELAY); - } - - void connected() - { - if (_bleMidiTransport->_connectedCallback) - _bleMidiTransport->_connectedCallback(); - } - - void disconnected() - { - if (_bleMidiTransport->_disconnectedCallback) - _bleMidiTransport->_disconnectedCallback(); - } -}; - -class MyClientCallbacks: public BLEClientCallbacks { -public: - MyClientCallbacks(BLEMIDI_Client_ESP32* bluetoothEsp32) - : _bluetoothEsp32(bluetoothEsp32) { - } - -protected: - BLEMIDI_Client_ESP32* _bluetoothEsp32 = nullptr; - - void onConnect(BLEClient*) { - if (_bluetoothEsp32) - _bluetoothEsp32->connected(); - }; - - void onDisconnect(BLEClient*) { - if (_bluetoothEsp32) - _bluetoothEsp32->disconnected(); - } -}; - -bool BLEMIDI_Client_ESP32::begin(const char* deviceName, BLEMIDI* bleMidiTransport) -{ - _bleMidiTransport = bleMidiTransport; - - BLEDevice::init(deviceName); - - _client = BLEDevice::createClient(); - _client->setCallbacks(new MyClientCallbacks(this)); - - // Retrieve a Scanner and set the callback we want to use to be informed when we - // have detected a new device. Specify that we want active scanning and start the - // scan to run for 5 seconds. - pBLEScan = BLEDevice::getScan(); - pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks(this)); - pBLEScan->setInterval(1349); - pBLEScan->setWindow(449); - pBLEScan->setActiveScan(true); - doScan = true; - pBLEScan->start(10, scanCompleteCB); - - return true; -} - -END_BLEMIDI_NAMESPACE From 3c8f54c459ffefc1e7c3a271af180240018c30b3 Mon Sep 17 00:00:00 2001 From: RobertoHE Date: Fri, 6 Aug 2021 08:29:48 +0200 Subject: [PATCH 17/17] Create BLE-MIDI_Client_ESP32.h For pull request only --- src/hardware/BLE-MIDI_Client_ESP32.h | 100 +++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 src/hardware/BLE-MIDI_Client_ESP32.h diff --git a/src/hardware/BLE-MIDI_Client_ESP32.h b/src/hardware/BLE-MIDI_Client_ESP32.h new file mode 100644 index 0000000..8b31f3b --- /dev/null +++ b/src/hardware/BLE-MIDI_Client_ESP32.h @@ -0,0 +1,100 @@ +#pragma once + +// Headers for ESP32 BLE +#include +#include +#include +#include + +BEGIN_BLEMIDI_NAMESPACE + +class BLEMIDI_Client_ESP32 +{ +private: + BLEClient* _client = nullptr; + + BLEMIDI* _bleMidiTransport = nullptr; + +public: + BLEMIDI_Client_ESP32() + { + } + + bool begin(const char*, BLEMIDI*); + + void end() + { + + } + + void write(uint8_t* data, uint8_t length) + { + _characteristic->setValue(data, length); + _characteristic->notify(); + } + + void receive(uint8_t* buffer, size_t length) + { + // Post the items to the back of the queue + // (drop the first 2 items) + for (size_t i = 2; i < length; i++) + xQueueSend(_bleMidiTransport->mRxQueue, &buffer[i], portMAX_DELAY); + } + + void connected() + { + if (_bleMidiTransport->_connectedCallback) + _bleMidiTransport->_connectedCallback(); + } + + void disconnected() + { + if (_bleMidiTransport->_disconnectedCallback) + _bleMidiTransport->_disconnectedCallback(); + } +}; + +class MyClientCallbacks: public BLEClientCallbacks { +public: + MyClientCallbacks(BLEMIDI_Client_ESP32* bluetoothEsp32) + : _bluetoothEsp32(bluetoothEsp32) { + } + +protected: + BLEMIDI_Client_ESP32* _bluetoothEsp32 = nullptr; + + void onConnect(BLEClient*) { + if (_bluetoothEsp32) + _bluetoothEsp32->connected(); + }; + + void onDisconnect(BLEClient*) { + if (_bluetoothEsp32) + _bluetoothEsp32->disconnected(); + } +}; + +bool BLEMIDI_Client_ESP32::begin(const char* deviceName, BLEMIDI* bleMidiTransport) +{ + _bleMidiTransport = bleMidiTransport; + + BLEDevice::init(deviceName); + + _client = BLEDevice::createClient(); + _client->setCallbacks(new MyClientCallbacks(this)); + + // Retrieve a Scanner and set the callback we want to use to be informed when we + // have detected a new device. Specify that we want active scanning and start the + // scan to run for 5 seconds. + pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks(this)); + pBLEScan->setInterval(1349); + pBLEScan->setWindow(449); + pBLEScan->setActiveScan(true); + doScan = true; + pBLEScan->start(10, scanCompleteCB); + + return true; +} + +END_BLEMIDI_NAMESPACE