手機遙控樂高機器人在以往 NXT 的時代,只能用藍牙點對點,手機端是 master,樂高機器人則是 slave。手機端能連多少台藍牙裝置是根據胎手機而定,不過一般來說兩台是沒問題的。今天如果改用 Wifi 的話就不會受限於Android 手機的藍芽配對裝置數量了。本文說明如何透過 Wifi 來控制兩台樂高 EV3 機器人,同步或非同步控制都可以,請看以下影片。
延伸閱讀:
[leJOS] 準備開機用 SD記憶卡 – 用 Java 程式控制樂高EV3機器人
這是使用 App Inventor 來控制兩台樂高 NXT 機器人:
接著來看程式碼,這是 Android 端的src,重點在於建立兩個 socket 來對兩台機器人發送指令(line 179,205):
Wifi 遙控,Android 端程式
package com.cavedu.ev3_socketremote;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
public class MainActivity extends ActionBarActivity implements OnClickListener {
//Device 1
Socket clientSocket1;
BufferedReader inbound;
PrintWriter outbound;
String sendStr = "";
Button Connect, FWD, BACK, LEFT, RIGHT, STOP;
EditText deviceIP, devicePort;
//Device 2
Socket clientSocket2;
BufferedReader inbound2;
PrintWriter outbound2;
String sendStr2 = "";
Button Connect2, FWD2, BACK2, LEFT2, RIGHT2, STOP2;
EditText deviceIP2, devicePort2;
//初始化
void init(){
FWD = (Button) findViewById(R.id.FWD);
BACK = (Button) findViewById(R.id.BACK);
RIGHT = (Button) findViewById(R.id.RIGHT);
LEFT = (Button) findViewById(R.id.LEFT);
STOP = (Button) findViewById(R.id.STOP);
FWD.setOnClickListener(this);
BACK.setOnClickListener(this);
LEFT.setOnClickListener(this);
RIGHT.setOnClickListener(this);
STOP.setOnClickListener(this);
deviceIP = (EditText) findViewById(R.id.device1_ip);
devicePort = (EditText) findViewById(R.id.device1_port);
Connect = (Button) findViewById(R.id.Connect);
FWD2 = (Button) findViewById(R.id.FWD2);
BACK2 = (Button) findViewById(R.id.BACK2);
RIGHT2 = (Button) findViewById(R.id.RIGHT2);
LEFT2 = (Button) findViewById(R.id.LEFT2);
STOP2 = (Button) findViewById(R.id.STOP2);
FWD2.setOnClickListener(this);
BACK2.setOnClickListener(this);
LEFT2.setOnClickListener(this);
RIGHT2.setOnClickListener(this);
STOP2.setOnClickListener(this);
deviceIP2 = (EditText) findViewById(R.id.device2_ip);
devicePort2 = (EditText) findViewById(R.id.device2_port);
Connect2 = (Button) findViewById(R.id.Connect2);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init(); //初始化
//設定前進、後退、左轉、右轉按鈕Enabled為Flase 在未連線時無法點選
setDeviceButton(false);
setDevice2Button(false);
//按下Connect使Device1連線
Connect.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
//Device 1 Socket Client連線
if (Connect.getText().equals("Connect")) {
//建立Socket連線
Thread t1 = new Thread(runnable);
t1.start();
}
//Device 1 Socket Client斷線
else if (clientSocket1.isConnected()) {
try {
Log.w("AndyDebug", "ClientSocket1 Close");
clientSocket1.close();
Connect.setText("Connect");
setDeviceButton(false);
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
//按下Connect使Device2連線
Connect2.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
//Device 1 Socket Client連線
if (Connect2.getText().equals("Connect")) {
Thread t1 = new Thread(divice2runnable);
t1.start();
}
//Device 2 Socket Client斷線
else if (clientSocket2.isConnected()) {
try {
Log.w("AndyDebug", "ClientSocket1 Close");
clientSocket2.close();
Connect2.setText("Connect");
setDevice2Button(false);
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
}
//Device 1 建立Socket連線
Runnable runnable = new Runnable() {
public void run() {
try {
String ip = deviceIP.getText().toString();
int port = Integer.parseInt(devicePort.getText().toString());
clientSocket1 = new Socket(ip, port); //建立Socket連線
if (clientSocket1.isConnected()) {
runOnUiThread(new Runnable() {
public void run() {
Connect.setText("Close");
setDeviceButton(true);
}
});
}
Log.d("AndyDebug", "is?" + clientSocket1.isConnected());
//Socket 接收
inbound = new BufferedReader(new InputStreamReader(
clientSocket1.getInputStream()));
//Socket 發送
outbound = new PrintWriter(clientSocket1.getOutputStream(),
true);
} catch (IOException ioe) {
System.err.println("IOException: " + ioe);
runOnUiThread(new Runnable() {
public void run() {
Connect.setText("Connect");
setDeviceButton(false);
}
});
}
}
};
//Device 1 透過Socket收發資料
Runnable runnable2 = new Runnable() {
public void run() {
try {
String sss = sendStr + "\r\n";
char send[] = sss.toCharArray();
Log.d("AndyDebug", "送出: " + sss);
outbound.println(send); //送出資料
String inputLine = "";
//接收資料
while ((inputLine = inbound.readLine()) == null) {
}
Log.d("AndyDebug", "收到:" + inputLine);
} catch (Exception e) {
System.err.println("IOException: " + e);
}
}
};
//Device 2 建立Socket連線
Runnable divice2runnable = new Runnable() {
public void run() {
try {
String ip = deviceIP2.getText().toString();
int port = Integer.parseInt(devicePort2.getText().toString());
clientSocket2 = new Socket(ip, port); //建立Socket連線
if (clientSocket2.isConnected()) {
runOnUiThread(new Runnable() {
public void run() {
Connect2.setText("Close");
setDevice2Button(true);
}
});
}
Log.d("AndyDebug", "clientSocket2"+clientSocket2.toString());
//Socket 接收
inbound2 = new BufferedReader(new InputStreamReader(
clientSocket2.getInputStream()));
//Socket 發送
outbound2 = new PrintWriter(clientSocket2.getOutputStream(),
true);
} catch (IOException ioe) {
System.err.println("IOException: " + ioe);
runOnUiThread(new Runnable() {
public void run() {
Connect2.setText("Connect");
setDevice2Button(false);
}
});
}
}
};
//Device 2 透過Socket收發資料
Runnable device2runnable2 = new Runnable() {
public void run() {
try {
String sss = sendStr2 + "\r\n";
Log.e("AndyDebug", sendStr2);
char send[] = sss.toCharArray();
Log.d("AndyDebug", "送出: " + sss);
outbound2.println(send); //送出資料
String inputLine = "";
//接收資料
while ((inputLine = inbound2.readLine()) == null) {
}
Log.d("AndyDebug", "收到:" + inputLine);
} catch (Exception e) {
System.err.println("IOException: " + e);
}
}
};
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.FWD:
sendStr = "FWD";
sendCommand();
break;
case R.id.BACK:
sendStr = "BACK";
sendCommand();
break;
case R.id.LEFT:
sendStr = "LEFT";
sendCommand();
break;
case R.id.RIGHT:
sendStr = "RIGHT";
sendCommand();
break;
case R.id.STOP:
sendStr = "STOP";
sendCommand();
break;
case R.id.FWD2:
sendStr2 = "FWD";
sendCommand2();
break;
case R.id.BACK2:
sendStr2 = "BACK";
sendCommand2();
break;
case R.id.LEFT2:
sendStr2 = "LEFT";
sendCommand2();
break;
case R.id.RIGHT2:
sendStr2 = "RIGHT";
sendCommand2();
break;
case R.id.STOP2:
sendStr2 = "STOP";
sendCommand2();
break;
default:
break;
}
}
private void sendCommand() {
Thread t2 = new Thread(runnable2);
t2.start();
}
private void sendCommand2() {
Thread t2 = new Thread(device2runnable2);
t2.start();
}
private void setDeviceButton(boolean b) {
FWD.setEnabled(b);
BACK.setEnabled(b);
LEFT.setEnabled(b);
RIGHT.setEnabled(b);
STOP.setEnabled(b);
}
private void setDevice2Button(boolean b) {
FWD2.setEnabled(b);
BACK2.setEnabled(b);
LEFT2.setEnabled(b);
RIGHT2.setEnabled(b);
STOP2.setEnabled(b);
}
}
樂高機器人端的程式,使用 leJOS 編寫。一樣是去判斷 socket 收到的內容之後執行對應的動作即可:
Wifi 遙控,機器人端程式
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import lejos.hardware.Button;
import lejos.hardware.Key;
import lejos.hardware.KeyListener;
import lejos.hardware.lcd.LCD;
import lejos.hardware.motor.Motor;
class LeJOS_WiFi {
static int angle = 270; // 馬達轉動的角度
private static boolean OutServer = false;
private static ServerSocket server;
private final static int ServerPort = 1234; // 要監控的port
public static void main(String[] args) {
//按下ESCAPE鍵離開程式
Button.ESCAPE.addKeyListener(new KeyListener() {
public void keyReleased(Key k) {
System.exit(0);
}
public void keyPressed(Key k) {}
});
print("Start");
Thread socketServer_thread = new Thread(socketServer_runnable);
socketServer_thread.start();
while (true);
}
//建立SocketServer
public static void SocketServer() {
try {
server = new ServerSocket(ServerPort);
} catch (java.io.IOException e) {
print("Socket Error!");
print("IOException :" + e.toString());
}
}
static Runnable socketServer_runnable = new Runnable() {
public void run() {
SocketServer();
Socket socket;
print("Socket Server OK!");
while (!OutServer) {
socket = null;
try {
synchronized (server) {
socket = server.accept();
}
print(socket.getInetAddress().toString()); //印出目前連線設備的ip
//Socket接收
BufferedReader inbound = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
//Socket發送
PrintWriter outbound = new PrintWriter(
socket.getOutputStream(), true);
while (true) {
String data = "";
outbound.println("Success");
while ((data = inbound.readLine()) == null)
;
if (data.length()>=3)
print("getData:" + data);
if (data.equals("FWD")) {
go();
outbound.print(data + " OK");
} else if (data.equals("BACK")) {
back();
outbound.print(data + " OK");
} else if (data.equals("LEFT")) {
left(angle);
outbound.print(data + " OK");
} else if (data.equals("RIGHT")) {
right(angle);
outbound.print(data + " OK");
} else if (data.equals("STOP")) {
stop();
outbound.print(data + " OK");
}
}
} catch (java.io.IOException e) {
print("Socket Error");
print("IOException :" + e.toString());
Thread socketServer_thread = new Thread(socketServer_runnable);
socketServer_thread.start();
}
}
}
};
//前進
private static void go() {
Motor.A.forward();
Motor.B.forward();
}
//後退
private static void back() {
Motor.A.backward();
Motor.B.backward();
}
//左轉
private static void left(int angle) {
Motor.A.rotate(-angle, true);
Motor.B.rotate(angle, true);
}
//右轉
private static void right(int angle) {
Motor.A.rotate(angle, true);
Motor.B.rotate(-angle, true);
}
//停止
private static void stop() {
Motor.A.stop();
Motor.B.stop();
}
//在EV3螢幕印出字串
private static void print(String str) {
LCD.clear();
LCD.drawString(str, 0, 3);
}
}
Post Views: 1,083
Related Posts
-
[3/29_C-Day有什麼?]系列之六:3D列印RAPIRO機器人公仔!!
RAPIRO機器人外形可愛,易...
-
[3/29_C-Day有什麼?]系列之二:手機遙控雙足機器人
本篇內容是由 CAVEDU ...
- 05/29(二)-大仁科大資訊工程系創意研習營---Android智慧型手機 + 機器人
http://cc3.taje...
-
[3/29_C-Day有什麼?]系列之八:CAVEDU 叢書展示
各位好朋友,3/29 希望能與...
您好,我參照了您的程式語言
運行在手機端和EV3上
可是利用手機連線時日誌檔出現了如下通知
IOException: java.net.SocketException: socket failed: EACCES (Permission denied)
可以幫助我理解這些問題嗎
您好,請檢查 Android 專案中的 Internet permission 有沒有開 (請參考https://developer.android.com/training/basics/network-ops/connecting.html) 他應該是一個要在 manifest.xml 中加入的 user-permission
非常謝謝您的協助!!