EV3を動かしてみる (2) 〜 pythonスクリプトを作る 〜

ロボティクス入門ゼミ 教材 2017-10-20 (最新版 2018-10-21)

pythonスクリプトの作成

テキストファイルを編集するためにエディタと呼ばれるソフトを使う。 どのエディタを使ってもよいが、以下では、軽量の nano というソフトを使う。

まずsshでEV3にログインし、自分の作業用のディレクトリを作成しておく。 下の例では work という名前のディレクトリを作成しているがどのような名前でもよい。

robot@ev3dev:~$ mkdir work
robot@ev3dev:~$ ls
robot@ev3dev:~$ cd work
robot@ev3dev:~/work$ pwd

nanoを起動する。 シュルで直接 nano というコマンドを入力する。

robot@ev3dev:~/work$ nano

起動すると次のような画面になる。

  GNU nano 2.2.6               New Buffer









^G Get Help  ^O WriteOut  ^R Read File ^Y Prev Page ^K Cut Text   ^C Cur Pos
^X Exit      ^J Justify   ^W Where Is  ^V Next Page ^U UnCut Text ^T To Spell

下のメニューに記されている ^ はcontrolキーを表している。 例えば、現在編集しているファイルを保存するには、controlキーを押しながら O を入力する。終了するには、controlキーを押しながら x を入力する。

まず、次の2行を入力する。 1行目は python3のプログラムとして実行するために必要で、 2行目は python-ev3dev (使用しているev3dev用に書かれたpythonライブラリ) を読みこんで使用するために必要である。 ライブラリを読むことでEV3のモータやセンサなどの「クラス」が使えるようになる。

#!/usr/bin/env python3
from ev3dev.ev3 import *

さらに、A端子に接続したモータを回転させる命令を追加してみる。 そのためにはモータの「インスタンス」を作り、モータを回転させる命令を 実行させる必要がある。

#!/usr/bin/env python3
from ev3dev.ev3 import *
m = LargeMotor('outA')
m.run_timed(time_sp=3000, speed_sp=500)

この例では、m という名前をつけてA端子に接続したモータのインスタンスを作り、 プロパティ speed_sp に指定された時間だけモータを回転させる run_timed というメソッド(関数)を使った。 また、初期状態では speed_sp の値は 0 なので、それも同時に指定した。

とりあえずこの4行が書けたらファイルを保存する。

ファイルの保存は、メニューで表示されているように、 controlキーを押しながら o を入力する。 まだファイルに名前をつけていなかったので、 「File Name to Write: 」と聞かれる。 そこでファイル名(例えば"myprog-01.py")を入力してエンターキーを押す。 ファイル名には日本語を用いず、半角英数字と数字、ハイフン、アンダースコアを使う。 また、pythonスクリプトなので、.py という拡張子をつける。

nanoの終了は、controlキーを押しながら x を入力する。 その他、終了をキャンセルする場合にはcontrolキーを押しながら c を入力、などメニューを見ればわかる。

nanoが正しく終了すれば、再びシェルのプロンプトに戻る。

robot@ev3dev:~/work$

ファイルが正しく保存されたかどうか確認しておく。

robot@ev3dev:~/work$ ls

pythonスクリプトの実行

このスクリプトを実行するためには、 さらにファイルのパーミッションを実行可能にする必要がある。

まず現在のパーミッションの状態を確認。

robot@ev3dev:~$ ls -l

ここで ls に -l (マイナス・エル)というオプションをつけることで long format (長い書式) でファイル一覧が表示される。 次のようにファイル名だけでなく、所有者、サイズ、変更日時などが 表示されればOK。

-rw-r--r-- 1 robot robot 111 Oct 16 06:23 myprog-01.py

パーミッション情報は行の最初に表示されている。 最初のマイナス記号に続く3つのフィールドは、 ファイルのユーザ(所有者)に対して、 読み込み許可が与えられているか(r)、 書き込み許可が与えられているか(w)、 実行許可が与えられているか(x)、を表している。 さらに続く3つのフィールドはそれらの権限がグループに対して与えられているか、 さらに最後の3つのフィールドは他人に対して権限が与えられているかどうかを表す。 つまり、上の例(myprog-01.pyというファイル)の場合、 読み込み許可は、所有者、グループ、他人に対して与えられていて、 書き込み許可は、所有者だけに与えられている。

このファイルを chmod というシェルコマンドを使って実行可能に変更する。

robot@ev3dev:~/work$ chmod +x myprog-01.py

再度 ls -l で表示させて、次のように実行許可(x)が付与されていればOK。

robot@ev3dev:~/work$ ls -l myprog-01.py
-rwxr-xr-x 1 robot robot 111 Oct 16 06:38 myprog-01.py

ちなみに上記の +x の代わりに 755 と指定しても同じ結果になる。

robot@ev3dev:~/work$ chmod 755 myprog-01.py
-rwxr-xr-x 1 robot robot 111 Oct 16 06:40 myprog-01.py

ファイルの所有者以外に読み書き実行をすべて拒否する場合には 755 の代わりに 700 を指定する。

