Roll back to 2d3a5a9 + CONFIG_BT_ENABLED + double-init patch

This commit is contained in:
ash
2026-07-01 07:06:45 +00:00
parent 01eaf9ce80
commit 9ba48b3f9c
6 changed files with 221 additions and 121 deletions
+3 -12
View File
@@ -20,21 +20,12 @@ public:
bool is_connected(); 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: 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; std::function<void(const MidiEvent&)> receive_callback;
bool initialized; bool initialized;
bool client_connected; bool client_connected;
uint8_t queued_channel;
uint8_t queued_cc;
uint8_t queued_value;
bool queued;
}; };
+2 -2
View File
@@ -8,8 +8,7 @@ framework = arduino
lib_deps = lib_deps =
adafruit/Adafruit TinyUSB [email protected] adafruit/Adafruit TinyUSB [email protected]
fastled/FastLED@^3.9.0 fastled/FastLED@^3.9.0
max22/ESP32-BLE-MIDI@^0.3.2 h2zero/NimBLE-Arduino@^1.4.0
h2zero/NimBLE-Arduino@^1.4.3
build_unflags = build_unflags =
-DARDUINO_USB_MODE=1 -DARDUINO_USB_MODE=1
@@ -19,6 +18,7 @@ build_flags =
-DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_CDC_ON_BOOT=1
-DUSE_TINYUSB=1 -DUSE_TINYUSB=1
-Wno-macro-redefined -Wno-macro-redefined
-DMYNEWT_VAL_BLE_EXT_ADV=0
monitor_speed = 115200 monitor_speed = 115200
+21 -21
View File
@@ -184,29 +184,29 @@ def patch_nimble_device():
with open(dev_path, 'r') as f: with open(dev_path, 'r') as f:
content = f.read() 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. # Arduino framework inits controller but does NOT enable it.
# We must enable even if init returns INVALID_STATE (already init'd). # Handle both init outcomes: skip if already initialized, but always try to enable.
old_block = ( patterns = [
'ESP_ERROR_CHECK(esp_bt_controller_init(&bt_cfg));\n' (r'(\s*)ESP_ERROR_CHECK\(esp_bt_controller_init\(&bt_cfg\)\);\s*'
' ESP_ERROR_CHECK(esp_bt_controller_enable(ESP_BT_MODE_BLE));\n' r'\1ESP_ERROR_CHECK\(esp_bt_controller_enable\(ESP_BT_MODE_BLE\)\);\s*'
' ESP_ERROR_CHECK(esp_nimble_hci_init());' r'\1ESP_ERROR_CHECK\(esp_nimble_hci_init\(\)\);',
) r'\1esp_err_t __bt_err = esp_bt_controller_init(&bt_cfg);\n'
new_block = ( r'\1if (__bt_err == ESP_OK || __bt_err == ESP_ERR_INVALID_STATE) {\n'
'esp_err_t __bt_err = esp_bt_controller_init(&bt_cfg);\n' r'\1 ESP_ERROR_CHECK(esp_bt_controller_enable(ESP_BT_MODE_BLE));\n'
' if (__bt_err == ESP_OK || __bt_err == ESP_ERR_INVALID_STATE) {\n' r'\1}\n'
' ESP_ERROR_CHECK(esp_bt_controller_enable(ESP_BT_MODE_BLE));\n' r'\1ESP_ERROR_CHECK(esp_nimble_hci_init());')
' }\n' ]
' ESP_ERROR_CHECK(esp_nimble_hci_init());'
)
if old_block in content: for pattern, replacement in patterns:
content = content.replace(old_block, new_block) new_content = re.sub(pattern, replacement, content)
with open(dev_path, 'w') as f: if new_content != content:
f.write(content) content = new_content
print(" Patched esp_bt_controller_init to skip if already initialized") with open(dev_path, 'w') as f:
else: f.write(content)
print(" WARNING: Could not find init pattern in NimBLEDevice.cpp (may already be patched)") 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() patch_usb_ids()
+1 -1
View File
@@ -54,7 +54,7 @@ void AppTask::update() {
pending_cc = false; pending_cc = false;
delay(1); delay(1);
usb_midi->send_cc(pending_channel, pending_cc_num, pending_value); 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
View File
@@ -1,10 +1,39 @@
#include "ble_midi_transport.h" #include "ble_midi_transport.h"
#include <Arduino.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; 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; instance = this;
} }
@@ -12,94 +41,188 @@ BleMidiTransport::~BleMidiTransport() {
if (instance == this) instance = nullptr; 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() { bool BleMidiTransport::begin() {
Serial.println("[BLE] Initializing BLE MIDI..."); Serial.println("[BLE] Initializing BLE MIDI...");
BLEMidiServer.begin("JOC Midi");
BLEMidiServer.setOnConnectCallback(on_connect); // NimBLE-Arduino v1.4.0+ handles ESP32-S3 internally, using
BLEMidiServer.setOnDisconnectCallback(on_disconnect); // bt_cfg.bluetooth_mode = ESP_BT_MODE_BLE. Do NOT pre-init the
BLEMidiServer.setControlChangeCallback(on_control_change); // BT controller here or NimBLEDevice::init() will see an
BLEMidiServer.setNoteOnCallback(on_note_on); // error from double-init and abort via ESP_ERROR_CHECK.
BLEMidiServer.setNoteOffCallback(on_note_off); Serial.println("[BLE] NimBLEDevice init...");
Serial.println("[BLE] BLE MIDI advertising as 'JOC Midi'"); 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; initialized = true;
Serial.println("[BLE] BLE MIDI advertising as 'JOC Midi'");
return true; return true;
} }
void BleMidiTransport::update() { void BleMidiTransport::update() {
client_connected = BLEMidiServer.isConnected(); client_connected = device_connected;
if (queued) {
queued = false;
BLEMidiServer.controlChange(queued_channel, queued_cc, queued_value);
}
} }
void BleMidiTransport::on_midi_receive(std::function<void(const MidiEvent&)> callback) { void BleMidiTransport::on_midi_receive(std::function<void(const MidiEvent&)> callback) {
receive_callback = callback; receive_callback = callback;
} }
void BleMidiTransport::queue_cc(uint8_t channel, uint8_t cc, uint8_t value) { void BleMidiTransport::send_midi_packet(const uint8_t* data, uint8_t len) {
queued_channel = channel; if (!initialized || !client_connected || !ble_char) return;
queued_cc = cc;
queued_value = value; uint16_t timestamp = micros() & 0x3FFF;
queued = true; 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) { 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) { 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) { 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() { bool BleMidiTransport::is_connected() {
return initialized && client_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
View File
@@ -23,20 +23,12 @@ AppTask controller(&led_driver, &switch_driver, &midi_transport, &ble_midi_trans
TaskHandle_t midi_task_handle = NULL; 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) { void midi_task(void* parameter) {
Serial.println("[TASK] MIDI task started on core 0"); Serial.println("[TASK] MIDI task started on core 0");
while (true) { while (true) {
midi_transport.update(); midi_transport.update();
ble_midi_transport.update(); ble_midi_transport.update();
controller.update();
vTaskDelay(1); vTaskDelay(1);
} }
} }
@@ -354,23 +346,16 @@ void setup() {
switch_driver.set_mux(&mux); switch_driver.set_mux(&mux);
switch_driver.begin(); switch_driver.begin();
Serial.println("[INIT] Initializing BLE MIDI...");
delay(1000);
ble_midi_transport.begin();
Serial.println("[INIT] Initializing USB MIDI..."); Serial.println("[INIT] Initializing USB MIDI...");
midi_transport.begin(); midi_transport.begin();
delay(500); delay(500);
Serial.println("[INIT] Registering MIDI callbacks..."); Serial.println("[INIT] Registering MIDI callbacks...");
controller.begin(); controller.begin();
Serial.println("[INIT] Starting BLE init on core 0...");
xTaskCreatePinnedToCore(
ble_init_task,
"ble_init",
8192,
NULL,
3,
NULL,
0
);
xTaskCreatePinnedToCore( xTaskCreatePinnedToCore(
midi_task, midi_task,
@@ -391,6 +376,7 @@ void setup() {
void loop() { void loop() {
led_driver.update(); led_driver.update();
controller.update();
if (Serial.available()) { if (Serial.available()) {
String cmd = Serial.readStringUntil('\n'); String cmd = Serial.readStringUntil('\n');