chakokuのブログ(rev4)

テック・コミック・DTM・・・ごくまれにチャリ

CircuitPythonでTV会議Muteボタンを作る

課題:E-WorkでTV会議している時、急に振られてMuteボタン押すつもりが退室ボタンを押してしまったり、2画面でマウスがどっかに行ってたりして困る
解決策:Mute On/Offボタンを作る (通信販売で、そういった商品は売られているけど高いし、PythonのUSBライブラリ使うと作れるはず)
アプローチ:CircuitPythonにはUSBライブラリが提供されていて、HID Keyboardとして実装できるはず
結論: ESP32PICOを搭載したM5 ATOM S3では、Windows PCと接続した際、正しくUSBキーボードと認識されなかった。
詳細:
普段はESP32DevKitを使ってるが、デカいので、コンパクトなM5 Atomを使ってみる。


M5 ATOM Lite用CurcuitPython FirmをDLしてインストールする
ATOM Lite ESP32 IoT Download
マイコンはESP32Picoらしい
FirmDLモードはGPIO0をGNDに落とすと記憶しているが、USB Serial チップにつながっている。パッケージに入ってると自力でどうにかするというのが段々難しくなる(し、ESP32/M5 Stack/Stick/ATOM etc...)バリエーションが多くてどうやってファームを焼くのか混乱)
binなので、esptool等で焼くのだろうと推測
と思っていたら、AdafruitのDLサイトで、DLボタンの下がInstollerボタンで、ブラウザからローカルPCのUSBシリアルを叩いてファームが焼けるのであった。すごい技術。。
待ってる間にキャプチャしてみる

CircuitPython 8.1.10が入った。そんなに他人(=Adafruit社様)任せでいいのか・・自分。。

Adafruit CircuitPython 8.1.0 on 2023-05-22; M5Stack Atom Lite with ESP32
>>> help('modules')
__future__        canio             mdns              struct
__main__          collections       memorymap         supervisor
_asyncio          countio           microcontroller   synthio
_pixelmap         digitalio         micropython       sys
adafruit_bus_device                 displayio         msgpack           terminalio
adafruit_bus_device.i2c_device      dualbank          neopixel_write    time
adafruit_bus_device.spi_device      errno             nvm               touchio
adafruit_pixelbuf espidf            onewireio         traceback
aesio             espnow            os                ulab
alarm             espulp            ps2io             ulab.numpy
analogbufio       fontio            pulseio           ulab.numpy.fft
analogio          framebufferio     pwmio             ulab.numpy.linalg
array             frequencyio       rainbowio         ulab.scipy
atexit            gc                random            ulab.scipy.linalg
audiobusio        getpass           re                ulab.scipy.optimize
audiocore         hashlib           rotaryio          ulab.scipy.signal
audiomixer        i2cperipheral     rtc               ulab.scipy.special
binascii          i2ctarget         sdcardio          ulab.utils
bitbangio         io                select            uselect
bitmaptools       ipaddress         sharpdisplay      vectorio
board             json              socketpool        watchdog
builtins          keypad            ssl               wifi
busio             math              storage           zlib
Plus any modules on the filesystem

Adafruitのバンドル(ライブラリ一式)をDLサイトから落としてきて、必要な分だけATOMに転送する。
adafruit_hid/

$ ampy --port /dev/ttyS19 mkdir lib/adafruit_hid

ampyではbinaryは転送できないようなので、ソースを置くことにする。
Gitのレポジトリは以下
Adafruit_CircuitPython_HID/adafruit_hid at main · adafruit/Adafruit_CircuitPython_HID · GitHub

作業していてきづいたのだが、M5 Atom用CircuitPythonにはusbドライバが入っていない。これはESP32 Picoのアーキテクチャによるものだろうか。ハードブロックとかポーティングソースを見ていないので原因は分からないが、無いものは無いので、USBデバイスとしては動かせない。ということで、他のM5 Atomを確認する。確かに、USB/Serialは外付けで、ESP32の周辺I/OとしてUSBを内蔵していないからUSBはシリアルとしてしか使えないということか?

