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

テーマ.6-3 車型ロボット用コントローラの制作

車型ロボットを操作するオリジナルコントローラーを制作しよう

このレッスンで学ぶこと

このレッスンでは、クラスの継承を重ねる方法同時に複数のクラスを継承する方法を新たに学びます。また、レッスンの後半では、テーマ.6-1で製作した車型ロボットのクラスと新たに製作するコントローラーのクラスの2つを継承させることで、コントローラーで操作できる車型ロボットを製作します。

新しいPython文法の学習

前回のレッスンでは、クラスの継承と、継承元がもつメソッドをオーバーライド(上書き)する方法を学びました。このクラスの継承は1つ限りということはなく、継承を重ねたり、複数のクラスを同時に継承することもできます。では、それぞれの継承について詳しく見ていきましょう。

2. 1 クラスの継承を重ねる

Pythonでは、別のクラスを継承して作成されたクラスをさらに継承することができます。例えば次のコードでは、クラスBはクラスAを継承しており、さらにクラスCはクラスBを継承しています。これによって、クラスCはクラスAとクラスBの2つからプロパティとメソッドを継承します。

class A:
    .
    .
	
class B(A):
    .
    .
	
class C(B):
    .
    .

では、RPGの世界に例えて具体的なコードを書いてみましょう。RPGというジャンルでは、キャラクターの職業がレベルアップしていくことで、新たに強い技を覚える仕様のゲームが多数あります。そこで、「ソルジャー」⇒「ナイト」⇒「ロイヤルナイト」と職業がレベルアップしていくことで、前の職業の技も継承しつつ新たな技も習得するという設定で以下のコードを考えてみます。

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

このコードでは、SoldierからKnightへ継承し、さらにKnightからRoyalKnightへの継承を行っています。継承の過程でget_job()get_action()の2つのメソッドをオーバーライドしています。このプログラムをMuのエディタエリアに複製して実行してみましょう。

(実行結果)

get_job()メソッドを実行すると、RoyalKnightで定義した処理だけが行われています。一方で、get_action()メソッドでは、SoldierKnightRoyalKnightの3つのクラスで定義した処理がすべて実行されています。

この2つの違いはsuper()関数でスーパークラスを呼び出し、そのメソッドを実行しているかどうかという点にあります。このように、super()関数でスーパークラスを呼び出す連鎖をつくることで、継承を重ねた全てのクラスのメソッドを引き継ぐことができます。

2. 2 複数のクラスを継承する

【 サンプルコード 2-1-1 】のように、「ソルジャー」⇒「ナイト」⇒「ロイヤルナイト」と順序立てができる場合は、継承を重ねる方法が適していますが、「ソーサラー(魔法使い)」と「モンク(僧侶)」という順序立てが出来ない2つの職業を習得して「賢者」へとレベルアップできるという仕様では、この方法はあまり向いているとは言えません。

Pythonにはクラスの継承を重ねる以外にも、複数のクラスを同時に継承する方法が用意されています。以下の例では、クラスAとクラスBを同時に継承したクラスCを作成しています。

class A:
    .
    .
	
class B:
    .
    .
	
class C(A, B):  # 「,」で区切ることで複数のクラスを継承できる
    .
    .

では、この方法を使用して「ソーサラー(Sorcerer)」と「モンク(Monk)」の2つのクラスを継承した「賢者(Sage)」のクラスを定義してみましょう。

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

複数のクラスを継承すると、それぞれのプロパティとメソッドを引き継ぐことができます。では、このコードをエディアエリアに複製して実行してみましょう。

(実行結果)

見事に、SageクラスのインスタンスからSorcererクラスとMonkクラスから継承したメソッドを呼び出すことができました。

■ 複数のクラスを継承する場合の注意点

複数のクラスを継承すると、それぞれのクラスのプロパティとメソッドを引き継ぐことができるというメリットがありましたが、継承したクラスの中に同じ名前のメソッドが含まれている場合はどうなるでしょうか?

次のコードでは、どちらも__init__()メソッドをもつ「Super_1クラス」と「Super_2クラス」を継承した「Subクラス」を定義し、そのインスタンスを作成しています。

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

Subクラスの__init__()メソッド内でsuper()関数を実行し、スーパークラスを呼び出しています。しかし、このコードを実行するとSuper_1クラスの__init__()メソッドしか実行されません。

(実行結果)

このような結果となるのは、MicroPythonがsuper()関数で1つのクラスしか呼び出せない仕様になっているためです。そのため、この場合はクラスを重ねて継承する方法が有効です。

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

このコードを実行すると、Super_1クラスとSuper_2クラスのどちの__init__()メソッドも呼び出せていることが確認できます。

(実行結果)

コントローラーの組み立て

