櫻白色的喵窩quq

解決 TourBox Lite 國行限定版的驅動問題

quqCreated on
quqDailyquqRustTourBoxWinUSBAI

之前看便宜+好奇買了個 TourBox Lite 國行限定版。然後到手後發現官方驅動不能用!看了一眼介紹,哎呀原來系統語言一定要是簡體中文才能用,那我肯定不能就為了一個tourbox把系統改成簡體吧,不過這難不到我,Locale Emulator完美解決。

但這真的好用嗎,我能不能直接把官方驅動揚了?

0x1 AI

一開始其實是太閒了,最近AI又很火,我就拿Grok和Gemini cli試試,意外地挺好用的做了大部份的東西,比如一開始幾千個wireshark的usb抓包分析就是 grok 干的(我可不想看這堆東西),原型腳本也是 grok 寫的,然後又讓gemini cli生成了gui和channel之類之類的,雖然最後還是嫌 gemini 太蠢了自己修了

0x2 Tourbox 技術分析

其實我並沒有完全搞懂 tourbox 的指令,不過說到底我也不需要搞懂。只要你裝上了官方提到的 usbser,你就可以走 /dev/ttyACM0 或者 COM3 來和你的設備通訊,根據grok的提取和我的分析:

指令類型 (1 byte)未知 (1 byte)長度 (1 byte)內容 (x byte)包尾 (1 byte)
0x550x000x070x88, 0x94, 0x00, 0x1a0xfe
0xd50x000x0f0x8a, 0xd0, 0xd6, 0x60, 0xef, 0x17, 0x11, 0x75, 0x5f, 0x35, 0xb0, 0xd80xfe
0xb50x000x070x04, 0x00, 0x09, 0x000xfe

頭兩個指令個人推測是設備信息,設備收到後會返回一串東西,第三個推測是按鍵的某些模式。

哪怕不發送這些指令,設備天然就會在按鍵時向串口發送指令:

完整按鍵值字典

(其中放開鍵+0x80,反向鍵+0x40)

按鍵
0x00長鍵
0x02橫鍵
0x03短鍵
0x04旋鈕逆時針
0x09向下滾動
0x0A滾動鍵
0x22左鍵
0x23右鍵
0x2ATour鍵
0x37旋鈕按下
0x44旋鈕順時針
0x49向上滾動
0x80長鍵放開
0x82橫鍵放開
0x83短鍵放開
0x8A滾動鍵放開
0xA2左鍵放開
0xA3右鍵放開
0xAATour鍵放開
0xB7旋鈕放開
其中旋鈕和滾動都需要在第三個指令發送過才能用,原理未知。

0x3 軟件實現

有了上面的東西後其實你也可以自己做一個了,這裡給一個 Grok 寫的原型 Python 腳本:

import serial

# 按鍵映射字典(包含所有鍵碼,包括 0x02 和 0x82)
KEY_MAP = {
    0x00: "左功能鍵",
    0x02: "額外功能鍵",
    0x03: "右功能鍵",
    0x04: "旋鈕逆時針",
    0x09: "向下滾動",
    0x0A: "滾動鍵",
    0x22: "左鍵",
    0x23: "右鍵",
    0x2A: "主功能鍵",
    0x37: "旋鈕按下",
    0x44: "旋鈕順時針",
    0x49: "向上滾動",
    # 放開鍵碼(+0x80)
    0x80: "左功能鍵放開",
    0x82: "額外功能鍵放開",
    0x83: "右功能鍵放開",
    0x8A: "滾動鍵放開",
    0xA2: "左鍵放開",
    0xA3: "右鍵放開",
    0xAA: "主功能鍵放開",
    0xB7: "旋鈕放開",
}

def initialize_serial_device(port):
    try:
        ser = serial.Serial(
            port=port,
            baudrate=115200,
            bytesize=serial.EIGHTBITS,
            parity=serial.PARITY_NONE,
            stopbits=serial.STOPBITS_ONE,
            timeout=1,
        )
        ser.baudrate = 115200
        ser.bytesize = serial.EIGHTBITS
        ser.parity = serial.PARITY_NONE
        ser.stopbits = serial.STOPBITS_ONE

        ser.dtr = False
        ser.rts = False

        init_command = bytearray(
            [0xB5, 0x00, 0x07, 0x04, 0x00, 0x09, 0x00] + [0xFE]
        )

        ser.write(init_command)
        if ser.in_waiting > 0:  # 檢查是否有數據
            data = ser.read(ser.in_waiting)  # 讀取所有可用數據
            print(
                "接收到數據:", "".join("{:02x}".format(x) for x in list(data))
            )  # 以 UTF-8 解碼顯示

        # 清空緩衝區
        ser.reset_input_buffer()
        ser.reset_output_buffer()
        print("串口初始化完成")

        return ser
    except Exception as e:
        print(f"初始化失敗: {e}")
        return None
  
def process_key_event(key_code):
    """處理按鍵事件,返回動作描述"""
    if key_code in KEY_MAP:
        return KEY_MAP[key_code]
    elif key_code & 0x80:  # 放開事件(bit 7 = 1)
        base_key = key_code & 0x7F  # 清除 bit 7
        if base_key in KEY_MAP:
            return f"{KEY_MAP[base_key]} 放開 (推測)"
        return f"未知放開鍵碼: 0x{key_code:02x}"
    elif key_code & 0x40:  # 方向事件(bit 6 = 1)
        base_key = key_code & 0xBF  # 清除 bit 6
        if base_key in KEY_MAP:
            return f"{KEY_MAP[base_key]} 反向 (推測)"
        return f"未知方向鍵碼: 0x{key_code:02x}"
    return f"未知鍵碼: 0x{key_code:02x}"

def monitor_keys(port):
    ser = initialize_serial_device(port)
    if not ser:
        return
    try:
        print("開始監聽按鍵事件 (按 Ctrl+C 停止)...")
        while True:
            # 讀取單字節鍵碼
            data = ser.read(1)
            if data:
                key_code = data[0]
                action = process_key_event(key_code)
                print(f"鍵碼: 0x{key_code:02x}, 動作: {action}")
    except KeyboardInterrupt:
        print("\n停止監聽")
    except Exception as e:
        print(f"監聽錯誤: {e}")
    finally:
        ser.close()
        print("串口已關閉")

if __name__ == "__main__":
    # 替換為你的設備串口名稱
    PORT = "COM6"  # Windows 示例,或使用 '/dev/ttyACM0' (Linux/macOS)
    monitor_keys(PORT)

同時還有 Rust 實現的完整版(除了tour menu,那個太麻煩了所以之後再說吧)
routbox