This commit is contained in:
pszsh 2026-03-09 01:56:29 -07:00
parent 2187943124
commit 75ba8b6f4a
18 changed files with 589 additions and 111 deletions

View File

@ -5,7 +5,7 @@ option(CS_MIDI_BLE "Enable BLE MIDI interface (BTstack)" ON)
option(CS_MIDI_USB "Enable USB MIDI interface (TinyUSB)" OFF)
option(CS_MIDI_SERIAL "Enable Serial MIDI interface (UART)" OFF)
option(CS_MIDI_APPLEMIDI "Enable AppleMIDI interface (RTP-MIDI over WiFi)" OFF)
option(CS_MIDI_HID_MOUSE "Advertise as HID mouse for BLE auto-reconnect" OFF)
option(CS_MIDI_HID_KEYBOARD "HID keyboard + Battery Service for BLE auto-reconnect" OFF)
# Core sources always compiled
set(CS_MIDI_CORE_SOURCES
@ -80,7 +80,7 @@ target_compile_definitions(cs_midi PUBLIC
$<$<BOOL:${CS_MIDI_USB}>:CS_MIDI_USB=1>
$<$<BOOL:${CS_MIDI_SERIAL}>:CS_MIDI_SERIAL=1>
$<$<BOOL:${CS_MIDI_APPLEMIDI}>:CS_MIDI_APPLEMIDI=1>
$<$<BOOL:${CS_MIDI_HID_MOUSE}>:CS_MIDI_HID_MOUSE=1>
$<$<BOOL:${CS_MIDI_HID_KEYBOARD}>:CS_MIDI_HID_KEYBOARD=1>
)
target_link_libraries(cs_midi

View File

@ -8,15 +8,15 @@ namespace cs::midi_ble_btstack {
namespace {
#ifdef CS_MIDI_HID_MOUSE
#ifdef CS_MIDI_HID_KEYBOARD
uint8_t adv_data[] {
// Flags general discoverable
0x02, BLUETOOTH_DATA_TYPE_FLAGS, 0x06,
// HID Service UUID (16-bit)
0x03, BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS,
0x12, 0x18,
// Appearance: Mouse (0x03C2)
0x03, BLUETOOTH_DATA_TYPE_APPEARANCE, 0xC2, 0x03,
// Appearance: Keyboard (0x03C1)
0x03, BLUETOOTH_DATA_TYPE_APPEARANCE, 0xC1, 0x03,
// MIDI Service UUID (128-bit)
0x11, BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS,
0x00, 0xc7, 0xc4, 0x4e, 0xe3, 0x6c, 0x51, 0xa7, 0x33, 0x4b, 0xe8, 0xed,
@ -42,7 +42,7 @@ uint8_t adv_rsp_data[LE_ADVERTISING_DATA_SIZE] {
' ', 'M', 'I', 'D', 'I'};
uint8_t adv_rsp_data_len() { return adv_rsp_data[0] + 1; }
#ifdef CS_MIDI_HID_MOUSE
#ifdef CS_MIDI_HID_KEYBOARD
void set_adv_connection_interval(uint16_t, uint16_t) {}
#else
void set_adv_connection_interval(uint16_t min_itvl, uint16_t max_itvl) {

View File

@ -11,9 +11,10 @@
#include "../BLEAPI.hpp"
#include "advertising.hpp"
#ifdef CS_MIDI_HID_MOUSE
#ifdef CS_MIDI_HID_KEYBOARD
#include "gatt_midi_hog.h"
#include <ble/gatt-service/hids_device.h>
#include <ble/gatt-service/battery_service_server.h>
#else
#include "gatt_midi.h"
#endif
@ -36,34 +37,31 @@ BLESettings settings;
btstack_packet_callback_registration_t hci_event_callback_registration;
btstack_packet_callback_registration_t sm_event_callback_registration;
#ifdef CS_MIDI_HID_MOUSE
const uint8_t hid_descriptor_mouse[] = {
#ifdef CS_MIDI_HID_KEYBOARD
const uint8_t hid_descriptor_keyboard[] = {
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x02, // Usage (Mouse)
0x09, 0x06, // Usage (Keyboard)
0xa1, 0x01, // Collection (Application)
0x85, 0x01, // Report ID 1
0x09, 0x01, // Usage (Pointer)
0xa1, 0x00, // Collection (Physical)
0x05, 0x09, // Usage Page (Buttons)
0x19, 0x01, // Usage Minimum (1)
0x29, 0x03, // Usage Maximum (3)
0x05, 0x07, // Usage Page (Keyboard/Keypad)
0x19, 0xe0, // Usage Minimum (Left Control)
0x29, 0xe7, // Usage Maximum (Right GUI)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x95, 0x03, // Report Count (3)
0x75, 0x01, // Report Size (1)
0x81, 0x02, // Input (Data,Var,Abs)
0x95, 0x08, // Report Count (8)
0x81, 0x02, // Input (Data,Var,Abs) — modifiers
0x95, 0x01, // Report Count (1)
0x75, 0x05, // Report Size (5)
0x81, 0x03, // Input (Const,Var,Abs) — padding
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x15, 0x81, // Logical Minimum (-127)
0x25, 0x7f, // Logical Maximum (127)
0x75, 0x08, // Report Size (8)
0x95, 0x02, // Report Count (2)
0x81, 0x06, // Input (Data,Var,Rel)
0xc0, // End Collection
0x81, 0x01, // Input (Const) — reserved
0x95, 0x06, // Report Count (6)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xff, 0x00, // Logical Maximum (255)
0x05, 0x07, // Usage Page (Keyboard/Keypad)
0x19, 0x00, // Usage Minimum (0)
0x29, 0xff, // Usage Maximum (255)
0x81, 0x00, // Input (Data,Ary,Abs) — 6KRO keycodes
0xc0 // End Collection
};
@ -73,7 +71,6 @@ constexpr uint16_t gap_device_name_handle =
void hid_packet_handler(uint8_t packet_type, uint16_t, uint8_t *packet, uint16_t) {
if (packet_type != HCI_EVENT_PACKET) return;
if (hci_event_packet_get_type(packet) != HCI_EVENT_HIDS_META) return;
// Accept HID events but never send mouse reports
}
#endif
@ -225,7 +222,7 @@ uint16_t att_read_callback([[maybe_unused]] hci_con_handle_t connection_handle,
[[maybe_unused]] uint16_t offset,
[[maybe_unused]] uint8_t *buffer,
[[maybe_unused]] uint16_t buffer_size) {
#ifdef CS_MIDI_HID_MOUSE
#ifdef CS_MIDI_HID_KEYBOARD
if (att_handle == gap_device_name_handle) {
const char *name = settings.device_name;
auto len = static_cast<uint16_t>(std::strlen(name));
@ -294,9 +291,10 @@ void le_midi_setup(const BLESettings &ble_settings) {
SM_AUTHREQ_BONDING);
// setup ATT server
att_server_init(profile_data, att_read_callback, att_write_callback);
#ifdef CS_MIDI_HID_MOUSE
hids_device_init(0, hid_descriptor_mouse, sizeof(hid_descriptor_mouse));
#ifdef CS_MIDI_HID_KEYBOARD
hids_device_init(0, hid_descriptor_keyboard, sizeof(hid_descriptor_keyboard));
hids_device_register_packet_handler(hid_packet_handler);
battery_service_server_init(100);
#endif
// setup advertisements
le_midi_setup_adv(ble_settings);
@ -368,4 +366,10 @@ void notify(BLEConnectionHandle conn_handle,
DEBUGREF("all done " << micros() - t0);
}
#ifdef CS_MIDI_HID_KEYBOARD
void set_battery_level(uint8_t percent) {
battery_service_server_set_battery_value(percent);
}
#endif
} // namespace cs::midi_ble_btstack

View File

@ -8,4 +8,8 @@ bool init(MIDIBLEInstance &instance, BLESettings settings);
void notify(BLEConnectionHandle conn_handle,
BLECharacteristicHandle char_handle, BLEDataView data);
#ifdef CS_MIDI_HID_KEYBOARD
void set_battery_level(uint8_t percent);
#endif
} // namespace cs::midi_ble_btstack

View File

@ -3,8 +3,8 @@ CHARACTERISTIC, GATT_DATABASE_HASH, READ,
PRIMARY_SERVICE, GAP_SERVICE
CHARACTERISTIC, GAP_DEVICE_NAME, READ | DYNAMIC,
// 0x03C2 = HID Mouse
CHARACTERISTIC, GAP_APPEARANCE, READ, C2 03
// 0x03C1 = HID Keyboard
CHARACTERISTIC, GAP_APPEARANCE, READ, C1 03
// MIDI Service
PRIMARY_SERVICE, 03B80E5A-EDE8-4B33-A751-6CE34EC4C700
@ -12,3 +12,6 @@ CHARACTERISTIC, 7772E5DB-3868-4112-A1A9-F2669D106BF3, READ | WRITE_WITHOUT_RESPO
// HID Service
#import <hids.gatt>
// Battery Service
#import <battery_service.gatt>

View File

@ -1,12 +1,19 @@
// Generated from gatt_midi_hog.gatt — MIDI + HoG Mouse combined GATT database
// Regenerate: compile_gatt.py gatt_midi_hog.gatt gatt_midi_hog.h
// clang-format off
// gatt_midi_hog.h generated from gatt_midi_hog.gatt for BTstack
// it needs to be regenerated when the .gatt file is updated.
// To generate gatt_midi_hog.h:
// compile_gatt.py gatt_midi_hog.gatt gatt_midi_hog.h
// att db format version 1
// binary attribute representation:
// - size in bytes (16), flags(16), handle (16), uuid (16/128), value(...)
#include <stdint.h>
// Reference: https://en.cppreference.com/w/cpp/feature_test
#if __cplusplus >= 200704L
constexpr
#endif
@ -19,89 +26,133 @@ const uint8_t profile_data[] =
0x0a, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x28, 0x01, 0x18,
// 0x0002 CHARACTERISTIC-GATT_DATABASE_HASH - READ
0x0d, 0x00, 0x02, 0x00, 0x02, 0x00, 0x03, 0x28, 0x02, 0x03, 0x00, 0x2a, 0x2b,
// 0x0003 VALUE CHARACTERISTIC-GATT_DATABASE_HASH - READ
0x18, 0x00, 0x02, 0x00, 0x03, 0x00, 0x2a, 0x2b, 0xce, 0x0f, 0xc6, 0xd0, 0xa1, 0x9c, 0xd7, 0xa2, 0x98, 0xad, 0x7d, 0x4c, 0x9c, 0x85, 0xed, 0x81,
// 0x0003 VALUE CHARACTERISTIC-GATT_DATABASE_HASH - READ -''
// READ_ANYBODY
0x18, 0x00, 0x02, 0x00, 0x03, 0x00, 0x2a, 0x2b, 0xab, 0x5a, 0x3b, 0x8c, 0x3f, 0x95, 0x12, 0x5d, 0x5a, 0xa5, 0x27, 0xb7, 0x75, 0x27, 0xfc, 0x2b,
// 0x0004 PRIMARY_SERVICE-GAP_SERVICE
0x0a, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x28, 0x00, 0x18,
// 0x0005 CHARACTERISTIC-GAP_DEVICE_NAME - READ | DYNAMIC
0x0d, 0x00, 0x02, 0x00, 0x05, 0x00, 0x03, 0x28, 0x02, 0x06, 0x00, 0x00, 0x2a,
// 0x0006 VALUE CHARACTERISTIC-GAP_DEVICE_NAME - READ | DYNAMIC
// READ_ANYBODY
0x08, 0x00, 0x02, 0x01, 0x06, 0x00, 0x00, 0x2a,
// 0x0007 CHARACTERISTIC-GAP_APPEARANCE - READ - Mouse (0x03C2)
// 0x03C1 = HID Keyboard
// 0x0007 CHARACTERISTIC-GAP_APPEARANCE - READ
0x0d, 0x00, 0x02, 0x00, 0x07, 0x00, 0x03, 0x28, 0x02, 0x08, 0x00, 0x01, 0x2a,
// 0x0008 VALUE CHARACTERISTIC-GAP_APPEARANCE - READ
0x0a, 0x00, 0x02, 0x00, 0x08, 0x00, 0x01, 0x2a, 0xC2, 0x03,
// 0x0008 VALUE CHARACTERISTIC-GAP_APPEARANCE - READ -'C1 03'
// READ_ANYBODY
0x0a, 0x00, 0x02, 0x00, 0x08, 0x00, 0x01, 0x2a, 0xC1, 0x03,
// MIDI Service
// 0x0009 PRIMARY_SERVICE-03B80E5A-EDE8-4B33-A751-6CE34EC4C700
0x18, 0x00, 0x02, 0x00, 0x09, 0x00, 0x00, 0x28, 0x00, 0xc7, 0xc4, 0x4e, 0xe3, 0x6c, 0x51, 0xa7, 0x33, 0x4b, 0xe8, 0xed, 0x5a, 0x0e, 0xb8, 0x03,
// 0x000a CHARACTERISTIC-7772E5DB-3868-4112-A1A9-F2669D106BF3 - READ | WRITE_WITHOUT_RESPONSE | NOTIFY | DYNAMIC
0x1b, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x03, 0x28, 0x16, 0x0b, 0x00, 0xf3, 0x6b, 0x10, 0x9d, 0x66, 0xf2, 0xa9, 0xa1, 0x12, 0x41, 0x68, 0x38, 0xdb, 0xe5, 0x72, 0x77,
// 0x000b VALUE CHARACTERISTIC-7772E5DB-3868-4112-A1A9-F2669D106BF3 - READ | WRITE_WITHOUT_RESPONSE | NOTIFY | DYNAMIC
// READ_ANYBODY, WRITE_ANYBODY
0x16, 0x00, 0x06, 0x03, 0x0b, 0x00, 0xf3, 0x6b, 0x10, 0x9d, 0x66, 0xf2, 0xa9, 0xa1, 0x12, 0x41, 0x68, 0x38, 0xdb, 0xe5, 0x72, 0x77,
// 0x000c CLIENT_CHARACTERISTIC_CONFIGURATION
// READ_ANYBODY, WRITE_ANYBODY
0x0a, 0x00, 0x0e, 0x01, 0x0c, 0x00, 0x02, 0x29, 0x00, 0x00,
// HID Service
// HID Service (hids.gatt)
// #import <hids.gatt> -- BEGIN
// Specification Type org.bluetooth.service.human_interface_device
// https://www.bluetooth.com/api/gatt/xmlfile?xmlFileName=org.bluetooth.service.human_interface_device.xml
// Human Interface Device 1812
// 0x000d PRIMARY_SERVICE-ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE
0x0a, 0x00, 0x02, 0x00, 0x0d, 0x00, 0x00, 0x28, 0x12, 0x18,
// 0x000e CHARACTERISTIC-PROTOCOL_MODE - DYNAMIC | READ | WRITE_WITHOUT_RESPONSE
// 0x000e CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_PROTOCOL_MODE - DYNAMIC | READ | WRITE_WITHOUT_RESPONSE
0x0d, 0x00, 0x02, 0x00, 0x0e, 0x00, 0x03, 0x28, 0x06, 0x0f, 0x00, 0x4e, 0x2a,
// 0x000f VALUE CHARACTERISTIC-PROTOCOL_MODE
// 0x000f VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_PROTOCOL_MODE - DYNAMIC | READ | WRITE_WITHOUT_RESPONSE
// READ_ANYBODY, WRITE_ANYBODY
0x08, 0x00, 0x06, 0x01, 0x0f, 0x00, 0x4e, 0x2a,
// 0x0010 CHARACTERISTIC-REPORT (Input, ID=1) - DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16
// 0x0010 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16
0x0d, 0x00, 0x02, 0x00, 0x10, 0x00, 0x03, 0x28, 0x1a, 0x11, 0x00, 0x4d, 0x2a,
// 0x0011 VALUE CHARACTERISTIC-REPORT
// 0x0011 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16
// READ_ENCRYPTED, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16
0x08, 0x00, 0x0b, 0xf5, 0x11, 0x00, 0x4d, 0x2a,
// 0x0012 CLIENT_CHARACTERISTIC_CONFIGURATION
// READ_ANYBODY, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16
0x0a, 0x00, 0x0f, 0xf1, 0x12, 0x00, 0x02, 0x29, 0x00, 0x00,
// 0x0013 REPORT_REFERENCE - Report ID=1, Type=Input(1)
// fixed report id = 1, type = Input (1)
// 0x0013 REPORT_REFERENCE-READ-1-1
0x0a, 0x00, 0x02, 0x00, 0x13, 0x00, 0x08, 0x29, 0x1, 0x1,
// 0x0014 CHARACTERISTIC-REPORT (Output, ID=2) - DYNAMIC | READ | WRITE | WRITE_WITHOUT_RESPONSE | ENCRYPTION_KEY_SIZE_16
// 0x0014 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | WRITE_WITHOUT_RESPONSE | ENCRYPTION_KEY_SIZE_16
0x0d, 0x00, 0x02, 0x00, 0x14, 0x00, 0x03, 0x28, 0x0e, 0x15, 0x00, 0x4d, 0x2a,
// 0x0015 VALUE CHARACTERISTIC-REPORT
// 0x0015 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | WRITE_WITHOUT_RESPONSE | ENCRYPTION_KEY_SIZE_16
// READ_ENCRYPTED, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16
0x08, 0x00, 0x0f, 0xf5, 0x15, 0x00, 0x4d, 0x2a,
// 0x0016 REPORT_REFERENCE - Report ID=2, Type=Output(2)
// fixed report id = 2, type = Output (2)
// 0x0016 REPORT_REFERENCE-READ-2-2
0x0a, 0x00, 0x02, 0x00, 0x16, 0x00, 0x08, 0x29, 0x2, 0x2,
// 0x0017 CHARACTERISTIC-REPORT (Feature, ID=3) - DYNAMIC | READ | WRITE | ENCRYPTION_KEY_SIZE_16
// 0x0017 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | ENCRYPTION_KEY_SIZE_16
0x0d, 0x00, 0x02, 0x00, 0x17, 0x00, 0x03, 0x28, 0x0a, 0x18, 0x00, 0x4d, 0x2a,
// 0x0018 VALUE CHARACTERISTIC-REPORT
// 0x0018 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | ENCRYPTION_KEY_SIZE_16
// READ_ENCRYPTED, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16
0x08, 0x00, 0x0b, 0xf5, 0x18, 0x00, 0x4d, 0x2a,
// 0x0019 REPORT_REFERENCE - Report ID=3, Type=Feature(3)
// fixed report id = 3, type = Feature (3)
// 0x0019 REPORT_REFERENCE-READ-3-3
0x0a, 0x00, 0x02, 0x00, 0x19, 0x00, 0x08, 0x29, 0x3, 0x3,
// 0x001a CHARACTERISTIC-REPORT_MAP - DYNAMIC | READ
// 0x001a CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP - DYNAMIC | READ
0x0d, 0x00, 0x02, 0x00, 0x1a, 0x00, 0x03, 0x28, 0x02, 0x1b, 0x00, 0x4b, 0x2a,
// 0x001b VALUE CHARACTERISTIC-REPORT_MAP
// 0x001b VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP - DYNAMIC | READ
// READ_ANYBODY
0x08, 0x00, 0x02, 0x01, 0x1b, 0x00, 0x4b, 0x2a,
// 0x001c CHARACTERISTIC-BOOT_KEYBOARD_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY
// 0x001c CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY
0x0d, 0x00, 0x02, 0x00, 0x1c, 0x00, 0x03, 0x28, 0x1a, 0x1d, 0x00, 0x22, 0x2a,
// 0x001d VALUE CHARACTERISTIC-BOOT_KEYBOARD_INPUT_REPORT
// 0x001d VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY
// READ_ANYBODY, WRITE_ANYBODY
0x08, 0x00, 0x0a, 0x01, 0x1d, 0x00, 0x22, 0x2a,
// 0x001e CLIENT_CHARACTERISTIC_CONFIGURATION
// READ_ANYBODY, WRITE_ANYBODY
0x0a, 0x00, 0x0e, 0x01, 0x1e, 0x00, 0x02, 0x29, 0x00, 0x00,
// 0x001f CHARACTERISTIC-BOOT_KEYBOARD_OUTPUT_REPORT - DYNAMIC | READ | WRITE | WRITE_WITHOUT_RESPONSE
// 0x001f CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT - DYNAMIC | READ | WRITE | WRITE_WITHOUT_RESPONSE
0x0d, 0x00, 0x02, 0x00, 0x1f, 0x00, 0x03, 0x28, 0x0e, 0x20, 0x00, 0x32, 0x2a,
// 0x0020 VALUE CHARACTERISTIC-BOOT_KEYBOARD_OUTPUT_REPORT
// 0x0020 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT - DYNAMIC | READ | WRITE | WRITE_WITHOUT_RESPONSE
// READ_ANYBODY, WRITE_ANYBODY
0x08, 0x00, 0x0e, 0x01, 0x20, 0x00, 0x32, 0x2a,
// 0x0021 CHARACTERISTIC-BOOT_MOUSE_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY
// 0x0021 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY
0x0d, 0x00, 0x02, 0x00, 0x21, 0x00, 0x03, 0x28, 0x1a, 0x22, 0x00, 0x33, 0x2a,
// 0x0022 VALUE CHARACTERISTIC-BOOT_MOUSE_INPUT_REPORT
// 0x0022 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY
// READ_ANYBODY, WRITE_ANYBODY
0x08, 0x00, 0x0a, 0x01, 0x22, 0x00, 0x33, 0x2a,
// 0x0023 CLIENT_CHARACTERISTIC_CONFIGURATION
// READ_ANYBODY, WRITE_ANYBODY
0x0a, 0x00, 0x0e, 0x01, 0x23, 0x00, 0x02, 0x29, 0x00, 0x00,
// 0x0024 CHARACTERISTIC-HID_INFORMATION - READ
// bcdHID = 0x101 (v1.0.1), bCountryCode 0, remote wakeable = 0 | normally connectable 2
// 0x0024 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION - READ
0x0d, 0x00, 0x02, 0x00, 0x24, 0x00, 0x03, 0x28, 0x02, 0x25, 0x00, 0x4a, 0x2a,
// 0x0025 VALUE CHARACTERISTIC-HID_INFORMATION - bcdHID=0x0101, country=0, flags=0x02
// 0x0025 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION - READ -'01 01 00 02'
// READ_ANYBODY
0x0c, 0x00, 0x02, 0x00, 0x25, 0x00, 0x4a, 0x2a, 0x01, 0x01, 0x00, 0x02,
// 0x0026 CHARACTERISTIC-HID_CONTROL_POINT - DYNAMIC | WRITE_WITHOUT_RESPONSE
// 0x0026 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT - DYNAMIC | WRITE_WITHOUT_RESPONSE
0x0d, 0x00, 0x02, 0x00, 0x26, 0x00, 0x03, 0x28, 0x04, 0x27, 0x00, 0x4c, 0x2a,
// 0x0027 VALUE CHARACTERISTIC-HID_CONTROL_POINT
// 0x0027 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT - DYNAMIC | WRITE_WITHOUT_RESPONSE
// WRITE_ANYBODY
0x08, 0x00, 0x04, 0x01, 0x27, 0x00, 0x4c, 0x2a,
// #import <hids.gatt> -- END
// Battery Service
// #import <battery_service.gatt> -- BEGIN
// Specification Type org.bluetooth.service.battery_service
// https://www.bluetooth.com/api/gatt/xmlfile?xmlFileName=org.bluetooth.service.battery_service.xml
// Battery Service 180F
// 0x0028 PRIMARY_SERVICE-ORG_BLUETOOTH_SERVICE_BATTERY_SERVICE
0x0a, 0x00, 0x02, 0x00, 0x28, 0x00, 0x00, 0x28, 0x0f, 0x18,
// 0x0029 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BATTERY_LEVEL - DYNAMIC | READ | NOTIFY
0x0d, 0x00, 0x02, 0x00, 0x29, 0x00, 0x03, 0x28, 0x12, 0x2a, 0x00, 0x19, 0x2a,
// 0x002a VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BATTERY_LEVEL - DYNAMIC | READ | NOTIFY
// READ_ANYBODY
0x08, 0x00, 0x02, 0x01, 0x2a, 0x00, 0x19, 0x2a,
// 0x002b CLIENT_CHARACTERISTIC_CONFIGURATION
// READ_ANYBODY, WRITE_ANYBODY
0x0a, 0x00, 0x0e, 0x01, 0x2b, 0x00, 0x02, 0x29, 0x00, 0x00,
// #import <battery_service.gatt> -- END
// END
0x00, 0x00,
}; // total size 252 bytes
}; // total size 275 bytes
//
@ -123,6 +174,10 @@ const uint8_t profile_data[] =
#define ATT_SERVICE_ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE_END_HANDLE 0x0027
#define ATT_SERVICE_ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE_01_START_HANDLE 0x000d
#define ATT_SERVICE_ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE_01_END_HANDLE 0x0027
#define ATT_SERVICE_ORG_BLUETOOTH_SERVICE_BATTERY_SERVICE_START_HANDLE 0x0028
#define ATT_SERVICE_ORG_BLUETOOTH_SERVICE_BATTERY_SERVICE_END_HANDLE 0x002b
#define ATT_SERVICE_ORG_BLUETOOTH_SERVICE_BATTERY_SERVICE_01_START_HANDLE 0x0028
#define ATT_SERVICE_ORG_BLUETOOTH_SERVICE_BATTERY_SERVICE_01_END_HANDLE 0x002b
//
// list mapping between characteristics and handles
@ -145,3 +200,5 @@ const uint8_t profile_data[] =
#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT_01_CLIENT_CONFIGURATION_HANDLE 0x0023
#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION_01_VALUE_HANDLE 0x0025
#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT_01_VALUE_HANDLE 0x0027
#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_BATTERY_LEVEL_01_VALUE_HANDLE 0x002a
#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_BATTERY_LEVEL_01_CLIENT_CONFIGURATION_HANDLE 0x002b

View File

@ -0,0 +1,126 @@
#ifndef _BOARDS_WAVESHARE_RP2350B_PLUS_W_H
#define _BOARDS_WAVESHARE_RP2350B_PLUS_W_H
pico_board_cmake_set(PICO_PLATFORM, rp2350)
pico_board_cmake_set(PICO_CYW43_SUPPORTED, 1)
#define WAVESHARE_RP2350B_PLUS_W
// --- RP2350 VARIANT ---
#define PICO_RP2350A 0
// --- UART ---
#ifndef PICO_DEFAULT_UART
#define PICO_DEFAULT_UART 0
#endif
#ifndef PICO_DEFAULT_UART_TX_PIN
#define PICO_DEFAULT_UART_TX_PIN 0
#endif
#ifndef PICO_DEFAULT_UART_RX_PIN
#define PICO_DEFAULT_UART_RX_PIN 1
#endif
// --- LED ---
#ifndef PICO_DEFAULT_LED_PIN
#define PICO_DEFAULT_LED_PIN 23
#endif
// --- I2C ---
#ifndef PICO_DEFAULT_I2C
#define PICO_DEFAULT_I2C 1
#endif
#ifndef PICO_DEFAULT_I2C_SDA_PIN
#define PICO_DEFAULT_I2C_SDA_PIN 6
#endif
#ifndef PICO_DEFAULT_I2C_SCL_PIN
#define PICO_DEFAULT_I2C_SCL_PIN 7
#endif
// --- SPI ---
#ifndef PICO_DEFAULT_SPI
#define PICO_DEFAULT_SPI 0
#endif
#ifndef PICO_DEFAULT_SPI_SCK_PIN
#define PICO_DEFAULT_SPI_SCK_PIN 18
#endif
#ifndef PICO_DEFAULT_SPI_TX_PIN
#define PICO_DEFAULT_SPI_TX_PIN 19
#endif
#ifndef PICO_DEFAULT_SPI_RX_PIN
#define PICO_DEFAULT_SPI_RX_PIN 16
#endif
#ifndef PICO_DEFAULT_SPI_CSN_PIN
#define PICO_DEFAULT_SPI_CSN_PIN 17
#endif
// --- FLASH ---
#define PICO_BOOT_STAGE2_CHOOSE_W25Q080 1
#ifndef PICO_FLASH_SPI_CLKDIV
#define PICO_FLASH_SPI_CLKDIV 2
#endif
pico_board_cmake_set_default(PICO_FLASH_SIZE_BYTES, (16 * 1024 * 1024))
#ifndef PICO_FLASH_SIZE_BYTES
#define PICO_FLASH_SIZE_BYTES (16 * 1024 * 1024)
#endif
// --- CYW43 (RM2) on B-package extra pins ---
#ifndef CYW43_WL_GPIO_COUNT
#define CYW43_WL_GPIO_COUNT 3
#endif
#ifndef CYW43_WL_GPIO_LED_PIN
#define CYW43_WL_GPIO_LED_PIN 0
#endif
#ifndef CYW43_WL_GPIO_SMPS_PIN
#define CYW43_WL_GPIO_SMPS_PIN 1
#endif
#ifndef CYW43_WL_GPIO_VBUS_PIN
#define CYW43_WL_GPIO_VBUS_PIN 2
#endif
#ifndef CYW43_USES_VSYS_PIN
#define CYW43_USES_VSYS_PIN 1
#endif
#ifndef PICO_VSYS_PIN
#define PICO_VSYS_PIN 29
#endif
#ifndef CYW43_PIN_WL_DYNAMIC
#define CYW43_PIN_WL_DYNAMIC 0
#endif
#ifndef CYW43_DEFAULT_PIN_WL_REG_ON
#define CYW43_DEFAULT_PIN_WL_REG_ON 36u
#endif
#ifndef CYW43_DEFAULT_PIN_WL_DATA_OUT
#define CYW43_DEFAULT_PIN_WL_DATA_OUT 37u
#endif
#ifndef CYW43_DEFAULT_PIN_WL_DATA_IN
#define CYW43_DEFAULT_PIN_WL_DATA_IN 37u
#endif
#ifndef CYW43_DEFAULT_PIN_WL_HOST_WAKE
#define CYW43_DEFAULT_PIN_WL_HOST_WAKE 37u
#endif
#ifndef CYW43_DEFAULT_PIN_WL_CLOCK
#define CYW43_DEFAULT_PIN_WL_CLOCK 39u
#endif
#ifndef CYW43_DEFAULT_PIN_WL_CS
#define CYW43_DEFAULT_PIN_WL_CS 38u
#endif
pico_board_cmake_set_default(PICO_RP2350_A2_SUPPORTED, 1)
#ifndef PICO_RP2350_A2_SUPPORTED
#define PICO_RP2350_A2_SUPPORTED 1
#endif
#endif

View File

@ -23,6 +23,7 @@
// MIDI interfaces — common
#include <MIDI_Interfaces/MIDI_Pipes.hpp>
#include <MIDI_Interfaces/MIDI_Callbacks.hpp>
// MIDI interfaces — transport-specific
#ifdef CS_MIDI_BLE
@ -93,4 +94,13 @@ using BluetoothMIDI_Interface =
GenericBLEMIDI_Interface<BTstackBackgroundBackend>;
#endif
#if defined(CS_MIDI_HID_KEYBOARD) && defined(CS_MIDI_BLE)
namespace midi_ble_btstack {
void set_battery_level(uint8_t percent);
}
inline void setBLEBatteryLevel(uint8_t percent) {
midi_ble_btstack::set_battery_level(percent);
}
#endif
END_CS_NAMESPACE

2
docs

@ -1 +1 @@
Subproject commit 3927d073e938973be113ee08db4e596c7d853ad3
Subproject commit be1ee309670a861adca5fb8f46aefe6a723a6bb4

View File

@ -69,6 +69,9 @@ set(EXAMPLE_SOURCES
output/program_changer.cpp
output/cc_potentiometers.cpp
output/borrowed_cc_rotary_encoder.cpp
output/cc_potentiometer_map.cpp
output/finished_controller.cpp
output/enable_disable.cpp
# Input elements
input/note_led.cpp
@ -84,6 +87,11 @@ set(EXAMPLE_SOURCES
# Interfaces
interfaces/ble_midi.cpp
interfaces/midi_pipes.cpp
interfaces/midi_output.cpp
interfaces/send_midi_notes.cpp
interfaces/send_all_midi_messages.cpp
interfaces/midi_input_callback.cpp
interfaces/midi_pipes_filter.cpp
# Banks
banks/bank_cc_values.cpp

View File

@ -0,0 +1,30 @@
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include <cs_midi.h>
using namespace cs;
BluetoothMIDI_Interface midi;
struct MyMIDI_Callbacks : MIDI_Callbacks {
void onChannelMessage(MIDI_Interface &, ChannelMessage msg) override {
(void)msg;
}
void onSysExMessage(MIDI_Interface &, SysExMessage msg) override {
(void)msg;
}
void onRealTimeMessage(MIDI_Interface &, RealTimeMessage msg) override {
(void)msg;
}
} callback;
int main() {
stdio_init_all();
if (cyw43_arch_init()) return 1;
midi.begin();
midi.setCallbacks(callback);
while (true) {
midi.update();
sleep_ms(1);
}
}

View File

@ -0,0 +1,23 @@
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include <cs_midi.h>
using namespace cs;
BluetoothMIDI_Interface midi;
const MIDIAddress address {MIDI_Notes::C[4], Channel_1};
const uint8_t velocity = 0x7F;
int main() {
stdio_init_all();
if (cyw43_arch_init()) return 1;
midi.begin();
while (true) {
midi.sendNoteOn(address, velocity);
sleep_ms(500);
midi.sendNoteOff(address, velocity);
sleep_ms(500);
midi.update();
}
}

View File

@ -0,0 +1,41 @@
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include <cs_midi.h>
using namespace cs;
BluetoothMIDI_Interface midi;
struct NoteFilter : MIDI_Pipe {
void mapForwardMIDI(ChannelMessage msg) override {
switch (msg.getMessageType()) {
case MIDIMessageType::NoteOff:
case MIDIMessageType::NoteOn:
msg.setChannel(Channel_5);
if (msg.data1 >= 12)
msg.data1 -= 12;
sourceMIDItoSink(msg);
break;
default:
break;
}
}
void mapForwardMIDI(SysExMessage) override {}
void mapForwardMIDI(SysCommonMessage) override {}
void mapForwardMIDI(RealTimeMessage msg) override {
sourceMIDItoSink(msg);
}
};
NoteFilter filter;
int main() {
stdio_init_all();
if (cyw43_arch_init()) return 1;
midi >> filter >> midi;
midi.begin();
while (true) {
midi.update();
sleep_ms(1);
}
}

View File

@ -0,0 +1,51 @@
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include <cs_midi.h>
using namespace cs;
BluetoothMIDI_Interface midi;
int main() {
stdio_init_all();
if (cyw43_arch_init()) return 1;
midi.begin();
// Channel voice messages
MIDIAddress note = {MIDI_Notes::C[4], Channel_6};
midi.sendNoteOn(note, 127);
midi.sendNoteOff(note, 127);
midi.sendKeyPressure(note, 64);
MIDIAddress controller = {MIDI_CC::Channel_Volume, Channel_2};
midi.sendControlChange(controller, 120);
MIDIAddress program = {MIDI_PC::Harpsichord, Channel_9};
midi.sendProgramChange(program);
midi.sendChannelPressure(Channel_3, 64);
midi.sendPitchBend(Channel_3, 16383);
// System common messages
midi.sendMTCQuarterFrame(2, 15);
midi.sendSongPositionPointer(10000);
midi.sendSongSelect(70);
midi.sendTuneRequest();
// System exclusive
uint8_t sysex[] = {0xF0, 0x00, 0x01, 0x02, 0x03, 0xF7};
midi.sendSysEx(sysex);
// Real-time messages
midi.sendTimingClock();
midi.sendStart();
midi.sendContinue();
midi.sendStop();
midi.sendActiveSensing();
midi.sendSystemReset();
while (true) {
midi.update();
sleep_ms(1);
}
}

View File

@ -0,0 +1,27 @@
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include <cs_midi.h>
using namespace cs;
BluetoothMIDI_Interface midi;
AH::Button pushbutton {5};
const MIDIAddress noteAddress {MIDI_Notes::C[4], Channel_1};
const uint8_t velocity = 0x7F;
int main() {
stdio_init_all();
if (cyw43_arch_init()) return 1;
pushbutton.begin();
midi.begin();
while (true) {
midi.update();
pushbutton.update();
if (pushbutton.getState() == AH::Button::Falling)
midi.sendNoteOn(noteAddress, velocity);
else if (pushbutton.getState() == AH::Button::Rising)
midi.sendNoteOff(noteAddress, velocity);
}
}

View File

@ -0,0 +1,30 @@
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include <cs_midi.h>
using namespace cs;
BluetoothMIDI_Interface midi;
CCPotentiometer potentiometer {26, {MIDI_CC::Channel_Volume, Channel_1}};
constexpr analog_t maxRawValue = CCPotentiometer::getMaxRawValue();
constexpr analog_t minimumValue = maxRawValue / 64;
constexpr analog_t maximumValue = maxRawValue - maxRawValue / 64;
analog_t mappingFunction(analog_t raw) {
if (raw < minimumValue) raw = minimumValue;
if (raw > maximumValue) raw = maximumValue;
return (raw - minimumValue) * maxRawValue / (maximumValue - minimumValue);
}
int main() {
stdio_init_all();
if (cyw43_arch_init()) return 1;
potentiometer.map(mappingFunction);
Control_Surface.begin();
while (true) {
Control_Surface.loop();
sleep_ms(1);
}
}

View File

@ -0,0 +1,32 @@
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include <cs_midi.h>
using namespace cs;
BluetoothMIDI_Interface midi;
NoteButton button1 {5, {MIDI_Notes::C[4], Channel_1}};
NoteButton button2 {6, {MIDI_Notes::D[4], Channel_1}};
AH::Button toggleBtn {7};
bool button2Enabled = true;
int main() {
stdio_init_all();
if (cyw43_arch_init()) return 1;
toggleBtn.begin();
Control_Surface.begin();
while (true) {
Control_Surface.loop();
toggleBtn.update();
if (toggleBtn.getState() == AH::Button::Falling) {
button2Enabled = !button2Enabled;
if (button2Enabled)
button2.enable();
else
button2.disable();
}
sleep_ms(1);
}
}

View File

@ -0,0 +1,32 @@
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include <cs_midi.h>
using namespace cs;
BluetoothMIDI_Interface midi;
// 4 banks, 2 tracks per bank
Bank<4> bank(2);
// Two buttons to cycle banks
IncrementDecrementSelector<4> bankSelector {bank, {2, 3}, Wrap::Wrap};
// Bankable CC value readers — bank switches the CC address
Bankable::CCValue<4> volume1 {{bank, BankType::ChangeAddress}, {16, Channel_1}};
Bankable::CCValue<4> volume2 {{bank, BankType::ChangeAddress}, {17, Channel_1}};
// Non-bankable output elements
NoteButton button {5, {MIDI_Notes::C[4], Channel_1}};
CCPotentiometer pot {26, {MIDI_CC::Channel_Volume, Channel_1}};
CCRotaryEncoder enc {{0, 1}, {MIDI_CC::Pan, Channel_1}, 1, 4};
int main() {
stdio_init_all();
if (cyw43_arch_init()) return 1;
Control_Surface.begin();
while (true) {
Control_Surface.loop();
sleep_ms(1);
}
}