Pythonロボティクスコース レッスン 22

テーマ.6-1 2つのDCモーターを使用した車型ロボットの制作

車型ロボットを制作して前後左右に走行するプログラムを書こう

テーマ6を通して学ぶこと

テーマ6では全体を通して、オブジェクト指向のプログラム言語がもつ様々な特長を学び、それらを利用して効率的にプログラムを書く方法を詳しく見ていきます。はじめに、テーマ.6-1では車型ロボットを製作し、その制御を行うための「クラス」を定義します。続くテーマ.6-2~テーマ.6-4では、そのクラスを「クラスの継承」や「メソッドのオーバーライド」を利用して拡張し、「線に沿って走るライントレース機能」や「コントローラーで操作できる機能」を作成していきます。「継承」と「オーバーライド」の2つの新しい言葉が出てきましたが、順番に説明をしていきますので、まずはこのレッスンでクラスについて復習していきましょう。ここまでできたらクリック

このレッスンで学ぶこと

このレッスンでは、レッスン8(テーマ.2-3)で学習したプロパティとメソッドをもつオブジェクトの型となる「クラス」について振り返りを行います。そして、2つのDCモーターを使用して車型ロボットを製作し、その走行を制御するためのプロパティやメソッドをもつ独自のクラスを定義します。また、関数やメソッドの内容を変更せずに機能を拡張できる「デコレータ」という便利な仕組みについても合わせて学習します。

Pythonの文法の復習

ここでは、簡単なサンプルプログラムを通して、クラスについて復習しましょう。

3. 1 クラスについて

クラスは例えるなら、クッキーの型抜き器のようなものです。型抜き器が1枚1枚のクッキーの形を決めるのと同じように、クラスは1つ1つのオブジェクトの型を定義しています。同じクラスから作成されたオブジェクトは同じ名前のプロパティとメソッドを持っています。このようにクラスによってつくられたオブジェクトは「インスタンス」と呼ばれます。

3. 2 クラスの定義方法

クラスを定義するときは「class」というキーワードを使います。「 :(コロン)」の後にインデントを入れた部分がクラスの適用範囲になっていて、この中で定義された変数や関数は、「プロパティ」や「メソッド」と呼ばれます。

class MyClass:	# クラス名の定義

    property = "..."	# プロパティの定義

    def method(self, ...): 	# メソッドの定義
        .
        .

例えば、次の「犬」の特徴をまとめたクラスを新たに定義する場合、次のようにコードを書きます。

【 サンプルコード 3-2-1 】

プロパティは変数を定義するときと全く同じですが、メソッドは関数の定義と違うところがあります。それは、必ず1番目の引数として「self」を渡すという点です。このselfはインスタンス自身を表しており、「.(ドット)」を使ってプロパティやメソッドを呼び出すことができます。

※ 第1引数にself以外の名前を付けても問題なくインスタンス自身が受け渡されます。しかし、特別な理由がない限りは分かりやすさからselfとしておきましょう。

3. 3 クラスからインスタンスを作成する方法

インスタンスを作成するときは、次のように書きます。

インスタンス名 = クラス名()

実際に【 サンプルコード 3-2-1 】を実行し、Dogクラスのインスタンスを作成して、プロパティやメソッドを呼び出してみましょう。また、メソッドの第1引数のselfは自動的に渡されるため、呼び出すときに指定する必要はありません。

3. 4 コンストラクタという特別なメソッド

クラスからインスタンスを作成するときに自動的に実行されるメソッドを「コンストラクタ」と呼びます。この特別なメソッドは、「__init__」という名前で定義します。

class MyClass:	# クラス名の定義

    def __init__(self, ...): 	# コンストラクタ
        .
        .

通常、コンストラクタの中では、インスタンスが独自に持つプロパティを設定したり、初期処理として必要なメソッドを実行したりします。

例として、【 サンプルコード 3-2-1 】に犬の名前をプロパティとして加えるコンストラクタを追加してみましょう。

【 サンプルコード 3-4-1 】
追加・変更【4行目、5行目、8行目】

では、【 サンプルコード 3-4-1 】を実行して、2匹の犬「Taro」と「Jiro」を作成し、それぞれbark()メソッドを実行してみましょう。

