念電子佛經,積賽博功德 - 使用CircuitPython的HID庫實現漢字輸入

不才狸子發表於2024-03-17

CircuitPython

CircuitPython說是MicroPython的分支,支援更多晶片,有更多驅動,相對也更復雜。我用它主要是為了MicroPython上面沒有的HID,實現鍵鼠輸入。程式上和MicroPython有區別,尤其是GPIO的使用,這也影響了程式碼的複用。不過總歸是Python,移植也不難。標準庫的使用可以自行檢視官方文件,至於其他庫和驅動則要到處找,我也沒玩明白。

安裝庫

我使用的是源地的RP2040開發板,和我的這篇部落格中的相同也就是樹莓派PICO的晶片。安裝好CircuitPython後標準庫中自帶usb_hid庫。
要實現打字的功能還要安裝adafruit_hid庫,文件在這:Adafruit HID Library
CircuitPython提供了包管理器cirup,確保你的開發板以隨身碟形式連線到電腦後,使用cirup install adafruit_hid,即可自動檢測開發板型號並安裝庫。cirup使用期間可能需要掛梯子,也可以從adafruit_hid的倉庫clone下來用thonny直接丟進開發板目錄。

拼音輸入

我的拼音設定的是預設英文模式,按Shift切換漢語拼音。

import time
import board
import digitalio
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS

kbd = Keyboard(usb_hid.devices)
layout = KeyboardLayoutUS(kbd)


usrkey = digitalio.DigitalInOut(board.GP24)
usrkey.direction = digitalio.Direction.INPUT
usrkey.pull = digitalio.Pull.UP

def launch_notepad():
    kbd.send(Keycode.WINDOWS, Keycode.R)
    time.sleep(0.3)
    layout.write('notepad\n')
    time.sleep(0.5)


while True:
    # 按下微控制器上的按鈕開始輸入
    if not usrkey.value:
        launch_notepad()
        kbd.send(Keycode.SHIFT)
        layout.write('meinvheguanzaixianfapai')
        time.sleep(0.1)
        layout.write('1\n')
    time.sleep(0.2)

現象:

雖然成功輸入了漢字,但不同輸入法候選詞都有很大不同,就這個中英文切換的設定每個人都可能做自己的設定。這種方法無法保障在每臺電腦上正常輸入一樣的漢字。

Unicode輸入

確保輸入法在英文模式,不會開啟任何候選欄。
我希望有一種通用的輸入方式,最好是所有Unicode涵蓋的字元通用。bing了一下“windows輸入Unicode”,得到了一種方法。在notepad裡輸入16進位制的Unicode,緊跟著按Alt + x即可轉為對應的文字。
今年是龍年,龘對應的Unicode是9f98,在notepad輸入

然後按Alt + x

先將準備好的大悲咒儲存在開發板目錄中

import time
import board
import digitalio
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
import board

kbd = Keyboard(usb_hid.devices)
layout = KeyboardLayoutUS(kbd)


usrkey = digitalio.DigitalInOut(board.GP24)
usrkey.direction = digitalio.Direction.INPUT
usrkey.pull = digitalio.Pull.UP


def launch_notepad():
    kbd.send(Keycode.WINDOWS, Keycode.R)
    time.sleep(0.3)
    layout.write('notepad\n')
    time.sleep(0.5)


def typing_unicode(line):
    line = line.strip()
    for char in line:
        # 下面這一套函式看不懂可以自己一個個拆開試試
        layout.write(hex(ord(char))[2:])
        kbd.send(Keycode.ALT, Keycode.X)
    kbd.send(Keycode.ENTER)


while True:
    time.sleep(0.2)
    if not usrkey.value:
        launch_notepad()
        with open("dbz.txt", "r") as f:
            for line in f:
                if not usrkey.value:
                    break
                typing_unicode(line)

支援按鍵中止輸入

現在已經比較滿意了,但是在輸入較長的文章時,輸一半我不想繼續了,只能重新插拔。一般想法是把按鍵繫結中斷函式,按下改變輸入狀態,在輸入的時候同時輪詢輸入狀態。
結果一搜傻眼了,CircuitPython沒有提供MicroPython那種按鍵中斷,參考這篇文章,要用asyncio來管理按鍵中斷。這個asyncio也要如上文使用cirup安裝,不像MicroPython可以直接用。

import time
import board
import digitalio
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
import asyncio
import board
import keypad

kbd = Keyboard(usb_hid.devices)
layout = KeyboardLayoutUS(kbd)


def launch_notepad():
    kbd.send(Keycode.WINDOWS, Keycode.R)
    time.sleep(0.3)
    layout.write('notepad\n')
    time.sleep(0.5)
    

async def typing_unicode(line):
    line = line.strip()
    for char in line:
        layout.write(hex(ord(char))[2:])
        kbd.send(Keycode.ALT, Keycode.X)
        await asyncio.sleep(0)
    kbd.send(Keycode.ENTER)
        

async def usrkey_handler():
    with keypad.Keys((board.GP24,), value_when_pressed=False) as keys:
        is_typing = False
        loop = asyncio.get_event_loop()
        # 這裡cancel一次,這樣typing_task初始為done的狀態
        typing_task = asyncio.create_task(typing_csb())
        typing_task.cancel()
        while True:
            event = keys.events.get()
            if event:
                if event.released:
                    # 如果沒有初始化為done而是none,首次按鍵會拋異常
                    if not typing_task.done() and is_typing:                            
                        typing_task.cancel()
                        is_typing = False
                    else:
                        typing_task = asyncio.create_task(typing_csb())
                        is_typing = True                 

            await asyncio.sleep(0)


async def typing_dbz():
    launch_notepad()
    with open("dbz.txt", "r") as f:
        for line in f:
            await typing_unicode(line)
            

asyncio.run(usrkey_handler())

相關文章