目次
#contents
*はじめに [#neee5304]
今回はまったく余裕がなく、新しいことに多数挑戦したため自分だけの試走はともかく相方の機体との通信を伴う試走に入れたのは当日になってからだった。~
今回はev3dev(2016-10-17版)にMQTTをインストールした。前回と同様にSSH接続でプログラミングをし、下書き用にPyCharmを利用した。&size(7){今回の課題においてはWifiの安定性が非常に重要であった、ラグによって結果が変わってしまうため、別の工夫を行えばよかったと後悔している};

*課題3について [#f778acdb]
今回の課題は野球である。具体的に何を行うかといえばピッチャー、バッターに分かれて移動し、通信を行って野球盤のようなものを行う。&size(7){試験後の短期間にこれを2人で二台は時間が圧倒的に足りない・・・};~
自分はピッチャーを作成し、プログラムを行った。
**動きについて [#u7f552fa]
今回の機体のコンセプトはロマンである。時間はなかったが使ってみたかった履帯を使ってみたかったためやってみた。履帯の弱点は精密な角度制御が難しい点であった。逆に利点は、重く重心が高い機体が安定することである。
#ref(2016b/Member/being/Mission3/第一段階.jpg,50%,第一段階)
前回ジャイロセンサーを使えるようになたため今回はジャイロセンサーで旋回角度を制御した。一番最初に機体を45度回転させる。~
次に機体後部に取り付けたカラーセンサーで黒い線の数を数える。
#ref(2016b/Member/being/Mission3/第2段階.jpg,100%,第2段階)
4本目の線を検知したら機体を止める。この時、高速で移動しているためモーターを止めた後も機体が重くとゴムがなく滑りやすい履帯のせいで暫くは滑ってしまう。これを修正するために一回低速で機体を下げ基準を合わせ、一定角度進みそこから90度旋回した後にもう一度機体を下げ、また光センサーで黒線を検知し一定角度進むことで位置を合わせる。こうすることで位置をある程度合わせることができる。
*機体について [#v9b964a0]
**移動について [#nce5c464]
#ref(2016b/Member/being/Mission3/機体概観1.jpg,100%,機体概観1)
サイズ制限に収めるために機体は縦に長く、重心が高くなっている。今回はペアがびっくりドッキリメカを作ったため4つモーターを使っている。

#ref(2016b/Member/being/Mission3/P_20170207_170041.jpg,50%,機体後部)
機体の後部にカラーセンサーを搭載している。
**再装填機構について [#aba113e4]
#ref(2016b/Member/being/Mission3/P_20170207_170249.jpg,50%,玉設置部)
ここに玉を落とし込むと、指してあるピンによって適当な位置に玉が固定される。
#ref(2016b/Member/being/Mission3/P_20170207_170259.jpg,50%,再装填部)

再装填を行う時以外は再装填アームが玉を押し上げることによって予期せぬタイミングに玉が出てしまうことを防ぐ。また、揺れ動くパーツにより再装填時に玉がはねて装填に失敗することがないようにしている。
#ref(2016b/Member/being/Mission3/P_20170207_170241.jpg,100%,再装填アーム)
このアームはただ往復動作をするだけである。あまり強度はない。
**投球について [#s4379974]
#ref(2016b/Member/being/Mission3/P_20170207_170203.jpg,100%,ピン設置部)
ここにあるピンを用いて玉の置き場所を確定させる。
#ref(2016b/Member/being/Mission3/P_20170207_170309.jpg,100%,投球部)
設置された玉をこのアームで打ち出す。モーターの出力で飛距離を調整可能である。&size(7){斜めになっているガイドを交換することで地面を転がすことも、カーブも可能である};~

*プログラムについて [#o32c6568]
以下のプログラムは''CC BY-SA 4.0''とします。ライセンスは[[こちら:https://creativecommons.org/licenses/by-sa/4.0/legalcode]]&size(7){htmlタグが使えないためこのように表記しておきます};
**自作関数について [#g43092a9]
*move.py [#pf33231b]
 #!/usr/bin/env python3
***move.py [#pf33231b]
 #!/usr/bin/env python3      #このプログラムはpython3で記述されている
 import ev3dev.ev3 as ev3
 import time
 import time                

 mr = ev3.LargeMotor('outB')
 mr = ev3.LargeMotor('outB') #ここから↓
 ml = ev3.LargeMotor('outC')

 cs = ev3.ColorSensor('in1')
 gy = ev3.GyroSensor('in2')
 gy = ev3.GyroSensor('in2')  #ここまで↑テンプレ

 limen = 20
 limen = 20         #ライトセンサーの閾値、やはり優秀で、しっかりと黒に塗られていれば一桁の値を返す。しかし、今回の会場の環境がどうなっているのかわからないので安全を見る(苦い思い出)

 def move_to_Q():
     cs_count= 0
     gy_old = gy.value()
     while abs(gy.value() - gy_old) < 45:
         mr.run_forever(speed_sp=200)
 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:
         print(cs.value())
         mr.run_forever(speed_sp=500)
     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:
         if cs.value() < limen:        #カラーセンサーが黒線を検知したらカウンターに1を足す
             cs_count = cs_count + 1
         while cs.value() < limen:
             print(cs_count)
         while cs.value() < limen:       #カラーセンサーが黒線を抜けるまではここでとめておく、そうしないと一本の黒線でカウンタが埋まってしまう。
             print(cs_count)          #デバッグのためにPCに現在のカウンタの値を表示
     mr.stop()
     ml.stop()
     time.sleep(1)
     ml.stop()                 #出力停止
     time.sleep(1)               #機体が縦に大きく、重心が高いため動揺を抑える
     mr.reset()
     ml.reset()
     while cs.value() > limen:
     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 [#f95ab6b9]
***test2.py [#f95ab6b9]

 #!/usr/bin/env python
 import paho.mqtt.client as mqtt
 #!/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' 
 port = 1883
 topic = 'pi/st'
 
 arm = ev3.LargeMotor('outA')
 bolthandle = ev3.MediumMotor('outD')
 
 def reset_arm(t):
     arm.run_forever(speed_sp=60)
     time.sleep(t)
 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():
     arm.run_forever(speed_sp=-600)
     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():
     bolthandle.run_forever(speed_sp=60)

 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              #接続時の処理を実行
 
 def throw():
     reset_arm(2)
     send_message(5)
     Feuer()
     reset_arm(4)
     nachladen()
 move. move_to_Q()                     #move.pyからmove_to_Qを実行する
 for i in range(100):                   #100回玉を投げる(千本ノックならぬ百本ノック)
     throw()
     time.sleep(1.5)
 
 def on_connect(client, userdata, flags, respons_code):
     print('status {0}'.format(respons_code))
     client.subscribe(topic)
 client.disconnect()                    #接続を切る

*結果 [#c69b588a]
試走が足りない・・・圧倒的に足りない・・・という状況になった、なってしまった。&size(7){全部試験が悪いんや・・・};~
今回は今までと異なりロマン、ネタに走った。得点を稼ぐことより野球を再現しようとした。転がすのではなく投げて打ちたかった。それだけを考えて機体を作り、プログラムをした。~
完全に調整不足であったのでなかなか成功しなかったが、後半日あれば(というか原因不明のエラーがなければ)もっとうまく動作しただろう。

*感想 [#ff9e9ffa]
時間不足でありさらにロマンに極振りしたため点数は低く、完成度は低いものになっていたがMQTTの使い方を学んだことは大きな進歩であった。今までの機体を基本的に自分一人で組み上げていたことを少々後悔してはいる。何はともあれ、ロマンは大事である。


*雑記 [#rfe85408]
モーターのduty_cycle_sp値を直接いじらずに制御するために[[ここby Takahiro Ikeuchi:https://librabuch.jp/blog/2015/09/mosquiito_paho_python_mqtt/]]を参考にした。(環境に合わせるのために一部改変してある。)
**MQTTについて [#t501bbfc]
当初はメッセージを受信した時に動作を行う方法がわからなかったが、on_messageに直接記述して解決した。以下はサンプルプログラムである。なんとなくではあるが解説を入れていく。brokerはpublidherと兼任。
***pub.py[#bd731158]
                                                           #テスト用の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つだけなので単純に名付ける
 
 def send_message(m):
     client.publish(topic, m)
 client.connect(host, port=port, keepalive=60)       #クライアントに接続する
 
 for i in range(3):                    #三回、topicに5を送信する、5に特に意味はない
     client.publish(topic, 5)
     sleep(0.2)
 
 client = mqtt.Client(protocol=mqtt.MQTTv311)
 client.connect(host, port=port, keepalive=60)
 client.on_connect = on_connect
 client.disconnect()                    #終わり次第切断する


***sub.py [#xec35d9c]
                                                          #テスト用の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__': 
 
 cs_count = 0
     client = mqtt.Client(protocol=mqtt.MQTTv311)     #インスタンスを作成する。今回は1つだけなので単純に名付ける
     client.on_connect = on_connect            #各々を定義する
     client.on_message = on_message 
 
 move. move_to_Q()
 time.sleep(1)
 for i in range(100):
     throw()
     time.sleep(1.5)
     client.connect(host, port=port, keepalive=60)        #接続を行う
 
 client.disconnect()
    client.loop_forever()                #ずっと購読をしつづける

今回の課題においてMQTTはただ、『これから投げるよー』と伝えるためだけである。ここでペアのプログラムのon_connectを示すと
 def on_message(client, userdata, msg):
     t = 0.4
     time.sleep(t)
     swing()
となっている。メッセージを受信し何秒か後にバットをふっているだけである。ここに多く記述すれば複雑な場合も容易であると思われる。
しかしながら、この方法には課題がある。通信によるラグである。今回のパターンでは持ち込んだwifiを使用していたが、この状況であってもメッセージが届くのにラグが存在する。メッセージを受信した時にアームを振るという組み方ではラグが存在するときにはタイミングがずれてしまう。これは原理的にどうしようもないラグである。解決策は時刻規制が容易であると思われる。メッセージを受信したら、ではなくたとえば次の00秒に、という情報をやり取りしそのタイミングで動作を行えばずれる心配はないものと思われる。
**原因不明のエラーについて [#y6dfdf51]
今回、間に合わなかった最大の原因は当日になって現れた原因不明のエラーである。最終的にOSをクローンして強引に解決を行った。現状わかっていることは、ある状態においてpython3ではpahoが使えず、python2ではev3devが読み込めないということである。OSの再インストールやパッケージの導入のし直しをすれば直るとは思われるが検証はできていない。~
また、
move.pyでは
 #!/usr/bin/env python3 
と記述しているが、test2.pyでは
 #!/usr/bin/env python
としている。前回の課題ではLCDを使うためにpython3に変えたが、なぜかpython3ではpahoがうまくimportできない。なのでpython2と3を使い分けて記述している。&size(7){move.pyをpython3で記述する必要はない?君のような勘のいいガキは嫌いだよ!!};~


トップ   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS