Files
loopy_midi_controller/pre_build.py
T
ash 62d1e430d5 Switch from NimBLE-Arduino to ESP32-BLE-MIDI library
Replace direct NimBLE-Arduino dependency with max22/ESP32-BLE-MIDI
wrapper library (v0.3.2). Rewrite ble_midi_transport to use
BLEMidiServer API instead of raw NimBLEDevice calls.

Removes patch_nimble_device() from pre_build.py — no longer needed
since BT controller init is managed by the library internally.

The library uses NimBLE v1.4.1 internally and configures advertising
without setScanResponse() or setName(), which may resolve the invisible
BLE advertising issue on ESP32-S3.
2026-07-01 06:34:36 +00:00

167 lines
5.9 KiB
Python

import os
import re
def patch_usb_ids():
# Find the core's pins_arduino.h for ESP32-S3 DevKitC-1
core_variants = os.path.expanduser("~/.platformio/packages/framework-arduinoespressif32/variants")
if not os.path.exists(core_variants):
# Try PlatformIO default location
core_variants = os.path.join(os.environ.get("PLATFORMIO_PACKAGES_DIR", ""), "framework-arduinoespressif32", "variants")
pins_file = os.path.join(core_variants, "esp32s3_devkitc", "pins_arduino.h")
if not os.path.exists(pins_file):
# Try alternative variant name
pins_file = os.path.join(core_variants, "esp32s3", "pins_arduino.h")
if os.path.exists(pins_file):
print(f"Patching {pins_file} with JOC Midi USB descriptors")
# Read and replace
with open(pins_file, 'r') as f:
content = f.read()
# Replace USB_VID and USB_PID
content = content.replace(
'#define USB_VID 0x303a',
'#define USB_VID 0x1235'
)
content = content.replace(
'#define USB_PID 0x1001',
'#define USB_PID 0x0103'
)
# Add or replace manufacturer/product
if 'USB_MANUFACTURER' not in content:
content = content.replace(
'#define USB_PID 0x0103',
'#define USB_PID 0x0103\n#define USB_MANUFACTURER "JOC"\n#define USB_PRODUCT "JOC Midi"'
)
else:
content = content.replace('USB_MANUFACTURER "Novation"', 'USB_MANUFACTURER "JOC"')
content = content.replace('USB_PRODUCT "Launchpad X"', 'USB_PRODUCT "JOC Midi"')
with open(pins_file, 'w') as f:
f.write(content)
print("USB VID/PID patched successfully")
else:
print(f"WARNING: Could not find pins_arduino.h at {pins_file}")
def patch_sdkconfig_bt():
# Find the precompiled sdkconfig.h for this board variant
sdk_dir = os.path.expanduser(
"~/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32s3/qio_opi/include"
)
alt_name = os.path.join(os.environ.get("PLATFORMIO_PACKAGES_DIR", ""),
"framework-arduinoespressif32/tools/sdk/esp32s3/qio_opi/include")
if not os.path.exists(sdk_dir):
sdk_dir = alt_name
sdkconfig_file = os.path.join(sdk_dir, "sdkconfig.h")
if not os.path.exists(sdkconfig_file):
print(f"WARNING: sdkconfig.h not found at {sdkconfig_file}")
return
print(f"Patching {sdkconfig_file} with BLE/NimBLE support")
with open(sdkconfig_file, 'r') as f:
content = f.read()
defines = {
# Do NOT set CONFIG_BT_ENABLED here — forces Arduino framework
# to pre-init BT controller at startup, which conflicts with
# TinyUSB on ESP32-S3 (causes reboot on USB MIDI send).
# NimBLEDevice::init() handles its own init.
"CONFIG_BTDM_CTRL_MODE_BLE_ONLY": 1,
"CONFIG_BT_NIMBLE_MAX_CONNECTIONS": 1,
"CONFIG_BT_NIMBLE_TASK_STACK_SIZE": 6144,
}
changed = False
for name, value in defines.items():
pattern = re.compile(
r'^[#/]*\s*#?\s*(define\s+' + re.escape(name) + r'\b).*$',
re.MULTILINE
)
if pattern.search(content):
# Already defined, replace the line
content = pattern.sub(r'#define ' + name + ' ' + str(value), content)
changed = True
elif f'#define {name}' not in content:
# Not present at all, add it
content += f'\n#define {name} {value}'
changed = True
if changed:
with open(sdkconfig_file, 'w') as f:
f.write(content)
print("BLE/NimBLE sdkconfig patched successfully")
else:
print("BLE/NimBLE already configured in sdkconfig")
def patch_nimconfig():
# Find nimconfig.h in the NimBLE-Arduino library
search_dirs = [
os.path.join(os.getcwd(), ".pio", "libdeps"),
os.path.expanduser("~/.platformio/lib"),
]
for env_dir in ["esp32s3", ""]:
candidate = os.path.join(
os.getcwd(), ".pio", "libdeps",
env_dir, "NimBLE-Arduino", "src", "nimconfig.h"
) if env_dir else ""
if candidate and os.path.exists(candidate):
nimconfig_path = candidate
break
else:
# Walk search dirs as fallback
nimconfig_path = None
for base in search_dirs:
if not os.path.exists(base):
continue
for root, _dirs, files in os.walk(base):
if "nimconfig.h" in files:
nimconfig_path = os.path.join(root, "nimconfig.h")
break
if nimconfig_path:
break
if not nimconfig_path:
print("WARNING: nimconfig.h not found, skipping ROLE define patch")
return
print(f"Patching {nimconfig_path} with #ifndef guards for ROLE defines")
with open(nimconfig_path, 'r') as f:
content = f.read()
for role in ["CENTRAL", "OBSERVER", "PERIPHERAL", "BROADCASTER"]:
pattern = re.compile(
'#ifndef CONFIG_BT_NIMBLE_ROLE_' + role + '_DISABLED\n'
'#define CONFIG_BT_NIMBLE_ROLE_' + role + '\n'
'#endif'
)
replacement = (
'#ifndef CONFIG_BT_NIMBLE_ROLE_' + role + '_DISABLED\n'
'#ifndef CONFIG_BT_NIMBLE_ROLE_' + role + '\n'
'#define CONFIG_BT_NIMBLE_ROLE_' + role + '\n'
'#endif\n'
'#endif'
)
if pattern.search(content):
content = pattern.sub(replacement, content)
print(f" Guarded CONFIG_BT_NIMBLE_ROLE_{role}")
else:
print(f" NOTE: CONFIG_BT_NIMBLE_ROLE_{role} pattern not found (may already be guarded)")
with open(nimconfig_path, 'w') as f:
f.write(content)
print("nimconfig.h patched successfully")
patch_usb_ids()
patch_sdkconfig_bt()
patch_nimconfig()