AtomS3 Lite ESP32S3用のFirmであればusbドライバ内蔵なので、これを使ってみる。先ほど書いたようにブラウザからインストールできてしまうのでめちゃ楽(だけどどういう仕組みでDLできるのか全く分からず)
Webからインストールしようとしたが、インジケータが全く進まず、ESP32S3(Atom S3)にはインストーラからインストールできない。 teratermで繋ぐと以下と表示される。ファームが不整合か?

invalid header: 0xffffffff
invalid header: 0xffffffff
invalid header: 0xfESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x7 (TG0WDT_SYS_RST),boot:0x28 (SPI_FAST_FLASH_BOOT)
Saved PC:0x40048839
invalid header: 0xffffffff
invalid header: 0xffffffff

WebSerialがだめならesptoolを使えという説明ページがあり、開始番地は0x0の様である

esptool.py --chip esp32 --port /dev/tty.usbserial-1144440 erase_flash
esptool.py --port /dev/tty.usbserial-1144440 write_flash -z 0x0 firmware.bin

上記を信じて0番地からで焼いてみる。
Command line ESPTool | CircuitPython on ESP32 Quick Start | Adafruit Learning System
チップがesp32s3なのでそこは変更する
実際のFlash消去、firmインストールの手順は以下(MacBookにインストールしたUbuntuで実行)

$ esptool.py --chip esp32s3 --port /dev/ttyACM0 erase_flash
esptool.py v4.6.1
Serial port /dev/ttyACM0
Connecting...
Chip is ESP32-S3 (revision v0.1)
Features: WiFi, BLE
Crystal is 40MHz
MAC: dc:54:75:cb:bb:8c
Uploading stub...
Running stub...
Stub running...
Erasing flash (this may take a while)...
Chip erase completed successfully in 1.0s
Hard resetting via RTS pin...

