用Java控制樂高機器人
用Java控制樂高機器人
文∕趙英傑
本文將使用Lego Mindstorms機器人套件與leJOS實作一個碰碰車。這台小車運用兩個碰撞感測器(銜接在RCX的接頭1及接頭3),以及兩個馬達(銜接在A與C接頭),如下圖所示:

碰碰車運作的影片收錄在筆者的《碼上就會:Flash 8 動畫設計寶典》書籍光碟。當RCX的Run按鈕被按下時,碰碰車會持續前進;若左邊(接在接頭1的)碰撞感測器碰到東西時,碰碰車會先後退1秒鐘,然後在原地向右旋轉1.5秒後,再次向前行走。
使用Behavior介面
leJOS提供了一個稱為Behavior(行為)的介面(完整合法名稱:josx.robotics.Behavior),讓我們定義機器人元件的行為(例如:馬達向前進、碰撞感測器撞到東西的反應…),並撰寫出易於維護和擴充的機器人Java程式。
用Behavior定義行為後,再透過另一個Arbitrator(仲裁者)類別(完整合法名稱:josx.robotics.Arbitrator)來決定各個行為的先後執行順序。
底下先說明Behavior的方法,並撰寫碰碰車的一些行為,再說明Arbitator類別:
- boolean takeControl():傳回代表是否讓此行為掌控機器人動作的布林值;或者說,是否要啟動此行為程式。
- void action():行為被啟動之後所要執行的程式。
- void suppress():事件程式執行過程中,若有另一個等級更高的事件被觸發,這個方法將被自動執行,以便取消當前執行的行為。
底下是採用Behavior介面撰寫的碰撞偵測程式。當銜接在S1接頭的感應器傳回true時,action()方法將被執行,控制馬達倒退及原地旋轉:
import josx.robotics.*;
import josx.platform.rcx.*;
public class HitS1 implements Behavior {
public boolean takeControl() {
// 傳回S1接頭的偵測值
return Sensor.S1.readBooleanValue();
}
public void suppress() {
Motor.A.stop();
Motor.C.stop();
}
public void action() {
// 後退
Motor.A.backward();
Motor.C.backward();
try {
// 持續1秒
Thread.sleep(1000);
} catch (Exception e) {
}
/* A(左邊的)馬達前進
C(右邊的)馬達仍舊後退
讓機器人在原地向右轉 */
Motor.A.forward();
try {
// 持續1.5秒
Thread.sleep(1500);
} catch (Exception e) {
}
}
}
我們可以運用相同的程式結構,輕易地撰寫機器人的其他行為,例如,底下是感測銜接在S3(右邊)的碰撞感測器的行為程式:
import josx.robotics.*;
import josx.platform.rcx.*;
public class HitS3 implements Behavior {
public boolean takeControl() {
return Sensor.S3.readBooleanValue();
}
public void suppress() {
Motor.A.stop();
Motor.C.stop();
}
public void action() {
// 後退
Motor.A.backward();
Motor.C.backward();
try {
// 持續1秒
Thread.sleep(1000);
} catch (Exception e) {
}
/* C(右邊的)馬達前進
A(左邊的)馬達仍舊後退
讓機器人原地向左轉 */
Motor.C.forward();
try {
// 持續1.5秒
Thread.sleep(1500);
} catch (Exception e) {
}
}
}
下面是讓機器人的左右兩邊馬達前進的程式。在一般的狀態下,我們希望這個機器人能持續前進,所以把takeControl()方法的傳回值設定成true,讓主程式一開始就先執行這個行為裡的action()方法。
import josx.robotics.*;
import josx.platform.rcx.*;
public class DriveForward implements Behavior {
public boolean takeControl() {
return true;
}
public void suppress() {
Motor.A.stop();
Motor.C.stop();
}
public void action() {
Motor.A.forward();
Motor.C.forward();
}
}
使用Arbitator類別決定各個行為的執行時機
Arbitator類別的建構函數能接收所有Behavior物件,並將它們存放在陣列中;位於陣列越後面的元素,其執行的優先權也越高。例如,假設S1和S3接頭的感測器同時被觸發,HitS3的action()方法將被執行。建立收集了所有Behavior物件的陣列後,再呼叫start()方法,即可啟動Arbitator的執行緒開始運行程式。
import josx.robotics.*;
public class BumperCar {
public static void main(String [] args) {
// 建立行為物件
Behavior b1 = new DriveForward();
Behavior b2 = new HitS1();
Behavior b3 = new HitS3();
// 把所有行為物件存入bArray陣列
Behavior [] bArray = {b1, b2, b3};
// 建立Arbitator物件,並傳遞bArray給它。
Arbitrator arb = new Arbitrator(bArray);
arb.start();
}
}
將上面四段程式命名儲存成HitS1.java, HitS3.java, DriveForward.java及BumperCar.java,並透過lejosc指令編譯後,最後執行lejos指令上傳程式到RCX,即可進行測試。
改良碰撞感測程式
上面的HitS1與HitS3類別(偵測左、右感測器的類別)之間,僅有少部分不同,因此其實我們可以將這兩個類別合併成一個,如下所示:
import josx.robotics.*;
import josx.platform.rcx.*;
class CheckSensor implements Behavior {
private int sensorNum;
// 建立兩個「常數」,存放感測器接頭的編號。
static final int S1 = 1;
static final int S3 = 3;
// 從建構函數決定要檢測的感測器
CheckSensor (int sensorNum) {
this.sensorNum = sensorNum;
}
public boolean takeControl() {
boolean s = false;
if (sensorNum == S1) {
s = Sensor.S1.readBooleanValue();
} else if (sensorNum == S3) {
s = Sensor.S3.readBooleanValue();
}
return s;
}
public void suppress() {
Motor.A.stop();
Motor.C.stop();
}
public void action() {
Motor.A.backward();
Motor.C.backward();
try {
Thread.sleep(1000);
} catch (Exception e) {
}
if (sensorNum == S1) {
Motor.A.forward();
} else if (sensorNum == S3) {
Motor.C.forward();
}
try {
Thread.sleep(1500);
} catch (Exception e) {
}
}
}
修改行為程式後,主程式當然也要跟著改變。底下是運用上面的CheckSensor類別的主程式碼:
import josx.robotics.*;
public class BumperCar2 {
public static void main(String [] args) {
Behavior b1 = new DriveForward();
// 檢測S1接頭的碰撞感測器
Behavior b2 = new CheckSensor(CheckSensor.S1);
// 檢測S3接頭的碰撞感測器
Behavior b3 = new CheckSensor(CheckSensor.S3);
Behavior [] bArray = {b1, b2, b3};
Arbitrator arb = new Arbitrator(bArray);
arb.start();
}
}
重新編譯並上傳程式到RCX,一台簡易的樂高碰碰車就完成了!