tl;dr
- CircuitPython と ESP32 を使うと簡単に、HTTP Server を立てることができる。
- CircuitPython の pulseio を使うと、制御が抽象化されて、簡単に信号制御ができる。
- M5StampC3 と、IR Unit ならケースに入っているし、そのままお家にデプロイしても良さそう。
- CircuitPython 上でアプリケーションを作り込むより、Raspberry Piなどの上でアプリケーションを作って、HTTP経由でMCUに制御させた方が、アプリケーションの作り込みがやりやすそう。
作ったもの 赤外線送受信 HTTP Server
自宅の WiFi に繋いで、HTTP経由で赤外線を送受信できる Web Server を M5StampC3 と IR Unit で作成しました。
POST /api/send_ir
で以下のような JSON を送ります(ちなみに、わが家の SONY の電源ボタン)。
{ "pulse": [ 2403, 642, 1170, 645, 588, 621, 1167, 648, 588, 621, 1191, 624, 588, 621, 588, 621, 1167, 648, 588, 621, 588, 624, 585, 624, 588 ] }
すると、赤外線信号が送信されます。
また、POST /api/receive_ir
を叩いて、赤外線ユニットに向けてリモコンノボタンを押すと以下のようにレスポンスします。
{ "success": true, "data": { "pulses": [ 2403, 615, 594, 618, 1194, 621, 588, 621, 591, 621, 588, 621, 588, 624, 612, 618, 1170, 645, 567, 645, 564, 645, 564, 645, 567, 27126, 2400, 621, 588, 642, 1170, 645, 564, 645, 567, 645, 588, 621, 588, 621, 588, 624, 1191, 621, 591, 621, 588, 621, 588, 621, 588, 27105, 2397, 645, 564, 645, 1170, 645, 588, 621, 564, 645, 567, 645, 564, 645, 588, 621, 1170, 645, 588, 621, 567, 642, 591, 621, 588, 27105, 2421, 621, 567, 642, 1170, 645, 564, 645, 564, 645, 567, 645, 588, 621, 564, 645, 1194, 621, 588, 621, 591, 618, 591, 621, 588 ] } }
これで、パソコンからでもHTTP操作で、リモコンの操作ができるようになりました。
M5StampC3 に CircuitPython をインストールする
今回の工作には、お安めの M5StampC3 (¥1,188)を使ってみることにしました。
M5Stamp C3 Matewww.switch-science.com
そしてファームウェアのランタイムには CircuitPython を選びました。これは、単にのちほど M5StampS3 を使って赤外線機能を持ったTV横USBキーボードデバイスを作ろうとしていて、同じCircuitPython上で実装したコードが使えることを狙ったためです。CircuitPythonのファームウェアは以下からダウンロードできます。
インストール方法は下記にあります。私は別途 ESP32 の開発環境が合ったため、 esptool.py を使いましたが、ここにはWebブラウザ経由でインストールする方法も書かれています。
CircuitPython はインストールすると、通常は USBメモリとして認識するドライブに、ファイルをコピーすることで使うことができます。M5StampC3 で使われている ESP32-C3 にはUSBデバイス機能はありません。では、どうやってファイルを送るかというと、M5StampC3 をUSBで繋ぐと出てくるシリアルコンソール上で、WiFi の設定を入力してM5StampC3 をWiFi につなぎ、すると WebServer が自動で立ち上がるようになるので、HTTP経由で送ります。コンソールに以下のようなPythonのコマンドを打ち込んで、設定ファイルを作ります。
f = open('settings.toml', 'w') f.write('CIRCUITPY_WIFI_SSID = "wifissid"\n') f.write('CIRCUITPY_WIFI_PASSWORD = "wifipassword"\n') f.write('CIRCUITPY_WEB_API_PASSWORD = "webpassword"\n') f.close()
そしてUSBの再接続すると、IP アドレスが書かれているので、そのIPにブラウザでアクセスすることができます。
私の場合は、固定IPにしたかったので、お家のWiFiルータの設定でDHCPでこのIPを割り当てた MACアドレスに固定IPを割り振るようにしました。
これで、curl でファイルを転送することができるようになりました。
curl -v -u :webpassword -T main.py -L --location-trusted http://192.168.1.19/fs/code.py
CircuitPython で WebServer を立てる
WiFi の設定をしただけで WebServer が立っている訳ですが、adafruit_httpserver を使うと、Flask と似た感じに API を実装することができます。
import wifi import json import socketpool from adafruit_httpserver import Server, Request, Response pool = socketpool.SocketPool(wifi.radio) server = Server(pool, "/static", debug=True) @server.route("/api/send_ir", methods="POST") def send_ir_api(request: Request): print("access /api/send_ir") print(request.body) data = json.loads(request.body) pulse = data["pulse"] print(pulse) send_ir(pulse) res = {"success": True} return Response(request, json.dumps(res)) server.serve_forever(str(wifi.radio.ipv4_address))
adafruit_httpserver は、Adafruit CircuitPython Library Bundle に含まれています。以下のリポジトリからダウンロードし、libs/adafruit_httpserver ディレクトリを、CircuitPythonのlib ディレクトリに転送して、使えるようにしておきます。
M5Stack IR Unit を接続する
M5StampC3 には Grove ポートが付いており、Grove ポートで使えるようになる 赤外線モジュールがM5Stackから出ています。
M5Stack用赤外線送受信ユニット [U002]www.switch-science.com
お値段も ¥649 とお手頃です。M5Stack のモジュールはケースに入っているので、そのままお家にデプロイしても良さそうです。
CircuitPython の pulseio を使って、赤外線の信号を送受信する
CircuitPython にはこのような信号をやりとりする仕組みとして、pulseio があります。その裏は、MCUごとの機能を使って実装されているようです。
まず受信の関数は以下のようにしました。len(pulsein) で受信したかどうかわかり、pulsein.popleft() で値を取ります。これを受信できるまでループさせるようにします。
import board import pulseio import time pulsein = pulseio.PulseIn(board.G0, maxlen=120, idle_state=True) pulsein.pause() def receive_ir() -> list[int]: timeout = 10 pulses = [] start = time.monotonic() is_received = False pulsein.clear() pulsein.resume() while not is_received and (time.monotonic() - start) < timeout: # 値が取れるか、タイムアウトまでループ time.sleep(0.1) while pulsein: pulse = pulsein.popleft() pulses.append(pulse) is_received = True pulsein.pause() if pulses is None: print("cannot receive pulses") else: print("Heard", len(pulses), "Pulses:", pulses) return pulses
なお、pulseio.PulseIn に関しては、一度 pulseio.PulseIn() でインスタンスを作り deinit() でインスタンスを解除しても、再度 pulseio.PulseIn() でインスタンスを作ったとしても、使えなくなる問題が発生しました。これは最初の一度だけ初期化するようにします。また、一度初期化してしまうと CircuitPython のリセットを行っても再度使える状態には鳴りませんでした。その時は仕方なく、MCU レベルでリセットするようにしました。
import microcontroller; microcontroller.reset()
次に送信は以下のようにします。こちらは単純です。
def send_ir(pulse: list[int]): with pulseio.PulseOut(IR_SEND_PIN_NO) as pulseout: print("send") for _ in range(3): pulseout.send(array.array("H", data)) time.sleep(0.025) print("send done")
こんな感じに配列を渡すだけで送信ができます。
この 2つの関数を WebServer から呼べるように実装しました。
これで赤外線の IO となる WebServer ができた
ほんの100行に満たないコードで、赤外線リモコンの IO となる Web API を構築することができました。
ここから更にやりたいことは以下のようなことです。
- TVの電源オン、チェンネル1に変更、入力切り替えでTV横PCのHDMI に、1ボタンで切り替える
- PS5 の起動後のAmazon Prime アプリを起動するまでの、シーケンスを、TVリモコン越しに行う
これらには複数のボタンを、時間間隔と一緒に制御する必要があります。
ですが、M5StampC3 で宅内 Web API になったことで、既にある Raspberry Pi 4 上に操作盤となる Webアプリを作り込めば、CircuitPython の小さな世界ではなく、Pythonなどの機能を存分に使って実装することができます。これに気づいたことから、マイコンでは、センサとの IO だけやるようにしようと思うようになりました。
今回作成したコードは以下のリポジトリで公開しています。
Appendix: CircutPython のプロジェクトで Python のコード補完を効かせる
VS Code で実装していると、コード補完機能には助けられます。CircuitPython用のコード補完用のモジュールは以下で公開されているため、これをインストールします。しかし、これを使っても完全に補完できる状態にはならないようです。
また、adafruit_httpserver のようなモジュールでは、libディレクトリに配置するのは mpy というバイナリ変換ファイルになります。バイナリファイルでは補完が効かないため、プロジェクトディレクトリにgit submodule で変換前のファイルを取得して参照するようにしました。
git submodule add https://github.com/adafruit/Adafruit_CircuitPython_HTTPServer.git ./imports/Adafruit_Circui tPython_HTTPServer ln -s imports/Adafruit_CircuitPython_HTTPServer/adafruit_httpserver ./