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 = { "CONFIG_BT_ENABLED": 1, "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") def patch_nimble_device(): search_dirs = [ os.path.join(os.getcwd(), ".pio", "libdeps"), os.path.expanduser("~/.platformio/lib"), ] dev_path = None for base in search_dirs: if not os.path.exists(base): continue for root, _dirs, files in os.walk(base): if "NimBLEDevice.cpp" in files: dev_path = os.path.join(root, "NimBLEDevice.cpp") break if dev_path: break if not dev_path: print("WARNING: NimBLEDevice.cpp not found, skipping controller init patch") return print(f"Patching {dev_path} to handle double BT controller init") with open(dev_path, 'r') as f: content = f.read() # Arduino framework inits controller but does NOT enable it. # 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());') ] 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() patch_sdkconfig_bt() patch_nimconfig() patch_nimble_device()