A while ago I wrote a sketch providing Midi over BLE. It allows me to connect apps on my iPhone to get paired with my ESP32. I can send Midi commands back and forth. It works with all apps I tried so far. I assume it is more or less based on common example we all can find in the internet. I did put it into a class - here's my code:
Code: Select all
#include "BleMidi.h"
extern midi::MidiInterface<HardwareSerial> midiA;
extern Settings settings;
extern BleMidi *pBleMidi;
extern uint8_t midiPacket[];
BLEServer *pServer = NULL;
class BleMidiServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
Serial.println("Connected to client");
BleMidi::deviceConnected = true;
Serial.println(">> Readvertising");
pServer->getAdvertising()->start();
};
void onDisconnect(BLEServer* pServer) {
Serial.println("Disconneted from client");
BleMidi::deviceConnected = false;
}
};
class BleMidiCharacteristicCallbacks: public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
std::string rxValue = pCharacteristic->getValue();
pBleMidi->pCharacteristic->setValue((uint8_t*)pCharacteristic->getValue().c_str(), rxValue.length()); // packet, length in bytes
pBleMidi->pCharacteristic->notify();
uint8_t midiMessageLength = rxValue.length();
uint8_t processedBytes = 0;
uint8_t type, note, velocity, channel, d1, d2;
if (midiMessageLength < 3) {
Serial.println("Received Midi Message too short. Aborting!");
return;
}
Serial.println("*********");
Serial.print("Received Value: ");
for (int i = 0; i < rxValue.length(); i++) {
Serial.print(rxValue[i], HEX);
Serial.print(" ");
}
Serial.println();
Serial.println("*********");
// timestamp #1 and timestamp #2
processedBytes += 2;
while(processedBytes < midiMessageLength) {
channel = rxValue[processedBytes] % 16;
type = rxValue[processedBytes++] - channel;
switch(type) {
case midi::ControlChange:
note = rxValue[processedBytes++];
velocity = rxValue[processedBytes++];
midiA.sendControlChange(note, velocity, channel+1);
Serial.println(String("Control Change: controller=") + note + ", value=" + velocity);
break;
case midi::NoteOn:
note = rxValue[processedBytes++];
velocity = rxValue[processedBytes++];
channel = rxValue[processedBytes++];
midiA.sendNoteOn(note, velocity, channel+1);
if (velocity > 0) {
Serial.println(String("Note On: ch=") + channel + ", note=" + note + ", velocity=" + velocity);
} else {
Serial.println(String("Note Off: ch=") + channel + ", note=" + note);
}
break;
case midi::NoteOff:
note = rxValue[processedBytes++];
velocity = rxValue[processedBytes++];
channel = rxValue[processedBytes++];
midiA.sendNoteOff(note, velocity, channel+1);
Serial.println(String("Note Off: ch=") + channel + ", note=" + note + ", velocity=" + velocity);
break;
case midi::ProgramChange:
note = rxValue[processedBytes++];
midiA.sendProgramChange(note, channel+1);
Serial.println(String("Program Change: ch=") + channel + String(", program=") + note);
break;
case midi::SystemExclusive: {
uint8_t *sysexData = new uint8_t[midiMessageLength];
uint8_t byteCnt = 0;
int iF0 = -1;
int iF7 = -1;
uint8_t timingTag = 0x80;
for(int i=0 ; i<midiMessageLength ; i++) {
if(rxValue[i] == 0xF0) {
if(iF0 != -1)
Serial.println("Already found a F0 - should not happen");
iF0 = i;
if(i > 0)
timingTag = rxValue[i-1];
}
else if(rxValue[i] == 0xF7) {
if(iF7 != -1)
Serial.println("Already found a F7 - should not happen");
iF7 = i;
}
if(iF0 != -1 && iF7 != -1) {
if(iF7 < iF0)
Serial.println("iF7 is smaller than iF0 -> should never happen");
else {
for(int j=iF0; ; j++) {
if(rxValue[j] == timingTag)
continue;
sysexData[byteCnt++] = rxValue[j];
if(rxValue[j] == 0xF7)
break;
}
if(byteCnt > 3) {
if(sysexData[0] == 0xF0 && sysexData[1] == 0x33 && sysexData[2] == 0x20 && sysexData[3] == 0x00) {
settings.processMidi(sysexData, byteCnt, pCharacteristic);
}
else {
midiA.sendSysEx(byteCnt, sysexData, true);
Serial.print("Data to Physical Midi Out: ");
for (int i = 0; i < byteCnt ; i++) {
Serial.print(sysexData[i], HEX); Serial.print(" ");
}
Serial.println();
}
}
iF0 = iF7 = -1;
byteCnt = 0;
timingTag = 0x80;
}
}
}
delete sysexData;
return;
break;
}
default:
break;
}
// skip one byte as we expected the next timestamp
processedBytes++;
}
}
};
bool BleMidi::deviceConnected = false;
BleMidi::BleMidi() {
Serial.print("Initialising BLE device... ");
char name[17];
strcpy(name, settings.name); name[16] = '\0';
BLEDevice::init(name);
Serial.print("My address: "); Serial.println(BLEDevice::getAddress().toString().c_str());
// Create the BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new BleMidiServerCallbacks());
BLEDevice::setEncryptionLevel((esp_ble_sec_act_t)ESP_LE_AUTH_REQ_SC_BOND);
Serial.println("Done!");
Serial.print("Creating MIDI BLE Service... ");
// Create the BLE Service
BLEService *pService = pServer->createService(BLEUUID(MIDI_SERVICE_UUID));
// Create a BLE Characteristic
pCharacteristic = pService->createCharacteristic(
BLEUUID(MIDI_CHARACTERISTIC_UUID),
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_NOTIFY |
BLECharacteristic::PROPERTY_WRITE_NR
);
pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
Serial.println("Done!");
// https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
// Create a BLE Descriptor
pCharacteristic->addDescriptor(new BLE2902());
BleMidiCharacteristicCallbacks *pBleMidiCallbacks = new BleMidiCharacteristicCallbacks();
pCharacteristic->setCallbacks(pBleMidiCallbacks);
Serial.print("Starting BLE service... ");
pService->start();
Serial.println("Done!");
Serial.print("Starting advertisement... ");
BLESecurity *pSecurity = new BLESecurity();
pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND);
pSecurity->setCapability(ESP_IO_CAP_NONE);
pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);
pServer->getAdvertising()->addServiceUUID(MIDI_SERVICE_UUID);
pServer->getAdvertising()->start();
Serial.println("Done!");
Serial.print("BLE service is running - device discoverable as: "); Serial.println(settings.name);
}
Code: Select all
/**
* A BLE client example that is rich in capabilities.
* There is a lot new capabilities implemented.
* author unknown
* updated by chegewara
*/
#include "BLEDevice.h"
//#include "BLEScan.h"
// The remote service we wish to connect to.
static BLEUUID serviceUUID("03b80e5a-ede8-4b33-a751-6ce34ec4c700");
// The characteristic of the remote service we are interested in.
static BLEUUID charUUID("7772e5db-3868-4112-a1a9-f2669d106bf3");
static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;
static BLEAdvertisedDevice* myDevice;
static void notifyCallback(
BLERemoteCharacteristic* pBLERemoteCharacteristic,
uint8_t* pData,
size_t length,
bool isNotify) {
Serial.print("Notify callback for characteristic ");
Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
Serial.print(" of data length ");
Serial.println(length);
Serial.print("data: ");
Serial.println((char*)pData);
}
class MyClientCallback : public BLEClientCallbacks {
void onConnect(BLEClient* pclient) {
}
void onDisconnect(BLEClient* pclient) {
connected = false;
Serial.println("onDisconnect");
}
};
bool connectToServer() {
Serial.print("Forming a connection to ");
Serial.println(myDevice->getAddress().toString().c_str());
BLEClient* pClient = BLEDevice::createClient();
Serial.println(" - Created client");
pClient->setClientCallbacks(new MyClientCallback());
// Connect to the remove BLE Server.
pClient->connect(myDevice); // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
Serial.println(" - Connected to server");
// Obtain a reference to the service we are after in the remote BLE server.
BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
if (pRemoteService == nullptr) {
Serial.print("Failed to find our service UUID: ");
Serial.println(serviceUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println(" - Found our service");
// Obtain a reference to the characteristic in the service of the remote BLE server.
pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
if (pRemoteCharacteristic == nullptr) {
Serial.print("Failed to find our characteristic UUID: ");
Serial.println(charUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println(" - Found our characteristic");
// Read the value of the characteristic.
if(pRemoteCharacteristic->canRead()) {
std::string value = pRemoteCharacteristic->readValue();
Serial.print("The characteristic value was: ");
Serial.println(value.c_str());
}
if(pRemoteCharacteristic->canNotify())
pRemoteCharacteristic->registerForNotify(notifyCallback);
connected = true;
return true;
}
/**
* Scan for BLE servers and find the first one that advertises the service we are looking for.
*/
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
/**
* Called for each advertising BLE server.
*/
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.print("BLE Advertised Device found: ");
Serial.println(advertisedDevice.toString().c_str());
// We have found a device, let us now see if it contains the service we are looking for.
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {
BLEDevice::getScan()->stop();
myDevice = new BLEAdvertisedDevice(advertisedDevice);
doConnect = true;
doScan = true;
} // Found our server
} // onResult
}; // MyAdvertisedDeviceCallbacks
void setup() {
Serial.begin(115200);
Serial.println("Starting Arduino BLE Client application...");
BLEDevice::init("");
// 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.
BLEScan* pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setInterval(1349);
pBLEScan->setWindow(449);
pBLEScan->setActiveScan(true);
pBLEScan->start(5, false);
} // End of setup.
// This is the Arduino main loop function.
void loop() {
// If the flag "doConnect" is true then we have scanned for and found the desired
// BLE Server with which we wish to connect. Now we connect to it. Once we are
// connected we set the connected flag to be true.
if (doConnect == true) {
if (connectToServer()) {
Serial.println("We are now connected to the BLE Server.");
} else {
Serial.println("We have failed to connect to the server; there is nothin more we will do.");
}
doConnect = false;
}
// If we are connected to a peer BLE Server, update the characteristic each time we are reached
// with the current time since boot.
if (connected) {
String newValue = "Time since boot: " + String(millis()/1000);
Serial.println("Setting new characteristic value to \"" + newValue + "\"");
// Set the characteristic's value to be the array of bytes that is actually a string.
pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());
}else if(doScan){
BLEDevice::getScan()->start(0); // this is just example to start scan after disconnect, most likely there is better way to do it in arduino
}
delay(1000); // Delay a second between loops.
} // End of loop
Code: Select all
void onWrite(BLECharacteristic *pCharacteristic)
Next, I installed a couple of BLE tools on my mobile, such as nRF Connect. Here, I can also connect to my server and send values.
If I send a ByteArray as shown in the attached picture I can properly invoke my BLE server's
Code: Select all
void onWrite(BLECharacteristic *pCharacteristic)
Thus, I believe my server sketch is okay. Also, I need some help sending values to from my client to the server. Any idea what I am doing wrong here?
Thanks in advance!