ここからは、コントローラーで操作できる車型ロボットを製作していきます。早速、レッスン22(テーマ.6-1)で制作した車型ロボットを準備し、新たに赤外線フォトリフレクタとカラーセンサーを使用したコントローラーを組み立てましょう。

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

【 パーツ一覧 】
  • レッスン22で製作した車型ロボット×1
  • 赤外線フォトリフレクタ×1
  • カラーセンサー×1
  • センサー接続コード(3芯30cm)×1
  • センサー接続コード(4芯30cm)×1
  • ブロック基本四角(白)×4
  • ブロック基本四角(グレー)×2
  • ブロック基本四角(赤)×1
  • ブロック基本四角(緑)×1
  • ブロック基本四角(青)×1
  • ブロック基本四角(黄)×1
  • ブロックハーフA(グレー)×2
  • ブロックハーフC(白)×6
  • ステー×1
  • 回転軸×2
【 アーテックブロックの形状 】

3. 2 組立説明書

車型ロボットを分解している場合は、以下のリンク先から組立説明書を確認して、再度組み立てを行ってください。

レッスン22で製作した車型ロボットの組立説明書

コントローラーの組立説明書

車型ロボットを操作するプログラムの作成

レッスン22で作成した車型ロボットの制御クラス「VehicleRobot」と、組み立てたコントローラーを制御する新たなクラス「Controller」の2つを継承し、コントローラー付きの車型ロボットのクラス「ControlRobot」を作成します。

このように、何かを製作するときに、部品や機能ごとにクラスを分けて定義しておくと、他のものを製作するときに、クラスを再利用して素早く作業を進めることができます。

では、はじめにコントローラーのクラスを定義し、そのあとでコントローラー付き車型ロボットのクラスを定義しましょう。

4. 1 コントローラーのクラスの定義

組み立てたコントローラーの操作は左側と右側に分かれています。

それぞれ回転軸を取り付けたブロックを回し、センサーの正面に色の付いたブロックを置くことで、次の信号(文字列)を出力します。

【 左側で出力する信号 】
赤外線フォトリフレクタ の正面に置くブロックの色出力する信号(文字列)
白色"on"
なし"off"
【 右側で出力する信号 】
カラーセンサー の正面に置くブロックの色出力する信号(文字列)
青色"b"
赤色"r"
緑色"g"
なし"none"
※ "b""r""g"はそれぞれの色を表す英単語の頭文字です。
※ 右側の黄色のブロックは次のチャプターの課題で使用します。

そして、この信号を出力を制御するために、次のプロパティとメソッドを持つ「Controllerクラス」を定義します。

【 Controllerクラスのプロパティ 】
プロパティ名役割
irpIRPhotoReflectorクラスのインスタンスを格納する。
csColorSensorクラスのインスタンスを格納する。
threshold_irp赤外線フォトリフレクタで白色のブロックが正面にあるかどうかを判別するためのしきい値
【 Controllerクラスのメソッド 】
メソッド名役割引数戻り値
set_controller()__init__()メソッドに代わるコンストラクタ。pin_irp:赤外線フォトリフレクタの接続端子名 pin_cs:カラーセンサーの接続端子名なし
get_signals()左右の信号を取得するメソッド。なし(signal_l , signal_r) 左側の信号と右側の信号を格納したタプル

通常はコンストラクタとして、__init__()メソッドを定義しますが、このControllerクラスは他のクラス(ここではVehicleRobotクラス)と組み合わせて継承されることを前提としているため、そのままでは__init__()メソッドが衝突してしまいます。

そこで、その代わりとなるset_controller()メソッドを定義して、継承されたControlRobotクラスの__init__()メソッドの中で呼び出します。

では、順番にこれら2つのメソッドを定義していきましょう。

■ set_controller()メソッドの定義

まずは、Controllerクラスを宣言し、その中でset_controller()メソッドを宣言します。set_controller()メソッドでは、IRPhotoReflectorクラス(赤外線フォトリフレクタ)とColorSensorクラス(カラーセンサー)のインスタンスを作成するため、そのために必要な引数として、接続先の端子名が渡されるpin_irppin_csを用意します。これらの引数の前に「*(アスタリスク)」を入れて、キーワード引数として渡すように強制しておきましょう。

次に、引数として受け取った端子名を渡し、IRPhotoReflectorクラスとColorSensorクラスのインスタンスを作成します。作成したインスタンスはそれぞれirpプロパティとcsプロパティに格納しましょう。

追加【4行目~8行目】

最後に、赤外線フォトリフレクタで白色のブロックが正面にあるかどうかを判別するためのしきい値を設定します。新しいプログラムを作成し、以下のコードを実行して、赤外線フォトリフレクタの正面に白色のブロックを置いたときと、置いていないときの値を先に調べておきましょう。調べた結果からその平均をしきい値として設定します。

