Roll back to 2d3a5a9 + CONFIG_BT_ENABLED + double-init patch
This commit is contained in:
@@ -20,21 +20,12 @@ public:
|
||||
|
||||
bool is_connected();
|
||||
|
||||
void queue_cc(uint8_t channel, uint8_t cc, uint8_t value);
|
||||
void send_midi_packet(const uint8_t* data, uint8_t len);
|
||||
void on_receive(const uint8_t* data, size_t len);
|
||||
size_t parse_ble_midi(uint8_t status, const uint8_t* data, size_t len, size_t offset, MidiEvent& event);
|
||||
|
||||
private:
|
||||
static void on_control_change(uint8_t channel, uint8_t controller, uint8_t value, uint16_t timestamp);
|
||||
static void on_note_on(uint8_t channel, uint8_t note, uint8_t velocity, uint16_t timestamp);
|
||||
static void on_note_off(uint8_t channel, uint8_t note, uint8_t velocity, uint16_t timestamp);
|
||||
static void on_connect();
|
||||
static void on_disconnect();
|
||||
|
||||
std::function<void(const MidiEvent&)> receive_callback;
|
||||
bool initialized;
|
||||
bool client_connected;
|
||||
|
||||
uint8_t queued_channel;
|
||||
uint8_t queued_cc;
|
||||
uint8_t queued_value;
|
||||
bool queued;
|
||||
};
|
||||
|
||||
+2
-2
@@ -8,8 +8,7 @@ framework = arduino
|
||||
lib_deps =
|
||||
adafruit/Adafruit TinyUSB [email protected]
|
||||
fastled/FastLED@^3.9.0
|
||||
max22/ESP32-BLE-MIDI@^0.3.2
|
||||
h2zero/NimBLE-Arduino@^1.4.3
|
||||
h2zero/NimBLE-Arduino@^1.4.0
|
||||
|
||||
build_unflags =
|
||||
-DARDUINO_USB_MODE=1
|
||||
@@ -19,6 +18,7 @@ build_flags =
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DUSE_TINYUSB=1
|
||||
-Wno-macro-redefined
|
||||
-DMYNEWT_VAL_BLE_EXT_ADV=0
|
||||
|
||||
monitor_speed = 115200
|
||||
|
||||
|
||||
+21
-21
@@ -184,29 +184,29 @@ def patch_nimble_device():
|
||||
with open(dev_path, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# v1.4.3 code has ESP_ERROR_CHECK(esp_bt_controller_init(&bt_cfg));
|
||||
# Arduino framework inits controller but does NOT enable it.
|
||||
# We must enable even if init returns INVALID_STATE (already init'd).
|
||||
old_block = (
|
||||
'ESP_ERROR_CHECK(esp_bt_controller_init(&bt_cfg));\n'
|
||||
' ESP_ERROR_CHECK(esp_bt_controller_enable(ESP_BT_MODE_BLE));\n'
|
||||
' ESP_ERROR_CHECK(esp_nimble_hci_init());'
|
||||
)
|
||||
new_block = (
|
||||
'esp_err_t __bt_err = esp_bt_controller_init(&bt_cfg);\n'
|
||||
' if (__bt_err == ESP_OK || __bt_err == ESP_ERR_INVALID_STATE) {\n'
|
||||
' ESP_ERROR_CHECK(esp_bt_controller_enable(ESP_BT_MODE_BLE));\n'
|
||||
' }\n'
|
||||
' ESP_ERROR_CHECK(esp_nimble_hci_init());'
|
||||
)
|
||||
# Handle both init outcomes: skip if already initialized, but always try to enable.
|
||||
patterns = [
|
||||
(r'(\s*)ESP_ERROR_CHECK\(esp_bt_controller_init\(&bt_cfg\)\);\s*'
|
||||
r'\1ESP_ERROR_CHECK\(esp_bt_controller_enable\(ESP_BT_MODE_BLE\)\);\s*'
|
||||
r'\1ESP_ERROR_CHECK\(esp_nimble_hci_init\(\)\);',
|
||||
r'\1esp_err_t __bt_err = esp_bt_controller_init(&bt_cfg);\n'
|
||||
r'\1if (__bt_err == ESP_OK || __bt_err == ESP_ERR_INVALID_STATE) {\n'
|
||||
r'\1 ESP_ERROR_CHECK(esp_bt_controller_enable(ESP_BT_MODE_BLE));\n'
|
||||
r'\1}\n'
|
||||
r'\1ESP_ERROR_CHECK(esp_nimble_hci_init());')
|
||||
]
|
||||
|
||||
if old_block in content:
|
||||
content = content.replace(old_block, new_block)
|
||||
with open(dev_path, 'w') as f:
|
||||
f.write(content)
|
||||
print(" Patched esp_bt_controller_init to skip if already initialized")
|
||||
else:
|
||||
print(" WARNING: Could not find init pattern in NimBLEDevice.cpp (may already be patched)")
|
||||
for pattern, replacement in patterns:
|
||||
new_content = re.sub(pattern, replacement, content)
|
||||
if new_content != content:
|
||||
content = new_content
|
||||
with open(dev_path, 'w') as f:
|
||||
f.write(content)
|
||||
print(" Patched esp_bt_controller_init to skip if already initialized")
|
||||
return
|
||||
|
||||
print(" WARNING: Could not find init pattern in NimBLEDevice.cpp")
|
||||
|
||||
|
||||
patch_usb_ids()
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ void AppTask::update() {
|
||||
pending_cc = false;
|
||||
delay(1);
|
||||
usb_midi->send_cc(pending_channel, pending_cc_num, pending_value);
|
||||
if (ble_midi) ble_midi->queue_cc(pending_channel, pending_cc_num, pending_value);
|
||||
if (ble_midi) ble_midi->send_cc(pending_channel, pending_cc_num, pending_value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+189
-66
@@ -1,10 +1,39 @@
|
||||
#include "ble_midi_transport.h"
|
||||
#include <Arduino.h>
|
||||
#include <BLEMidi.h>
|
||||
#include <NimBLEDevice.h>
|
||||
|
||||
#define BLE_MIDI_SERVICE_UUID "03B80E5A-EDE8-4B33-A751-6CE34EC4C700"
|
||||
#define BLE_MIDI_CHAR_UUID "7772E5DB-3868-4112-A1A9-F2669D106BF3"
|
||||
|
||||
static BleMidiTransport* instance = nullptr;
|
||||
|
||||
BleMidiTransport::BleMidiTransport() : initialized(false), client_connected(false), queued(false) {
|
||||
static NimBLEServer* ble_server = nullptr;
|
||||
static NimBLEService* ble_service = nullptr;
|
||||
static NimBLECharacteristic* ble_char = nullptr;
|
||||
static bool device_connected = false;
|
||||
|
||||
class ServerCallbacks : public NimBLEServerCallbacks {
|
||||
void onConnect(NimBLEServer* server) override {
|
||||
device_connected = true;
|
||||
Serial.println("[BLE] Client connected");
|
||||
}
|
||||
void onDisconnect(NimBLEServer* server) override {
|
||||
device_connected = false;
|
||||
Serial.println("[BLE] Client disconnected, restarting advertising");
|
||||
NimBLEDevice::startAdvertising();
|
||||
}
|
||||
};
|
||||
|
||||
class CharCallbacks : public NimBLECharacteristicCallbacks {
|
||||
void onWrite(NimBLECharacteristic* characteristic) override {
|
||||
std::string value = characteristic->getValue();
|
||||
if (value.length() > 0 && instance) {
|
||||
instance->on_receive((const uint8_t*)value.data(), value.length());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BleMidiTransport::BleMidiTransport() : initialized(false), client_connected(false) {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
@@ -12,94 +41,188 @@ BleMidiTransport::~BleMidiTransport() {
|
||||
if (instance == this) instance = nullptr;
|
||||
}
|
||||
|
||||
void BleMidiTransport::on_connect() {
|
||||
Serial.println("[BLE] Client connected");
|
||||
if (instance) instance->client_connected = true;
|
||||
}
|
||||
|
||||
void BleMidiTransport::on_disconnect() {
|
||||
Serial.println("[BLE] Client disconnected");
|
||||
if (instance) instance->client_connected = false;
|
||||
}
|
||||
|
||||
void BleMidiTransport::on_control_change(uint8_t channel, uint8_t controller, uint8_t value, uint16_t timestamp) {
|
||||
if (!instance || !instance->receive_callback) return;
|
||||
MidiEvent event;
|
||||
event.type = MidiEvent::CONTROL_CHANGE;
|
||||
event.channel = channel;
|
||||
event.data1 = controller;
|
||||
event.data2 = value;
|
||||
event.timestamp = timestamp;
|
||||
instance->receive_callback(event);
|
||||
}
|
||||
|
||||
void BleMidiTransport::on_note_on(uint8_t channel, uint8_t note, uint8_t velocity, uint16_t timestamp) {
|
||||
if (!instance || !instance->receive_callback) return;
|
||||
MidiEvent event;
|
||||
event.type = MidiEvent::NOTE_ON;
|
||||
event.channel = channel;
|
||||
event.data1 = note;
|
||||
event.data2 = velocity;
|
||||
event.timestamp = timestamp;
|
||||
instance->receive_callback(event);
|
||||
}
|
||||
|
||||
void BleMidiTransport::on_note_off(uint8_t channel, uint8_t note, uint8_t velocity, uint16_t timestamp) {
|
||||
if (!instance || !instance->receive_callback) return;
|
||||
MidiEvent event;
|
||||
event.type = MidiEvent::NOTE_OFF;
|
||||
event.channel = channel;
|
||||
event.data1 = note;
|
||||
event.data2 = velocity;
|
||||
event.timestamp = timestamp;
|
||||
instance->receive_callback(event);
|
||||
}
|
||||
|
||||
bool BleMidiTransport::begin() {
|
||||
Serial.println("[BLE] Initializing BLE MIDI...");
|
||||
BLEMidiServer.begin("JOC Midi");
|
||||
BLEMidiServer.setOnConnectCallback(on_connect);
|
||||
BLEMidiServer.setOnDisconnectCallback(on_disconnect);
|
||||
BLEMidiServer.setControlChangeCallback(on_control_change);
|
||||
BLEMidiServer.setNoteOnCallback(on_note_on);
|
||||
BLEMidiServer.setNoteOffCallback(on_note_off);
|
||||
Serial.println("[BLE] BLE MIDI advertising as 'JOC Midi'");
|
||||
|
||||
// NimBLE-Arduino v1.4.0+ handles ESP32-S3 internally, using
|
||||
// bt_cfg.bluetooth_mode = ESP_BT_MODE_BLE. Do NOT pre-init the
|
||||
// BT controller here or NimBLEDevice::init() will see an
|
||||
// error from double-init and abort via ESP_ERROR_CHECK.
|
||||
Serial.println("[BLE] NimBLEDevice init...");
|
||||
NimBLEDevice::init("JOC Midi");
|
||||
Serial.println("[BLE] NimBLEDevice initialized");
|
||||
|
||||
Serial.println("[BLE] Creating server...");
|
||||
ble_server = NimBLEDevice::createServer();
|
||||
if (!ble_server) {
|
||||
Serial.println("[BLE] FAILED to create server");
|
||||
return false;
|
||||
}
|
||||
Serial.println("[BLE] Server created, setting callbacks...");
|
||||
ble_server->setCallbacks(new ServerCallbacks());
|
||||
|
||||
Serial.println("[BLE] Creating service...");
|
||||
ble_service = ble_server->createService(BLE_MIDI_SERVICE_UUID);
|
||||
if (!ble_service) {
|
||||
Serial.println("[BLE] FAILED to create service");
|
||||
return false;
|
||||
}
|
||||
|
||||
Serial.println("[BLE] Creating characteristic...");
|
||||
Serial.flush();
|
||||
ble_char = ble_service->createCharacteristic(
|
||||
BLE_MIDI_CHAR_UUID,
|
||||
NIMBLE_PROPERTY::READ |
|
||||
NIMBLE_PROPERTY::WRITE_NR |
|
||||
NIMBLE_PROPERTY::NOTIFY
|
||||
);
|
||||
Serial.println("[BLE] characteristic pointer ok");
|
||||
if (!ble_char) {
|
||||
Serial.println("[BLE] FAILED to create characteristic");
|
||||
return false;
|
||||
}
|
||||
// 0x2902 (CCCD) is auto-created by NimBLE stack when characteristic
|
||||
// has NOTIFY or INDICATE property - do NOT manually create it.
|
||||
Serial.println("[BLE] Setting callbacks...");
|
||||
ble_char->setCallbacks(new CharCallbacks());
|
||||
Serial.println("[BLE] Callbacks set");
|
||||
|
||||
Serial.println("[BLE] Starting service...");
|
||||
ble_service->start();
|
||||
Serial.println("[BLE] Service started");
|
||||
|
||||
Serial.println("[BLE] Starting advertising...");
|
||||
NimBLEAdvertising* adv = NimBLEDevice::getAdvertising();
|
||||
adv->addServiceUUID(BLE_MIDI_SERVICE_UUID);
|
||||
adv->setScanResponse(true);
|
||||
adv->start();
|
||||
|
||||
if (adv->isAdvertising()) {
|
||||
Serial.println("[BLE] Advertising confirmed started");
|
||||
} else {
|
||||
Serial.println("[BLE] WARNING: isAdvertising() reports false!");
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
Serial.println("[BLE] BLE MIDI advertising as 'JOC Midi'");
|
||||
return true;
|
||||
}
|
||||
|
||||
void BleMidiTransport::update() {
|
||||
client_connected = BLEMidiServer.isConnected();
|
||||
|
||||
if (queued) {
|
||||
queued = false;
|
||||
BLEMidiServer.controlChange(queued_channel, queued_cc, queued_value);
|
||||
}
|
||||
client_connected = device_connected;
|
||||
}
|
||||
|
||||
void BleMidiTransport::on_midi_receive(std::function<void(const MidiEvent&)> callback) {
|
||||
receive_callback = callback;
|
||||
}
|
||||
|
||||
void BleMidiTransport::queue_cc(uint8_t channel, uint8_t cc, uint8_t value) {
|
||||
queued_channel = channel;
|
||||
queued_cc = cc;
|
||||
queued_value = value;
|
||||
queued = true;
|
||||
void BleMidiTransport::send_midi_packet(const uint8_t* data, uint8_t len) {
|
||||
if (!initialized || !client_connected || !ble_char) return;
|
||||
|
||||
uint16_t timestamp = micros() & 0x3FFF;
|
||||
uint8_t packet[16];
|
||||
uint8_t idx = 0;
|
||||
packet[idx++] = 0x80 | (timestamp >> 7);
|
||||
packet[idx++] = timestamp & 0x7F;
|
||||
for (uint8_t i = 0; i < len && idx < 16; i++) {
|
||||
packet[idx++] = data[i];
|
||||
}
|
||||
|
||||
ble_char->setValue(packet, idx);
|
||||
ble_char->notify();
|
||||
}
|
||||
|
||||
void BleMidiTransport::send_note_on(uint8_t channel, uint8_t note, uint8_t velocity) {
|
||||
queue_cc(channel, note, velocity);
|
||||
uint8_t packet[3] = {(uint8_t)(0x90 | (channel - 1)), note, velocity};
|
||||
send_midi_packet(packet, 3);
|
||||
Serial.printf("[BLE OUT] Ch:%d NOTE_ON:%d:%d\n", channel, note, velocity);
|
||||
}
|
||||
|
||||
void BleMidiTransport::send_note_off(uint8_t channel, uint8_t note, uint8_t velocity) {
|
||||
queue_cc(channel, note, velocity);
|
||||
uint8_t packet[3] = {(uint8_t)(0x80 | (channel - 1)), note, velocity};
|
||||
send_midi_packet(packet, 3);
|
||||
Serial.printf("[BLE OUT] Ch:%d NOTE_OFF:%d:%d\n", channel, note, velocity);
|
||||
}
|
||||
|
||||
void BleMidiTransport::send_cc(uint8_t channel, uint8_t cc, uint8_t value) {
|
||||
queue_cc(channel, cc, value);
|
||||
uint8_t packet[3] = {(uint8_t)(0xB0 | (channel - 1)), cc, value};
|
||||
send_midi_packet(packet, 3);
|
||||
}
|
||||
|
||||
bool BleMidiTransport::is_connected() {
|
||||
return initialized && client_connected;
|
||||
}
|
||||
|
||||
void BleMidiTransport::on_receive(const uint8_t* data, size_t len) {
|
||||
if (len < 3 || !receive_callback) return; // need timestamp(2) + status(1)
|
||||
|
||||
// BLE MIDI format: [ts_hi|0x80] [ts_lo] [status ...]
|
||||
size_t offset = 2; // skip timestamp header
|
||||
|
||||
while (offset < len) {
|
||||
uint8_t status = data[offset++];
|
||||
if (status < 0x80) continue; // skip non-status bytes
|
||||
|
||||
uint8_t type = status & 0xF0;
|
||||
uint8_t channel = (status & 0x0F) + 1;
|
||||
|
||||
MidiEvent event;
|
||||
event.channel = channel;
|
||||
event.timestamp = millis();
|
||||
event.type = MidiEvent::NOTE_ON; // default
|
||||
|
||||
offset = parse_ble_midi(status, data, len, offset, event);
|
||||
|
||||
if (event.type == MidiEvent::SYSEX) {
|
||||
Serial.printf("[BLE IN] Sysex len=%zu\n", len);
|
||||
} else {
|
||||
Serial.printf("[BLE IN] Ch:%d %s:%d:%d\n",
|
||||
event.channel,
|
||||
event.type == MidiEvent::NOTE_ON ? "NOTE_ON" :
|
||||
event.type == MidiEvent::NOTE_OFF ? "NOTE_OFF" :
|
||||
event.type == MidiEvent::CONTROL_CHANGE ? "CC" : "OTHER",
|
||||
event.data1, event.data2);
|
||||
}
|
||||
|
||||
if (receive_callback) {
|
||||
receive_callback(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t BleMidiTransport::parse_ble_midi(uint8_t status, const uint8_t* data, size_t len, size_t offset, MidiEvent& event) {
|
||||
uint8_t type = status & 0xF0;
|
||||
event.data1 = 0;
|
||||
event.data2 = 0;
|
||||
|
||||
if (offset >= len) return offset;
|
||||
|
||||
event.data1 = data[offset++];
|
||||
|
||||
switch (type) {
|
||||
case 0x80:
|
||||
event.type = MidiEvent::NOTE_OFF;
|
||||
if (offset < len) event.data2 = data[offset++];
|
||||
break;
|
||||
case 0x90:
|
||||
event.type = MidiEvent::NOTE_ON;
|
||||
if (offset < len) event.data2 = data[offset++];
|
||||
if (event.data2 == 0) event.type = MidiEvent::NOTE_OFF;
|
||||
break;
|
||||
case 0xB0:
|
||||
event.type = MidiEvent::CONTROL_CHANGE;
|
||||
if (offset < len) event.data2 = data[offset++];
|
||||
break;
|
||||
case 0xC0:
|
||||
event.type = MidiEvent::PROGRAM_CHANGE;
|
||||
break;
|
||||
case 0xE0:
|
||||
event.type = MidiEvent::PITCH_BEND;
|
||||
if (offset < len) event.data2 = data[offset++];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
+5
-19
@@ -23,20 +23,12 @@ AppTask controller(&led_driver, &switch_driver, &midi_transport, &ble_midi_trans
|
||||
|
||||
TaskHandle_t midi_task_handle = NULL;
|
||||
|
||||
void ble_init_task(void* parameter) {
|
||||
Serial.println("[TASK] BLE init task started on core 0");
|
||||
ble_midi_transport.begin();
|
||||
Serial.println("[TASK] BLE init complete, deleting task");
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void midi_task(void* parameter) {
|
||||
Serial.println("[TASK] MIDI task started on core 0");
|
||||
|
||||
while (true) {
|
||||
midi_transport.update();
|
||||
ble_midi_transport.update();
|
||||
controller.update();
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
@@ -354,6 +346,10 @@ void setup() {
|
||||
switch_driver.set_mux(&mux);
|
||||
switch_driver.begin();
|
||||
|
||||
Serial.println("[INIT] Initializing BLE MIDI...");
|
||||
delay(1000);
|
||||
ble_midi_transport.begin();
|
||||
|
||||
Serial.println("[INIT] Initializing USB MIDI...");
|
||||
midi_transport.begin();
|
||||
delay(500);
|
||||
@@ -361,17 +357,6 @@ void setup() {
|
||||
Serial.println("[INIT] Registering MIDI callbacks...");
|
||||
controller.begin();
|
||||
|
||||
Serial.println("[INIT] Starting BLE init on core 0...");
|
||||
xTaskCreatePinnedToCore(
|
||||
ble_init_task,
|
||||
"ble_init",
|
||||
8192,
|
||||
NULL,
|
||||
3,
|
||||
NULL,
|
||||
0
|
||||
);
|
||||
|
||||
xTaskCreatePinnedToCore(
|
||||
midi_task,
|
||||
"midi_task",
|
||||
@@ -391,6 +376,7 @@ void setup() {
|
||||
|
||||
void loop() {
|
||||
led_driver.update();
|
||||
controller.update();
|
||||
|
||||
if (Serial.available()) {
|
||||
String cmd = Serial.readStringUntil('\n');
|
||||
|
||||
Reference in New Issue
Block a user