voiceがすべてのインスタンスが共通して同じ値をもつプロパティであるのに対し、nameはインスタンスごとに違う値をもつプロパティです。voiceのようなプロパティは「クラスメンバ変数」、nameのようなプロパティは「インスタンスメンバ変数」と呼ばれ、区別されています。ここまでできたらクリック

新しいPython文法の学習

ここでは、簡単なサンプルプログラムを通して、「デコレータ」について学習しましょう。

4. 1 デコレータについて

デコレータは、「引数として関数を受け取り、新たに別の関数を返す関数」です。ちょっとわかりづらいですが、ポイントはデコレータは関数であり、次の3つの性質を持っているということです。

  1. 関数を引数として受け取る
  2. 内部で新たな関数を定義する
  3. 2の関数を戻り値として返す

では、具体的なデコレータの例を見てみましょう。まずは、複数の数値の足し算と掛け算を行い、その結果を戻す2つの関数を用意します。

これら2つの関数に新たにprint文で結果を表示する機能を追加したいとします。そのために次のデコレータを定義します。

追加【1行目~5行目】

デコレータを追加する方法は2つあります。1つは次のように、デコレータ関数を実行し、その戻り値を変数に格納して再び実行する方法です。

これだと少し手作業が増えてしまいます。もう1つの方法は、追加したい関数の直前に「@デコレータ関数名」を書くことです。こちらの方がコードもすっきりとします。

【 サンプルコード 4-1-1 】
追加【7行目、14行目】

この【 サンプルコード 4-1-1 】を実行して、それぞれの関数を呼び出してみましょう。

結果を表示する機能が見事に追加されています。このようにデコレータを利用することで、関数の内容を変更せずに機能を拡張することができるだけでなく、同じデコレータを別の関数へ再利用することができます。上の例では関数にデコレータを付与しましたが、メソッドにも付与することができます。以下の例は【 サンプルコード 3-4-1 】に、2回メソッドを実行するデコレータrepeat_twiceを付与したものです。

【 サンプルコード 4-1-2 】
追加【1行目~5行目、13行目】
(実行結果)

また、デコレータは重ねて付与することもできます。以下は上の例にさらに3回メソッドを実行するデコレータrepeat_three_timesを追加して付与しています。

【 サンプルコード 4-1-3 】
追加【7行目~11行目、19行目】
(実行結果)

車型ロボットの組み立て

それでは、ここからは車型ロボットの製作に入ります。早速、組立説明書を開き、手順に沿って車型ロボットの組み立てを行いましょう。

5. 1 組み立てに必要なパーツ

【 パーツ一覧 】
  • Studuino:bit×1
  • ロボット拡張ユニット×1
  • 電池ボックス×1
  • DCモーター×2
  • ブロック基本四角(黒)×2
  • ブロック基本四角(赤)×2
  • ブロック三角(赤)×2
  • ブロックハーフB(グレー)×1
  • ブロックハーフB(黒)×2
  • ブロックハーフB(赤)×2
  • ブロックハーフC(白)×4
  • ブロックハーフD(白)×6
  • ステー×4
  • 丸(目玉)×1
【 アーテックブロックの形状 】

5. 2 組立説明書

以下のリンク先から組立説明書を開いてください。

車型ロボットの組立説明書

車型ロボットのプログラム作成

ここからは、組み立てた車型ロボットの動作を制御するプログラムを作成していきます。

6. 1 復習:DCモーターを制御するためのメソッド

DCモーターの制御を行うDCMotorクラスには以下のメソッドがありました。

【 DCMotorクラスのメソッド一覧 】
メソッド名(引数)動作
__init__(pin)コンストラクタ(インスタンスを作成するときに最初に実行されるメソッド)。引数pinにはDCモーターを接続した先の端子名として、"M1"または"M2"を指定します。
power(power)引数powerに出力の大きさを「0~255」の範囲で指定して、DCモーターの速さを制御します。
cw()DCモーターを正転方向に回転します。
ccw()DCモーターを逆転方向に回転します。
stop()DCモーターを接続した出力端子を開放することで、ゆっくりと回転が止まります。
brake()DCモーターを接続した出力端子を短絡させて、ブレーキをかけて回転を止めます。