robot@ev3dev:~/work$ chmod 700 myprog-01.py
-rwx------ 1 robot robot 111 Oct 16 06:42 myprog-01.py

このファイル(スクリプト)を実行するには、基本的にはファイル名を入力するだけでよいが、 ファイルがどこにあるファイルか、つまり「パス」を含めて指定する、ということに注意する。 ただし、現在の作業ディレクトリにある場合は、単に ./ をファイル名の前につけるだけでよい。

robot@ev3dev:~/work$ ./myprog-01.py

なお、ファイル名を入力する時、シェルの補完機能を使うと便利である。 m で始まるファイルが他に無い場合には、 m だけ入力してtabキーを押せば自動的にファイル名が補完される。 他にmで始まるファイル名がある場合には、さらにtabキーを押すことで 候補を表示してくれる。

エンターキーを押した後、しばらくしてモータが3秒間回り続ければOK。 うまく回らない場合、再度ファイルをチェックし、間違いがあれば修正する。

nano の後に続けてファイル名を入力することで、直接そのファイルを編集できる。

robot@ev3dev:~/work$ nano myprog-01.py

編集・保存したら、再度実行してみる。 シェルで上の矢印キーを押せば、以前入力したコマンドが表示されるので便利。

モータのいろいろな動かし方

これまでの例では、スピードと回転時間をrun_timedというメソッドの 引数として指定し、モータを回転させた。 別の方法として、次の例のように speed_sp や time_sp というプロパティを 前もって指定しておくこともできる。

#!/usr/bin/env python3
from ev3dev.ev3 import *
m = LargeMotor('outA')
m.time_sp = 1000
m.speed_sp = 300
m.stop_action = 'brake'
m.run_timed()

上の例では stop_action も指定した。 stop_action は 'coast' 'brake' 'hold' のいずれかを指定できる。

ちなみに最後の命令 m.run_timed() の代わりに、m.command = 'run-timed' でも同じ結果になる。

いずれの方法、指定された プロパティの値は、そのまま残っているので、 新規にプログラムを走らせる時は、 reset()メソッドを使って一旦モータの各種設定をリセットしておく方が間違いが少ない。

#!/usr/bin/env python3
from ev3dev.ev3 import *
m = LargeMotor('outA')
m.reset()
m.run_timed(time_sp=3000, speed_sp=500, stop_action='brake')

現在の位置から決められた角度だけモータを回す (run to relative position)。

#!/usr/bin/env python3
from ev3dev.ev3 import *
m = LargeMotor('outA')
m.reset()
m.run_to_rel_pos(position_sp=360, speed_sp=500, stop_action='hold')

stop_action を 'hold にした場合と 'brake にした場合を比較しておく。

#!/usr/bin/env python3
from ev3dev.ev3 import *
m = LargeMotor('outA')
m.reset()
m.run_to_rel_pos(position_sp=360, speed_sp=500, stop_action='brake')

'brake' の場合は、360度でブレーキをかけても、丁度360度では止まらない、ということに注意する。 一方、'hold' の場合は、360度からずれたらすぐに360度になるように強制的に調整してくれる。

run_to_rel_pos() に対し、run_to_abs_pos() はモータをリセットした時点の位置を基準(0度)として、 そこからの指定した角度になるように回転させる。

#!/usr/bin/env python3
from ev3dev.ev3 import *
m = LargeMotor('outA')
m.reset()
m.run_to_abs_pos(position_sp=360, speed_sp=500, stop_action='hold')

あらかじめ角度をずらしておいて、それでもきちんと指定した角度まで回るかどうか確認する。 この際、stop_action が 'hold' なら手でモータを回せないので、シェルからリセットしておくか stop_action='coast' を指定したプログラムを走らせておく。シェルからリセットするには、

robot@ev3dev:~/work$ echo reset > /sys/class/tacho-motor/motor0/command

左右のモータを同時に回す。

#!/usr/bin/env python3
from ev3dev.ev3 import *
mL = LargeMotor('outA')
mR = LargeMotor('outB')
mL.reset()
mR.reset()
mL.run_timed(time_sp=3000, speed_sp=500, stop_action='brake')
mR.run_timed(time_sp=3000, speed_sp=500, stop_action='brake')

run_timed() の変えて run_forever() を使ってみる。 sleep関数を使うために time ライブラリをインポートする。

#!/usr/bin/env python3
from ev3dev.ev3 import *
from time import sleep
mL = LargeMotor('outA')
mR = LargeMotor('outB')
mL.reset()
mR.reset()
mL.run_forever(speed_sp=500, stop_action='brake')
mR.run_forever(speed_sp=500, stop_action='brake')
sleep(3)
mL.stop()
mR.stop()

sleep()の引数である時間の単位は1/1000秒ではなくて、1秒なので注意する。 つまり、0.1秒スリープしたい場合は sleep(0.1) とする。

左右のタイヤを反転させて、その場で旋回させる。

#!/usr/bin/env python3
from ev3dev.ev3 import *
from time import sleep
mL = LargeMotor('outA')
mR = LargeMotor('outB')
mL.reset()
mR.reset()
mL.run_forever(speed_sp=500, stop_action='brake')
mR.run_forever(speed_sp=-500, stop_action='brake')
sleep(3)
mL.stop()
mR.stop()

