Compare commits

..

No commits in common. "master" and "v2.1.0" have entirely different histories.

16 changed files with 362 additions and 1253 deletions

View File

@ -1,10 +0,0 @@
name: build
on: [pull_request, push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Build on Arduino CLI
run: bash ci/build-arduino.sh

4
.gitignore vendored
View File

@ -3,7 +3,3 @@ examples/.DS_Store
src/.DS_Store src/.DS_Store
.vscode/settings.json .vscode/settings.json
.vscode/c_cpp_properties.json .vscode/c_cpp_properties.json
test/msvc/.vs
test/msvc/x64
test/msvc/ConsoleApplication2.vcxproj.user
test/msvc/ConsoleApplication2.vcxproj.filters

View File

@ -1,6 +1,4 @@
# Arduino BLE-MIDI Transport # Arduino BLE-MIDI Transport
[![arduino-library-badge](https://www.ardu-badge.com/badge/BLE-MIDI.svg?)](https://www.ardu-badge.com/BLE-MIDI)
This library implements the BLE-MIDI transport layer for the [FortySevenEffects Arduino MIDI Library](https://github.com/FortySevenEffects/arduino_midi_library) This library implements the BLE-MIDI transport layer for the [FortySevenEffects Arduino MIDI Library](https://github.com/FortySevenEffects/arduino_midi_library)
## Installation ## Installation
@ -11,7 +9,7 @@ When installing this library from the Arduino IDE, the dependency be downloaded
When manually installing this library, you have to manually download [Arduino MIDI Library](https://github.com/FortySevenEffects/arduino_midi_library) from github and install it in the same directory as this library - without this additional install, this library will not be able to compile. When manually installing this library, you have to manually download [Arduino MIDI Library](https://github.com/FortySevenEffects/arduino_midi_library) from github and install it in the same directory as this library - without this additional install, this library will not be able to compile.
When using `ESP32` consider using NimBLE (`NimBLE-Arduino`). When using `ESP32` consider using NimBLE (`NimBLE-Arduino`).
When using the `Arduino NANO 33 BLE` or `Arduino NANO RP2040 Connect`, you must install `ArduinoBLE` When using the `Arduino NANO 33 BLE`, you have to install `ArduinoBLE`
## Usage ## Usage
### Basic / Default ### Basic / Default
@ -65,9 +63,8 @@ void OnDisconnected() {
will create a instance named `BLEMIDI` and listens to incoming MIDI. will create a instance named `BLEMIDI` and listens to incoming MIDI.
## Tested boards/modules ## Tested boards/modules
- ESP32 (OOB BLE and NimBLE) - ESP32 (OOB BLE and NimBLE)
- Arduino NANO 33 BLE - Arduino NANO 33 BLE
- Arduino NANO RP2040 Connect
## Other Transport protocols: ## Other Transport protocols:
The libraries below the same calling mechanism (API), making it easy to interchange the transport layer. The libraries below the same calling mechanism (API), making it easy to interchange the transport layer.

View File

@ -1,54 +0,0 @@
#!/bin/bash
# Exit immediately if a command exits with a non-zero status.
set -e
# Enable the globstar shell option
shopt -s globstar
# Make sure we are inside the github workspace
cd $GITHUB_WORKSPACE
# Create directories
mkdir $HOME/Arduino
mkdir $HOME/Arduino/libraries
# Install Arduino IDE
export PATH=$PATH:$GITHUB_WORKSPACE/bin
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh
arduino-cli config init
arduino-cli config set library.enable_unsafe_install true
# arduino-cli core update-index --additional-urls https://arduino.esp8266.com/stable/package_esp8266com_index.json
arduino-cli core update-index --additional-urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
arduino-cli core update-index
# Install Arduino AVR core
arduino-cli core install arduino:avr
arduino-cli core install arduino:samd
# arduino-cli core install esp8266:esp8266
arduino-cli core install esp32:esp32
# List the boards
arduino-cli board list
# Link Arduino library
ln -s $GITHUB_WORKSPACE $HOME/Arduino/libraries/CI_Test_Library
arduino-cli lib install "MIDI library"
arduino-cli lib install ArduinoBLE
arduino-cli lib install NimBLE-Arduino
# Compile all *.ino files for the Arduino Uno
# for f in **/AVR_*.ino ; do
# arduino-cli compile -b arduino:avr:uno $f
# done
# Compile all *.ino files for the Arduino Uno
# for f in **/SAMD_*.ino ; do
# arduino-cli compile -b arduino:samd:mkrzero $f
# done
# Compile all *.ino files for the Arduino Uno
# for f in **/ESP8266_*.ino ; do
# arduino-cli compile -b arduino:esp8266:??? $f
# done
# Compile all *.ino files for the Arduino Uno
for f in **/*.ino ; do
arduino-cli compile -b arduino:esp32:??? $f
done

View File

View File

@ -1,7 +1,7 @@
#include <BLEMIDI_Transport.h> #include <BLEMIDI_Transport.h>
//#include <hardware/BLEMIDI_ESP32_NimBLE.h> #include <hardware/BLEMIDI_ESP32_NimBLE.h>
#include <hardware/BLEMIDI_ESP32.h> //#include <hardware/BLEMIDI_ESP32.h>
//#include <hardware/BLEMIDI_nRF52.h> //#include <hardware/BLEMIDI_nRF52.h>
//#include <hardware/BLEMIDI_ArduinoBLE.h> //#include <hardware/BLEMIDI_ArduinoBLE.h>
@ -11,9 +11,7 @@ unsigned long t0 = millis();
bool isConnected = false; bool isConnected = false;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// When BLE connected, LED will turn on (indication 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() void setup()
{ {
@ -22,22 +20,11 @@ void setup()
pinMode(LED_BUILTIN, OUTPUT); pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW); digitalWrite(LED_BUILTIN, LOW);
BLEMIDI.setHandleConnected([]() { BLEMIDI.setHandleConnected(OnConnected);
isConnected = true; BLEMIDI.setHandleDisconnected(OnDisconnected);
digitalWrite(LED_BUILTIN, HIGH);
});
BLEMIDI.setHandleDisconnected([]() { MIDI.setHandleNoteOn(OnNoteOn);
isConnected = false; MIDI.setHandleNoteOff(OnNoteOff);
digitalWrite(LED_BUILTIN, LOW);
});
MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) {
digitalWrite(LED_BUILTIN, LOW);
});
MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) {
digitalWrite(LED_BUILTIN, HIGH);
});
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -51,6 +38,38 @@ void loop()
{ {
t0 = millis(); t0 = millis();
MIDI.sendNoteOn (60, 100, 1); // note 60, velocity 100 on channel 1 MIDI.sendNoteOn (60, 100, 1); // note 60, velocity 127 on channel 1
} }
} }
// ====================================================================================
// Event handlers for incoming MIDI messages
// ====================================================================================
// -----------------------------------------------------------------------------
// Device connected
// -----------------------------------------------------------------------------
void OnConnected() {
isConnected = true;
digitalWrite(LED_BUILTIN, HIGH);
}
// -----------------------------------------------------------------------------
// Device disconnected
// -----------------------------------------------------------------------------
void OnDisconnected() {
isConnected = false;
digitalWrite(LED_BUILTIN, LOW);
}
// -----------------------------------------------------------------------------
// Received note on
// -----------------------------------------------------------------------------
void OnNoteOn(byte channel, byte note, byte velocity) {
}
// -----------------------------------------------------------------------------
// Received note off
// -----------------------------------------------------------------------------
void OnNoteOff(byte channel, byte note, byte velocity) {
}

View File

@ -1,141 +0,0 @@
/**
* --------------------------------------------------------
* 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 <Arduino.h>
#include <BLEMIDI_Transport.h>
#include <hardware/BLEMIDI_Client_ESP32.h>
//#include <hardware/BLEMIDI_ESP32_NimBLE.h>
//#include <hardware/BLEMIDI_ESP32.h>
//#include <hardware/BLEMIDI_nRF52.h>
//#include <hardware/BLEMIDI_ArduinoBLE.h>
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);
}

View File

@ -1,11 +1,11 @@
name=BLE-MIDI name=BLE-MIDI
version=2.2 version=2.1.0
author=lathoub author=lathoub
maintainer=lathoub <lathoub@gmail.com> maintainer=lathoub <lathoub@gmail.com>
sentence=BLE-MIDI I/Os for Arduino sentence=BLE-MIDI I/Os for Arduino
paragraph=MIDI over Bluetooth Low Energy (BLE-MIDI) 1.0 for Arduino paragraph=MIDI over Bluetooth Low Energy (BLE-MIDI) 1.0 for Arduino
category=Communication category=Communication
url=https://github.com/lathoub/Arduino-BLE-MIDI url=https://github.com/lathoub/Arduino-BLE-MIDI
architectures=esp32,samd,megaavr,mbed,apollo3,mbed_nano,mbed_portenta architectures=esp32,samd,megaavr,mbed,nrf52
includes=BLEMIDI_Transport.h includes=BLEMIDI_Transport.h
depends=MIDI Library, NimBLE-Arduino, ArduinoBLE depends=MIDI Library, NimBLE-Arduino, ArduinoBLE

View File

@ -2,6 +2,13 @@
#include "BLEMIDI_Namespace.h" #include "BLEMIDI_Namespace.h"
// As specified in
// Specification for MIDI over Bluetooth Low Energy (BLE-MIDI)
// Version 1.0a, NOvember 1, 2015
// 3. BLE Service and Characteristics Definitions
#define SERVICE_UUID "03b80e5a-ede8-4b33-a751-6ce34ec4c700"
#define CHARACTERISTIC_UUID "7772e5db-3868-4112-a1a9-f2669d106bf3"
#if ARDUINO #if ARDUINO
#include <Arduino.h> #include <Arduino.h>
#else #else

View File

@ -12,18 +12,7 @@
BEGIN_BLEMIDI_NAMESPACE BEGIN_BLEMIDI_NAMESPACE
using namespace MIDI_NAMESPACE; template<class T, class _Settings = DefaultSettings>
// As specified in
// Specification for MIDI over Bluetooth Low Energy (BLE-MIDI)
// Version 1.0a, November 1, 2015
// 3. BLE Service and Characteristics Definitions
static const char *const SERVICE_UUID = "03b80e5a-ede8-4b33-a751-6ce34ec4c700";
static const char *const CHARACTERISTIC_UUID = "7772e5db-3868-4112-a1a9-f2669d106bf3";
#define MIDI_TYPE 0x80
template <class T, class _Settings = DefaultSettings>
class BLEMIDI_Transport class BLEMIDI_Transport
{ {
typedef _Settings Settings; typedef _Settings Settings;
@ -34,45 +23,40 @@ private:
byte mTxBuffer[Settings::MaxBufferSize]; // minimum 5 bytes byte mTxBuffer[Settings::MaxBufferSize]; // minimum 5 bytes
unsigned mTxIndex = 0; unsigned mTxIndex = 0;
char mDeviceName[24]; char mDeviceName[24];
uint8_t mTimestampLow; uint8_t mTimestampLow;
private: private:
T mBleClass; T mBleClass;
public: public:
BLEMIDI_Transport(const char *deviceName) BLEMIDI_Transport(const char* deviceName)
{ {
strncpy(mDeviceName, deviceName, sizeof(mDeviceName)); strncpy(mDeviceName, deviceName, sizeof(mDeviceName));
mRxIndex = 0; mRxIndex = 0;
mTxIndex = 0; mTxIndex = 0;
} }
public: public:
static const bool thruActivated = false; static const bool thruActivated = false;
void begin() void begin()
{ {
mBleClass.begin(mDeviceName, this); mBleClass.begin(mDeviceName, this);
} }
void end()
{
mBleClass.end();
}
bool beginTransmission(MIDI_NAMESPACE::MidiType type) bool beginTransmission(MIDI_NAMESPACE::MidiType type)
{ {
getMidiTimestamp(&mTxBuffer[0], &mTxBuffer[1]); getMidiTimestamp(&mTxBuffer[0], &mTxBuffer[1]);
mTxIndex = 2; mTxIndex = 2;
mTimestampLow = mTxBuffer[1]; // or generate new ? mTimestampLow = mTxBuffer[1]; // or generate new ?
return true; return true;
} }
void write(byte inData) void write(byte inData)
{ {
if (mTxIndex >= sizeof(mTxBuffer)) if (mTxIndex >= sizeof(mTxBuffer))
@ -86,26 +70,26 @@ public:
void endTransmission() void endTransmission()
{ {
if (mTxBuffer[mTxIndex - 1] == SystemExclusiveEnd) if (mTxBuffer[mTxIndex - 1] == 0xF7)
{ {
if (mTxIndex >= sizeof(mTxBuffer)) if (mTxIndex >= sizeof(mTxBuffer))
{ {
mBleClass.write(mTxBuffer, mTxIndex - 1); mBleClass.write(mTxBuffer, mTxIndex - 1);
mTxIndex = 1; // keep header mTxIndex = 1; // keep header
mTxBuffer[mTxIndex++] = mTimestampLow; // or generate new ? mTxBuffer[mTxIndex++] = mTimestampLow; // or generate new ?
} }
else else
{ {
mTxBuffer[mTxIndex - 1] = mTimestampLow; // or generate new ? mTxBuffer[mTxIndex - 1] = mTimestampLow; // or generate new ?
} }
mTxBuffer[mTxIndex++] = SystemExclusiveEnd; mTxBuffer[mTxIndex++] = 0xF7;
} }
mBleClass.write(mTxBuffer, mTxIndex); mBleClass.write(mTxBuffer, mTxIndex);
mTxIndex = 0; mTxIndex = 0;
} }
byte read() byte read()
{ {
return mRxBuffer[--mRxIndex]; return mRxBuffer[--mRxIndex];
@ -115,13 +99,13 @@ public:
{ {
uint8_t byte; uint8_t byte;
auto success = mBleClass.available(&byte); auto success = mBleClass.available(&byte);
if (!success) if (!success) return mRxIndex;
return mRxIndex;
mRxBuffer[mRxIndex++] = byte; mRxBuffer[mRxIndex++] = byte;
return mRxIndex; return mRxIndex;
} }
protected: protected:
/* /*
The first byte of all BLE packets must be a header byte. This is followed by timestamp bytes and MIDI messages. The first byte of all BLE packets must be a header byte. This is followed by timestamp bytes and MIDI messages.
@ -133,6 +117,7 @@ protected:
The header byte contains the topmost 6 bits of timing information for MIDI events in the BLE 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. The remaining 7 bits of timing information for individual MIDI messages encoded in a
packet is expressed by timestamp bytes. packet is expressed by timestamp bytes.
Timestamp Byte Timestamp Byte
bit 7 Set to 1. bit 7 Set to 1.
bits 6-0 timestampLow: Least Significant 7 bits of timestamp information. bits 6-0 timestampLow: Least Significant 7 bits of timestamp information.
@ -167,46 +152,33 @@ protected:
and the MSB of both bytes are set to indicate that this is a header byte. 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. 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; auto currentTimeStamp = millis() & 0x01FFF;
*header = ((currentTimeStamp >> 7) & 0x3F) | 0x80; // 6 bits plus MSB *header = ((currentTimeStamp >> 7) & 0x3F) | 0x80; // 6 bits plus MSB
*timestamp = (currentTimeStamp & 0x7F) | 0x80; // 7 bits plus MSB *timestamp = (currentTimeStamp & 0x7F) | 0x80; // 7 bits plus MSB
} }
static uint16_t setMidiTimestamp(uint8_t header, uint8_t timestamp) static void setMidiTimestamp (uint8_t header, uint8_t *timestamp)
{ {
auto timestampHigh = 0x3f & header;
auto timestampLow = 0x7f & timestamp;
return (timestampLow + (timestampHigh << 7));
} }
public:
// callbacks
void(*_connectedCallback)() = nullptr;
void(*_disconnectedCallback)() = nullptr;
public: public:
// callbacks void setHandleConnected(void(*fptr)()) {
void (*_connectedCallback)() = nullptr; _connectedCallback = fptr;
void (*_disconnectedCallback)() = nullptr; }
BLEMIDI_Transport &setName(const char *deviceName) void setHandleDisconnected(void(*fptr)()) {
{ _disconnectedCallback = fptr;
strncpy(mDeviceName, deviceName, sizeof(mDeviceName)); }
return *this;
};
public: /*
BLEMIDI_Transport &setHandleConnected(void (*fptr)())
{
_connectedCallback = fptr;
return *this;
}
BLEMIDI_Transport &setHandleDisconnected(void (*fptr)())
{
_disconnectedCallback = fptr;
return *this;
}
/*
The general form of a MIDI message follows: The general form of a MIDI message follows:
n-byte MIDI Message n-byte MIDI Message
Byte 0 MIDI message Status byte, Bit 7 is Set to 1. Byte 0 MIDI message Status byte, Bit 7 is Set to 1.
@ -236,179 +208,83 @@ public:
MIDI messages. In the MIDI BLE protocol, the System Real-Time messages must be deinterleaved MIDI messages. In the MIDI BLE protocol, the System Real-Time messages must be deinterleaved
from other messages except for System Exclusive messages. from other messages except for System Exclusive messages.
*/ */
void receive(byte* buffer, size_t length)
/** {
* If #define RUNNING_ENABLE is commented/disabled, 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. // Pointers used to search through payload.
int lPtr = 0; byte lPtr = 0;
int rPtr = 0; byte rPtr = 0;
// lastStatus used to capture runningStatus // lastStatus used to capture runningStatus
byte lastStatus; byte lastStatus;
// previousStatus used to continue a runningStatus interrupted by a timeStamp or a System Message. // Decode first packet -- SHALL be "Full MIDI message"
byte previousStatus = InvalidType; lPtr = 2; //Start at first MIDI status -- SHALL be "MIDI status"
byte headerByte = buffer[lPtr++];
auto timestampHigh = 0x3f & headerByte;
byte timestampByte = buffer[lPtr++];
uint16_t timestamp = 0;
bool sysExContinuation = false;
bool runningStatusContinuation = false;
if (timestampByte >= MIDI_TYPE) // if bit 7 is 1, it's a timestampByte
{
timestamp = setMidiTimestamp(headerByte, timestampByte);
// what do to with the timestamp?
}
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
}
//While statement contains incrementing pointers and breaks when buffer size exceeded. //While statement contains incrementing pointers and breaks when buffer size exceeded.
while (true) while (true)
{ {
lastStatus = buffer[lPtr]; lastStatus = buffer[lPtr];
if (previousStatus == InvalidType) if( (buffer[lPtr] < 0x80))
{ return; // Status message not present, bail
if ((lastStatus < MIDI_TYPE) && !sysExContinuation)
return; // Status message not present and it is not a runningStatus continuation, bail
}
else if (lastStatus < MIDI_TYPE)
{
lastStatus = previousStatus;
runningStatusContinuation = true;
}
// Point to next non-data byte // Point to next non-data byte
rPtr = lPtr; rPtr = lPtr;
while ((buffer[rPtr + 1] < MIDI_TYPE) && (rPtr < (length - 1))) while( (buffer[rPtr + 1] < 0x80) && (rPtr < (length - 1)) )
rPtr++; rPtr++;
if (buffer[rPtr + 1] == 0xF7) rPtr++;
if (!runningStatusContinuation) // 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 // If not System Common or System Real-Time, send it as running status
switch(buffer[lPtr] & 0xF0)
auto midiType = lastStatus & 0xF0;
if (sysExContinuation)
midiType = SystemExclusive;
switch (midiType)
{ {
case NoteOff: case 0x80:
case NoteOn: case 0x90:
case AfterTouchPoly: case 0xA0:
case ControlChange: case 0xB0:
case PitchBend: case 0xE0:
#ifdef RUNNING_ENABLE
mBleClass.add(lastStatus);
#endif
for (auto i = lPtr; i < rPtr; i = i + 2) for (auto i = lPtr; i < rPtr; i = i + 2)
{ {
#ifndef RUNNING_ENABLE
mBleClass.add(lastStatus); mBleClass.add(lastStatus);
#endif
mBleClass.add(buffer[i + 1]); mBleClass.add(buffer[i + 1]);
mBleClass.add(buffer[i + 2]); mBleClass.add(buffer[i + 2]);
} }
break; break;
case ProgramChange: case 0xC0:
case AfterTouchChannel: case 0xD0:
#ifdef RUNNING_ENABLE
mBleClass.add(lastStatus);
#endif
for (auto i = lPtr; i < rPtr; i = i + 1) for (auto i = lPtr; i < rPtr; i = i + 1)
{ {
#ifndef RUNNING_ENABLE
mBleClass.add(lastStatus); mBleClass.add(lastStatus);
#endif
mBleClass.add(buffer[i + 1]); mBleClass.add(buffer[i + 1]);
} }
break; break;
case SystemExclusive: case 0xF0:
mBleClass.add(lastStatus); mBleClass.add(buffer[lPtr]);
for (auto i = lPtr; i < rPtr; i++) for (auto i = lPtr; i < rPtr; i++)
mBleClass.add(buffer[i + 1]); mBleClass.add(buffer[i + 1]);
break; break;
default: default:
break; break;
} }
} }
else
{
#ifndef RUNNING_ENABLE
auto midiType = lastStatus & 0xF0;
switch (midiType)
{
case NoteOff:
case NoteOn:
case AfterTouchPoly:
case ControlChange:
case PitchBend:
//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]);
}
break;
case ProgramChange:
case AfterTouchChannel:
//2 bytes full Midi -> 1 byte runningStatus
for (auto i = lPtr; i <= rPtr; i = i + 1)
{
mBleClass.add(lastStatus);
mBleClass.add(buffer[i]);
}
break;
default:
break;
}
#else
mBleClass.add(lastStatus);
for (auto i = lPtr; i <= rPtr; i++)
mBleClass.add(buffer[i]);
#endif
runningStatusContinuation = false;
}
if (++rPtr >= length)
return; // end of packet
if (lastStatus < SystemExclusive) //exclude System Message. They must not be RunningStatus
{
previousStatus = lastStatus;
}
timestampByte = buffer[rPtr++];
if (timestampByte >= MIDI_TYPE) // is bit 7 set?
{
timestamp = setMidiTimestamp(headerByte, timestampByte);
// what do to with the timestamp?
}
// Point to next status // Point to next status
lPtr = rPtr; lPtr = rPtr + 2;
if (lPtr >= length) if(lPtr >= length)
return; //end of packet return; //end of packet
} }
} }
}; };
struct MySettings : public MIDI_NAMESPACE::DefaultSettings struct MySettings : public MIDI_NAMESPACE::DefaultSettings
@ -417,3 +293,4 @@ struct MySettings : public MIDI_NAMESPACE::DefaultSettings
}; };
END_BLEMIDI_NAMESPACE END_BLEMIDI_NAMESPACE

View File

@ -0,0 +1,95 @@
#pragma once
// Headers for ESP32 BLE
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>
BEGIN_BLEMIDI_NAMESPACE
class BLEMIDI_Client_ESP32
{
private:
BLEClient* _client = nullptr;
BLEMIDI<class BLEMIDI_Client_ESP32>* _bleMidiTransport = nullptr;
public:
BLEMIDI_Client_ESP32()
{
}
bool begin(const char*, BLEMIDI<class BLEMIDI_Client_ESP32>*);
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<class BLEMIDI_ESP32>* 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

View File

@ -75,11 +75,6 @@ public:
bool begin(const char*, BLEMIDI_Transport<class BLEMIDI_ArduinoBLE>*); bool begin(const char*, BLEMIDI_Transport<class BLEMIDI_ArduinoBLE>*);
void end()
{
}
void write(uint8_t* buffer, size_t length) void write(uint8_t* buffer, size_t length)
{ {
// TODO: test length // TODO: test length
@ -170,7 +165,6 @@ protected:
{ {
if (_bleMidiTransport->_disconnectedCallback) if (_bleMidiTransport->_disconnectedCallback)
_bleMidiTransport->_disconnectedCallback(); _bleMidiTransport->_disconnectedCallback();
_central = nullptr; _central = nullptr;
} }

View File

@ -1,636 +0,0 @@
#pragma once
/*
#############################################
########## USER DEFINES BEGINNING ###########
####### Modify only these parameters ########
#############################################
*/
/*
##### BLE DEVICE NAME #####
*/
/**
* Set always the same name independently of name server
*/
//#define BLEMIDI_CLIENT_FIXED_NAME "BleMidiClient"
#ifndef BLEMIDI_CLIENT_FIXED_NAME //Not modify
/**
* When client tries to connect to specific server, BLE name is composed as follows:
* BLEMIDI_CLIENT_NAME_PREFIX + <NameServer/addrServer> + BLEMIDI_CLIENT_NAME_SUBFIX
*
* example:
* BLEMIDI_CLIENT_NAME_PREFIX "Client-"
* <NameServer/addrServer> "AX-Edge"
* BLEMIDI_CLIENT_NAME_SUBFIX "-Midi1"
*
* Result: "Client-AX-Edge-Midi1"
*/
#define BLEMIDI_CLIENT_NAME_PREFIX "C-"
#define BLEMIDI_CLIENT_NAME_SUBFIX ""
/**
* When client tries to connect to the first midi server found:
*/
#define BLEMIDI_CLIENT_DEFAULT_NAME "BLEMIDI-CLIENT"
#endif //Not modify
/*
###### TX POWER #####
*/
/**
* Set power transmision
*
* ESP_PWR_LVL_N12 // Corresponding to -12dbm Minimum
* ESP_PWR_LVL_N9 // Corresponding to -9dbm
* ESP_PWR_LVL_N6 // Corresponding to -6dbm
* ESP_PWR_LVL_N3 // Corresponding to -3dbm
* ESP_PWR_LVL_N0 // Corresponding to 0dbm
* ESP_PWR_LVL_P3 // Corresponding to +3dbm
* ESP_PWR_LVL_P6 // Corresponding to +6dbm
* ESP_PWR_LVL_P9 // Corresponding to +9dbm Maximum
*/
#define BLEMIDI_TX_PWR ESP_PWR_LVL_P9
/*
###### 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 the security method.
* bonding
* man in the middle protection
* pair. secure connections
*
* More info in nimBLE lib
*
* Uncomment what you need
* These are the default values.
* You can select some simultaneously.
*/
#define BLEMIDI_CLIENT_BOND
//#define BLEMIDI_CLIENT_MITM
#define BLEMIDI_CLIENT_PAIR
/**
* This callback function defines what will be done when server requieres PassKey.
* Add your custom code here.
*/
static uint32_t userOnPassKeyRequest()
{
//FILL WITH YOUR CUSTOM AUTH METHOD CODE 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 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 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,
* 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 200 //2000ms
/*
###### BLE FORCE NEW CONNECTION ######
*/
/**
* This parameter force to skip the "soft-reconnection" and force to create a new connection after a disconnect event.
* "Soft-reconnection" save some time and energy in comparation with performming a new connection, but some BLE devices
* don't support or don't perform correctly a "soft-reconnection" after a disconnection event.
* Uncomment this define if your device doesn't work propertily after a reconnection.
*
*/
//#define BLEMIDI_FORCE_NEW_CONNECTION
/**
*
*/
/*
#############################################
############ USER DEFINES END ###############
#############################################
*/
// Headers for ESP32 nimBLE
#include <NimBLEDevice.h>
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 the 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:
NimBLEAdvertisedDevice advDevice;
bool doConnect = false;
bool scanDone = false;
bool specificTarget = false;
bool enableConnection = false;
std::string nameTarget;
protected:
void onResult(NimBLEAdvertisedDevice *advertisedDevice)
{
if (enableConnection) //not begin() or end()
{
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 public variable that the client can use*/
advDevice = *advertisedDevice;
/** stop scan before connecting */
NimBLEDevice::getScan()->stop();
}
else
{
Serial.println("Name error");
}
}
else
{
doConnect = false;
}
}
};
};
/** Define a funtion to handle the callbacks when scan ends */
void scanEndedCB(NimBLEScanResults results);
/** Define the class that performs Client Midi (nimBLE) */
class BLEMIDI_Client_ESP32
{
private:
BLEClient *_client = nullptr;
BLEAdvertising *_advertising = nullptr;
BLERemoteCharacteristic *_characteristic = nullptr;
BLERemoteService *pSvc = nullptr;
bool firstTimeSend = true; //First writeValue get sends like Write with reponse for clean security flags. After first time, all messages are send like WriteNoResponse for increase transmision speed.
BLEMIDI_Transport<class BLEMIDI_Client_ESP32> *_bleMidiTransport = nullptr;
bool specificTarget = false;
friend class AdvertisedDeviceCallbacks;
friend class MyClientCallbacks;
friend class MIDI_NAMESPACE::MidiInterface<BLEMIDI_Transport<BLEMIDI_Client_ESP32>, MySettings>; //
AdvertisedDeviceCallbacks myAdvCB;
protected:
QueueHandle_t mRxQueue;
public:
BLEMIDI_Client_ESP32()
{
}
bool begin(const char *, BLEMIDI_Transport<class BLEMIDI_Client_ESP32> *);
bool end()
{
myAdvCB.enableConnection = false;
xQueueReset(mRxQueue);
_client->disconnect();
_client = nullptr;
return true;
}
void write(uint8_t *data, uint8_t length)
{
if (!myAdvCB.enableConnection)
return;
if (_characteristic == NULL)
return;
if (firstTimeSend)
{
_characteristic->writeValue(data, length, true);
firstTimeSend = false;
return;
}
if (!_characteristic->writeValue(data, length, !_characteristic->canWriteNoResponse()))
firstTimeSend = true;
return;
}
bool available(byte *pvBuffer);
void add(byte value)
{
// called from BLE-MIDI, to add it to a buffer here
xQueueSend(mRxQueue, &value, portMAX_DELAY / 2);
}
protected:
void receive(uint8_t *buffer, size_t length)
{
// forward the buffer so that it can be parsed
_bleMidiTransport->receive(buffer, length);
}
void connectCallbacks(MIDI_NAMESPACE::MidiInterface<BLEMIDI_Transport<BLEMIDI_Client_ESP32>, MySettings> *MIDIcallback);
void connected()
{
if (_bleMidiTransport->_connectedCallback)
{
_bleMidiTransport->_connectedCallback();
}
firstTimeSend = true;
}
void disconnected()
{
if (_bleMidiTransport->_disconnectedCallback)
{
_bleMidiTransport->_disconnectedCallback();
}
firstTimeSend = true;
}
void notifyCB(NimBLERemoteCharacteristic *pRemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify);
void scan();
bool connect();
};
/** Define the class that performs interruption callbacks */
class MyClientCallbacks : public BLEClientCallbacks
{
public:
MyClientCallbacks(BLEMIDI_Client_ESP32 *bluetoothEsp32)
: _bluetoothEsp32(bluetoothEsp32)
{
}
protected:
BLEMIDI_Client_ESP32 *_bluetoothEsp32 = nullptr;
uint32_t onPassKeyRequest()
{
return userOnPassKeyRequest();
};
void onConnect(BLEClient *pClient)
{
//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)
{
_bluetoothEsp32->connected();
}
};
void onDisconnect(BLEClient *pClient)
{
//Serial.print(pClient->getPeerAddress().toString().c_str());
//Serial.println(" Disconnected - Starting scan");
if (_bluetoothEsp32)
{
_bluetoothEsp32->disconnected();
#ifdef BLEMIDI_FORCE_NEW_CONNECTION
// Try reconnection or search a new one
_bluetoothEsp32->scan();
#endif // BLEMIDI_FORCE_NEW_CONNECTION
}
#ifdef BLEMIDI_FORCE_NEW_CONNECTION
// Renew Client
NimBLEDevice::deleteClient(pClient);
NimBLEDevice::createClient();
pClient = nullptr;
#endif // BLEMIDI_FORCE_NEW_CONNECTION
//Try reconnection or search a new one
NimBLEDevice::getScan()->start(1, scanEndedCB);
}
bool onConnParamsUpdateRequest(NimBLEClient *pClient, const ble_gap_upd_params *params)
{
if (params->itvl_min < BLEMIDI_CLIENT_COMM_MIN_INTERVAL)
{ /** 1.25ms units */
return false;
}
else if (params->itvl_max > BLEMIDI_CLIENT_COMM_MAX_INTERVAL)
{ /** 1.25ms units */
return false;
}
else if (params->latency > BLEMIDI_CLIENT_COMM_LATENCY)
{ /** Number of intervals allowed to skip */
return false;
}
else if (params->supervision_timeout > BLEMIDI_CLIENT_COMM_TIMEOUT)
{ /** 10ms units */
return false;
}
pClient->updateConnParams(params->itvl_min, params->itvl_max, params->latency, params->supervision_timeout);
return true;
};
};
/*
##########################################
############# IMPLEMENTATION #############
##########################################
*/
bool BLEMIDI_Client_ESP32::begin(const char *deviceName, BLEMIDI_Transport<class BLEMIDI_Client_ESP32> *bleMidiTransport)
{
_bleMidiTransport = bleMidiTransport;
std::string strDeviceName(deviceName);
if (strDeviceName == "") // Connect to the 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 address
{
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); // Attention, it may need a passkey
NimBLEDevice::setSecurityAuth(BLEMIDI_CLIENT_SECURITY_AUTH);
/** Optional: set the transmit power, default is 3db */
NimBLEDevice::setPower(BLEMIDI_TX_PWR); /** +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)
{
if (this->_characteristic == pRemoteCharacteristic) //Redundant protection
{
receive(pData, length);
}
}
void BLEMIDI_Client_ESP32::scan()
{
// 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();
if (!pBLEScan->isScanning())
{
pBLEScan->setAdvertisedDeviceCallbacks(&myAdvCB);
pBLEScan->setInterval(600);
pBLEScan->setWindow(500);
pBLEScan->setActiveScan(true);
Serial.println("Scanning...");
pBLEScan->start(1, scanEndedCB);
}
};
bool BLEMIDI_Client_ESP32::connect()
{
using namespace std::placeholders; //<- for bind funtion in callback notification
#ifndef BLEMIDI_FORCE_NEW_CONNECTION
/** 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)
{
if (_client == NimBLEDevice::getClientByPeerAddress(myAdvCB.advDevice.getAddress()))
{
if (_client->connect(&myAdvCB.advDevice, false))
{
if (_characteristic->canNotify())
{
if (_characteristic->subscribe(true, std::bind(&BLEMIDI_Client_ESP32::notifyCB, this, _1, _2, _3, _4)))
{
//Re-connection SUCCESS
return true;
}
}
/** 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;
}
#endif //BLEMIDI_FORCE_NEW_CONNECTION
if (NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS)
{
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, 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);
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;
}
if (!_client->isConnected())
{
//Serial.println("Failed to connect");
_client->disconnect();
NimBLEDevice::deleteClient(_client);
_client = nullptr;
return false;
}
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)))
{
//Connection SUCCESS
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 */
void scanEndedCB(NimBLEScanResults results)
{
// Serial.println("Scan Ended");
}
END_BLEMIDI_NAMESPACE
/*! \brief Create an instance for ESP32 named <DeviceName>, and adviertise it like "Prefix + <DeviceName> + Subfix"
It will try to connect to a specific server with equal name or addr than <DeviceName>. If <DeviceName> is "", it will connect to first midi server
*/
#define BLEMIDI_CREATE_INSTANCE(DeviceName, Name) \
BLEMIDI_NAMESPACE::BLEMIDI_Transport<BLEMIDI_NAMESPACE::BLEMIDI_Client_ESP32> BLE##Name(DeviceName); \
MIDI_NAMESPACE::MidiInterface<BLEMIDI_NAMESPACE::BLEMIDI_Transport<BLEMIDI_NAMESPACE::BLEMIDI_Client_ESP32>, BLEMIDI_NAMESPACE::MySettings> Name((BLEMIDI_NAMESPACE::BLEMIDI_Transport<BLEMIDI_NAMESPACE::BLEMIDI_Client_ESP32> &)BLE##Name);
/*! \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("", MIDI)

View File

@ -11,37 +11,32 @@ BEGIN_BLEMIDI_NAMESPACE
class BLEMIDI_ESP32 class BLEMIDI_ESP32
{ {
private: private:
BLEServer *_server = nullptr; BLEServer* _server = nullptr;
BLEAdvertising *_advertising = nullptr; BLEAdvertising* _advertising = nullptr;
BLECharacteristic *_characteristic = nullptr; BLECharacteristic* _characteristic = nullptr;
BLEMIDI_Transport<class BLEMIDI_ESP32>* _bleMidiTransport = nullptr;
BLEMIDI_Transport<class BLEMIDI_ESP32> *_bleMidiTransport = nullptr; friend class MyServerCallbacks;
friend class MyCharacteristicCallbacks;
friend class MyServerCallbacks;
friend class MyCharacteristicCallbacks;
protected: protected:
QueueHandle_t mRxQueue; QueueHandle_t mRxQueue;
public: public:
BLEMIDI_ESP32() BLEMIDI_ESP32()
{ {
} }
bool begin(const char *, BLEMIDI_Transport<class BLEMIDI_ESP32> *); bool begin(const char*, BLEMIDI_Transport<class BLEMIDI_ESP32>*);
void end() void write(uint8_t* buffer, size_t length)
{
Serial.println("end");
}
void write(uint8_t *buffer, size_t length)
{ {
_characteristic->setValue(buffer, length); _characteristic->setValue(buffer, length);
_characteristic->notify(); _characteristic->notify();
} }
bool available(byte *pvBuffer) bool available(byte* pvBuffer)
{ {
return xQueueReceive(mRxQueue, pvBuffer, 0); // return immediately when the queue is empty return xQueueReceive(mRxQueue, pvBuffer, 0); // return immediately when the queue is empty
} }
@ -53,128 +48,115 @@ public:
} }
protected: protected:
void receive(uint8_t *buffer, size_t length) void receive(uint8_t* buffer, size_t length)
{ {
// parse the incoming buffer // parse the incoming buffer
_bleMidiTransport->receive(buffer, length); _bleMidiTransport->receive(buffer, length);
} }
void connected() void connected()
{ {
if (_bleMidiTransport->_connectedCallback) if (_bleMidiTransport->_connectedCallback)
_bleMidiTransport->_connectedCallback(); _bleMidiTransport->_connectedCallback();
} }
void disconnected() void disconnected()
{ {
if (_bleMidiTransport->_disconnectedCallback) if (_bleMidiTransport->_disconnectedCallback)
_bleMidiTransport->_disconnectedCallback(); _bleMidiTransport->_disconnectedCallback();
}
end();
}
}; };
class MyServerCallbacks : public BLEServerCallbacks class MyServerCallbacks: public BLEServerCallbacks {
{
public: public:
MyServerCallbacks(BLEMIDI_ESP32 *bluetoothEsp32) MyServerCallbacks(BLEMIDI_ESP32* bluetoothEsp32)
: _bluetoothEsp32(bluetoothEsp32) : _bluetoothEsp32(bluetoothEsp32) {
{
} }
protected: protected:
BLEMIDI_ESP32 *_bluetoothEsp32 = nullptr; BLEMIDI_ESP32* _bluetoothEsp32 = nullptr;
void onConnect(BLEServer *) void onConnect(BLEServer*) {
{
if (_bluetoothEsp32) if (_bluetoothEsp32)
_bluetoothEsp32->connected(); _bluetoothEsp32->connected();
}; };
void onDisconnect(BLEServer *server) void onDisconnect(BLEServer*) {
{
if (_bluetoothEsp32) if (_bluetoothEsp32)
_bluetoothEsp32->disconnected(); _bluetoothEsp32->disconnected();
}
server->getAdvertising()->start();
}
}; };
class MyCharacteristicCallbacks : public BLECharacteristicCallbacks class MyCharacteristicCallbacks: public BLECharacteristicCallbacks {
{
public: public:
MyCharacteristicCallbacks(BLEMIDI_ESP32 *bluetoothEsp32) MyCharacteristicCallbacks(BLEMIDI_ESP32* bluetoothEsp32)
: _bluetoothEsp32(bluetoothEsp32) : _bluetoothEsp32(bluetoothEsp32 ) {
{
} }
protected: protected:
BLEMIDI_ESP32 *_bluetoothEsp32 = nullptr; BLEMIDI_ESP32* _bluetoothEsp32 = nullptr;
void onWrite(BLECharacteristic *characteristic) void onWrite(BLECharacteristic * characteristic) {
{
std::string rxValue = characteristic->getValue(); std::string rxValue = characteristic->getValue();
if (rxValue.length() > 0) if (rxValue.length() > 0) {
{ _bluetoothEsp32->receive((uint8_t *)(rxValue.c_str()), rxValue.length());
_bluetoothEsp32->receive((uint8_t *)(rxValue.c_str()), rxValue.length());
} }
} }
}; };
bool BLEMIDI_ESP32::begin(const char *deviceName, BLEMIDI_Transport<class BLEMIDI_ESP32> *bleMidiTransport) bool BLEMIDI_ESP32::begin(const char* deviceName, BLEMIDI_Transport<class BLEMIDI_ESP32>* bleMidiTransport)
{ {
_bleMidiTransport = bleMidiTransport; _bleMidiTransport = bleMidiTransport;
BLEDevice::init(deviceName); BLEDevice::init(deviceName);
// To communicate between the 2 cores. // To communicate between the 2 cores.
// Core_0 runs here, core_1 runs the BLE stack // Core_0 runs here, core_1 runs the BLE stack
mRxQueue = xQueueCreate(64, sizeof(uint8_t)); // TODO Settings::MaxBufferSize mRxQueue = xQueueCreate(64, sizeof(uint8_t)); // TODO Settings::MaxBufferSize
_server = BLEDevice::createServer(); _server = BLEDevice::createServer();
_server->setCallbacks(new MyServerCallbacks(this)); _server->setCallbacks(new MyServerCallbacks(this));
// Create the BLE Service // Create the BLE Service
auto service = _server->createService(BLEUUID(SERVICE_UUID)); auto service = _server->createService(BLEUUID(SERVICE_UUID));
// Create a BLE Characteristic // Create a BLE Characteristic
_characteristic = service->createCharacteristic( _characteristic = service->createCharacteristic(
BLEUUID(CHARACTERISTIC_UUID), BLEUUID(CHARACTERISTIC_UUID),
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_NOTIFY |
BLECharacteristic::PROPERTY_WRITE_NR); BLECharacteristic::PROPERTY_WRITE_NR
);
// Add CCCD 0x2902 to allow notify // Add CCCD 0x2902 to allow notify
_characteristic->addDescriptor(new BLE2902()); _characteristic->addDescriptor(new BLE2902());
_characteristic->setCallbacks(new MyCharacteristicCallbacks(this)); _characteristic->setCallbacks(new MyCharacteristicCallbacks(this));
auto _security = new BLESecurity();
_security->setAuthenticationMode(ESP_LE_AUTH_BOND);
// Start the service // Start the service
service->start(); service->start();
auto advertisementData = BLEAdvertisementData();
advertisementData.setFlags(0x04);
advertisementData.setCompleteServices(BLEUUID(SERVICE_UUID));
advertisementData.setName(deviceName);
// Start advertising // Start advertising
_advertising = _server->getAdvertising(); _advertising = _server->getAdvertising();
_advertising->addServiceUUID(service->getUUID()); _advertising->setAdvertisementData(advertisementData);
_advertising->setAppearance(0x00);
_advertising->start(); _advertising->start();
Serial.println("begin");
return true; return true;
} }
/*! \brief Create an instance for ESP32 named <DeviceName> /*! \brief Create an instance for ESP32 named <DeviceName>
*/ */
#define BLEMIDI_CREATE_INSTANCE(DeviceName, Name) \ #define BLEMIDI_CREATE_INSTANCE(DeviceName, Name) \
BLEMIDI_NAMESPACE::BLEMIDI_Transport<BLEMIDI_NAMESPACE::BLEMIDI_ESP32> BLE##Name(DeviceName); \ BLEMIDI_NAMESPACE::BLEMIDI_Transport<BLEMIDI_NAMESPACE::BLEMIDI_ESP32> BLE##Name(DeviceName); \
MIDI_NAMESPACE::MidiInterface<BLEMIDI_NAMESPACE::BLEMIDI_Transport<BLEMIDI_NAMESPACE::BLEMIDI_ESP32>, BLEMIDI_NAMESPACE::MySettings> Name((BLEMIDI_NAMESPACE::BLEMIDI_Transport<BLEMIDI_NAMESPACE::BLEMIDI_ESP32> &)BLE##Name); MIDI_NAMESPACE::MidiInterface<BLEMIDI_NAMESPACE::BLEMIDI_Transport<BLEMIDI_NAMESPACE::BLEMIDI_ESP32>, BLEMIDI_NAMESPACE::MySettings> Name((BLEMIDI_NAMESPACE::BLEMIDI_Transport<BLEMIDI_NAMESPACE::BLEMIDI_ESP32> &)BLE##Name);
/*! \brief Create a default instance for ESP32 named BLE-MIDI /*! \brief Create a default instance for ESP32 named BLE-MIDI
*/ */
#define BLEMIDI_CREATE_DEFAULT_INSTANCE() \ #define BLEMIDI_CREATE_DEFAULT_INSTANCE() \
BLEMIDI_CREATE_INSTANCE("Esp32-BLE-MIDI", MIDI) BLEMIDI_CREATE_INSTANCE("BLE-MIDI", MIDI)
END_BLEMIDI_NAMESPACE END_BLEMIDI_NAMESPACE

View File

@ -8,39 +8,35 @@ BEGIN_BLEMIDI_NAMESPACE
class BLEMIDI_ESP32_NimBLE class BLEMIDI_ESP32_NimBLE
{ {
private: private:
BLEServer *_server = nullptr; BLEServer* _server = nullptr;
BLEAdvertising *_advertising = nullptr; BLEAdvertising* _advertising = nullptr;
BLECharacteristic *_characteristic = nullptr; BLECharacteristic* _characteristic = nullptr;
BLEMIDI_Transport<class BLEMIDI_ESP32_NimBLE>* _bleMidiTransport = nullptr;
BLEMIDI_Transport<class BLEMIDI_ESP32_NimBLE> *_bleMidiTransport = nullptr; friend class MyServerCallbacks;
friend class MyCharacteristicCallbacks;
friend class MyServerCallbacks;
friend class MyCharacteristicCallbacks;
protected: protected:
QueueHandle_t mRxQueue; QueueHandle_t mRxQueue;
public: public:
BLEMIDI_ESP32_NimBLE() BLEMIDI_ESP32_NimBLE()
{ {
} }
bool begin(const char *, BLEMIDI_Transport<class BLEMIDI_ESP32_NimBLE> *); bool begin(const char*, BLEMIDI_Transport<class BLEMIDI_ESP32_NimBLE>*);
void end() void write(uint8_t* buffer, size_t length)
{
}
void write(uint8_t *buffer, size_t length)
{ {
_characteristic->setValue(buffer, length); _characteristic->setValue(buffer, length);
_characteristic->notify(); _characteristic->notify();
} }
bool available(byte *pvBuffer) bool available(byte* pvBuffer)
{ {
// return 1 byte from the Queue // return 1 byte from the Queue
return xQueueReceive(mRxQueue, (void *)pvBuffer, 0); // return immediately when the queue is empty return xQueueReceive(mRxQueue, (void*)pvBuffer, 0); // return immediately when the queue is empty
} }
void add(byte value) void add(byte value)
@ -50,121 +46,113 @@ public:
} }
protected: protected:
void receive(uint8_t *buffer, size_t length) void receive(uint8_t* buffer, size_t length)
{ {
// forward the buffer so it can be parsed // forward the buffer so it can be parsed
_bleMidiTransport->receive(buffer, length); _bleMidiTransport->receive(buffer, length);
} }
void connected() void connected()
{ {
if (_bleMidiTransport->_connectedCallback) if (_bleMidiTransport->_connectedCallback)
_bleMidiTransport->_connectedCallback(); _bleMidiTransport->_connectedCallback();
} }
void disconnected() void disconnected()
{ {
if (_bleMidiTransport->_disconnectedCallback) if (_bleMidiTransport->_disconnectedCallback)
_bleMidiTransport->_disconnectedCallback(); _bleMidiTransport->_disconnectedCallback();
} }
}; };
class MyServerCallbacks : public BLEServerCallbacks class MyServerCallbacks: public BLEServerCallbacks {
{
public: public:
MyServerCallbacks(BLEMIDI_ESP32_NimBLE *bluetoothEsp32) MyServerCallbacks(BLEMIDI_ESP32_NimBLE* bluetoothEsp32)
: _bluetoothEsp32(bluetoothEsp32) : _bluetoothEsp32(bluetoothEsp32) {
{
} }
protected: protected:
BLEMIDI_ESP32_NimBLE *_bluetoothEsp32 = nullptr; BLEMIDI_ESP32_NimBLE* _bluetoothEsp32 = nullptr;
void onConnect(BLEServer *) void onConnect(BLEServer*) {
{
if (_bluetoothEsp32) if (_bluetoothEsp32)
_bluetoothEsp32->connected(); _bluetoothEsp32->connected();
}; };
void onDisconnect(BLEServer *) void onDisconnect(BLEServer*) {
{
if (_bluetoothEsp32) if (_bluetoothEsp32)
_bluetoothEsp32->disconnected(); _bluetoothEsp32->disconnected();
} }
}; };
class MyCharacteristicCallbacks : public BLECharacteristicCallbacks class MyCharacteristicCallbacks: public BLECharacteristicCallbacks {
{
public: public:
MyCharacteristicCallbacks(BLEMIDI_ESP32_NimBLE *bluetoothEsp32) MyCharacteristicCallbacks(BLEMIDI_ESP32_NimBLE* bluetoothEsp32)
: _bluetoothEsp32(bluetoothEsp32) : _bluetoothEsp32(bluetoothEsp32 ) {
{
} }
protected: protected:
BLEMIDI_ESP32_NimBLE *_bluetoothEsp32 = nullptr; BLEMIDI_ESP32_NimBLE* _bluetoothEsp32 = nullptr;
void onWrite(BLECharacteristic *characteristic) void onWrite(BLECharacteristic * characteristic) {
{
std::string rxValue = characteristic->getValue(); std::string rxValue = characteristic->getValue();
if (rxValue.length() > 0) if (rxValue.length() > 0) {
{ _bluetoothEsp32->receive((uint8_t *)(rxValue.c_str()), rxValue.length());
_bluetoothEsp32->receive((uint8_t *)(rxValue.c_str()), rxValue.length());
} }
} }
}; };
bool BLEMIDI_ESP32_NimBLE::begin(const char *deviceName, BLEMIDI_Transport<class BLEMIDI_ESP32_NimBLE> *bleMidiTransport) bool BLEMIDI_ESP32_NimBLE::begin(const char* deviceName, BLEMIDI_Transport<class BLEMIDI_ESP32_NimBLE>* bleMidiTransport)
{ {
_bleMidiTransport = bleMidiTransport; _bleMidiTransport = bleMidiTransport;
BLEDevice::init(deviceName); BLEDevice::init(deviceName);
// To communicate between the 2 cores. // To communicate between the 2 cores.
// Core_0 runs here, core_1 runs the BLE stack // Core_0 runs here, core_1 runs the BLE stack
mRxQueue = xQueueCreate(64, sizeof(uint8_t)); // TODO Settings::MaxBufferSize mRxQueue = xQueueCreate(64, sizeof(uint8_t)); // TODO Settings::MaxBufferSize
_server = BLEDevice::createServer(); _server = BLEDevice::createServer();
_server->setCallbacks(new MyServerCallbacks(this)); _server->setCallbacks(new MyServerCallbacks(this));
_server->advertiseOnDisconnect(true);
// Create the BLE Service // Create the BLE Service
auto service = _server->createService(BLEUUID(SERVICE_UUID)); auto service = _server->createService(BLEUUID(SERVICE_UUID));
// Create a BLE Characteristic // Create a BLE Characteristic
_characteristic = service->createCharacteristic( _characteristic = service->createCharacteristic(
BLEUUID(CHARACTERISTIC_UUID), BLEUUID(CHARACTERISTIC_UUID),
NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE |
NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::NOTIFY |
NIMBLE_PROPERTY::WRITE_NR); NIMBLE_PROPERTY::WRITE_NR
);
_characteristic->setCallbacks(new MyCharacteristicCallbacks(this)); _characteristic->setCallbacks(new MyCharacteristicCallbacks(this));
auto _security = new NimBLESecurity();
_security->setAuthenticationMode(ESP_LE_AUTH_BOND);
// Start the service // Start the service
service->start(); service->start();
auto advertisementData = BLEAdvertisementData();
advertisementData.setFlags(0x04);
advertisementData.setCompleteServices(BLEUUID(SERVICE_UUID));
advertisementData.setName(deviceName);
// Start advertising // Start advertising
_advertising = _server->getAdvertising(); _advertising = _server->getAdvertising();
_advertising->addServiceUUID(service->getUUID()); _advertising->setAdvertisementData(advertisementData);
_advertising->setAppearance(0x00);
_advertising->start(); _advertising->start();
return true; return true;
} }
/*! \brief Create an instance for ESP32 named <DeviceName> /*! \brief Create an instance for ESP32 named <DeviceName>
*/ */
#define BLEMIDI_CREATE_INSTANCE(DeviceName, Name) \ #define BLEMIDI_CREATE_INSTANCE(DeviceName, Name) \
BLEMIDI_NAMESPACE::BLEMIDI_Transport<BLEMIDI_NAMESPACE::BLEMIDI_ESP32_NimBLE> BLE##Name(DeviceName); \ BLEMIDI_NAMESPACE::BLEMIDI_Transport<BLEMIDI_NAMESPACE::BLEMIDI_ESP32_NimBLE> BLE##Name(DeviceName); \
MIDI_NAMESPACE::MidiInterface<BLEMIDI_NAMESPACE::BLEMIDI_Transport<BLEMIDI_NAMESPACE::BLEMIDI_ESP32_NimBLE>, BLEMIDI_NAMESPACE::MySettings> Name((BLEMIDI_NAMESPACE::BLEMIDI_Transport<BLEMIDI_NAMESPACE::BLEMIDI_ESP32_NimBLE> &)BLE##Name); MIDI_NAMESPACE::MidiInterface<BLEMIDI_NAMESPACE::BLEMIDI_Transport<BLEMIDI_NAMESPACE::BLEMIDI_ESP32_NimBLE>, BLEMIDI_NAMESPACE::MySettings> Name((BLEMIDI_NAMESPACE::BLEMIDI_Transport<BLEMIDI_NAMESPACE::BLEMIDI_ESP32_NimBLE> &)BLE##Name);
/*! \brief Create a default instance for ESP32 named BLE-MIDI /*! \brief Create a default instance for ESP32 named BLE-MIDI
*/ */
#define BLEMIDI_CREATE_DEFAULT_INSTANCE() \ #define BLEMIDI_CREATE_DEFAULT_INSTANCE() \
BLEMIDI_CREATE_INSTANCE("Esp32-NimBLE-MIDI", MIDI) BLEMIDI_CREATE_INSTANCE("Esp32-BLE-MIDI", MIDI)
END_BLEMIDI_NAMESPACE END_BLEMIDI_NAMESPACE

View File

@ -24,11 +24,6 @@ public:
bool begin(const char*, BLEMIDI_NAMESPACE::BLEMIDI_Transport<class BLEMIDI_nRF52>*); bool begin(const char*, BLEMIDI_NAMESPACE::BLEMIDI_Transport<class BLEMIDI_nRF52>*);
void end()
{
}
void write(uint8_t* buffer, size_t length) void write(uint8_t* buffer, size_t length)
{ {
} }