また、stop()メソッドとbrake()メソッドはどちらもDCモーターの回転を止める命令ですが、内部の電気制御の違いから、命令が実行されてから停止するまでに掛かる時間が異なります。

下の動画のように、stop()メソッドはそれまでの回転の勢いが残ったままゆっくりと停止し、brake()メソッドはその場ですぐに停止します。

【 stop()メソッドとbrake()メソッドの違い 】

6. 2 DCモーターの回転方向と車型ロボットの進行方向の関係

組み立てた車型ロボットでは、DCモーターの回転方向とタイヤの回転方向に次の図ような関係があります。

【 DCモーターの回転方向とタイヤの回転方向 】

そのため、左右2つのDCモーターそれぞれ次の組み合わせで回転させることで、車型ロボットの進行方向を制御します。

【 DCモーターの回転方向と車型ロボットの動作の関係 】
動作M1のDCモーター (左側のタイヤ)M2のDCモーター (右側のタイヤ)
前進逆転逆転
後退正転正転
左回転正転逆転
右回転逆転正転
左に曲がる停止逆転
右に曲がる逆転停止

「左回転」や「右回転」はその場で回り、「左に曲がる」や「右に曲がる」は、停止したタイヤを中心として、円弧を描くように回ります。「右や左への回転」は「右や左へ曲がる」ときと比べて、回るときに描く円の半径が半分になるため、およそ半分の時間で向きを変えることができます。

【 向きを変えるためのかかる時間の違い 】

6. 3 車型ロボットのクラス定義

これらの動作をそれぞれ関数として定義して利用することもできますが、プロパティ(属性情報)やメソッドをもつクラスとして定義する方が管理しやすく、また後のレッスンで車型ロボットの機能を拡張するときにも再利用しやすくなります。そこで、オリジナルのクラス「VehicleRobot」を新たに定義し、次のプロパティとメソッドを持たせましょう。

※ 英語で「車」のことを「vehicle」といいます。
【 VehicleRobotクラスで定義するプロパティ 】
プロパティ名役割
dcm_l左のタイヤに取り付けたDCモーターを扱うために、
DCMotorクラスのインスタンスを格納する。
dcm_r右のタイヤに取り付けたDCモーターを扱うために、
DCMotorクラスのインスタンスを格納する。
【 VehicleRobotクラスで定義するメソッド 】
メソッド名振る舞い
__init__()初期化関数。2つのDCMotorクラスのインスタンスや、
初期のDCモーターの速さを設定する。
move_forward()前進する
move_backward()後退する
rotate_left()左方向へその場で回転する
※左右のタイヤは互いに反対向きに回転
rotate_right()右方向へその場で回転する
※左右のタイヤは互いに反対向きに回転
curve_left()左方向に曲がる
※左のタイヤは停止し、右のタイヤのみ回転
curve_right()右方向に曲がる
※左のタイヤのみ回転し、右のタイヤは停止
stop()ブレーキをかけずにゆっくりと止まる
set_speed_to_left()左のタイヤの回転の速さを1~10の10段階で設定する
set_speed_to_right()右のタイヤの回転の速さを1~10の10段階で設定する
※「rotate」は、日本語で「回転する」を意味しています。

■ コンストラクタ(__init__()メソッド)の定義

コンストラクタである__init__()メソッドでは、DCMotorクラスのインスタンスを作成して、プロパティに格納する処理を行います。DCMotorクラスのインスタンスを作成するときは引数として端子名の文字列("M1"または"M2")が必要なため、__init__()メソッドでも同じように、左と右のタイヤを取り付けたDCモーターの接続先の端子名を引数として受け取るようにしましょう。

4行目で、__init__(self, *, pin_l, pin_r)と定義しているように「*,」より後ろの引数はキーワード引数として強制されるようになります。そのため、このクラスのインスタンスを作成するときは次のようにコードを書きます。

続けて、作成したDCMotorクラスのインスタンスのpower()メソッドを呼び出して、初期のDCモーターの出力の大きさを設定します。ここでは、100としておきましょう。また、プロパティを呼び出すときは必ず先頭にself.を付けることに注意してください。

追加【7行目、8行目】

■ 前進・後退を行うメソッドの定義

