之前看便宜+好奇買了個 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) |
---|---|---|---|---|
0x55 | 0x00 | 0x07 | 0x88, 0x94, 0x00, 0x1a | 0xfe |
0xd5 | 0x00 | 0x0f | 0x8a, 0xd0, 0xd6, 0x60, 0xef, 0x17, 0x11, 0x75, 0x5f, 0x35, 0xb0, 0xd8 | 0xfe |
0xb5 | 0x00 | 0x07 | 0x04, 0x00, 0x09, 0x00 | 0xfe |
頭兩個指令個人推測是設備信息,設備收到後會返回一串東西,第三個推測是按鍵的某些模式。
哪怕不發送這些指令,設備天然就會在按鍵時向串口發送指令:
完整按鍵值字典
(其中放開鍵+0x80,反向鍵+0x40)
值 | 按鍵 |
---|---|
0x00 | 長鍵 |
0x02 | 橫鍵 |
0x03 | 短鍵 |
0x04 | 旋鈕逆時針 |
0x09 | 向下滾動 |
0x0A | 滾動鍵 |
0x22 | 左鍵 |
0x23 | 右鍵 |
0x2A | Tour鍵 |
0x37 | 旋鈕按下 |
0x44 | 旋鈕順時針 |
0x49 | 向上滾動 |
0x80 | 長鍵放開 |
0x82 | 橫鍵放開 |
0x83 | 短鍵放開 |
0x8A | 滾動鍵放開 |
0xA2 | 左鍵放開 |
0xA3 | 右鍵放開 |
0xAA | Tour鍵放開 |
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