◆ Lego Mindstoms NXT - leJOS - (13) 振舞い(Behavior)と調停者(Arbitrator)
黒線で囲まれたところからロボットが出ずに、その中だけで障害物を避
けながらロボットを動かす」プログラムを前回は、lejos.subsumption
パッケージの API を使わずに作りました。
■lejos.subsumption をつかわない実装
再掲します。
------------------------------------------------------
import l.EscapeListener;
import lejos.navigation.Pilot;
import lejos.nxt.Button;
import lejos.nxt.LCD;
import lejos.nxt.LightSensor;
import lejos.nxt.Motor;
import lejos.nxt.SensorPort;
import lejos.nxt.Sound;
import lejos.nxt.TouchSensor;
/**
* まっすぐ走り、障害物にぶつかったら、少しバックして方向を換えて
* また前に進む。または、黒線をまたいだら150度方向転換するプログラム。
* @author marunomaruno
* @version 1.0, 2008/09/29
* @since 1.0
*/
public class Bumper12 {
private static final int THRESHOLD = 40; // 黒線を判断するしきい値
public static void main(String [] args) {
// ESCAPE ボタンを押すとプログラム停止する
Button.ESCAPE.addButtonListener(new EscapeListener());
// トライボットのオブジェクトを生成する
Pilot trybot = new Pilot(5.6f, 13f, Motor.C, Motor.B);
// タッチセンサーオブジェクトをポート1で生成する
TouchSensor touchSensor = new TouchSensor(SensorPort.S1);
// 光センサーオブジェクトをポート1で生成する
LightSensor lightSensor = new LightSensor(SensorPort.S2);
while (true) {
trybot.forward(); // 直進する
while (!touchSensor.isPressed() && (lightSensor.readValue() >= THRESHOLD)); // タッチされるか黒線を超えるまで待つ
LCD.clear();
LCD.drawString(touchSensor.isPressed() ? "true" : "false", 1, 1);
LCD.drawInt(lightSensor.readValue(), 1, 2);
if (touchSensor.isPressed()) {
// 障害物にぶつかった場合
trybot.backward(); // 後退する
Sound.pause(500);
trybot.rotate(90); // 回転する
} else {
// 黒線を越えた場合
trybot.stop(); // 停止する
trybot.rotate(150); // 回転する
}
}
}
}
------------------------------------------------------
このプログラムは、
まっすぐ走る
障害物にぶつかったら少し後退して方向を換える
黒線をまたいだら、黒線をまたいだら 150 度回転して、進行方向
を逆にする
という 3 つの振舞いの複合になっており、まっすぐ走っている最中に、
障害物にぶつかる、黒線をまたぐ、といったイベントに対して、それぞ
れ、少し後退して方向を換える、150度回転する、といった振舞いを実
装しないといけないからです。
まだ、3つの振舞いだけであれば何とかなっても、ここに、別の振舞い
を入れると、プログラムのコードは飛躍的に難しくなります。
これを、上記の 3 つの振舞いを持ったオブジェクトに分解して、それ
ぞれのオブジェクトを制御する形でメインメソッドを作ると、プログラ
ムはけっこうすっきりした形になります。
このための API が、Behavior インターフェースと Arbitrator クラス
で提供されているのです。
Behavior インターフェースには、それを実装したオブジェクトの振舞
い(実行部)を記述する action メソッドと、実行すべきかどうかを判断
する条件部(takeControl メソッド)を記述します。また、他の振舞いに
制御が移るときに実行する suppress メソッドを記述します。
Arbitrator (調停者)クラスのオブジェクトは、そのコンストラクター
の引数に、Behavior オブジェクトの配列を指定して作ります。
Arbitrator クラスの start メソッドを実行することで、調停を開始し
ます。これは、ロボットの状態から、Behavior.takeControl メソッド
を実行して、ロボットの状態に合致する振舞いオブジェクトを選択し、
そのオブジェクトの action メソッドを実行します。
それでは、上記の3つの振舞いをそれぞれ
DriveForward (直進する)
HitWall (障害物にぶつかったときに後退して90度回転する)
DarkLine (黒線を超えたときに150度回転する)
クラスに記します。なお、これらのクラスは、 bhvr パッケージに入れ
ることにします。
■直進する振舞いを規定するクラス
------------------------------------------------------
package bhvr;
import lejos.navigation.Pilot;
import lejos.subsumption.Behavior;
/**
* 直進する振舞いを規定するクラス
* @author marunomaruno
* @version 1.0, 2008/09/29
* @since 1.0
*/
public class DriveForward implements Behavior { // (1)
private Pilot pilot;
public DriveForward(Pilot pilot) { // (2)
this.pilot = pilot;
}
public boolean takeControl() { // (3)
return true;
}
public void suppress() { // (4)
pilot.stop();
}
public void action() { // (5)
pilot.forward();
}
}
------------------------------------------------------
□ public class DriveForward implements Behavior { // (1)
Behavior インターフェースを実装して、振舞いを規定します。
□ Behavior インターフェースのメソッド
---
void action()
ロボットの振舞い(実行するアクション)を記す。
void suppress()
このアクションが終わるときの振舞いを記す。
boolean takeControl()
action() を行うかどうかを判断する。
---
□ public DriveForward(Pilot pilot) { // (2)
Pilot オブジェクトを受け取るコンストラクター。
□ public boolean takeControl() { // (3)
action() を行うかどうかを判断します。ここでは、ひたすら直進する
だけなので、常に true を返します。
□ public void suppress() { // (4)
このアクションが終わるときの振舞いを記します。
□ public void action() { // (5)
ロボットが実行すべきアクションを記します。このオブジェクトはひた
すら直進するだけです。
■障害物にぶつかったときに後退して90度回転する振舞いを規定するクラス
------------------------------------------------------
package bhvr;
import lejos.navigation.Pilot;
import lejos.nxt.SensorPort;
import lejos.nxt.Sound;
import lejos.subsumption.Behavior;
/**
* 障害物にぶつかったときに後退して90度回転する振舞いを規定するクラス
* @author marunomaruno
* @version 1.0, 2008/09/29
* @since 1.0
*/
public class HitWall implements Behavior {
private Pilot pilot;
public HitWall(Pilot pilot) {
this.pilot = pilot;
}
public boolean takeControl() {
return SensorPort.S1.readBooleanValue();
}
public void suppress() {
Sound.systemSound(true, 2);
pilot.stop();
}
public void action() {
pilot.backward(); // 後退する
Sound.pause(500);
pilot.rotate(90); // 回転する
}
}
------------------------------------------------------
□ takeControl メソッド
タッチセンサーを使って、タッチされたかどうかを判断します。タッチ
された場合、true を返し、これによって、action メソッドが動くこと
になります。
□ action メソッド
500ミリ秒後退して、90度回転します。
■ 黒線を超えたときに 150 度回転する振舞いを規定するクラス
------------------------------------------------------
package bhvr;
import lejos.navigation.Pilot;
import lejos.nxt.LCD;
import lejos.nxt.LightSensor;
import lejos.nxt.SensorPort;
import lejos.nxt.Sound;
import lejos.subsumption.Behavior;
/**
* 黒線を超えたときに 150 度回転する振舞いを規定するクラス
* @author marunomaruno
* @version 1.0, 2008/09/29
* @since 1.0
*/
public class DarkLine implements Behavior {
private static final int THRESHOLD = 40; // 黒線を判断するしきい値
private Pilot pilot;
private LightSensor lightSensor;
public DarkLine(Pilot pilot) {
this.pilot = pilot;
lightSensor = new LightSensor(SensorPort.S2);
}
public boolean takeControl() {
LCD.drawInt(lightSensor.readValue(), 1, 1);
return lightSensor.readValue() < THRESHOLD;
}
public void suppress() {
Sound.systemSound(true, 3);
pilot.stop();
}
public void action() {
pilot.stop();
pilot.rotate(150);
}
}
------------------------------------------------------
□ takeControl メソッド
光センサーを使って、黒線を超えたかどうかを判断します。超えた場合、
つまり、光度がしきい値(40)未満の場合は true を返し、これによって、
action メソッドが動くことになります。
□ action メソッド
ロボットを停止して、150 度回転します。
■黒線で書かれた枠の中で、障害物を避けながら走るプログラム
いよいよ、main メソッドを持つクラスです。
上記の3つの振舞いを持つオブジェクトを、Arbitrator クラスに渡して、
これらの振舞いを制御します。
------------------------------------------------------
package bhvr;
import l.EscapeListener;
import lejos.navigation.Pilot;
import lejos.nxt.Button;
import lejos.nxt.Motor;
import lejos.subsumption.Arbitrator;
import lejos.subsumption.Behavior;
/**
* まっすぐ走る、
* 障害物にぶつかったら少し後退して方向を換える、
* 黒線をまたいだら、黒線をまたいだら360度回転して、進行方向を逆にする
*
* @author marunomaruno
* @version 1.0, 2008/09/29
* @since 1.0
*/
public class Bumper21 {
public static void main(String[] args) {
// ESCAPE ボタンを押すとプログラム停止する
Button.ESCAPE.addButtonListener(new EscapeListener());
// トライボットのオブジェクトを生成する
Pilot trybot = new Pilot(5.6f, 13f, Motor.C, Motor.B);
Behavior[] behaviors = { // (1)
new DriveForward(trybot), // 直進する
new HitWall(trybot), // 障害物にぶつかったときに後退して90度回転する
new DarkLine(trybot), // 黒線を超えたときに後退し、150度回転する
};
Arbitrator arbitrator = new Arbitrator(behaviors); // (2)
arbitrator.start(); // (3)
}
}
------------------------------------------------------
□ Behavior[] behaviors = { // (1)
new DriveForward(trybot), // 直進する
new HitWall(trybot), // 障害物にぶつかったときに後退して90度回転する
new DarkLine(trybot), // 黒線を超えたときに150度回転する
};
振舞いを制御するオブジェクトを Behavior 配列に入れます。
□ Arbitrator arbitrator = new Arbitrator(behaviors); // (2)
Arbitrator は、このコンストラクタの引数で指定された振舞い制御オ
ブジェクトの配列の中から、現在のロボットの状態に合う振舞い制御オ
ブジェクトを選択し、それを実行します。
もし、2つの振舞いがこのロボットの状態に合う場合は、behaviors 配
列の添字の大きい方が優先されます。すなわち、ここでは
DarkLine (黒線を超えたときに150度回転する)
HitWall (障害物にぶつかったときに後退して90度回転する)
DriveForward (直進する)
オブジェクトの順になります。
□ Arbitrator クラスのコンストラクタ
---
Arbitrator(Behavior[] behaviors)
振舞いを制御するオブジェクトの配列を指定して、このインスタン
スを生成する。
---
□ arbitrator.start(); // (3)
指定された振舞いオブジェクトの調停を開始します。
□ Arbitrator クラスのメソッド
---
void start()
振舞いの制御を開始する。
---
以上