--- tags: share, Theme.6 --- Pythonロボティクス講座 レッスン22 テーマ.6-1 2つのDCモーターを使用した車型ロボットの制作 === 車型ロボットを制作して前後左右に走行するプログラムを書こう ## テーマ6を通して学ぶこと テーマ6では全体を通して、オブジェクト指向のプログラム言語がもつ様々な特長を学び、それらを利用して効率的にプログラムを書く方法を詳しく見ていきます。はじめに、テーマ.6-1では車型ロボットを製作し、その制御を行うための「**クラス**」を定義します。続くテーマ.6-2~テーマ.6-4では、そのクラスを「**クラスの継承**」や「**メソッドのオーバーライド**」を利用して拡張し、「線に沿って走るライントレース機能」や「コントローラーで操作できる機能」を作成していきます。「継承」と「オーバーライド」の2つの新しい言葉が出てきましたが、順番に説明をしていきますので、まずはこのレッスンでクラスについて復習していきましょう。 ## このレッスンで学ぶこと このレッスンでは、レッスン8(テーマ.2-3)で学習したプロパティとメソッドをもつオブジェクトの型となる「**クラス**」について振り返りを行います。そして、2つのDCモーターを使用して車型ロボットを製作し、その走行を制御するためのプロパティやメソッドをもつ独自のクラスを定義します。また、関数やメソッドの内容を変更せずに機能を拡張できる「**デコレータ**」という便利な仕組みについても合わせて学習します。 :::info <概要説明動画> vimeo https://vimeo.com/techacademy/review/381406518/d730fcf15e ::: ## Pythonの文法の復習 ここでは、簡単なサンプルプログラムを通して、クラスについて復習しましょう。 ### クラスについて クラスは例えるなら、クッキーの型抜き器のようなものです。型抜き器が1枚1枚のクッキーの形を決めるのと同じように、クラスは1つ1つのオブジェクトの型を定義しています。同じクラスから作成されたオブジェクトは同じ名前のプロパティとメソッドを持っています。このようにクラスによってつくられたオブジェクトは「**インスタンス**」と呼ばれます。 <img src="https://www.artec-kk.co.jp/school/cl/textbooks/material/theme_6-1/6-1_1.png"/> ### クラスの定義方法 クラスを定義するときは「**class**」というキーワードを使います。「 :(コロン)」の後にインデントを入れた部分がクラスの適用範囲になっていて、この中で定義された変数や関数は、「**プロパティ**」や「**メソッド**」と呼ばれます。 ``` class MyClass: # クラス名の定義 property = "..." # プロパティの定義 def method(self, ...): # メソッドの定義 . . ``` 例えば、次の「犬」の特徴をまとめたクラスを新たに定義する場合、次のようにコードを書きます。 <img src="https://www.artec-kk.co.jp/school/cl/textbooks/material/theme_6-1/6-1_2.png"/> ##### 【 サンプルコード 3-2-1 】 ```python==1 class Dog: voice = 'Bow!' # 鳴き声のプロパティ def bark(self): # プロパティで決められた鳴き声で吠える print(self.voice) ``` プロパティは変数を定義するときと全く同じですが、メソッドは関数の定義と違うところがあります。それは、必ず1番目の引数として「**`self`**」を渡すという点です。この`self`はインスタンス自身を表しており、「`.`(ドット)」を使ってプロパティやメソッドを呼び出すことができます。 ###### ※ 第1引数に`self`以外の名前を付けても問題なくインスタンス自身が受け渡されます。しかし、特別な理由がない限りは分かりやすさから`self`としておきましょう。 ### クラスからインスタンスを作成する方法 インスタンスを作成するときは、次のように書きます。 ``` インスタンス名 = クラス名() ``` 実際に【 サンプルコード 3-2-1 】を実行し、`Dog`クラスのインスタンスを作成して、プロパティやメソッドを呼び出してみましょう。また、メソッドの第1引数の`self`は自動的に渡されるため、呼び出すときに指定する必要はありません。 <pre class="prettyprint"> &gt;&gt;&gt; dog = Dog() &gt;&gt;&gt; print(dog.voice) Bow! &gt;&gt;&gt; dog.bark() Bow! </pre> ### コンストラクタという特別なメソッド クラスからインスタンスを作成するときに自動的に実行されるメソッドを「**コンストラクタ**」と呼びます。この特別なメソッドは、「**`__init__`**」という名前で定義します。 ``` class MyClass: # クラス名の定義 def __init__(self, ...): # コンストラクタ . . ``` 通常、コンストラクタの中では、インスタンスが独自に持つプロパティを設定したり、初期処理として必要なメソッドを実行したりします。 例として、【 サンプルコード 3-2-1 】に犬の名前をプロパティとして加えるコンストラクタを追加してみましょう。 ##### 【 サンプルコード 3-4-1 】 ###### 追加・変更【4行目、5行目、8行目】 ```python=1 class Dog: voice = 'Bow!' def __init__(self, name): # コンストラクタ self.name = name # 犬の名前 def bark(self): print(self.name, self.voice) # 名前も合わせて表示 ``` では、【 サンプルコード 3-4-1 】を実行して、2匹の犬「`Taro`」と「`Jiro`」を作成し、それぞれ`bark()`メソッドを実行してみましょう。 <pre class="prettyprint"> &gt;&gt;&gt; taro = Dog("Taro") &gt;&gt;&gt; jiro = Dog("Jiro") &gt;&gt;&gt; taro.bark() Taro Bow! &gt;&gt;&gt; jiro.bark() Jiro Bow! </pre> `voice`がすべてのインスタンスが共通して同じ値をもつプロパティであるのに対し、`name`はインスタンスごとに違う値をもつプロパティです。`voice`のようなプロパティは「**クラスメンバ変数**」、`name`のようなプロパティは「**インスタンスメンバ変数**」と呼ばれ、区別されています。 ## 新しいPython文法の学習 ここでは、簡単なサンプルプログラムを通して、「**デコレータ**」について学習しましょう。 ### デコレータについて デコレータは、「**引数として関数を受け取り、新たに別の関数を返す関数**」です。ちょっとわかりづらいですが、ポイントはデコレータは関数であり、次の3つの性質を持っているということです。 1. 関数を引数として受け取る 2. 内部で新たな関数を定義する 3. 2の関数を戻り値として返す では、具体的なデコレータの例を見てみましょう。まずは、複数の数値の足し算と掛け算を行い、その結果を戻す2つの関数を用意します。 ```python=1 def addition(*args): # 足し算 result = 0 for num in args: result += num return result def multiplication(*args): # 掛け算 result = 0 for num in args: if(result == 0): result = num # 1つめの数値のみそのまま代入 else: result *= num return result ``` これら2つの関数に新たに`print`文で結果を表示する機能を追加したいとします。そのために次のデコレータを定義します。 ###### 追加【1行目~5行目】 ```python=1 def decorator(func): def new_func(*args): # 新たに定義した関数、funcと同じ引数を取る result = func(*args) # funcに引数を渡して実行 print("Result:",result) # 追加で行う処理 return new_func # 新たに定義した関数を返す def addition(*args): result = 0 . . . ``` デコレータを追加する方法は2つあります。1つは次のように、デコレータ関数を実行し、その戻り値を変数に格納して再び実行する方法です。 <pre class="prettyprint"> &gt;&gt;&gt; new_addition = decorator(addition) &gt;&gt;&gt; new_addition(1, 2, 3) Result: 6 </pre> これだと少し手作業が増えてしまいます。もう1つの方法は、追加したい関数の直前に「`@デコレータ関数名`」を書くことです。こちらの方がコードもすっきりとします。 ##### 【 サンプルコード 4-1-1 】 ###### 追加【7行目、14行目】 ```python=1 def decorator(func): def new_func(*args): result = func(*args) print("Result:",result) return new_func @decorator def addition(*args): result = 0 for num in args: result += num return result @decorator def multiplication(*args): result = 0 for num in args: if(result == 0): result = num else: result *= num return result ``` この【 サンプルコード 4-1-1 】を実行して、それぞれの関数を呼び出してみましょう。 <pre class="prettyprint"> &gt;&gt;&gt; addition(1, 2, 3) Result: 6 &gt;&gt;&gt; multiplication(2, 3, 4) Result: 24 </pre> 結果を表示する機能が見事に追加されています。このようにデコレータを利用することで、関数の内容を変更せずに機能を拡張することができるだけでなく、同じデコレータを別の関数へ再利用することができます。上の例では関数にデコレータを付与しましたが、メソッドにも付与することができます。以下の例は【 サンプルコード 3-4-1 】に、2回メソッドを実行するデコレータ`repeat_twice`を付与したものです。 ##### 【 サンプルコード 4-1-2 】 ###### 追加【1行目~5行目、13行目】 ```python=1 def repeat_twice(func): def new_func(self): for _ in range(2): func(self) return new_func class Dog: voice = 'Bow!' def __init__(self, name): self.name = name @repeat_twice def bark(self): print(self.name, self.voice) ``` ##### (実行結果) <pre class="prettyprint"> &gt;&gt;&gt; dog = Dog("Taro") &gt;&gt;&gt; dog.bark() Taro Bow! Taro Bow! </pre> また、デコレータは重ねて付与することもできます。以下は上の例にさらに3回メソッドを実行するデコレータ`repeat_three_times`を追加して付与しています。 ##### 【 サンプルコード 4-1-3 】 ###### 追加【7行目~11行目、19行目】 ```python=1 def repeat_twice(func): def new_func(self): for _ in range(2): func(self) return new_func def repeat_three_times(func): def new_func(self): for _ in range(3): func(self) return new_func class Dog: voice = 'Bow!' def __init__(self, name): self.name = name @repeat_three_times @repeat_twice def bark(self): print(self.name, self.voice) ``` ##### (実行結果) <pre class="prettyprint"> &gt;&gt;&gt; dog = Dog("Taro") &gt;&gt;&gt; dog.bark() Taro Bow! Taro Bow! Taro Bow! Taro Bow! Taro Bow! Taro Bow! </pre> ## 車型ロボットの組み立て それでは、ここからは車型ロボットの製作に入ります。早速、組立説明書を開き、手順に沿って車型ロボットの組み立てを行いましょう。 ### 組み立てに必要なパーツ ##### 【 パーツ一覧 】 * Studuino:bit×1 * ロボット拡張ユニット×1 * 電池ボックス×1 * DCモーター×2 * ブロック基本四角(黒)×2 * ブロック基本四角(赤)×2 * ブロック三角(赤)×2 * ブロックハーフB(グレー)×1 * ブロックハーフB(黒)×2 * ブロックハーフB(赤)×2 * ブロックハーフC(白)×4 * ブロックハーフD(白)×6 * ステー×4 * 丸(目玉)×1 ##### 【 アーテックブロックの形状 】 <img src="https://www.artec-kk.co.jp/school/cl/textbooks/material/theme_6-1/6-1_3.png"/> ### 組立説明書 以下のリンク先から組立説明書を開いてください。 [車型ロボットの組立説明書](https://www.artec-kk.co.jp/school/cl/textbooks/material/theme_6-1/6-1_composition.pdf) ## 車型ロボットのプログラム作成 ここからは、組み立てた車型ロボットの動作を制御するプログラムを作成していきます。 ### 復習: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()`メソッドの違い 】 {%youtube 8Fs1sFuQ0cM %} :::info vimeo https://vimeo.com/techacademy/review/380649220/91cbf48add ::: ### DCモーターの回転方向と車型ロボットの進行方向の関係 組み立てた車型ロボットでは、DCモーターの回転方向とタイヤの回転方向に次の図ような関係があります。 ##### 【 DCモーターの回転方向とタイヤの回転方向 】 <img src="https://www.artec-kk.co.jp/school/cl/textbooks/material/theme_6-1/6-1_4.png"/> そのため、左右2つのDCモーターそれぞれ次の組み合わせで回転させることで、車型ロボットの進行方向を制御します。 ##### 【 DCモーターの回転方向と車型ロボットの動作の関係 】 |動作|図|M1のDCモーター</br>(左側のタイヤ)|M2のDCモーター</br>(右側のタイヤ)| |:---|:---|:---|:---| |前進|<img src="https://www.artec-kk.co.jp/school/cl/textbooks/material/theme_6-1/6-1_5.png"/>|逆転|逆転| |後退|<img src="https://www.artec-kk.co.jp/school/cl/textbooks/material/theme_6-1/6-1_6.png"/>|正転|正転| |左回転|<img src="https://www.artec-kk.co.jp/school/cl/textbooks/material/theme_6-1/6-1_7.png"/>|正転|逆転| |右回転|<img src="https://www.artec-kk.co.jp/school/cl/textbooks/material/theme_6-1/6-1_8.png"/>|逆転|正転| |左に曲がる|<img src="https://www.artec-kk.co.jp/school/cl/textbooks/material/theme_6-1/6-1_9.png"/>|停止|逆転| |右に曲がる|<img src="https://www.artec-kk.co.jp/school/cl/textbooks/material/theme_6-1/6-1_10.png"/>|逆転|停止| 「左回転」や「右回転」はその場で回り、「左に曲がる」や「右に曲がる」は、停止したタイヤを中心として、円弧を描くように回ります。**「右や左への回転」は「右や左へ曲がる」ときと比べて、回るときに描く円の半径が半分になるため、およそ半分の時間で向きを変えることができます。** ##### 【 向きを変えるためのかかる時間の違い 】 {%youtube WHzvS5Bhakg %} :::info vimeo https://vimeo.com/techacademy/review/380649232/e06234c96d ::: ### 車型ロボットのクラス定義 これらの動作をそれぞれ関数として定義して利用することもできますが、プロパティ(属性情報)やメソッドをもつクラスとして定義する方が管理しやすく、また後のレッスンで車型ロボットの機能を拡張するときにも再利用しやすくなります。そこで、オリジナルのクラス「`VehicleRobot`」を新たに定義し、次のプロパティとメソッドを持たせましょう。 ###### ※ 英語で「車」のことを「vehicle」といいます。 ##### 【 `VehicleRobot`クラスで定義するプロパティ 】 |プロパティ名|役割| |:---|:---| |`dcm_l`|**左のタイヤ**に取り付けたDCモーターを扱うために、<br/>DCMotorクラスのインスタンスを格納する。| |`dcm_r`|**右のタイヤ**に取り付けたDCモーターを扱うために、<br/>DCMotorクラスのインスタンスを格納する。| ##### 【 `VehicleRobot`クラスで定義するメソッド 】 |メソッド名|振る舞い| |:---|:---| |`__init__()`|初期化関数。2つのDCMotorクラスのインスタンスや、<br/>初期のDCモーターの速さを設定する。| |`move_forward()`|前進する| |`move_backward()`|後退する| |`rotate_left()`|左方向へその場で回転する<br/>※左右のタイヤは互いに反対向きに回転| |`rotate_right()`|右方向へその場で回転する<br/>※左右のタイヤは互いに反対向きに回転| |`curve_left()`|左方向に曲がる<br/>※左のタイヤは停止し、右のタイヤのみ回転| |`curve_right()`|右方向に曲がる<br/>※左のタイヤのみ回転し、右のタイヤは停止| |`stop()`|ブレーキをかけずにゆっくりと止まる| |`set_speed_to_left()`|左のタイヤの回転の速さを1~10の10段階で設定する| |`set_speed_to_right()`|右のタイヤの回転の速さを1~10の10段階で設定する| ###### ※「`rotate`」は、日本語で「回転する」を意味しています。 #### ■ コンストラクタ(`__init__()`メソッド)の定義 コンストラクタである`__init__()`メソッドでは、`DCMotor`クラスのインスタンスを作成して、プロパティに格納する処理を行います。`DCMotor`クラスのインスタンスを作成するときは引数として端子名の文字列(`"M1"`または`"M2"`)が必要なため、`__init__()`メソッドでも同じように、左と右のタイヤを取り付けたDCモーターの接続先の端子名を引数として受け取るようにしましょう。 ```python==1 from pyatcrobo2.parts import DCMotor class VehicleRobot: # pin_l が左側の端子、pin_r は右側の端子 def __init__(self, *, pin_l, pin_r): self.dcm_l = DCMotor(pin_l) self.dcm_r = DCMotor(pin_r) ``` 4行目で、`__init__(self, *, pin_l, pin_r)`と定義しているように「`*,`」より後ろの引数はキーワード引数として強制されるようになります。そのため、このクラスのインスタンスを作成するときは次のようにコードを書きます。 <pre class="prettyprint"> robo = VehicleRobot(pin_l="M1", pin_r="M2") </pre> 続けて、作成した`DCMotor`クラスのインスタンスの`power()`メソッドを呼び出して、初期のDCモーターの出力の大きさを設定します。ここでは、100としておきましょう。また、プロパティを呼び出すときは必ず先頭に`self.`を付けることに注意してください。 ###### 追加【7行目、8行目】 ```python==1 from pyatcrobo2.parts import DCMotor class VehicleRobot: 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) ``` #### ■ 前進・後退を行うメソッドの定義 次に、車型ロボットの前進と後退のメソッドを定義します。 |動作|メソッド名|`self.dcm_l`</br>(左側のタイヤ)|`self.dcm_r`</br>(右側のタイヤ)| |:---|:---|:---|:---|:---| |前進|`move_forward()`|`ccw()`:逆転|`ccw()`:逆転| |後退|`move_backward()`|`cw()`:正転|`cw()`:正転| 上の表を確認して、それぞれ次のように定義しましょう。 ###### 追加【10行目~16行目】 ```python==1 from pyatcrobo2.parts import DCMotor class VehicleRobot: 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) def move_forward(self): self.dcm_l.ccw() self.dcm_r.ccw() def move_backward(self): self.dcm_l.cw() self.dcm_r.cw() ``` #### ■ 左回転・右回転を行うメソッドの定義 続けて、車型ロボットの左回転と右回転のメソッドを定義します。 |動作|メソッド名|`self.dcm_l`</br>(左側のタイヤ)|`self.dcm_r`</br>(右側のタイヤ)| |:---|:---|:---|:---|:---| |左回転|`rotate_left()`|`cw()`:正転|`ccw()`:逆転| |右回転|`rotate_right()`|`ccw()`:逆転|`cw()`:正転| 上の表を確認して、それぞれ次のように定義しましょう。 ###### 追加【18行目~24行目】 ###### ※ インデントの位置に注意してください。 ```python==14 def move_backward(self): self.dcm_l.cw() self.dcm_r.cw() def rotate_left(self): self.dcm_l.cw() self.dcm_r.ccw() def rotate_right(self): self.dcm_l.ccw() self.dcm_r.cw() ``` #### ■ 左に曲がる・右に曲がるを行うメソッドの定義 今度は、車型ロボットが左に曲がるときと右に曲がるときのメソッドを定義します。 |動作|メソッド名|`self.dcm_l`</br>(左側のタイヤ)|`self.dcm_r`</br>(右側のタイヤ)| |:---|:---|:---|:---|:---| |左に曲がる|`curve_left()`|`barake()`:停止|`ccw()`:逆転| |右に曲がる|`curve_right()`|`ccw()`:逆転|`brake()`:停止| 上の表を確認して、それぞれ次のように定義しましょう。 ###### 追加【26行目~32行目】 ```python==22 def rotate_right(self): self.dcm_l.ccw() self.dcm_r.cw() def curve_left(self): self.dcm_l.brake() self.dcm_r.ccw() def curve_right(self): self.dcm_l.ccw() self.dcm_r.brake() ``` #### ■ 停止を行うメソッドの定義 また、動作を停止する2つのメソッドも定義します。 |動作|メソッド名|`self.dcm_l`</br>(左側のタイヤ)|`self.dcm_r`</br>(右側のタイヤ)| |:---|:---|:---|:---|:---| |ブレーキをかけずに</br>ゆっくりと止まる|`stop()`|`stop()`|`stop()`| |ブレーキをかけて</br>ピタっと止まる|`brake()`|`brake()`|`brake()`| 上の表を確認して、それぞれ次のように定義しましょう。 ###### 追加【34行目~40行目】 ```python==30 def curve_right(self): self.dcm_l.ccw() self.dcm_r.stop() def stop(self): self.dcm_l.stop() self.dcm_r.stop() def brake(self): self.dcm_l.brake() self.dcm_r.brake() ``` #### ■ タイヤの回転の速さを設定するメソッドの定義 最後に、タイヤの回転の速さを制御するためのメソッドを定義します。 |動作|メソッド名|`self.dcm_l`</br>(左側のタイヤ)|`self.dcm_r`</br>(右側のタイヤ)| |:---|:---|:---|:---|:---| |左のタイヤの回転の速さを</br>1~10の10段階で設定する|`set_speed_to_left(speed)`|`power(power)`|なし| |右のタイヤの回転の速さを</br>1~10の10段階で設定する|`set_speed_to_right(speed)`|なし|`power(power)`| これらのメソッドでは、引数`speed`に1~10を指定して10段階でタイヤの回転の速さを制御します。しかし、このメソッドの内部で実行する`DCMotor`クラスの`power()`メソッドは引数`power`に0~255の範囲で出力の大きさを指定します。そのため、メソッド内部で数値の大きさを次の式であらじめ変換しておきます。 ``` power = speed * 25 ``` この式によって、以下の図のように速さを出力の大きさへ置き換わります。 <img src="https://www.artec-kk.co.jp/school/cl/textbooks/material/theme_6-1/6-1_11.png"/> また、引数`speed`に整数以外の値が入力されたり、範囲外の値が指定されるとエラーが発生しますので、あらかじめメソッドの内部で回避しておきましょう。 ###### 追加【42行目~53行目】 ```python==38 def brake(self): self.dcm_l.brake() self.dcm_r.brake() def set_speed_to_left(self, speed): speed = int(speed) # 整数型に変換 speed = 1 if speed < 1 else speed # 1より小さいときは1に設定 speed = 10 if speed > 10 else speed # 10より大きいときは10に設定 power = speed * 25 self.dcm_l.power(power) def set_speed_to_right(self, speed): speed = int(speed) speed = 1 if speed < 1 else speed speed = 10 if speed > 10 else speed power = speed * 25 self.dcm_r.power(power) ``` #### クラス定義の確認 これで、クラスに必要なプロパティとメソッドが定義できました。書いたコードに誤りがないか、次のサンプルコードと見比べて確認しましょう。 ##### 【 サンプルコード 6-3-1 】 ```python==1 from pyatcrobo2.parts import DCMotor class VehicleRobot: 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) def move_forward(self): self.dcm_l.ccw() self.dcm_r.ccw() def move_backward(self): self.dcm_l.cw() self.dcm_r.cw() def rotate_left(self): self.dcm_l.cw() self.dcm_r.ccw() def rotate_right(self): self.dcm_l.ccw() self.dcm_r.cw() def curve_left(self): self.dcm_l.brake() self.dcm_r.ccw() def curve_right(self): self.dcm_l.ccw() self.dcm_r.brake() def stop(self): self.dcm_l.stop() self.dcm_r.stop() def brake(self): self.dcm_l.brake() self.dcm_r.brake() def set_speed_to_left(self, speed): speed = int(speed) speed = 1 if speed < 1 else speed speed = 10 if speed > 10 else speed power = speed * 25 self.dcm_l.power(power) def set_speed_to_right(self, speed): speed = int(speed) speed = 1 if speed < 1 else speed speed = 10 if speed > 10 else speed power = speed * 25 self.dcm_r.power(power) ``` ### クラスを定義したファイルをモジュールにして利用する この`VehicleRobot`クラスは次からのレッスンでも利用していきます。そこで、そこでStuduino:bit内に保存して、モジュールとして利用できるようにしてみましょう。 #### ■ Studuino:bit内にファイルを保存する 以下の手順で作成したファイルをStuduino:bit内に保存しましょう。 1. `vehicle(.py)`と付けてPC上にファイルを保存します。 2. メニューの「ファイル」をクリックし、1のファイルをStuduino:bit内に保存します。 3. ファイルの転送が完了したことを確認して、もう一度メニューの「ファイル」をクリック、ウィンドウを閉じます。 4. Studuino:bitのリセットボタンを押します。 #### ■ モジュールからクラスをインポートして利用する メニューの「新規」をクリックして、新しいファイルを作成します。このファイルから保存した`vechicle(.py)`モジュールから`VehicleRobot`クラスをインポートして、インスタンスを作成してみましょう。 ```python==1 from vehicle import VehicleRobot robo = VehicleRobot(pin_l="M1", pin_r="M2") ``` 次の順番でメソッドを**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行目】 ```python==1 from vehicle import VehicleRobot import time robo = VehicleRobot(pin_l="M1", pin_r="M2") robo.move_forward() time.sleep_ms(1000) robo.move_backward() time.sleep_ms(1000) robo.rotate_left() time.sleep_ms(1000) robo.rotate_right() time.sleep_ms(1000) robo.curve_left() time.sleep_ms(1000) robo.curve_right() time.sleep_ms(1000) robo.brake() ``` ### 車型ロボットを走らせる プログラムが問題なく動作することが確認できたら、次は以下のように車型ロボットを走らせるにはどのようなコードを書けば良いか考えてみましょう。それぞれのサンプルコードは最後に書いています。 ##### 【 逆S字を描くように走らせる 】 <img src="https://www.artec-kk.co.jp/school/cl/textbooks/material/theme_6-1/6-1_12.png"/> ##### 【 正方形を描くように走らせる 】 <img src="https://www.artec-kk.co.jp/school/cl/textbooks/material/theme_6-1/6-1_13.png"/> ##### 【 サンプルコード 4-5-1 】 逆S字を描くように走らせる ###### ※ DCモーターの回転の速さは電池の残量によってプログラム上では同じ数値設定でも実際の速さが異なります。下記のプログラムでS字を描かない場合は、`time.sleep_ms()`メソッドの引数を調整してください。 ```python==1 from vehicle import VehicleRobot import time robo = VehicleRobot(pin_l="M1", pin_r="M2") robo.curve_right() time.sleep_ms(6000) robo.curve_left() time.sleep_ms(6000) robo.brake() ``` ##### 【 サンプルコード 6-5-2 】 正方形を描くように走らせる ###### ※ 正方形を描くときは、「右回転」⇒「前進」の動きを3回繰り返しています。そのため、以下のように`for`文を使用して簡潔にプログラムをまとめることもできます。 ```python==1 from vehicle import VehicleRobot import time robo = VehicleRobot(pin_l="M1", pin_r="M2") robo.move_forward() time.sleep_ms(2000) for _ in range(3): robo.rotate_right() time.sleep_ms(1000) robo.move_forward() time.sleep_ms(2000) robo.brake() ``` ## 課題:デコレータを利用した走行時間の制御機能の追加 チャプター4のプログラムでは、それぞれの動作を行う時間を制御するために、その都度`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()`メソッド ### プログラムの作成例 デコレータとして、`vehicle(.py)`で定義している`VehicleRobot`クラスに次の`time_control()`メソッドを追加します。このデコレータは引数`func`として、指定されたメソッドを受け取り、内部でこのメソッドの実行と、追加で実行する処理を定義した新たなメソッドを戻します。 ###### 追加【2行目、11行目~17行目】 ```python==1 from pyatcrobo2.parts import DCMotor import time # timeモジュールを新たにインポートしていることに注意! class VehicleRobot: 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) def time_control(func): def new_func(self, duration=-1): func(self) if duration > 0: time.sleep_ms(int(duration)) self.brake() return new_func ``` 新たなメソッドは、`def new_func(self, duration=-1)`の通り、`-1`をデフォルト値としてもつ引数`duration`が追加されています。このように`-1`をデフォルト値としていることで、14行目~16行目の処理で、引数が省略された場合は、`time.sleep_ms()`メソッドと`brake()`メソッドが実行されず、元のメソッドと同じ処理が行われるようになっています。 そして、このデコレータを各メソッドの上に修飾すると、プログラムの完成となります。 ##### 【 サンプルコード 7-1-1 】 ```python==1 from pyatcrobo2.parts import DCMotor import time class VehicleRobot: 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) def time_control(func): def new_func(self, duration=-1): func(self) if duration > 0: time.sleep_ms(int(duration)) self.brake() return new_func @time_control def move_forward(self): self.dcm_l.ccw() self.dcm_r.ccw() @time_control def move_backward(self): self.dcm_l.cw() self.dcm_r.cw() @time_control def rotate_left(self): self.dcm_l.cw() self.dcm_r.ccw() @time_control def rotate_right(self): self.dcm_l.ccw() self.dcm_r.cw() @time_control def curve_left(self): self.dcm_l.brake() self.dcm_r.ccw() @time_control def curve_right(self): self.dcm_l.ccw() self.dcm_r.brake() def stop(self): self.dcm_l.stop() self.dcm_r.stop() def brake(self): self.dcm_l.brake() self.dcm_r.brake() def set_speed_to_left(self, speed): speed = int(speed) speed = 1 if speed < 1 else speed speed = 10 if speed > 10 else speed power = speed * 25 self.dcm_l.power(power) def set_speed_to_right(self, speed): speed = int(speed) speed = 1 if speed < 1 else speed speed = 10 if speed > 10 else speed power = speed * 25 self.dcm_r.power(power) ``` ## おわりに ### このレッスンのまとめ このレッスンでは、クラスの定義方法について振り返りを行い、新たに「デコレータ」という仕組みを学びました。レッスンの後半では、車型ロボットを製作し、その走行の制御を行うための独自クラスを定義しました。 また、実際にそのクラスを使って作成したオブジェクトで逆S字や四角を描くように走行させるプログラムを作成しました。これらの走行プログラムは、独自クラスを定義しなくても書くことはできますが、その場合それらのプログラムを他の走行プログラムへ再利用することは難しくなります。 今回のようにクラスとして共通で使用できそうな処理をまとめておくことで、他のプログラムへも再利用しやすくなり、必要な機能を最小限の労力でつくれるようになります。 ### 次のレッスンについて 次のレッスンでは、オブジェクト指向なプログラム言語がもつ重要な特長として、「**クラスの継承**」と「**メソッドのオーバーライド**」について詳しく説明します。また、このれらの特長を活かして、今回製作した車型ロボットに、紙に描かれた線に沿って走行する「**ライントレース機能**」を追加します。 [次のレッスン【テーマ.6-2】へ](https://hackmd.io/74t1CHTCTKmyqQwMIr2K7Q)