Pythonロボティクスコース レッスン 23
テーマ.6-2 ライントレース機能付きの車型ロボットの制作
制作した車型ロボットにライントレース機能を追加しよう
チャプター1
このレッスンで学ぶこと
このレッスンでは、オブジェクト指向なプログラム言語がもつ重要な特長として、新たに「クラスの継承」と「メソッドのオーバーライド」について学習します。また、前回のレッスンで製作した車型ロボットとその制御クラスVechicleRobot
をクラスの継承とメソッドのオーバーライドによって拡張し、紙に描かれた線に沿って走らせます。
チャプター2
新しいPython文法の学習
ここでは、簡単なサンプルプログラムを通して、クラスの継承とメソッドのオーバーライドについて学習しましょう。
2. 1 クラスの継承
クラスの継承とは、あるクラスがもつプロパティやメソッドを引き継いだ新しいクラスを作成することです。このとき継承元となるクラスを「スーパークラス」または「親クラス」と呼び、それを継承したクラスを「サブクラス」または「子クラス」と呼びます。
では、このクラスの継承が役に立つ具体的な場面を考えてみましょう。例えば、RPG(ロールプレイングゲーム)をつくるときに、キャラクターの職業として「戦士」「魔法使い」「賢者」の3つの職業を用意したいとします。そこで、これらの職業を別々のクラスとして定義し、そのインスタンスとしてキャラクターを作成できるように考えました。
このとき、3つの職業で共通するものがいくつかあります。「名前」「HP」「MP」の3つのプロパティと「なぐる」というメソッドはどの職業も持っています。そこで、この共通した要素をもつクラスを定義し、それを継承してそれぞれの職業のクラスを作成します。サブクラス(継承後のクラス)はスーパークラス(継承元のクラス)がもつプロパティとメソッドを引き継いでいるため、サブクラスでは独自のプロパティとメソッドだけを定義すればよく、効率的にコードを書くことができます。
では、実際にクラスを継承する方法を確認していきましょう。
■ クラスの継承方法
クラスを継承するときは、次のように書きます。サブクラスはスーパークラスのプロパティやメソッドをそのまま引き継いでいるため、追加で必要なプロパティやメソッドのみ定義します。
class スーパークラス名: . . # プロパティやメソッドの定義 . class サブクラス名(継承するスーパークラス名): . . # 新たに追加するプロパティやメソッドの定義 .
早速、スーパークラスとなる「Human」を定義しみましょう。
class Human():
def __init__(self, name, hp, mp):
self.name = name
self.hp = hp
self.mp = mp
def hit(self):
print(self.name, "hits!")
次に、このHuman
クラスを継承した戦士のSoldier
クラスを定義しましょう。
【 サンプルコード 2-1-1 】
追加【11行目~16行目】
class Human:
def __init__(self, name, hp, mp):
self.name = name
self.hp = hp
self.mp = mp
def hit(self):
print(self.name, "hits!")
class Soldier(Human):
def cut_down(self): # 剣で斬る
print(self.name, "cuts down!")
def guard(self): # 盾でガード
print(self.name, "guards!")
Soldier
クラスの中で、__init__()
メソッドやhit()
メソッドは定義していませんが、Human
クラスを継承したため、使用することができます。実際にこのプログラムを実行して、ターミナル上でインスタンスを作成し、動作を確認しましょう。
>>> character = Soldier("Kai", 100, 20) >>> character.hit() Kai hits! >>> character.cut_down() Kai cuts down! >>> character.guard() Kai guards!
2. 2 メソッドのオーバーライド
スーパークラスから引き継いだメソッドは上書きして機能を追加することもできます。これをメソッドの「オーバーライド」といいます。オーバーライドの方法は簡単で、スーパークラスと同じ名前でメソッドを定義するだけです。
例として、【 サンプルコード 2-1-1 】でHuman
クラスから継承したhit()
メソッドをSoldier
クラスでオーバーライドしてみましょう。
【 サンプルコード 2-2-1 】
追加【12行目、13行目】
class Soldier(Human):
def hit(self): # メソッドのオーバーライド
print(self.name, "hits strongly!") # 表示を「強く殴る」に変更
def cut_down(self):
print(self.name, "cuts down!")
def guard(self):
print(self.name, "guards!")
では、変更したプログラムを実行してもう一度同じ引数でインスタンスを作成し、hit()
メソッドを呼び出してみましょう。
>>> character = Soldier("Kai", 100, 20) >>> character.hit() Kai hits strongly!
このように、スーパークラスのメソッドを上書きすることができました。この例ではスーパークラスのメソッドを完全に上書きしてしまいましたが、スーパークラスのメソッドの処理を引き継ぎつつ、新たに処理を加えることもできます。そのときに使用するのが、「super()
関数」です。
■ スーパークラスを呼び出すsuper()
関数
super()
関数はスーパークラスを呼び出せるPythonの標準関数です。実際にこれを使って、【 サンプルコード 2-2-1 】をスーパークラスのHuman
のhit()
メソッドをサブクラスSoldier
のhit()
メソッドの中で呼び出すように変更してみましょう。
【 サンプルコード 2-2-2 】
追加【13行目】
class Soldier(Human):
def hit(self):
super().hit() # スーパークラスのメソッドの呼び出し
print(self.name, "hits strongly!")
def cut_down(self):
print(self.name, "cuts down!")
def guard(self):
print(self.name, "guards!")
これで再度プログラムを実行し、インスタンスを作成してhit()
メソッドを実行してみましょう。
>>> character = Soldier("Kai", 100, 20) >>> character.hit() Kai hits! Kai hits strongly!
見事にスーパークラスのhit()
メソッドの処理が行われています。このように、super()
関数を使用すると、スーパークラスのメソッドをサブクラスでも使いつつ、新たな処理を追加することができます。
2. 3 モジュールを複数の箇所でインポートしたときの扱い
これから先に複数のクラスを定義するような、大きなプログラムを作成していくと、同じモジュールやオブジェクトを別のファイル内や別のクラス内などでインポートして使うケースが出てきます。
他のプログラム言語では、これがエラーの原因となったり、エラーにはならなくとも無駄にメモリを消費してしまうことがあります。しかし、Pythonではすべて同じオブジェクトを参照するためそういったことが起こりません。
では、実際にStuduiniBitDisplay
クラスのインスタンスdisplay
と、time
モジュールを複数の箇所でインポートし、id値(各オブジェクトに自動的に付与された固有の値)を調べてみましょう。
以下のコードをコピーして貼り付け、実行してください。
【 サンプルコード 2-3-1 】
from pystubit.board import display
import time
print("display:", id(display)) # id値はid()関数で調べることができます
print("time:", id(time))
class Sample:
def __init__(self):
from pystubit.board import display
import time
print("display:", id(display))
print("time:", id(time))
def func(self):
from pystubit.board import display
import time
print("display:", id(display))
print("time:", id(time))
sample = Sample()
sample.func()
(実行結果の例)
※ display
オブジェクトのid値は実行するたびに変わります
display 1065514160 time: 1061294916 display 1065514160 time: 1061294916 display 1065514160 time: 1061294916
【 サンプルコード 2-3-1 】では、3つの箇所で同じオブジェクトとモジュールをインポートしています。それぞれのid値が同じものになっていることから、同じオブジェクトを参照していることが分かります。
また、基本的にオブジェクトやモジュールのインポートは各プログラムファイルの先頭で行いますが、限定した場所で使いたい場合は、クラスの定義内や関数やメソッドの中で行うこともできるということも合わせて覚えておきましょう。
チャプター3
車型ロボットへのセンサーの取り付け
レッスン22(テーマ.6-1)で制作した車型ロボットに赤外線フォトリフレクタとカラーセンサーを取り付けます。
3. 1 組み立てに必要なパーツ
【 パーツ一覧 】
- レッスン22で製作した車型ロボット×1
- 赤外線フォトリフレクタ×1
- カラーセンサー×1
- センサー接続コード(3芯30cm)×1
- センサー接続コード(4芯30cm)×1
- ブロック基本四角(白)×1
【 アーテックブロックの形状 】
3. 2 組立説明書
前のレッスンで組み立てた車型ロボットを分解している場合は、以下のリンク先から組立説明書を確認して、再度組み立てを行ってください。
チャプター4
ライントレース機能をもつ車型ロボットの製作
ここでは、コース上に描かれた黒線を読み取り、線に沿いながら走行するライントレース機能をもった車型ロボットを製作します
黒線に沿って車型ロボットを走らせる最もシンプルな方法としては、赤外線フォトリフレクタを使用し、黒線の外側(白色の用紙)と内側を判別して、進行方向を制御する方法があります。
この制御によって、黒線を行ったり来たりしながら、黒線のふちに沿って走らせることができます。
このことから、ライントレース機能をもつ車型ロボットのプログラムでは、「左に曲がる」処理や「右に曲がる」処理を書くことになりますが、これらの処理はレッスン22で製作した車型ロボットの制御クラスVehicleRobot
で既に作成していました。そこで、VehicleRobot
クラスの継承とメソッドのオーバーライドを行い、ライントレース機能をもつ新しい車型ロボットのクラスを作成します。
4. 1 コースの準備
ライントレース機能のテスト用に、キットに付属している以下のコースを使用します。
もし紛失してしまった場合は、以下のリンク先からデータをダウンロードして、A3用紙にカラー印刷してください。
A3用紙に印刷できない場合は、以下のデータをA4用紙2枚に印刷して、切り取り線に沿って余白を切り離し、2枚をテープ等で留めてください。
4. 2 継承した新しいクラスの定義
レッスン22(テーマ.6-1)で制作したvehicle.py
を読み込み、VehicleRobot
クラスをインポートします。このクラスを継承し、新たに「Linetracer
クラス」を作成します。
Linetracer
クラスでは、親クラスの__init__()
メソッドをオーバーライドし、また新たに次のプロパティとメソッドを追加します。
【 新たに追加するプロパティ 】
プロパティ名 | 役割 |
---|---|
irp | IRPhotoReflector クラスのインスタンスを格納。 |
threshold | 赤外線フォトリフレクタで黒線の外側と内側を判別するためのしきい値。 |
【 オーバーライドするメソッド 】
__init__()
メソッドIRPhotoReflector
クラスのインスタンスを作成し、irp
プロパティに格納します。また、赤外線フォトリフレクタで黒線の外側と内側を判別するためのしきい値を決めるset_threshold()
メソッドを実行します。
【 新たに追加するメソッド 】
set_threshold()
メソッド
黒線の内側と外側を判別するための赤外線フォトリフレクタのしきい値を計算し、threshold
プロパティに格納します。このしきい値は黒線の外側と内側で調べた赤外線フォトレリフレクタの値の平均値として算出します。linetrace()
メソッドset_threshold()
メソッドで決めたしきい値を利用して、車型ロボットの走行を制御します。具体的には、黒線の外側にある場合(しきい値より大きい場合)は、親クラスのcurve_righ()
メソッドで右に曲がり、黒線上にある場合(しきい値以下の場合)はcurve_left()
メソッドで左に曲がる動作を行います。これで、黒線のふちに沿って走行させることができます。start()
メソッド
ボタンを押すとライントレースを開始するように、linetrace()
メソッドの実行を制御する処理を行います。基本的に作成したインスタンスから呼び出すのは、このメソッドだけになります。
また、上記のメソッドを組み合わせて、以下の順番で処理を行います。
【 プログラム全体の処理の流れ 】
Linetracer
クラスのインスタンスを作成__init__()
メソッドを実行set_threshold()
メソッドを実行start()
メソッドを実行- ボタンBが押されると
linetrace()
メソッドを実行 - ライントレース中にボタンBが押されると停止
- 5と6をずっと繰り返す
それでは、ひとつずつコードを書いていきましょう。
■ VehicleRobot
クラスの継承
はじめに、VehicleRobot
クラスを継承したLinetracer
クラスを定義します。Studuino:bitに保存しているvehicle(.py)
からVehicleRobot
クラスをインポートし、次のようにLinetracer
クラスを作成しましょう。
from vehicle import VehicleRobot
class Linetracer(VehicleRobot):
※ Studuino:bit上からvehicle.pyのファイルを削除してしまった場合は、以下のリンクの上で右クリックをしてファイルをPCにダウンロードし、もう一度Studuino:bitへ保存しましょう。
■ メソッド名の宣言
続けて、これからオーバーライドするメソッドと新たに追加するメソッドの名前を先に宣言しておきましょう。Pythonでは、メソッドは中身が何もない場合でも、何かしらの処理を書いておかなければエラーになってしまいます。このように何も実行することがないが、何か書かなければならないときは、pass
文を書きます。pass
文が実行されても何かが行われることはありません。
追加【4行目~14行目】
from vehicle import VehicleRobot
class Linetracer(VehicleRobot):
def __init__(self):
pass
def set_threshold(self):
pass
def linetrace(self):
pass
def start(self):
pass
■ __init__()
メソッドのオーバーライド
__init__()
メソッドは継承したスーパークラスのVehicleRobot
にもありました。そのため、このメソッドはオーバーライドすることになります。
【 VechicleRobot
クラスの__init__()
メソッドの定義 】
def __init__(self, *, pin_l, pin_r): self.dcm_l = DCMotor(pin_l) self.dcm_r = DCMotor(pin_r) self.dcm_l.power(100) self.dcm_r.power(100)
ただし、オーバーライド後も「DCMotor
クラスのインスタンス作成」や「DCモーターの出力の大きさを設定する」処理は必要なため、super()
関数でスーパークラスを呼び出し、その__init__()
メソッドを実行します。このときに、引数としてDCモーターを接続した端子名を渡す必要があるため、オーバーライドした__init__()
メソッドも同じ引数を取るように変更します。
追加・変更【4行目、5行目】
from vehicle import VehicleRobot
class Linetracer(VehicleRobot):
def __init__(self, *, pin_l, pin_r):
super().__init__(pin_l=pin_l, pin_r=pin_r)
そして次に、新たに取り付けた赤外線フォトリフレクタのクラスIRPhotoReflector
のインスタンスを作成し、irp
プロパティに格納します。また、このインスタンスを作成するときに、引数として赤外線フォトリフレクタを接続した端子名を渡す必要があるため、__init__()
メソッドの引数にpin_irp
を追加します。
追加・変更【4行目、5行目、8行目】
from vehicle import VehicleRobot
class Linetracer(VehicleRobot):
def __init__(self, *, pin_l, pin_r, pin_irp):
from pyatcrobo2.parts import IRPhotoReflector
super().__init__(pin_l=pin_l, pin_r=pin_r)
self.irp = IRPhotoReflector(pin_irp)
最後に、set_threshold()
メソッドを実行して、黒線の外側と内側の判別に必要なしきい値を設定します。このメソッドの中身は次に書いていきます。
追加・変更【9行目】
from vehicle import VehicleRobot
class Linetracer(VehicleRobot):
def __init__(self, *, pin_l, pin_r, pin_irp):
from pyatcrobo2.parts import IRPhotoReflector
super().__init__(pin_l=pin_l, pin_r=pin_r)
self.irp = IRPhotoReflector(pin_irp)
self.set_threshold()
■ set_threshold()
メソッドの作成
では、黒線の外側と内側の判別に必要なしきい値を決めるメソッドを作成していきます。この処理は、プログラムですべてを自動で行うのではなく、使用者側に次の手順で操作してもらいます。
【 使用者側の操作手順(動画あり) 】
- LEDディスプレイに文字「W」が表示されている状態で、黒線の外側(白色の用紙)の上に赤外線フォトリフレクタが来るように車型ロボットを置く。
- ボタンAを押す。
- LEDディスプレイに文字「B」が表示されている状態で、黒線の上に赤外線フォトリフレクタが来るように車型ロボットを置く。
- ボタンAを押す。
この操作でしきい値を設定するためにプログラム側では次の処理を行います。
【 プログラム側の処理 】
- LEDディスプレイに文字「W」を表示する。
- ボタンAが押されるまで待つ。
- 赤外線フォトリフレクタの値を取得して、変数に格納する。
- LEDディスプレイに文字「B」を表示する。
- ボタンBが押されるまで待つ。
- 赤外線フォトリフレクタの値を取得して、変数に格納する。
- 3と6の変数の平均を計算で求め、プロパティ
threshold
に格納する。
少しややこしいように思えますが、赤外線フォトリフレクタの値は周囲の環境(時間帯や室内での場所)によって左右されやすいため、このようにプログラムを立ち上げるたびに調整を行うことで、そのときの環境に合わせてしきい値を設定することができ、動作を安定させることができます。
では、上記の処理を行うためのコードを書いていきましょう。
この処理では、ボタンAとLEDディスプレイ、それからtime
モジュールを使用します。先頭でそれぞれをインポートしましょう。
追加・変更【13行目、14行目】
def set_threshold(self):
from pystubit.board import button_a, display
import time
次に、LEDディスプレイに「W」の文字を表示して、使用者がボタンAを押すのを待つ処理を書きます。しかし、「ボタンが押されるまで待つ」という命令はStuduino:bitに用意されていません。そこで、以下のコードのように、while
文とボタンのis_pressed()
メソッド、not
演算子を組み合わせることで、「押されるまで待つ」という処理を用意します。このコードで、ボタンAが押されている「ではない」間は繰り返すことになり、ボタンAが押されていない間は繰り返す、つまりはボタンAが押されるまで待つとなります。
追加・変更【16行目~18行目】
def set_threshold(self):
from pystubit.board import button_a, display
import time
display.show("W", delay=0) # 「W」はWhiteの頭文字
while not button_a.is_pressed(): # ボタンAが押されている「ではない」間は繰り返す
pass
反対に、「ボタンが離されるまで待つ」という処理は以下のコードで実現できます。
while button_a.is_pressed(): pass
この次に、赤外線フォトリフレクタをの値を調べて、変数val_white
に格納しますが、ボタンAを押し続けられると、誤ってそのまま次の処理を実行してしまうため、この「ボタンが離されるまで待つ」処理を有効に使います。
追加・変更【19行目~22行目】
def set_threshold(self):
from pystubit.board import button_a, display
import time
display.show("W", delay=0)
while not button_a.is_pressed(): # ボタンAが押されるまで待つ
pass
val_white = self.irp.get_value() # 赤外線フォトリフレクタの値の読み取り
display.clear()
while button_a.is_pressed(): # ボタンAが離されるまで待つ
pass
ボタンAが離されてから、500ミリ秒後にLEDディスプレイに「B」の文字を表示して、同じ手順でボタンAが再び押されると、赤外線フォトリフレクタの値を読み取って、変数val_black
に格納するようにします。
追加・変更【23行目~30行目】
def set_threshold(self):
from pystubit.board import button_a, display
import time
display.show("W", delay=0)
while not button_a.is_pressed():
pass
val_white = self.irp.get_value()
display.clear()
while button_a.is_pressed():
pass
time.sleep_ms(500)
display.show("B", delay=0) # 「B」はBlackの頭文字
while not button_a.is_pressed():
pass
val_black = self.irp.get_value()
display.clear()
while button_a.is_pressed():
pass
そして最後に、変数val_white
とval_black
の値を足して2で割り、2つの値の平均を求めます。求めた平均値は、int()
関数で整数に変換して、プロパティthreshold
に格納します。
※ IRPhotoReflector
クラスのget_value()
メソッドの戻り値が整数であるため、それに合わせてしきい値も整数に変換しています。
追加・変更【31行目】
time.sleep_ms(500)
display.show("B", delay=0)
while not button_a.is_pressed():
pass
val_black = self.irp.get_value()
display.clear()
while button_a.is_pressed():
pass
self.threshold = int((val_white + val_black) / 2)
■ ここまでに書いたコードの動作確認
ここまでに書いたコードが正しく動作することを確認します。プログラムの末尾に、以下のコードを追加して、メニューの「実行」をクリックします。USBケーブルを接続したまま、ライントレース用コースを使用して、上で説明した操作を行い、ターミナル上に設定されたしきい値が表示されることを確かめましょう。
def linetrace(self):
pass
def start(self):
pass
robo = Linetracer(pin_l="M1", pin_r="M2", pin_irp="P0")
print(robo.threshold)
■ linetrace()
メソッドの作成
今度は、ライントレースを行うメソッドを書いていきます。このメソッドでは次の流れで処理を行います。
ボタンBが押されるまでは繰り返し赤外線フォトリフレクタの値を調べ、車型ロボットの走行を制御します。そのため、このコードはwhile
文を使って次のようにまとめることができます。
追加・変更【34行目~42行目】
def linetrace(self):
from pystubit.board import button_b
while not button_b.is_pressed(): # ボタンBが押されていない間は繰り返す
val = self.irp.get_value()
if val > self.threshold:
self.curve_right()
else:
self.curve_left()
self.brake()
■ start()
メソッドの作成
最後に、ボタンBを押すとライントレースを開始するためのメソッドを用意します。ここではシンプルに、ずっと繰り返しボタンBが押されたかどうかを調べ、ボタンBが押されるとlinetarace()
メソッドを実行するコードを書きましょう。
追加・変更【45行目~49行目】
def start(self):
from pystubit.board import button_b
while True:
if button_b.is_pressed():
self.linetrace()
しかし、このままでは、ボタンBを押すとすぐにlinetarace()
メソッドが呼び出され、メソッド内部のwhile
文の条件判定が行われてしまいます。そうなると、while
文の中のコードが全く実行されずに処理を抜けてしまうことになり、ライントレースが行われません。
そこで、以下のようにlinetrace()
メソッドのはじめに「ボタンBが離されるまで待つ」処理を追加します。同様に、ボタンBを押してライントレースを終了させるときも、そのままだとstart()
メソッドに処理が戻ったあと、またすぐにlinetrace()
メソッドが実行されてしまうため、ここにも「ボタンBが離されるまで待つ」処理を追加しましょう。
追加・変更【36行目、37行目、45行目、46行目】
def linetrace(self):
from pystubit.board import button_b
while button_b.is_pressed(): # ボタンBが離されるまで待つ
pass
while not button_b.is_pressed():
v = self.irp.get_value()
if v > self.threshold:
self.curve_right()
else:
self.curve_left()
self.brake()
while button_b.is_pressed(): # ボタンBが離されるまで待つ
pass
これでLinetracer
クラスの定義ができました。
4. 3 プログラムの動作確認
最後に作成したLinetracer
クラスのインスタンスrobo
から、start()
メソッドを呼び出すコードを書きます。【 サンプルコード 4-3-1 】を参考にコードが完了したら、ライントレース用のコースを使用して動作を確認しましょう。
【 コースを走らせる向き 】
※ 下の図のように右回りに黒線の外側のふちに沿って走らせてください。
うまく動作しない場合は、以下の完成したコードと見比べて、誤りがないか確認してください。
【 サンプルコード 4-3-1 】
変更【56行目】
from vehicle import VehicleRobot
class Linetracer(VehicleRobot):
def __init__(self, *, pin_l, pin_r, pin_irp):
from pyatcrobo2.parts import IRPhotoReflector
super().__init__(pin_l=pin_l, pin_r=pin_r)
self.irp = IRPhotoReflector(pin_irp)
self.set_threshold()
def set_threshold(self):
from pystubit.board import button_a, display
import time
display.show("W", delay=0)
while not button_a.is_pressed():
pass
val_white = self.irp.get_value()
display.clear()
while button_a.is_pressed():
pass
time.sleep_ms(500)
display.show("B", delay=0)
while not button_a.is_pressed():
pass
val_black = self.irp.get_value()
display.clear()
while button_a.is_pressed():
pass
self.threshold = int((val_white + val_black) / 2)
def linetrace(self):
from pystubit.board import button_b
while button_b.is_pressed():
pass
while not button_b.is_pressed():
val = self.irp.get_value()
if val > self.threshold:
self.curve_right()
else:
self.curve_left()
self.brake()
while button_b.is_pressed():
pass
def start(self):
from pystubit.board import button_b
while True:
if button_b.is_pressed():
self.linetrace()
robo = Linetracer(pin_l="M1", pin_r="M2", pin_irp="P0")
robo.start()
チャプター5
課題:コース上の目印を読み取って速度を切り替える機能の追加
ここでは、コース上に描かれた緑色と赤色の円の目印をカラーセンサーで読み取って、加速と減速を行う機能を追加する課題に取り組みましょう。
【 課題の完成動画 】
【 読み取る目印と切り替え後の速さの関係 】
読み取った目印 | 行う処理 | 実行するメソッド |
---|---|---|
緑色の円 | 速度を上げる(加速) | set_speed_to_left(10) </br>set_speed_to_right(10) |
赤色の円 | 速度をもとに戻す(減速) | set_speed_to_left(4) </br>set_speed_to_right(4) |
この課題はカラーセンサーの調整が少し難しいため、次の手順に沿ってプログラムを作成してみましょう。
5. 1 プログラムの作成手順
【 サンプルコード 4-3-1 】の次の2つのメソッドに、それぞれこの課題で必要な処理を追加します。
__init__()
メソッド
カラーセンサーのクラスColorSensor
のインスタンスを作成し、新たなプロパティとして格納する処理を追加します。また、次で説明する新しく定義したメソッドもここで呼び出します。linetrace()
メソッド
カラーセンサーで読み取った色の情報から、スーパークラス(VehicleRobot
)のset_speed_to_left()
メソッドとset_speed_to_right()
メソッドを使って、速さを変更します。
また、カラーセンサーで正確にコース上の赤色の円と緑色の円を認識するには、そのための色のしきい値を設定する必要があります。そこで、次のメソッドを新しく定義します。
set_threshold_cs()
メソッド
赤外線フォトリフレクタで黒線の外側と内側を判別するためのしきい値を設定したset_threshold()
メソッドと同じような手順で、「赤色の円 ⇒ 緑色の円」の順番でカラーセンサーの値を読み取り、その結果から、コース上の赤色と緑色を判別するためのしきい値を設定します。
では、順番にメソッドを変更・追加していきましょう。
■ __init__()
メソッドの変更
まず、ColorSensor
クラスのインスタンスを作成するために必要な接続先の端子名を受け取る引数として新たにpin_cs
を追加します。(5行目)
次に、ColorSensor
クラスをインポートするコードを追加します。(7行目)
そして、そのインスタンスを作成し、cs
プロパティに格納します。(11行目)
最後に、後で定義するset_threshold_cs()
メソッドを実行します。(13行目)
追加・変更【5行目、7行目、11行目、13行目】
class Linetracer(VehicleRobot):
def __init__(self, *, pin_l, pin_r, pin_irp, pin_cs):
from pyatcrobo2.parts import IRPhotoReflector
from pyatcrobo2.parts import ColorSensor
super().__init__(pin_l=pin_l, pin_r=pin_r)
self.irp = IRPhotoReflector(pin_irp)
self.cs = ColorSensor(pin_cs)
self.set_threshold()
self.set_threshold_cs()
■ set_threshold_cs()
メソッドの追加
今度は、カラーセンサーで色を認識するためのしきい値を設定するメソッドを追加します。ColorSensor
クラスには、get_values()
というメソッドがあり、このメソッドの戻り値は(赤色の強さ、緑色の強さ、青色の強さ、明るさ)の4つの数値からなるタプルです。
今回はカラーセンサーで認識する対象は「赤色の円」と「緑色の円」です。では、実際にそれぞれどのくらいの値がget_values()
メソッドから戻されるのか実際に確かめてみましょう。
新しいファイルを作成して、以下のコードを実行してください。
from pyatcrobo2.parts import ColorSensor
import time
cs = ColorSensor("I2C") while True:
print(cs.get_values())
time.sleep_ms(500)
それぞれの色の円を読むと次のような値が表示されます。
※ 下の値はあくまで参考です。実際の値は印刷した用紙やインクの種類によって異なります。
- 赤色の円を読んだとき
[76, 19, 14, 15]
- 緑色の円を読んだとき
[56, 93, 40, 12]
当たり前ですが、赤色の円のときは赤色の数値が大きくなり、緑色の円のときは緑色の数値が大きくなります。このことから、赤色の円と緑色の円を判別するときは、それぞれの赤色と緑色の値の平均をしきい値として設定し、次のような条件式で分けることができそうです。
※ 以下では、上の例を参考に計算を行っています。
赤色のしきい値: (76 + 56) / 2 = 66 緑色のしきい値: (19 + 93) / 2 = 56 もし、赤色の数値が「66」より大きいなら赤色 もし、緑色の数値が「56」より大きいなら緑色
一見これで良さそうに見えますが、コース上にはあと2つ別の色が存在しています。それが「白色(黒線の外側の白紙)」と「黒色(黒線)」です。赤色と緑色の2つの間の区別だけでなく、白色と黒色も含めて区別できるようにしなければいけません。
では、実際にそれぞれどのような値が戻されるのか確かめてみましょう。
※ 下の値はあくまで参考です。実際の値は印刷した用紙やインクの種類によって異なります。
- 白色(黒線の外側の白紙)を読んだとき
[87, 85, 78, 35]
- 黒色(黒線)を読んだとき
[51, 39, 32, 9]
調べてみると値が近いものもあり、さきほどの条件だけだと判別に失敗する可能性がありそうです。そこで、少し大変ではありますが、「赤色の強さ、緑色の強さ、青色の強さ、明るさ」の4つすべての値に対して、上限値と下限値となるしきい値を設定し、判別するようにします。
では、まずは緑色の円を判別するためのしきい値から設定していきます。set_threshold()
メソッドと同様に、ボタンAが押されるとセンサーの値を取得するようにします。違いとして、LEDディスプレイに表示する文字を「R」にし、カラーセンサーの値を取得します。
追加・変更【35行目~45行目】
def set_threshold_cs(self):
from pystubit.board import button_a, display
import time
display.show("R", delay=0)
while not button_a.is_pressed():
pass
vals_red = self.cs.get_values()
display.clear()
while button_a.is_pressed():
pass
次に、赤色の円を判別するためのしきい値を設定します。4つの値に対して、それぞれ上限値と下限値があるため、リストとタプルを以下のように組み合わせます。
self.threshold_red = [ (赤色の下限値 , 赤色の上限値), (緑色の下限値 , 緑色の上限値), (青色の下限値 , 青色の上限値), (明るさの下限値 , 明るさの上限値), ]
また、上限値と下限値はそれぞれ、調べた値の「+5」と「-5」にしておきましょう。
追加・変更【46行目~50行目】
def set_threshold_cs(self):
from pystubit.board import button_a, display
import time
display.show("R", delay=0)
while not button_a.is_pressed():
pass
vals_red = self.cs.get_values()
display.clear()
while button_a.is_pressed():
pass
self.threshold_red = []
self.threshold_red.append((vals_red[0] - 5, vals_red[0] + 5))
self.threshold_red.append((vals_red[1] - 5, vals_red[1] + 5))
self.threshold_red.append((vals_red[2] - 5, vals_red[2] + 5))
self.threshold_red.append((vals_red[3] - 5, vals_red[3] + 5))
今度は緑色の円を判別するためのしきい値を設定します。さきほどの500ミリ秒後に同じ手順でカラーセンサーの値を取得し、同じくリストとタプルを組み合わせて、それぞれの上限値と下限値を設定しましょう。
追加・変更【51行目~63行目】
def set_threshold_cs(self):
from pystubit.board import button_a, display
import time
display.show("R", delay=0)
while not button_a.is_pressed():
pass
vals_red = self.cs.get_values()
display.clear()
while button_a.is_pressed():
pass
self.threshold_red = []
self.threshold_red.append((vals_red[0] - 5, vals_red[0] + 5))
self.threshold_red.append((vals_red[1] - 5, vals_red[1] + 5))
self.threshold_red.append((vals_red[2] - 5, vals_red[2] + 5))
self.threshold_red.append((vals_red[3] - 5, vals_red[3] + 5))
time.sleep_ms(500)
display.show("G", delay=0)
while not button_a.is_pressed():
pass
vals_green = self.cs.get_values()
display.clear()
while button_a.is_pressed():
pass
self.threshold_green = []
self.threshold_green.append((vals_green[0] - 5, vals_green[0] + 5))
self.threshold_green.append((vals_green[1] - 5, vals_green[1] + 5))
self.threshold_green.append((vals_green[2] - 5, vals_green[2] + 5))
self.threshold_green.append((vals_green[3] - 5, vals_green[3] + 5))
■ linetrace()
メソッドの変更
最後にlinetrace()メソッドを変更しましょう。まずは、カラーセンサーから値を取得した後に、赤色の円であるかどうかを判定します。「赤色の強さ、緑色の強さ、青色の強さ、明るさ」の4つの値を比較するため、for
文と組み合わせ、1つずつ取得した値と比較します。もし、値がしきい値の範囲外だった場合はbreak
文でfor
文を抜けるようにし、正常にfor
文を終えた場合だけが赤色の円だったと分かるようにします。そして、else
文の中で速度を変更します。
【 サンプルコード 5-1-1 】
追加【76行目~82行目】
def linetrace(self):
from pystubit.board import button_b
while button_b.is_pressed():
pass
while not button_b.is_pressed():
val = self.irp.get_value()
if val > self.threshold:
self.curve_right()
else:
self.curve_left()
vals = self.cs.get_values()
for i in range(len(vals)):
if not (vals[i] >= self.threshold_red[i][0] and vals[i] <= self.threshold_red[i][1]):
break
else: # break文でfor文を抜けなかった場合のみ実行
self.set_speed_to_left(4)
self.set_speed_to_right(4)
self.brake()
while button_b.is_pressed():
pass
続けて、同じようなコードで、緑色の円であるかどうかを判定し、そうだった場合は速度を変更しましょう。
追加【83行目~88行目】
def linetrace(self):
from pystubit.board import button_b
while button_b.is_pressed():
pass
while not button_b.is_pressed():
val = self.irp.get_value()
if val > self.threshold:
self.curve_right()
else:
self.curve_left()
vals = self.cs.get_values()
for i in range(len(vals)):
if not (vals[i] >= self.threshold_red[i][0] and vals[i] <= self.threshold_red[i][1]):
break
else:
self.set_speed_to_left(4)
self.set_speed_to_right(4)
for i in range(len(vals)): # コードの構造は赤色の円の場合と同じ
if not (vals[i] >= self.threshold_green[i][0] and vals[i] <= self.threshold_green[i][1]):
break
else:
self.set_speed_to_left(10)
self.set_speed_to_right(10)
self.brake()
while button_b.is_pressed():
pass
■ 動作を確認する
プログラムを実行する前に、__init__()
メソッドの引数にカラーセンサーの端子名を渡す引数pin_cs
を追加したので、プログラムの末尾にあるコードを書き換えましょう。
変更【102行目】
robo = Linetracer(pin_l="M1", pin_r="M2", pin_irp="P0", pin_cs="I2C")
robo.start()
完成したプログラムが以下になります。動作をテストして、うまくいかなかった場合は、見比べて誤りがないかを確認しましょう。
【 サンプルコード 5-1-1 】
from vehicle import VehicleRobot
class Linetracer(VehicleRobot):
def __init__(self, *, pin_l, pin_r, pin_irp, pin_cs):
from pyatcrobo2.parts import IRPhotoReflector
from pyatcrobo2.parts import ColorSensor
super().__init__(pin_l=pin_l, pin_r=pin_r)
self.irp = IRPhotoReflector(pin_irp)
self.cs = ColorSensor(pin_cs)
self.set_threshold()
self.set_threshold_cs()
def set_threshold(self):
from pystubit.board import button_a, display
import time
display.show("W", delay=0)
while not button_a.is_pressed():
pass
val_white = self.irp.get_value()
display.clear()
while button_a.is_pressed():
pass
time.sleep_ms(500)
display.show("B", delay=0)
while not button_a.is_pressed():
pass
val_black = self.irp.get_value()
display.clear()
while button_a.is_pressed():
pass
self.threshold = int((val_white + val_black) / 2)
def set_threshold_cs(self):
from pystubit.board import button_a, display
import time
display.show("R", delay=0)
while not button_a.is_pressed():
pass
vals_red = self.cs.get_values()
display.clear()
while button_a.is_pressed():
pass
self.threshold_red = []
self.threshold_red.append((vals_red[0] - 5, vals_red[0] + 5))
self.threshold_red.append((vals_red[1] - 5, vals_red[1] + 5))
self.threshold_red.append((vals_red[2] - 5, vals_red[2] + 5))
self.threshold_red.append((vals_red[3] - 5, vals_red[3] + 5))
time.sleep_ms(500)
display.show("G", delay=0)
while not button_a.is_pressed():
pass
vals_green = self.cs.get_values()
display.clear()
while button_a.is_pressed():
pass
self.threshold_green = []
self.threshold_green.append((vals_green[0] - 5, vals_green[0] + 5))
self.threshold_green.append((vals_green[1] - 5, vals_green[1] + 5))
self.threshold_green.append((vals_green[2] - 5, vals_green[2] + 5))
self.threshold_green.append((vals_green[3] - 5, vals_green[3] + 5))
def linetrace(self):
from pystubit.board import button_b
while button_b.is_pressed():
pass
while not button_b.is_pressed():
val = self.irp.get_value()
if val > self.threshold:
self.curve_right()
else:
self.curve_left()
vals = self.cs.get_values()
for i in range(len(vals)):
if not (vals[i] >= self.threshold_red[i][0] and vals[i] <= self.threshold_red[i][1]):
break
else:
self.set_speed_to_left(4)
self.set_speed_to_right(4)
for i in range(len(vals)):
if not (vals[i] >= self.threshold_green[i][0] and vals[i] <= self.threshold_green[i][1]):
break
else:
self.set_speed_to_left(10)
self.set_speed_to_right(10)
self.brake()
while button_b.is_pressed():
pass
def start(self):
from pystubit.board import button_b
while True:
if button_b.is_pressed():
self.linetrace()
robo = Linetracer(pin_l="M1", pin_r="M2", pin_irp="P0", pin_cs="I2C")
robo.start()
チャプター6
おわりに
6. 1 このレッスンのまとめ
このレッスンでは、「クラスの継承」と「メソッドのオーバーライド」について新たに学びました。これらを上手に活用することで、効率良くプログラムが書けるだけでなく、一度書いたコードが他でも再利用しやすいというメリットもあります。
最近では、自分の書いたプログラムを誰でも無償で利用できるオープンソースとして公開する人が増えてきています。公開したオープンソースがPythonコミュニティ内で評価されれば、他の誰かが自分のプログラムのバグを改善してくれたり、そのプログラムをベースにして新たな機能を追加したプログラムを開発してくれたりすることもあります。
将来的にそういったことまでやってみたいと思う人はこのレッスンで学んだ内容をしっかりと押さえておきましょう。
6. 2 次のレッスンについて
次のレッスンでは、クラスの継承を重ねる方法と同時に複数のクラスを継承する方法について学習し、赤外線フォトリフレクタとカラーセンサーを使用したコントローラーで操作できる車型ロボットのプログラムを作成します。