cs-midi/MIDI_Interfaces/BLEMIDI/BTstackBackgroundBackend.hpp

135 lines
4.6 KiB
C++

#pragma once
#include <AH/Error/Error.hpp>
#include "BLEAPI.hpp"
#include "BTstack/gatt_midi.hpp"
#include "BufferedBLEMIDIParser.hpp"
#include "PollingBLEMIDISender.hpp"
#include <atomic>
#include <csignal>
#include <hardware/sync.h>
BEGIN_CS_NAMESPACE
/// Raspberry Pi Pico BTstack background backend intended to be plugged into
/// @ref GenericBLEMIDI_Interface.
///
/// @todo Implement BTstack timer-based sender timeouts.
class BTstackBackgroundBackend
: private PollingBLEMIDISender<BTstackBackgroundBackend>,
private MIDIBLEInstance {
private:
// Callbacks from the ArduinoBLE stack.
void handleConnect(BLEConnectionHandle conn_handle) override {
connected = conn_handle.conn;
}
void handleDisconnect(BLEConnectionHandle) override {
connected = subscribed = 0xFFFF;
}
void handleMTU(BLEConnectionHandle, uint16_t mtu) override {
Sender::updateMTU(mtu);
}
void handleSubscribe(BLEConnectionHandle,
BLECharacteristicHandle char_handle,
bool notify) override {
subscribed = notify ? char_handle.characteristic : 0xFFFF;
}
void handleData(BLEConnectionHandle, BLEDataGenerator &&data,
BLEDataLifetime) override {
while (true) {
BLEDataView packet = data();
if (packet.length == 0) {
break;
} else if (!parser.pushPacket(packet)) {
DEBUGREF(F("BLE packet dropped, size: ") << packet.length);
break;
}
}
}
private:
// We cannot use atomics here, because they might not be lock-free on the
// Pico's Cortex-M0+ cores.
static_assert(sizeof(sig_atomic_t) > sizeof(uint16_t));
/// Are we connected to a BLE Central?
volatile sig_atomic_t connected = 0xFFFF;
/// Did the BLE Central subscribe to be notified for the MIDI characteristic?
volatile sig_atomic_t subscribed = 0xFFFF;
struct VolatileSize {
constexpr static size_t alignment = alignof(sig_atomic_t);
VolatileSize(sig_atomic_t value) : value {value} {}
volatile sig_atomic_t value;
sig_atomic_t load_acquire() const {
auto state = save_and_disable_interrupts(); // Probably overkill ...
auto t = value;
restore_interrupts(state);
std::atomic_signal_fence(std::memory_order_acquire);
return t;
}
void add_release(sig_atomic_t t) {
std::atomic_signal_fence(std::memory_order_release);
auto state = save_and_disable_interrupts();
value += t;
restore_interrupts(state);
}
void sub_release(sig_atomic_t t) {
std::atomic_signal_fence(std::memory_order_release);
auto state = save_and_disable_interrupts();
value -= t;
restore_interrupts(state);
}
};
/// Contains incoming BLE MIDI data to be parsed.
BufferedBLEMIDIParser<1024, VolatileSize> parser;
public:
using IncomingMIDIMessage = AnyMIDIMessage;
/// Retrieve and remove a single incoming MIDI message from the buffer.
bool popMessage(IncomingMIDIMessage &incomingMessage) {
// This function is assumed to be polled regularly by the higher-level
// MIDI_Interface, so we check the sender's timer here.
auto lck = Sender::acquirePacket();
Sender::releasePacketAndNotify(lck);
// Actually get a MIDI message from the buffer
return parser.popMessage(incomingMessage);
}
public:
/// Initialize the BLE stack etc.
void begin(BLESettings ble_settings) {
midi_ble_btstack::init(*this, ble_settings);
Sender::begin();
}
/// Deinitialize the BLE stack.
/// @todo Not yet implemented.
void end() {}
/// Returns true if we are connected to a BLE Central device.
bool isConnected() const { return connected != 0xFFFF; }
private:
// Implement the interface for the BLE sender.
using Sender = PollingBLEMIDISender<BTstackBackgroundBackend>;
friend Sender;
/// Send the given MIDI BLE packet.
void sendData(BLEDataView data) {
if (connected != 0xFFFF && subscribed != 0xFFFF)
midi_ble_btstack::notify(
BLEConnectionHandle {static_cast<uint16_t>(connected)},
BLECharacteristicHandle {static_cast<uint16_t>(subscribed)},
data);
}
public:
// Expose the necessary BLE sender functions.
using Sender::acquirePacket;
using Sender::forceMinMTU;
using Sender::getMinMTU;
using Sender::releasePacketAndNotify;
using Sender::sendNow;
using Sender::setTimeout;
};
END_CS_NAMESPACE