cs-midi/MIDI_Interfaces/BLEMIDI/BLEAPI.hpp

169 lines
6.5 KiB
C++

#pragma once
/**
* @file
* Type definitions and callback interfaces for communication between the
* low-level BLE stacks and higher-level MIDI BLE backends.
*/
#include <Settings/NamespaceSettings.hpp>
#include "Util/compat.hpp"
#include <cstddef>
#include <cstdint>
BEGIN_CS_NAMESPACE
/// Represents a handle to the connection to another device.
struct BLEConnectionHandle {
uint16_t conn = 0xFFFF;
explicit operator bool() const { return conn != 0xFFFF; }
#if __cplusplus < 201402L
BLEConnectionHandle() = default;
BLEConnectionHandle(uint16_t conn) : conn {conn} {}
#endif
};
/// Represents a handle to a local GATT characteristic.
struct BLECharacteristicHandle {
uint16_t characteristic = 0xFFFF;
explicit operator bool() const { return characteristic != 0xFFFF; }
#if __cplusplus < 201402L
BLECharacteristicHandle() = default;
BLECharacteristicHandle(uint16_t characteristic)
: characteristic {characteristic} {}
#endif
};
/// Non-owning, std::span-style read-only view of BLE data.
struct BLEDataView {
const uint8_t *data = nullptr;
uint16_t length = 0;
explicit operator bool() const { return length > 0; }
#if __cplusplus < 201402L
BLEDataView() = default;
BLEDataView(const uint8_t *data, uint16_t length)
: data {data}, length {length} {}
#endif
};
/// Describes a byte buffer containing (part of) a BLE packet.
/// Packets can be stored across multiple buffers, in which case the first
/// first buffer has type `Packet` and subsequent buffers of the same packet
/// have the type `Continuation`.
enum class BLEDataType : uint8_t {
None = 0, ///< No buffers available.
Packet, ///< Buffer contains the start of a BLE packet.
Continuation, ///< Buffer contains a chunk of a BLE packet.
};
/// Callable that returns the next chunk of data from a BLE packet when called.
/// Uses type erasure with a static buffer (no dynamic memory allocations).
class BLEDataGenerator {
public:
/// Get the next chunk of data from the BLE packet.
/// Returns a chunk of size zero to indicate completion.
/// @pre This wrapper is not empty.
/// @pre There is still data available. Calling this function again after
/// the previous call returned an empty chunk is not allowed.
BLEDataView operator()();
/// Release the resources of the underlying data generator.
void clear();
/// Check if this wrapper contains an underlying data generator.
explicit operator bool() const { return instance; }
/// Create an empty BLEDataGenerator.
BLEDataGenerator() = default;
/// Store a callable of type @p T and initialize it by @p args.
template <class T, class... Args>
BLEDataGenerator(compat::in_place_type_t<T>, Args &&...args);
/// Store a callable of type @p T (with cv qualifiers and references
/// removed) and initialize it by forwarding @p t.
template <class T>
BLEDataGenerator(compat::in_place_t, T &&t);
BLEDataGenerator(const BLEDataGenerator &) = delete;
BLEDataGenerator &operator=(const BLEDataGenerator &) = delete;
BLEDataGenerator(BLEDataGenerator &&other) noexcept;
BLEDataGenerator &operator=(BLEDataGenerator &&other) noexcept;
~BLEDataGenerator() { clear(); }
private:
/// Type-erased interface.
struct Iface;
/// Specific class that implements the type-erased interface, wrapping the
/// type @p T.
template <class T>
struct Impl;
/// Alignment of the buffer to allocate the underlying data generator.
using buffer_align_t = max_align_t;
/// Size of the buffer to allocate the underlying data generator.
static constexpr size_t capacity = 4 * sizeof(void *) - sizeof(Iface *);
/// Buffer used for allocation of the underlying data generator.
alignas(buffer_align_t) compat::byte storage[capacity];
//// Type-erased pointer to the underlying data generator in @ref storage.
Iface *instance = nullptr;
};
/// Should a buffer of BLEData be consumed immediately inside of the callback,
/// or can we hold on to it and process it later?
enum class BLEDataLifetime {
/// Buffer is valid only during the callback. Do not keep any pointers to it.
ConsumeImmediately,
/// Buffer is valid for as long as the owning @ref BLEDataGenerator is not
/// resumed or destroyed.
Managed,
};
/// Defines the interface for callback functions registered by the low-level
/// BLE code.
/// @warning These functions may be called from different tasks/threads or
/// low-priority interrupt handlers. You cannot take locks, and you
/// need to synchronize appropriately (e.g. using `std::atomic` or
/// by using critical sections).
class MIDIBLEInstance {
public:
virtual ~MIDIBLEInstance() = default;
/// Called by the BLE stack when a connection is established.
virtual void handleConnect(BLEConnectionHandle conn_handle) = 0;
/// Called by the BLE stack when a connection is terminated.
virtual void handleDisconnect(BLEConnectionHandle conn_handle) = 0;
/// Called by the BLE stack when the maximum transmission unit for the
/// connection changes.
virtual void handleMTU(BLEConnectionHandle conn_handle, uint16_t mtu) = 0;
/// Called by the BLE stack when the central subscribes to receive
/// notifications for the MIDI GATT characteristic.
virtual void handleSubscribe(BLEConnectionHandle conn_handle,
BLECharacteristicHandle char_handle,
bool notify) = 0;
/// Called by the BLE stack when the central writes data to the MIDI GATT
/// characteristic.
virtual void handleData(BLEConnectionHandle conn_handle,
BLEDataGenerator &&data,
BLEDataLifetime lifetime) = 0;
};
/// Configuration options for the low-level BLE code.
struct BLESettings {
/// Device name (used for advertising)
const char *device_name = "Control Surface MIDI";
/// Connection intervals as multiples of 1.25 milliseconds
/// (e.g.0x000C = 15 ms).
struct {
uint16_t minimum = 0x000C;
uint16_t maximum = 0x000C;
} connection_interval {};
/// Set to true if you want the Arduino to always initiate the Bluetooth
/// bonding or secure connection. As a result, it will show up as a "paired"
/// device on your computer/phone/tablet. If set to false, security is still
/// supported, but the central device should take the initiative.
bool initiate_security = false;
};
END_CS_NAMESPACE
#include "BLEAPI.ipp"