[3/29_C-Day有什麼?]系列之七:leJOS Wifi 遙控多台樂高EV3 機器人

手機遙控樂高機器人在以往 NXT 的時代,只能用藍牙點對點,手機端是 master,樂高機器人則是 slave。手機端能連多少台藍牙裝置是根據胎手機而定,不過一般來說兩台是沒問題的。今天如果改用 Wifi 的話就不會受限於Android 手機的藍芽配對裝置數量了。本文說明如何透過 Wifi 來控制兩台樂高 EV3 機器人,同步或非同步控制都可以,請看以下影片。

延伸閱讀:

[leJOS] 準備開機用 SD記憶卡 – 用 Java 程式控制樂高EV3機器人

這是使用 App Inventor 來控制兩台樂高 NXT 機器人:

接著來看程式碼,這是 Android 端的src,重點在於建立兩個 socket 來對兩台機器人發送指令(line 179,205):

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);
	}
}
Wifi 遙控,Android 端程式

樂高機器人端的程式,使用 leJOS 編寫。一樣是去判斷 socket 收到的內容之後執行對應的動作即可:

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);
	}

}
Wifi 遙控,機器人端程式

 

About CAVEDU 阿吉 - 雜工 (2520 Articles)
CAVEDU 教育團隊 打雜
Contact: Website

3 Comments on [3/29_C-Day有什麼?]系列之七:leJOS Wifi 遙控多台樂高EV3 機器人

  1. 您好,我參照了您的程式語言
    運行在手機端和EV3上
    可是利用手機連線時日誌檔出現了如下通知
    IOException: java.net.SocketException: socket failed: EACCES (Permission denied)
    可以幫助我理解這些問題嗎

Leave a comment

Your email address will not be published.


*