関数を定義する

一連の動作は「関数」として定義しておく。 関数にしておくことで、毎回同じような命令を書かなくてもよくなる。

まず「3秒間前進する」という動作を関数にしてみる。

#!/usr/bin/env python3
from ev3dev.ev3 import *
from time import sleep

mL = LargeMotor('outA')
mR = LargeMotor('outB')

def move_forward():
    mL.run_forever(speed_sp=500, stop_action='brake')
    mR.run_forever(speed_sp=500, stop_action='brake')
    sleep(3)
    mL.stop()
    mR.stop()
    
mL.reset()
mR.reset()
move_forward()
sleep(1)
move_forward()

関数定義のためのキーワード def に続いて関数名を書き、最後の : を忘れないようにする。 2行目からは、インデント(字下げ)をして命令を書くことに注意。 pythonの場合、関数に限らず一つのブロックを指定するのに { } は使わず、 このように字下げで指定する。

関数は、単に関数名(と())で呼び出せる。

3秒間前進し、1秒間停止、さらに3秒間前進すればOK。

run_forever() の代わりに run_timed() を使ってみる。

#!/usr/bin/env python3
from ev3dev.ev3 import *
from time import sleep

mL = LargeMotor('outA')
mR = LargeMotor('outB')

def move_forward():
    mL.run_timed(time_sp=3000, speed_sp=500, stop_action='brake')
    mR.run_timed(time_sp=3000, speed_sp=500, stop_action='brake')
    
mL.reset()
mR.reset()
move_forward()
sleep(1)
move_forward()

実はこのプログラムでは、途中で1秒間停止することなく4秒間連続で動く。 これは、run_timed という命令自体には時間がかからず、 命令を発したらすぐに次の命令に移るからである。 簡単な修正方法は、関数の中に sleep をいれてモータの回転時間と同じだけ待たせる方法である。

#!/usr/bin/env python3
from ev3dev.ev3 import *
from time import sleep

mL = LargeMotor('outA')
mR = LargeMotor('outB')

def move_forward():
    mL.run_timed(time_sp=3000, speed_sp=500, stop_action='brake')
    mR.run_timed(time_sp=3000, speed_sp=500, stop_action='brake')
    sleep(3000)
    
mL.reset()
mR.reset()
move_forward()
sleep(1)
move_forward()

モータのリセットも初期化の関数として定義しておくと分かりやすい。

#!/usr/bin/env python3
from ev3dev.ev3 import *
from time import sleep

mL = LargeMotor('outA')
mR = LargeMotor('outB')

def motor_init():
    mL.reset()
    mR.reset()

def move_forward():
    mL.run_timed(time_sp=3000, speed_sp=500, stop_action='brake')
    mR.run_timed(time_sp=3000, speed_sp=500, stop_action='brake')
    sleep(3000)
    
motor_init()
move_forward()
sleep(1)
move_forward()

上の関数を改良して、モータの回転時間を自由に指定できるようにしてみる。 そのためには、関数を呼び出す時にその関数に伝える変数や数(これを「引数」とよぶ) が使えるようにする。

#!/usr/bin/env python3
from ev3dev.ev3 import *
from time import sleep

mL = LargeMotor('outA')
mR = LargeMotor('outB')

def motor_init():
    mL.reset()
    mR.reset()

def move_forward(t):
    mL.run_timed(time_sp=t, speed_sp=500, stop_action='brake')
    mR.run_timed(time_sp=t, speed_sp=500, stop_action='brake')
    sleep(t/1000)
    
motor_init()
move_forward(1000)
sleep(1)
move_forward(2000)
sleep(1)
move_forward(3000)

move_forwardの中のsleep関数の引数が t/1000 になっていることに注意する。

ところで、現在作成しているようなpythonのスクリプトは、他のスクリプトから import して使うこともできる。 その際、関数の定義だけを読み込んで、importされたスクリプトに記述してある実際の動作はさせたくない場合がよくある。 そのような時のことを考慮して、次のように、自分自身が直接シェルから呼ばれた時にだけ動作するように改良しておく。

#!/usr/bin/env python3
from ev3dev.ev3 import *
from time import sleep

mL = LargeMotor('outA')
mR = LargeMotor('outB')

def motor_init():
    mL.reset()
    mR.reset()

def move_forward(t):
    mL.run_timed(time_sp=t, speed_sp=500, stop_action='brake')
    mR.run_timed(time_sp=t, speed_sp=500, stop_action='brake')
    sleep(t/1000)

if __name__ == '__main__':
    motor_init()
    move_forward(1000)
    sleep(1)
    move_forward(2000)
    sleep(1)
    move_forward(3000)

if文の意味は、モジュールの名前が '__main__'、つまり直接実行されたスクリプトであるときだけ (このとき自動的に'__main__'という名前になる)、ifのブロックが実行される、ということである。

【練習問題】正三角形、正方形、正五角形、正六角形を描いて動くロボットを作りなさい。 ただし、move_forward の他に、turn_left あるいは turn_right のような関数も定義し 1辺の距離は自由に設定できるようにすること。