※ 太陽光には赤外線が含まれているため、赤外線フォトリフレクタの正面に日差しが当たらないように注意して、値を調べるようにしましょう。
【 赤外線フォトリフレタの値を調べるためのコード 】

求めたしきい値をthreshold_irpプロパティに格納します。カラーセンサーのしきい値は今回用意しません。しきい値を使用しない代わりにColorSensorクラスに用意されているget_colorcode()メソッドを使用して、「赤」や「緑」などの色として直接取得します。

追加【9行目】

なお、前のレッスンで取り組んだライントレース機能の作成では、環境に合わせてしきい値が設定できるように、プログラムの中で取得した赤外線フォトリフレクタの値からしきい値を求めるメソッドを用意していました。もちろん今回も同様のメソッドを用意することはできますが、コードの量が増えてしまうため、省略しています。

■ get_signals()メソッドの定義

次に、コントローラーの左側と右側の両方からの信号を取得するget_signals()メソッドを定義します。左側はさきほど設定したthreshold_irpプロパティのしきい値と取得した赤外線フォトリフレクタの値を比較して出力する信号を決定します。

追加【11行目~16行目】

右側はColorSensorクラスのget_colorcode()メソッドを使って出力する信号を決めます。このメソッドを実行すると、色に応じてColorSensorクラスで定義されている次のプロパティの値が返されます。

【 ColorSensorクラスのget_colorcode()メソッドで返される値 】
プロパティ名
ColorSensor.COLOR_RED1
ColorSensor.COLOR_GREEN2
ColorSensor.COLOR_BLUE3
ColorSensor.COLOR_WHITE4
ColorSensor.COLOR_YELLOW5
オレンジColorSensor.COLOR_ORANGE6
ColorSensor.COLOR_PURPLE7
該当なしColorSensor.COLOR_UNDEF0

これらは定数としての扱いになるため、取得した値から何色であるかを条件文で判定するときは、次のようにインスタンスまたはクラスからプロパティを呼び出して比較します。

from pyatcrobo2.parts import ColorSensor

cs = ColorSensor("I2C")
color = cs.get_colorcode()
if color == cs.COLOR_RED:	# 「color == 1」のように数値を書いて比較はしない。
    print("red")
else:
    print("not red")

そのため、以下のようなコードを書いて、出力する信号を決めましょう。

追加【18行目~26行目】

最後に、決定した2つの信号signal_lsignal_rをタプルとして返します。これで、Controllerクラスの定義ができました。

追加【28行目】

■ 前のレッスンでget_colorcode()メソッドを使用しなかった理由

前のレッスンでも課題の中で、カラーセンサーを使い、コースに描かれた円の色の識別を行いました。今回と同様にget_colorcode()メソッドを使用することもできましたが、このメソッドはあらかじめStuduino:bit側で設定されているしきい値から色を判別するものであるため、ブロックのように均一なものであればある程度正確に判別できますが、印刷物のようなインクの種類や印刷する紙の質によってばらつきがあるものでは、誤った判別をしてしまうことがあります。そのため、印刷されたものに合わせてしきい値を設定して、色を判別するということを行いました。

4. 2 コントローラー付き車型ロボットのクラスの定義

新たに定義したControllerクラスとレッスン22で用意したVechicleRobotクラスを継承してコントローラー付きの車型ロボットのクラスを定義します。このクラスでは、コントローラーから出力される信号に対して、それぞれ次のように動作を制御します。

【 左側の出力信号に対する動作 】
コントローラー の出力信号動作
"on"右側の出力信号で決められた動作を行う
"off"ブレーキをかけて停止
【 右側の出力信号に対する動作 】
コントローラー の出力信号動作
"b"(左側の出力信号が"on"のとき)前進
"r"(左側の出力信号が"on"のとき)右回転
"g"(左側の出力信号が"on"のとき)左回転
"none"ブレーキなしで停止
【 オーバーライドするメソッド 】
  • __itni__()メソッド
    継承するVehicleRobotクラスの__init__()メソッドをオーバーライドし、継承するもうひとつのControllerクラスのset_controller()メソッドを実行します。また、操作する車型ロボットの走行時の速さ(左右のタイヤの回転の速さ)を変更したい場合もこのメソッド内で処理します。
【 新たに追加するメソッド 】
  • start()メソッド
    インスタンスからこのメソッドを呼び出すことで、コントローラーで操作できるようにします。内部では、継承したControllerクラスのget_signals()メソッドを繰り返し実行して信号を取得し、それによって車型ロボットの走行を制御します。

では、順番に2つのメソッドを定義していきましょう。

