ETロボコンシミュレータ(大会用)について
ETロボコンは参加者向けに環境構築のためのリポジトリを公開しており、ここからETロボコンシミュレータ(大会用)をインストールすることができます。実は、参加者でない方でもサンプルコースだけが動くETロボコンシミュレータを体験することができます。作りかけのWEB版ETロボコンシミュレータと異なり、大会に必要な機能をすべて備えていますので、こちらのシミュレータをもっと活用できればいいのになぁ、と思う方もいらっしゃると思います。
今時C言語でロボコン?
などと言われるのですが、以前はJavaで参加するチームもいましたし、ETロボコン2021では実はmrubyが公式に使えます。やる気になればどうにでもなる大会なのです。mrubyは実機でも動作することを意識し、TOPPERS、Athrillの環境をベースにしているため、これはこれでとても興味深い環境だと思っています。
MiniScriptとか知らんし。
WEB版ETロボコンシミュレータではMiniScriptを採用しましたが、これを勉強したところで役に立たないよね?という意見も多いかと思います。まぁ、私もそうかなと思いつつも、今のところこれ以上いい案が思いついていないだけなのです。sessionStorageとWebAssemblyを使えば何でもできそうな気がしなくはないですが、まだモチベーションが上がっていません。
実はETロボコンシミュレータ(大会用)はPythonでも動かせる。
ここからが本題です。ETロボコンシミュレータは組込みCPUシミュレータAthrillとUDPで通信しています。ここの通信フォーマットを理解しているならば、Athrillでなくとも制御ができてしまいます。(もちろん、今大会では利用できませんけど!)
ということで理解している私がPythonクライアントを作りました。ETロボコンシミュレータを起動し適当なディレクトリで
$ git clone https://github.com/YoshitakaAtarashi/ETroboSimController
$ cd ETroboSimController
$ python test_Motor.py
とすると、
というような感じで走行体を動かすことができます。前提環境はPythonだけなのでインストール地獄にはならないはずです。Pythonは3.7.6以上で確認しています。ETroboSimServerがIO処理でブロックされないようasyncioを使っているため、古いPythonだと動かない可能性が高いです。
test_Motor.pyの解説
まず、EV3 C++ APIに似せたetrobosim.ev3api(as ev3)と、Athrillの代わりにETロボコンシミュレータを制御する etrobosim(as ets)をインポートします。
次に、各モータの初期化をします。この辺はWEB版ETロボコンシミュレータと違って、大会環境に似せようという過去の私の努力が見て取れます。
ets.Controllerで左コースか右コースかを選択できます。Python実装を見るとわかりますが、ここでUDPポートを切り替えています。controller.addHandlersは作成したデバイス(motor*)をここで追加することで、controllerの管理下に置くことができます。あとはお決まりのPWM値の設定です。
Ctrl+Cで停止できるように、KeyboardInterruptをキャッチしています。(これがなかなかうまく実装できなかった)
import etrobosim.ev3api as ev3
import etrobosim as ets
import time
motorR=ev3.Motor(ev3.ePortM.PORT_B,True,ev3.MotorType.LARGE_MOTOR)
motorL=ev3.Motor(ev3.ePortM.PORT_C,True,ev3.MotorType.LARGE_MOTOR)
motorARM=ev3.Motor(ev3.ePortM.PORT_A,True,ev3.MotorType.MEDIUM_MOTOR)
motorTAIL=ev3.Motor(ev3.ePortM.PORT_D,True,ev3.MotorType.LARGE_MOTOR)
motorR.reset()
motorL.reset()
# 1秒毎に直進と回転を切り替える。
walker=[(50,50),(50,0)]
wid=0
try:
controller=ets.Controller(ets.Course.LEFT)
controller.addHandlers([motorR,motorL,motorARM,motorTAIL])
controller.start(debug=False)
while controller.isAlive():
motorR.setPWM(walker[wid][0])
motorL.setPWM(walker[wid][1])
wid=(wid+1)%len(walker)
print("MotorR={},MotorL={}".format(motorR.getCount(),motorL.getCount()))
time.sleep(1)
controller.exit_process()
except KeyboardInterrupt:
controller.exit_process()
raise
test_LineTrace.pyによるライントレース
もちろんライントレースもできます。colorSensorを追加している点と、pidControlを周期的に動かすためにcontroller.runCyclic(pidControl)を実行している点が異なります。組込みOSと違って精度よく周期実行させることはできませんが、まぁまぁいい感じに動くのでぜひ試してみてください。
import etrobosim.ev3api as ev3
import etrobosim as ets
# ColorSensorのReflectを使ってP制御でライントレースする。
def calcPID(r, target=20, power=70,P=1.8):
p=r-target
left=power-P*p
right=power+P*p
return (int(left),int(right))
def pidControl(initARM_count=-50,initTAIL_count=0):
left,right=calcPID(colorSensor.getBrightness())
motorL.setPWM(left)
motorR.setPWM(right)
motorARM.setPWM(initARM_count-motorARM.getCount())
motorTAIL.setPWM(initTAIL_count-motorTAIL.getCount())
#print("MotorR={},MotorL={},MotorARM={},Color={}".format(motorR.getCount(),motorL.getCount(),motorARM.getCount(),colorSensor.getBrightness()))
motorR=ev3.Motor(ev3.ePortM.PORT_B,True,ev3.MotorType.LARGE_MOTOR)
motorL=ev3.Motor(ev3.ePortM.PORT_C,True,ev3.MotorType.LARGE_MOTOR)
motorARM=ev3.Motor(ev3.ePortM.PORT_A,True,ev3.MotorType.MEDIUM_MOTOR)
motorTAIL=ev3.Motor(ev3.ePortM.PORT_D,True,ev3.MotorType.LARGE_MOTOR)
motorR.reset()
motorL.reset()
colorSensor=ev3.ColorSensor(ev3.ePortS.PORT_2)
try:
controller=ets.Controller(ets.Course.LEFT)
controller.addHandlers([motorR,motorL,motorARM,motorTAIL,colorSensor])
controller.start(debug=True)
controller.runCyclic(pidControl)
controller.exit_process()
except KeyboardInterrupt:
controller.exit_process()
pass
最後に
UDPの通信仕様だけ知ってればいいんだからPythonで簡単に作れると思いきや、同期周りで苦労して、組込みOS的な機能がやはり欲しくなりました。asyncioは今回初めて使ったのですが、ちゃんと動くようになるまではイライラしっぱなしでした。非同期処理ってホント難しいですね。
これの応用ですが、大会で採用するとしたら機械学習とか強化学習とかに使えるんじゃないですかね。学習データをPythonで取っておいて、大会用プログラムはAthrill対応の言語(C、C++、mrubyなど)とするのも面白そうです。来年はさすがにシミュレータ大会オンリーじゃなくなっててほしいですけど。