LEGOのモーターやセンサーをSwiftで制御したい
LEGOのハブとLEGO Wireless Protocolを使ってBLE通信し、モーターやセンサーを制御できるSwift Packageを作りたい。既存のプログラミング環境を参考にどういうAPIがいいか考える。
SPIKEアプリのワードブロックでモーターの制御。毎回ポートを指定する。指定ポートにモーターが繋がっていない場合、命令は無視され、センサー値は0になる。
SPIKEアプリからMicroPythonでモーターの制御。ポート指定でMotor
のインスタンスを生成し、それに対して操作を行う。インスタンス生成時にモーターが接続されていないとRuntimeError
。
from spike import Motor
motor = Motor('A')
motor.run_for_rotations(1)
print(motor.get_position())
PybrickのMicroPythonでモーターの制御。ポート指定でMotor
のインスタンスを生成し、それに対して操作を行う。インスタンス生成時にモーターが接続されていないとOSError
。
from pybricks.pupdevices import Motor
from pybricks.parameters import Port
motor = Motor(Port.A)
motor.run_angle(500, 360) # 500 deg/sで1回転
print(motor.angle()) # 絶対位置は取得できない
基本的にはSPIKEアプリでプログラミングできる公式MicroPythonと同じ形がよさそう。時間がかかる命令の待機にはasync/awaitを使う。
let motor = Motor(Port.A)
await motor.runForRotations(1)
print(motor.position)
ただし、MicroPythonのプログラムはハブに書き込むことを前提としているため、Swiftの場合はBLEで遠隔制御することを考慮する必要がある。ハブの接続や切断、モーターやセンサーの接続などのイベントをとれるようにした方がよさそう。
Swift PlaygroundsでLEGO MINDSTORMS EV3のプログラミングをするときは、あらかじめ用意されているev3
というインスタンスに対して毎回ポート指定で制御する。ハブやモーターを接続していないときに実行するとどうなる?
ev3.motorOn(forDegrees: 360, on: .a, withPower: 75)
print(ev3.measureMotorDegrees(on: .a))
よく考えたら上記の設計だといくつか問題がある。
- Motorインスタンスからどうやって現在接続しているハブにBLEのコマンドを送るのか
- ハブの複数接続に対応できない
モーターやセンサーへは、ハブを管理するクラスを経由してアクセスするほうがよさそう。
node-poweredupを使うと、ハブとのBLE通信をNode.jsでプログラムすることができる。コードはハブのスキャンから始まり、async/awaitやイベントを使ってモーターの制御やセンサー値の取得をしていくスタイル。ハブの複数接続も可能。
const PoweredUP = require("node-poweredup");
const poweredUP = new PoweredUP.PoweredUP();
let latest_degrees = null;
poweredUP.on("discover", async (hub) => {
await hub.connect();
const motor = await hub.waitForDeviceAtPort("A");
hub.on("rotate", (device, { degrees }) => {
latest_degrees = degrees;
});
await motor.rotateByDegrees(360);
console.log(latest_degrees);
});
poweredUP.scan();
センサー値はイベントでしか受け取れない模様。ハブではなくモーターからのイベントとして受け取ることもできる。
motor.on("rotate", ({ degrees }) => {
latest_degrees = degrees;
});
このスタイルは理にかなっている気がする。もしセンサー値のプロパティを用意するならオプショナルのアンラップかtryが必要になりそう。