目次 はじめに †今回はまったく余裕がなく、新しいことに多数挑戦したため自分だけの試走はともかく相方の機体との通信を伴う試走に入れたのは当日になってからだった。 課題3について †今回の課題は野球である。具体的に何を行うかといえばピッチャー、バッターに分かれて移動し、通信を行って野球盤のようなものを行う。試験後の短期間にこれを2人で二台は時間が圧倒的に足りない・・・ 動きについて †今回の機体のコンセプトはロマンである。時間はなかったが使ってみたかった履帯を使ってみたかったためやってみた。履帯の弱点は精密な角度制御が難しい点であった。逆に利点は、重く重心が高い機体が安定することである。 前回ジャイロセンサーを使えるようになたため今回はジャイロセンサーで旋回角度を制御した。一番最初に機体を45度回転させる。 4本目の線を検知したら機体を止める。この時、高速で移動しているためモーターを止めた後も機体が重くとゴムがなく滑りやすい履帯のせいで暫くは滑ってしまう。これを修正するために一回低速で機体を下げ基準を合わせ、一定角度進みそこから90度旋回した後にもう一度機体を下げ、また光センサーで黒線を検知し一定角度進むことで位置を合わせる。こうすることで位置をある程度合わせることができる。 機体について †移動について †サイズ制限に収めるために機体は縦に長く、重心が高くなっている。今回はペアがびっくりドッキリメカを作ったため4つモーターを使っている。 機体の後部にカラーセンサーを搭載している。 再装填機構について †ここに玉を落とし込むと、指してあるピンによって適当な位置に玉が固定される。 再装填を行う時以外は再装填アームが玉を押し上げることによって予期せぬタイミングに玉が出てしまうことを防ぐ。また、揺れ動くパーツにより再装填時に玉がはねて装填に失敗することがないようにしている。 このアームはただ往復動作をするだけである。あまり強度はない。 投球について †ここにあるピンを用いて玉の置き場所を確定させる。 設置された玉をこのアームで打ち出す。モーターの出力で飛距離を調整可能である。斜めになっているガイドを交換することで地面を転がすことも、カーブも可能である プログラムについて †以下のプログラムはCC BY-SA 4.0とします。ライセンスはこちらhtmlタグが使えないためこのように表記しておきます 自作関数について †move.py †#!/usr/bin/env python3 #このプログラムはpython3で記述されている import ev3dev.ev3 as ev3 import time mr = ev3.LargeMotor('outB') #ここから↓ ml = ev3.LargeMotor('outC') cs = ev3.ColorSensor('in1') gy = ev3.GyroSensor('in2') #ここまで↑テンプレ limen = 20 #ライトセンサーの閾値、やはり優秀で、しっかりと黒に塗られていれば一桁の値を返す。しかし、今回の会場の環境がどうなっているのかわからないので安全を見る(苦い思い出) def move_to_Q(): #スタート地点からピッチャーマウントであるQへの移動 cs_count= 0 #カラーセンサー(反射率モード)が黒を検知した回数を格納する gy_old = gy.value() #ジャイロセンサーのスタート時の値を格納 while abs(gy.value() - gy_old) < 45: #45度右旋回し、直線でマウントに移動する mr.run_forever(speed_sp=200) ml.run_forever(speed_sp=-200) mr.stop() ml.stop() while cs_count < 4: #4回目の黒線でループを抜ける print(cs.value()) #デバッグのためにPCに現在のカラーセンサーの値を表示 mr.run_forever(speed_sp=500) #大体50%の出力で前進、全開でも特に問題はない ml.run_forever(speed_sp=500) if cs.value() < limen: #カラーセンサーが黒線を検知したらカウンターに1を足す cs_count = cs_count + 1 while cs.value() < limen: #カラーセンサーが黒線を抜けるまではここでとめておく、そうしないと一本の黒線でカウンタが埋まってしまう。 print(cs_count) #デバッグのためにPCに現在のカウンタの値を表示 mr.stop() ml.stop() #出力停止 time.sleep(1) #機体が縦に大きく、重心が高いため動揺を抑える mr.reset() ml.reset() while cs.value() > limen: #以下、動きについてで記述した動作を行う mr.run_forever(speed_sp=-200) ml.run_forever(speed_sp=-200) mr.stop() ml.stop() mr.run_to_rel_pos(position_sp=180,speed_sp=200, stop_action='hold') ml.run_to_rel_pos(position_sp=180,speed_sp=200, stop_action='hold') time.sleep(1) while abs(gy.value() - gy_old) < 135: mr.run_forever(speed_sp=200) ml.run_forever(speed_sp=-200) mr.stop() ml.stop() mr.reset() ml.reset() time.sleep(1) while cs.value() > limen: mr.run_forever(speed_sp=-200) ml.run_forever(speed_sp=-200) mr.run_to_rel_pos(position_sp=120, speed_sp=200, stop_action='hold') ml.run_to_rel_pos(position_sp=120, speed_sp=200, stop_action='hold') test2.py †#!/usr/bin/env python #このプログラムはpython2で記述されている、そして最大の敵はこの文であった(後述) import paho.mqtt.client as mqtt #paho(python用のMQTT)をインポート from time import sleep import ev3dev.ev3 as ev3 import time import move as move host = 'localhost' #こちらはpublisher兼brokerなのでローカルホストになる port = 1883 #ポートはデフォルト topic = 'pi/st' #トピックはpitcher/statusを略してpi/st arm = ev3.LargeMotor('outA') #アームはアーム、ボールを打ち出すやつ(ピッチャーなのに打つとはこれは如何に) bolthandle = ev3.MediumMotor('outD') #再装填用のアーム、役割と動きからボルトアクションを連想したためボルトハンドルとした def reset_arm(t): #アームの位置をリセットするための関数(ミスに気がついた) arm.run_forever(speed_sp=60) time.sleep(t) #アームを機体側に寄せる、引数を用いて再装填時の動作と発射直後にアームを戻す動作両方に対応している arm.stop() arm.run_forever(speed_sp=-5) #ホームページ作成時に発見、本番に見せていたなぞの動き(なぜか一回玉を押し出しかける)の原因 time.sleep(0.5) #まず、パワーの符号を間違えていたため動作が逆になり、さらにモーターを止め忘れたため玉を押し出しかけていた def Feuer(): #ドイツ語で発射(fire)の意味、最初作っていた時に、個々の動作を作りそれを組み合わせて一つの動作を作っていたため、個々の動作側は関数名被りを防ぐためにドイツ語を採用。 arm.run_forever(speed_sp=-600) #最初は全力の1050で動かしていた、実際に配置してみてこの程度のパワーがちょうどよいということがわかった time.sleep(0.8) arm.stop() def nachladen(): #上に同じく、ドイツ語で再装填(reload)の意味 bolthandle.run_forever(speed_sp=60) #単純に一往復するだけ time.sleep(2) bolthandle.stop() bolthandle.run_forever(speed_sp=-80) time.sleep(2) bolthandle.stop() def throw(): #玉を投げ(撃ち)、再装填するためのひとつの動作の関数 reset_arm(2) #アームの位置を下げ send_message(5) #投げるよーとバッターに伝え(5の数字に意味はない) Feuer() #発射し reset_arm(4) #アームの位置を直し nachladen() #再装填する def on_connect(client, userdata, flags, respons_code): #接続された時に実行される関数 print('status {0}'.format(respons_code)) #別述するが、、MQTTは接続時に状態を表すコードを返す、0が正常 client.subscribe(topic) #トピックの購読を開始 def send_message(m): #MQTTのメッセージを送信する client.publish(topic, m) #トピックにメッセージを送信する sleep(0.2) client = mqtt.Client(protocol=mqtt.MQTTv311) #MQTTプロトコルのクライアントを作成 client.connect(host, port=port, keepalive=60) #brokerに接続 client.on_connect = on_connect #接続時の処理を実行 move. move_to_Q() #move.pyからmove_to_Qを実行する for i in range(100): #100回玉を投げる(千本ノックならぬ百本ノック) throw() time.sleep(1.5) client.disconnect() #接続を切る 結果 †試走が足りない・・・圧倒的に足りない・・・という状況になった、なってしまった。全部試験が悪いんや・・・ 感想 †時間不足でありさらにロマンに極振りしたため点数は低く、完成度は低いものになっていたがMQTTの使い方を学んだことは大きな進歩であった。今までの機体を基本的に自分一人で組み上げていたことを少々後悔してはいる。何はともあれ、ロマンは大事である。 雑記 †モーターのduty_cycle_sp値を直接いじらずに制御するためにここby Takahiro Ikeuchiを参考にした。(環境に合わせるのために一部改変してある。) MQTTについて †当初はメッセージを受信した時に動作を行う方法がわからなかったが、on_messageに直接記述して解決した。以下はサンプルプログラムである。なんとなくではあるが解説を入れていく。brokerはpublidherと兼任。 pub.py †#テスト用のpublidherプログラム #!/usr/bin/env python #この場合python2.7が起動する(はずである:16/02/12時点 バージョンしだいでは変わるかも) from time import sleep import paho.mqtt.client as mqtt #python用のライブラリpahoをimport host = 'localhost' #hostにはbrokerのIPadressを記述、兼任なのでlocalに port = 1883 #portはデフォルトで topic = 'pi/st' #topicは適当に、/を使って階層分けすると複雑なものを組む時に便利 client = mqtt.Client(protocol=mqtt.MQTTv311) #インスタンスを作成する。今回は1つだけなので単純に名付ける client.connect(host, port=port, keepalive=60) #クライアントに接続する for i in range(3): #三回、topicに5を送信する、5に特に意味はない client.publish(topic, 5) sleep(0.2) client.disconnect() #終わり次第切断する sub.py †#テスト用のsubscriberプログラム #!/usr/bin/python from time import sleep import paho.mqtt.client as mqtt host = '0.0.0.0' #brokerのIPaderssを入れる port = 1883 topic = 'pi/st' def on_connect(client, userdata, flags, respons_code): #接続確立時の動作を指定する print('status {0}'.format(respons_code)) #MQTTでは接続を行った際にstatus codeを返す 0なら正常 client.subscribe(topic) #topicをsubscribe(購読)する
def on_message(client, userdata, msg): #メッセージを受け取った時の動作を指定する print('OK!') #メッセージを受信したらOK!と返す if __name__ == '__main__': client = mqtt.Client(protocol=mqtt.MQTTv311) #インスタンスを作成する。今回は1つだけなので単純に名付ける client.on_connect = on_connect #各々を定義する client.on_message = on_message client.connect(host, port=port, keepalive=60) #接続を行う client.loop_forever() #ずっと購読をしつづける 今回の課題においてMQTTはただ、『これから投げるよー』と伝えるためだけである。ここでペアのプログラムのon_connectを示すと def on_message(client, userdata, msg): t = 0.4 time.sleep(t) swing() となっている。メッセージを受信し何秒か後にバットをふっているだけである。ここに多く記述すれば複雑な場合も容易であると思われる。 しかしながら、この方法には課題がある。通信によるラグである。今回のパターンでは持ち込んだwifiを使用していたが、この状況であってもメッセージが届くのにラグが存在する。メッセージを受信した時にアームを振るという組み方ではラグが存在するときにはタイミングがずれてしまう。これは原理的にどうしようもないラグである。解決策は時刻規制が容易であると思われる。メッセージを受信したら、ではなくたとえば次の00秒に、という情報をやり取りしそのタイミングで動作を行えばずれる心配はないものと思われる。 原因不明のエラーについて †今回、間に合わなかった最大の原因は当日になって現れた原因不明のエラーである。最終的にOSをクローンして強引に解決を行った。現状わかっていることは、ある状態においてpython3ではpahoが使えず、python2ではev3devが読み込めないということである。OSの再インストールやパッケージの導入のし直しをすれば直るとは思われるが検証はできていない。 #!/usr/bin/env python3 と記述しているが、test2.pyでは #!/usr/bin/env python としている。前回の課題ではLCDを使うためにpython3に変えたが、なぜかpython3ではpahoがうまくimportできない。なのでpython2と3を使い分けて記述している。move.pyをpython3で記述する必要はない?君のような勘のいいガキは嫌いだよ!! |