次に、車型ロボットの前進と後退のメソッドを定義します。

動作メソッド名self.dcm_l(左側のタイヤ)self.dcm_r(右側のタイヤ)
前進move_forward()ccw():逆転ccw():逆転
後退move_backward()cw():正転cw():正転

上の表を確認して、それぞれ次のように定義しましょう。

追加【10行目~16行目】

■ 左回転・右回転を行うメソッドの定義

続けて、車型ロボットの左回転と右回転のメソッドを定義します。

動作メソッド名self.dcm_l(左側のタイヤ)self.dcm_r(右側のタイヤ)
左回転rotate_left()cw():正転ccw():逆転
右回転rotate_right()ccw():逆転cw():正転

上の表を確認して、それぞれ次のように定義しましょう。

追加【18行目~24行目】
※ インデントの位置に注意してください。

■ 左に曲がる・右に曲がるを行うメソッドの定義

今度は、車型ロボットが左に曲がるときと右に曲がるときのメソッドを定義します。

動作メソッド名self.dcm_l(左側のタイヤ)self.dcm_r(右側のタイヤ)
左に曲がるcurve_left()barake():停止ccw():逆転
右に曲がるcurve_right()ccw():逆転brake():停止

上の表を確認して、それぞれ次のように定義しましょう。

追加【26行目~32行目】

■ 停止を行うメソッドの定義

また、動作を停止する2つのメソッドも定義します。

動作メソッド名self.dcm_l(左側のタイヤ)self.dcm_r(右側のタイヤ)
ブレーキをかけずにゆっくりと止まるstop()stop()stop()
ブレーキをかけてピタっと止まるbrake()brake()brake()

上の表を確認して、それぞれ次のように定義しましょう。

追加【34行目~40行目】

■ タイヤの回転の速さを設定するメソッドの定義

最後に、タイヤの回転の速さを制御するためのメソッドを定義します。

動作メソッド名self.dcm_l(左側のタイヤ)self.dcm_r(右側のタイヤ)
左のタイヤの回転の速さを1~10の10段階で設定するset_speed_to_left(speed)power(power)なし
右のタイヤの回転の速さを1~10の10段階で設定するset_speed_to_right(speed)なしpower(power)

これらのメソッドでは、引数speedに1~10を指定して10段階でタイヤの回転の速さを制御します。しかし、このメソッドの内部で実行するDCMotorクラスのpower()メソッドは引数powerに0~255の範囲で出力の大きさを指定します。そのため、メソッド内部で数値の大きさを次の式であらじめ変換しておきます。

この式によって、以下の図のように速さを出力の大きさへ置き換わります。

また、引数speedに整数以外の値が入力されたり、範囲外の値が指定されるとエラーが発生しますので、あらかじめメソッドの内部で回避しておきましょう。

追加【42行目~53行目】

■ クラス定義の確認

これで、クラスに必要なプロパティとメソッドが定義できました。書いたコードに誤りがないか、次のサンプルコードと見比べて確認しましょう。

【 サンプルコード 6-3-1 】

6. 4 クラスを定義したファイルをモジュールにして利用する

このVehicleRobotクラスは次からのレッスンでも利用していきます。そこで、Studuino:bit内に保存して、モジュールとして利用できるようにしてみましょう。

■ Studuino:bit内にファイルを保存する

以下の手順で作成したファイルをStuduino:bit内に保存しましょう。

  1. vehicle(.py)と付けてPC上にファイルを保存します。
  2. メニューの「ファイル」をクリックし、1のファイルをStuduino:bit内に保存します。
  3. ファイルの転送が完了したことを確認して、もう一度メニューの「ファイル」をクリック、ウィンドウを閉じます。
  4. Studuino:bitのリセットボタンを押します。

■ モジュールからクラスをインポートして利用する

メニューの「新規」をクリックして、新しいファイルを作成します。このファイルから保存したvechicle(.py)モジュールからVehicleRobotクラスをインポートして、インスタンスを作成してみましょう。

次の順番でメソッドを1秒おきに呼び出すプログラムを作成して、実際に動作するか確認しましょう。

