diff --git a/src/ble/ble_remote_client.cpp b/src/ble/ble_remote_client.cpp index a064d03..ad56ec5 100644 --- a/src/ble/ble_remote_client.cpp +++ b/src/ble/ble_remote_client.cpp @@ -97,6 +97,8 @@ static void on_ctl_notify(NimBLERemoteCharacteristic* pChar, uint8_t* pData, siz } } +static uint8_t s_last_hogp_key = 0; + // HOGP HID Report Notification Callback static void on_hogp_report_notify(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) { if (length < 1) return; @@ -110,11 +112,53 @@ static void on_hogp_report_notify(NimBLERemoteCharacteristic* pChar, uint8_t* pD } app_log("HOGP_RAW", "Report (len %d): %s", (int)length, hex_str.c_str()); - uint8_t raw_key = pData[0]; - bool is_pressed = (length > 1) ? (pData[1] != 0) : (raw_key != 0); + uint8_t raw_key = 0; + bool is_pressed = false; - app_log("HOGP", "Key event: 0x%02X (%s)", raw_key, is_pressed ? "DOWN" : "UP"); - key_engine_feed_key(&g_key_engine, raw_key, is_pressed, millis()); + if (length >= 3) { + if (pData[2] != 0) { + raw_key = pData[2]; + is_pressed = true; + s_last_hogp_key = raw_key; + } else if (pData[0] != 0) { + raw_key = pData[0]; + is_pressed = true; + s_last_hogp_key = raw_key; + } else { + raw_key = s_last_hogp_key; + is_pressed = false; + s_last_hogp_key = 0; + } + } else if (length == 2) { + if (pData[1] != 0) { + raw_key = pData[1]; + is_pressed = true; + s_last_hogp_key = raw_key; + } else if (pData[0] != 0) { + raw_key = pData[0]; + is_pressed = true; + s_last_hogp_key = raw_key; + } else { + raw_key = s_last_hogp_key; + is_pressed = false; + s_last_hogp_key = 0; + } + } else { + if (pData[0] != 0) { + raw_key = pData[0]; + is_pressed = true; + s_last_hogp_key = raw_key; + } else { + raw_key = s_last_hogp_key; + is_pressed = false; + s_last_hogp_key = 0; + } + } + + if (raw_key != 0) { + app_log("HOGP", "Key event: 0x%02X (%s)", raw_key, is_pressed ? "DOWN" : "UP"); + key_engine_feed_key(&g_key_engine, raw_key, is_pressed, millis()); + } } static bool is_target_remote(NimBLEAdvertisedDevice* dev) { diff --git a/src/log/app_log.cpp b/src/log/app_log.cpp index 8c54e6a..4ee2aee 100644 --- a/src/log/app_log.cpp +++ b/src/log/app_log.cpp @@ -4,7 +4,10 @@ #include #include -static char s_log_lines[MAX_LOG_LINES][MAX_LOG_LINE_LEN]; +#define STATIC_LOG_LINES 250 +#define LOG_LINE_MAX_LEN 160 + +static char s_log_lines[STATIC_LOG_LINES][LOG_LINE_MAX_LEN]; static size_t s_log_head = 0; static size_t s_log_count = 0; static portMUX_TYPE s_log_mux = portMUX_INITIALIZER_UNLOCKED; @@ -26,18 +29,20 @@ void app_log(const char* tag, const char* format, ...) { uint32_t sec = now_ms / 1000; uint32_t ms = now_ms % 1000; - char full_line[MAX_LOG_LINE_LEN]; + char full_line[LOG_LINE_MAX_LEN]; snprintf(full_line, sizeof(full_line), "[%04u.%03u] [%s] %s", (unsigned int)sec, (unsigned int)ms, tag, msg_buf); - // 1. Output to Serial - Serial.println(full_line); + // 1. Output to Serial safely (if USB CDC is ready) + if (Serial) { + Serial.println(full_line); + } - // 2. Store to circular buffer + // 2. Store to circular buffer with spinlock protection taskENTER_CRITICAL(&s_log_mux); - strncpy(s_log_lines[s_log_head], full_line, MAX_LOG_LINE_LEN - 1); - s_log_lines[s_log_head][MAX_LOG_LINE_LEN - 1] = '\0'; - s_log_head = (s_log_head + 1) % MAX_LOG_LINES; - if (s_log_count < MAX_LOG_LINES) { + strncpy(s_log_lines[s_log_head], full_line, LOG_LINE_MAX_LEN - 1); + s_log_lines[s_log_head][LOG_LINE_MAX_LEN - 1] = '\0'; + s_log_head = (s_log_head + 1) % STATIC_LOG_LINES; + if (s_log_count < STATIC_LOG_LINES) { s_log_count++; } taskEXIT_CRITICAL(&s_log_mux); @@ -48,9 +53,9 @@ String app_log_get_json(void) { JsonArray arr = doc["logs"].to(); taskENTER_CRITICAL(&s_log_mux); - size_t start_idx = (s_log_count < MAX_LOG_LINES) ? 0 : s_log_head; + size_t start_idx = (s_log_count < STATIC_LOG_LINES) ? 0 : s_log_head; for (size_t i = 0; i < s_log_count; i++) { - size_t idx = (start_idx + i) % MAX_LOG_LINES; + size_t idx = (start_idx + i) % STATIC_LOG_LINES; arr.add(s_log_lines[idx]); } taskEXIT_CRITICAL(&s_log_mux); diff --git a/src/log/app_log.h b/src/log/app_log.h index 3344f87..6b51f86 100644 --- a/src/log/app_log.h +++ b/src/log/app_log.h @@ -4,9 +4,6 @@ #include #include -#define MAX_LOG_LINES 60 -#define MAX_LOG_LINE_LEN 160 - #ifdef __cplusplus extern "C" { #endif diff --git a/src/main.cpp b/src/main.cpp index d538d5c..64eb9d3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,13 +24,14 @@ static void ble_task_core0(void* param) { } void setup() { - // 1. Initialize USB Composite Stack (UAC Mic + HID Keyboard + Consumer + CDC) + // 1. Initialize Log System first + app_log_init(); + + // 2. Initialize USB Composite Stack (UAC Mic + HID Keyboard + Consumer + CDC) usb_composite_init(); Serial.begin(115200); - delay(500); + delay(200); - // 2. Initialize Global Log System - app_log_init(); app_log("SYSTEM", "=================================================="); app_log("SYSTEM", " %s v%s (%s)", FIRMWARE_NAME, FIRMWARE_VERSION, HARDWARE_TARGET); app_log("SYSTEM", " Xiaomi Remote Hardware Bridge (BLE -> USB + Web)"); diff --git a/src/usb/usb_composite.cpp b/src/usb/usb_composite.cpp index aebd02b..27b94df 100644 --- a/src/usb/usb_composite.cpp +++ b/src/usb/usb_composite.cpp @@ -1,5 +1,6 @@ #include "usb_composite.h" #include "audio/audio_pipeline.h" +#include "log/app_log.h" #include #include "USB.h" #include "USBHIDKeyboard.h" @@ -12,10 +13,6 @@ static bool s_usb_ready = false; extern "C" { void usb_composite_init(void) { - USB.productName("RemoteMapper Wireless Mic & Controller"); - USB.manufacturerName("RemoteMapper"); - USB.serialNumber("RM-ESP32S3-001"); - s_keyboard.begin(); s_consumer.begin(); USB.begin(); @@ -45,7 +42,7 @@ bool usb_hid_keyboard_release(void) { bool usb_hid_keyboard_tap(uint8_t modifier, uint8_t keycode) { if (!s_usb_ready) return false; usb_hid_keyboard_press(modifier, keycode); - delay(12); + delay(15); usb_hid_keyboard_release(); return true; } @@ -65,7 +62,7 @@ bool usb_hid_consumer_release(void) { bool usb_hid_consumer_tap(uint16_t usage_code) { if (!s_usb_ready) return false; s_consumer.press(usage_code); - delay(10); + delay(15); s_consumer.release(); return true; } @@ -73,6 +70,9 @@ bool usb_hid_consumer_tap(uint16_t usage_code) { void usb_hid_dispatch_action(const key_action_t *action) { if (!action) return; + app_log("USB_HID", "Emit Action: type=%d, mod=0x%02X, key=0x%02X, cons=0x%04X", + action->type, action->modifier, action->key_code, action->consumer_code); + switch (action->type) { case ACTION_KEYBOARD_TAP: usb_hid_keyboard_tap(action->modifier, action->key_code); diff --git a/tools/auto_flash.py b/tools/auto_flash.py new file mode 100644 index 0000000..057e661 --- /dev/null +++ b/tools/auto_flash.py @@ -0,0 +1,82 @@ +import serial +import serial.tools.list_ports +import subprocess +import time +import sys + +def get_esp_ports(): + ports = [] + for p in serial.tools.list_ports.comports(): + if "303A" in p.hwid.upper(): + ports.append(p.device) + return ports + +def auto_upload(): + ports = get_esp_ports() + if not ports: + print("No ESP32-S3 port found!") + sys.exit(1) + + port = ports[0] + print(f"Detected ESP32 port: {port}") + + # Trigger 1200bps DTR reset to enter bootloader + print("Triggering 1200bps CDC bootloader entry...") + try: + s = serial.Serial(port, 1200) + s.dtr = False + s.rts = True + time.sleep(0.1) + s.close() + except Exception as e: + pass + + # Wait up to 3 seconds for bootloader port to appear + boot_port = None + for _ in range(15): + time.sleep(0.2) + cur = get_esp_ports() + if cur: + boot_port = cur[0] + break + + if not boot_port: + boot_port = port + + print(f"Targeting bootloader port: {boot_port}") + + esptool_path = r"C:\Users\cuicuiV5\.platformio\packages\tool-esptoolpy\esptool.py" + bootloader = r".pio\build\esp32s3_n16r8\bootloader.bin" + partitions = r".pio\build\esp32s3_n16r8\partitions.bin" + boot_app0 = r"C:\Users\cuicuiV5\.platformio\packages\framework-arduinoespressif32\tools\partitions\boot_app0.bin" + firmware = r".pio\build\esp32s3_n16r8\firmware.bin" + + # Crucial: app0 offset is 0x20000 as defined in default_16MB.csv! + cmd = [ + sys.executable, esptool_path, + "--chip", "esp32s3", + "--port", boot_port, + "--baud", "460800", + "--before", "default_reset", + "--after", "hard_reset", + "write_flash", "-z", + "--flash_mode", "dio", + "--flash_freq", "80m", + "--flash_size", "16MB", + "0x0", bootloader, + "0x8000", partitions, + "0xe000", boot_app0, + "0x20000", firmware + ] + + print(f"Flashing firmware to {boot_port} (App offset: 0x20000, Mode: dio)...") + res = subprocess.run(cmd) + if res.returncode == 0: + print("\n==========================================") + print(" FLASH SUCCESSFUL! (App written to 0x20000)") + print("==========================================\n") + else: + sys.exit(res.returncode) + +if __name__ == "__main__": + auto_upload()