■ __init_()メソッドのオーバーライド

前の続きからコードを書いていきます。まずは先頭で、Studuino:bit内に保存しているvehicle(.py)からVehicleRobotクラスをインポートしましょう。

追加【1行目】

次に、Controllerクラスを定義したコードの下に、新しくContorolRobotクラスのコードを書いていきます。ControlRobotクラスは、VehicleRobotControllerの順番にクラスを継承します。

このクラスのコンストラクタである__init__()メソッドでは、VehicleRobotクラスの__init__()メソッドとControllerクラスのset_controller()メソッドの2つに必要な引数を受け取る必要があります。そのため、次のようにコードを書くことになります。

追加【36行目~38行目】

そして、タイヤの回転の速さを変更したい場合はこの中で、VehicleRobotクラスのset_speed_to_left()メソッドとset_speed_to_right()メソッドを呼び出します。

追加【39行目~40行目】

■ start()メソッドの定義

続けて、コントローラーの出力信号によって動作を制御するstart()メソッドを定義していきます。このメソッドでは次の流れで処理を行います。

まずは、get_signals()メソッドで出力信号を取得し、その左側の信号(インデックスが「0」のデータ)が"off"の場合は、brake()メソッドを呼び出すコードを書きましょう。

追加【40行目~45行目】

次に、左側の信号が"on"だった場合に、さらに右側の信号(インデックスが「1」のデータ)によって4つに分岐して動作を制御するコードを書きます。

追加【46行目~54行目】

これで、ControlRobotクラスが定義できました。

4. 3 プログラムの動作を確認する

定義したクラスが正しく動作することを確認するため、プログラムの末尾でインスタンスを作成し、start()メソッドを呼び出すコードを追加します。

完成したプログラムの全体像は次のようになります。誤りがないかもう一度確認しておきましょう。

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

USBケーブルを接続したまま動作を確認する場合は、メニューの「実行」をクリックしてプログラムを実行します。

USBケーブルを外して動作を確認したい場合は、このプログラムをmain(.py)と名前を付けてPC上に保存し、メニューのファイルからStuduino:bitへ写しましょう。Studuino:bitのリセットボタンを押すと、このプログラムが実行され、動作が確認できます。

※ Studuino:bit上からvehicle.pyのファイルを削除してしまった場合は、以下のリンクの上で右クリックをしてファイルをPCにダウンロードし、もう一度Studuino:bitへ保存しましょう。

vehicle.py

課題:コントローラーの信号を1つ追加しよう

この課題では、コントローラーの右側にある黄色のブロックについて、新たな出力信号を追加し、車型ロボットが後進する操作を行えるように【 サンプルコード 4-3-1 】を改造してみましょう。

【 追加する出力信号(右側) 】
出力信号(文字列)
"b"
"r"
"g"
"y"
なし"none"
【 追加する車型ロボットの動作 】
出力信号動作
"b"前進(左側の出力信号が"on"のとき)
"r"右回転(左側の出力信号が"on"のとき)
"g"左回転(左側の出力信号が"on"のとき)
"y"後進(左側の出力信号が"on"のとき)
"none"ブレーキなしで停止

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

この課題は【 サンプルコード 4-3-1 】の内容が理解できていれば、簡単に信号とそれに対する動作を追加することができます。

  • 信号の追加
    【 サンプルコード 4-3-1 】の19行目~27行目が、コントローラーの右側の出力信号を決める処理になっています。そのため、ここにもうひとつ条件文を追加して、色が黄色のときの出力信号を設定します。
  • 動作の追加
    コントローラーの右側の出力信号に対する車型ロボットの動作の制御は【 サンプルコード 4-3-1 】の46行目~53行目で行っています。そのため、こちらももうひとつ条件文を追加して、「”y”」の信号を取得したときに、後進する「move_backward()メソッド」を呼び出すようにします。

以上の2点の変更を行ったコードが以下になります。

【 サンプルコード 5-1-1 】
追加【26行目、27行目、54行目、55行目】

おわりに

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

このレッスンでは新たに「クラスを重ねて継承する方法」と「複数のクラスを継承する方法」について学習しました。このように複雑にクラスの継承が行えることは、コードの再利用性を高められるというメリットがある一方で、仕様をしっかりと理解していないと、思わぬところでつまづいてしまうこともあります。今回学んだ基礎をしっかりと理解して、徐々にクラスを使いこなせるようになっていきましょう。

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

次のレッスンでは、算術演算子や比較演算子などに対応する特殊なメソッドのオーバーライドと、クラスメソッドインスタンスメソッドという2つのメソッドのちがいについて学習します。レッスンの後半では、これまでの学習のまとめとして、ボタン操作で動きをプログラミングできる車型ロボットを製作します。

TOP