1.前進:move_forward()
2.後退:move_backward()
3.左回転:rotate_left()
4.右回転:rotate_right()
5.左に曲がる:curve_left()
6.右に曲がる:curve_right()
7.ブレーキをかけて止まる:brake()

【 サンプルコード 6-4-1 】
追加【5行目~17行目】

6. 5 車型ロボットを走らせる

プログラムが問題なく動作することが確認できたら、次は以下のように車型ロボットを走らせるにはどのようなコードを書けば良いか考えてみましょう。それぞれのサンプルコードは最後に書いています。

【 逆S字を描くように走らせる 】
【 正方形を描くように走らせる 】
【 サンプルコード 6-5-1 】 逆S字を描くように走らせる
※ DCモーターの回転の速さは電池の残量によってプログラム上では同じ数値設定でも実際の速さが異なります。下記のプログラムでS字を描かない場合は、time.sleep_ms()メソッドの引数を調整してください。
【 サンプルコード 6-5-2 】 正方形を描くように走らせる
※ 正方形を描くときは、「右回転」⇒「前進」の動きを3回繰り返しています。そのため、以下のようにfor文を使用して簡潔にプログラムをまとめることもできます。

課題:デコレータを利用した走行時間の制御機能の追加

チャプター6のプログラムでは、それぞれの動作を行う時間を制御するために、その都度timeモジュールのsleep_ms()メソッドを呼び出していました。もし、この時間制御を各メソッドの引数に時間を指定するだけで行うことができれば、プログラムをより簡潔に書けるようになります。

例えば、今は2秒前進して停止させるときに、以下のように3行で書いていたコードが、

robo.move_forward()
time.sleep_ms(2000)
robo.brake()

次のように1行にまとめることができます。

move_forward(2000) 

時間の制御は、引数として時間を受け取り、time.sleep_ms()メソッドと作成したクラスのbrake()メソッドを実行するという処理になるため、すべてのメソッドで共通の処理として扱うことができます。そこで、この課題ではチャプター4で学習した「デコレータ」を利用して、次の6つのメソッドに、内部のコードを変更することなく、時間制御の機能を追加してみましょう。

  • move_forward()メソッド
  • move_backward()メソッド
  • rotate_left()メソッド
  • rotate_right()メソッド
  • curve_left()メソッド
  • curve_right()メソッド

7. 1 プログラムの作成例

デコレータとして、vehicle(.py)で定義しているVehicleRobotクラスに次のtime_control()メソッドを追加します。このデコレータは引数funcとして、指定されたメソッドを受け取り、内部でこのメソッドの実行と、追加で実行する処理を定義した新たなメソッドを戻します。

追加【2行目、11行目~17行目】

新たなメソッドは、def new_func(self, duration=-1)の通り、-1をデフォルト値としてもつ引数durationが追加されています。このように-1をデフォルト値としていることで、14行目~16行目の処理で、引数が省略された場合は、time.sleep_ms()メソッドとbrake()メソッドが実行されず、元のメソッドと同じ処理が行われるようになっています。

そして、このデコレータを各メソッドの上に修飾すると、プログラムの完成となります。

【 サンプルコード 7-1-1 】

と入れてみましょう。
(【 サンプルコード 6-4-1 】を参照)

おわりに

8. 1 このレッスンのまとめ

このレッスンでは、クラスの定義方法について振り返りを行い、新たに「デコレータ」という仕組みを学びました。レッスンの後半では、車型ロボットを製作し、その走行の制御を行うための独自クラスを定義しました。

また、実際にそのクラスを使って作成したオブジェクトで逆S字や四角を描くように走行させるプログラムを作成しました。これらの走行プログラムは、独自クラスを定義しなくても書くことはできますが、その場合それらのプログラムを他の走行プログラムへ再利用することは難しくなります。

今回のようにクラスとして共通で使用できそうな処理をまとめておくことで、他のプログラムへも再利用しやすくなり、必要な機能を最小限の労力でつくれるようになります。

8. 2 次のレッスンについて

次のレッスンでは、オブジェクト指向なプログラム言語がもつ重要な特長として、「クラスの継承」と「メソッドのオーバーライド」について詳しく説明します。また、このれらの特長を活かして、今回製作した車型ロボットに、紙に描かれた線に沿って走行する「ライントレース機能」を追加します。

TOP