$ esptool.py --chip esp32s3 --port /dev/ttyACM0 write_flash   0 adafruit-circuitpython-m5stack_atoms3_lite-en_US-8.1.0.bin
esptool.py v4.6.1
Serial port /dev/ttyACM0
Connecting...
Chip is ESP32-S3 (revision v0.1)
Features: WiFi, BLE
Crystal is 40MHz
MAC: dc:54:75:cb:bb:8c
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Flash will be erased from 0x00000000 to 0x00164fff...
Compressed 1460240 bytes to 987170...
Wrote 1460240 bytes (987170 compressed) at 0x00000000 in 9.1 seconds (effective 1286.1 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...


ブートしてTeratermから接続して確認、以下の通り、usb_hidが組み込まれており、これでHIDが作れると期待

Adafruit CircuitPython 8.1.0 on 2023-05-22; M5Stack AtomS3 Lite with ESP32S3
>>> help('modules')
__future__        collections       micropython       terminalio
__main__          countio           msgpack           time
_asyncio          digitalio         neopixel_write    touchio
_bleio            displayio         nvm               traceback
_pixelmap         dualbank          onewireio         ulab
adafruit_bus_device                 errno             os                ulab.numpy
adafruit_bus_device.i2c_device      espidf            ps2io             ulab.numpy.fft
adafruit_bus_device.spi_device      espnow            pulseio           ulab.numpy.linalg
adafruit_pixelbuf espulp            pwmio             ulab.scipy
aesio             fontio            rainbowio         ulab.scipy.linalg
alarm             framebufferio     random            ulab.scipy.optimize
analogbufio       frequencyio       re                ulab.scipy.signal
analogio          gc                rgbmatrix         ulab.scipy.special
array             getpass           rotaryio          ulab.utils
atexit            hashlib           rtc               usb_cdc
audiobusio        i2cperipheral     sdcardio          usb_hid
audiocore         i2ctarget         select            usb_midi
audiomixer        io                sharpdisplay      uselect
binascii          ipaddress         socketpool        vectorio
bitbangio         json              ssl               watchdog
bitmaptools       keypad            storage           wifi
board             math              struct            zlib
builtins          mdns              supervisor
busio             memorymap         synthio
canio             microcontroller   sys
Plus any modules on the filesystem

ESP32S3でUSBブロックが内蔵されていてCircuitPythonでUSBがネイティサポートされているお陰か、ESP32S3のFlashがUSBストレージとしてPCから参照可能になっている。だから、HID用ドライバ類をDrag&Dropでコピーできてしまう。

Bundle for Version 8.xをPCにDLして展開して、lib/adafruit_hid配下のファイル一式をM5にコピーする。
コピーした後のCircuitPythonから見た /lib/adafruit_hid配下のファイル類

>>> os.listdir('lib/adafruit_hid')
['consumer_control.mpy', 'consumer_control_code.mpy', 'keyboard.mpy', 'keyboard_layout_base.mpy', 'keyboard_layout_us.mpy', 'keycode.mpy', 'mouse.mpy', '__init__.mpy']

AtomS3の液晶パネルはプッシュボタンにもなっていて、接続されているIOがマイコンの足のGPIOのどれに相当するのか分からず。Arduino的にはG41となっていて、基本的にGPIOと同じ番号のようなので、GPIO41か?

GitHubのM5Stack ATOMS3 ポーティングでは、以下のように、GPIO41は、MP_QSTR_BTNとして定義されている。

    { MP_ROM_QSTR(MP_QSTR_BTN), MP_ROM_PTR(&pin_GPIO41) },

circuitpython/ports/espressif/boards/m5stack_atoms3_lite/pins.c at main · adafruit/circuitpython · GitHub
boardモジュール内でもBTNが定義されており、これを使うとLEDのボタンの状態が取れるはず

>>> dir(board)
['__class__', '__name__', 'A1', 'A2', 'A4', 'A5', 'A6', 'A7', 'BTN', 'D1', 'D2', 'D38', 'D39', 'D5', 'D6', 'D7', 'D8', 'I2C', 'IR_LED', 'NEOPIXEL', 'PORTA_I2C', 'PORTA_SCL', 'PORTA_SDA', 'board_id']

以下でLCDのボタンの押下は取得できた

import digitalio
button = digitalio.DigitalInOut(board.BTN)
button.switch_to_input(pull=digitalio.Pull.UP)

>>> dir(button)
['__class__', '__enter__', '__exit__', 'value', 'deinit', 'direction', 'pull', 'switch_to_input', 'switch_to_output']
>>> button.value
True
>>> button.value
False

TV会議の音声Muteは、[Ctrl]+[Shift]+[M]を一度に押すのだけど、デバッグ目的で、まずは[M]を送るコードを書いてテストする。

import time
import digitalio
import board
import usb_hid

from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keyboard import Keycode

button = digitalio.DigitalInOut(board.BTN)
button.switch_to_input(pull=digitalio.Pull.UP)

keyboard = Keyboard(usb_hid.devices)

while True:
    if not button.value:
        print("p")
        # press  Ctrl(CONTROL) + Shift(SHIFT) + M(M)
        #keyboard.press(Keycode.CONTROL, Keycode.SHIFT, Keycode.M) 
        keyboard.send(Keycode.M) 
        time.sleep(0.1)  # wait 100ms
        #keyboard.release_all()
    time.sleep(0.1)  # wait 100ms
    print("..")

コードは上記なのだが、、Windowsでは動かない(Mが入力されない)。Ubuntu だとMが入力されるのだが。。
ループが回っているのかどうかもあやしい。
■追記
自宅にWindowsマシンが2台あって、普段開発に使ってるPCはNGで、E-Work用のPCはMが入力された。だから、E-Work用(=TV会議で使うPC)だとUSBキーボードとして使えそうだ。
■追記
最初は問題なかったが、何がきっかけか分からないが、「デバイスが正しく設定されていません」といったメッセージが出るようになった。結果、Windowsマシンは2台とも正しく接続できない状況 (MacUbuntuはOKっぽい)
■今後の取り組み
他のマイコンを使ってどう動くのか見てみる。特に、RPi PicoでUSBキーボードを作る事例があり、これだとうまくいってそうだ。ESP32PICO以外だと動くのかもしれない。

■参考URL
RaspberryPi Pico CircuitPythonでUSB HIDを作る方法【自作マウス・自作キーボード】