Category Archives: leJOS / Java

[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 遙控,機器人端程式

 

[Android 結合 Google Chart API] 機器人原地轉一圈產生雷達圖

機器人原地轉一圈之後,將超音波感測器值透過藍牙送給Android 手機,再呼叫 Google Chart API 雲端圖表功能來取得雷達圖。可以畫出非常精美的圖表。重點在於靈活應用 Google Chart API  的語法就可以囉!

直接點選以下連結就可以看到雷達圖,詳細參數設定請參閱 Google Chart API 官網或以下延伸閱讀

https://chart.googleapis.com/chart?cht=r&chs=320×320&chxt=x&chxl=0:|0|45|90|135|180|225|270|315&chd=t:60,40,30,100,76,99,22,57,80

本範例是結合樂高機器人的超音波感測器來達到簡易地圖掃描的效果,歡迎試試看。以下範例是2014年淡江大學智慧型行動裝置整合機器人控制課程的作業之一。感謝電機系周煜同學(已畢業)做出相當好的版本呢。

 

12345

[youtube=http://www.youtube.com/watch?v=DBvhrxkb24M]

延伸閱讀:

Google Chart API 教學

Google Chart 隨機產生折線圖

淡江電機 Android 機器人整合課程網站

140523淡江電機 Android 行動裝置整合機器人控制課程,期末專題展示

Code Android端

package com.example.nxtsense;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Set;
import java.util.UUID;



import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.ActionBar;
import android.support.v4.app.Fragment;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import android.os.Build;

public class MainActivity extends ActionBarActivity {
	private BluetoothAdapter adapter;
	private BluetoothSocket nxtSocket;
	public DataInputStream nxtDataIn;
	public DataOutputStream nxtDataOut;
    public final int MODE_CONNECT_NXT = 0, MODE_CONTROL = 1;
    private int mode;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.fragment_main);
        adapter = BluetoothAdapter.getDefaultAdapter();
    	if(adapter==null)
        {
        	Toast.makeText(this, "No Bluetooth adapter found", Toast.LENGTH_SHORT).show();
        	this.finish();
        }
    	if(!adapter.isEnabled())
			startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), 1);
    	
    	try {
			setMode(MODE_CONNECT_NXT);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    	
    	Button getvalue=(Button)findViewById(R.id.get);
    	getvalue.setOnClickListener(new View.OnClickListener() {
			
			@Override
			public void onClick(View v) {
				try {
					CommandNXT();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			}
		});
        
    }

    public void CommandNXT() throws IOException{
    	String y="";
    		nxtDataOut.writeInt(1);
    		nxtDataOut.flush();
    	
    	//	int x=nxtDataIn.readInt();
    		y=String.valueOf(nxtDataIn.readInt())+","
    				+String.valueOf(nxtDataIn.readInt())+","
    				+String.valueOf(nxtDataIn.readInt())+","
    				+String.valueOf(nxtDataIn.readInt())+","
    				+String.valueOf(nxtDataIn.readInt())+","
    				+String.valueOf(nxtDataIn.readInt())+","
    				+String.valueOf(nxtDataIn.readInt());
    	
					//TextView tv=(TextView)findViewById(R.id.value);
			    	//tv.setText(String.valueOf(x));
		
    	String myURL =" https://chart.googleapis.com/chart?cht=r&chs=320x320&chxt=x&chxl=0:|0|45|90|135|180|225|270|315&chd=t:"+y;         
        WebView myBrowser=(WebView)findViewById(R.id.mybrowser);  
  
        WebSettings websettings = myBrowser.getSettings();   
        websettings.setJavaScriptEnabled(true);  
         
        myBrowser.setWebViewClient(new WebViewClient());  
  
        myBrowser.loadUrl(myURL);  	
    	
    	
    	
    }
    public void setMode(int _mode) throws IOException
    {
    	Button connect=(Button)findViewById(R.id.buttonConnect);
    	mode = _mode;
    	if(mode==MODE_CONNECT_NXT)
    	{
    		
    		connect.setOnClickListener(new Button.OnClickListener() {
    			public void onClick(View arg0) {
    				connectNxt();
    				}
            });
    	}
    	
    	else if(mode==MODE_CONTROL)
    	{
    		connect.setEnabled(false);
    	}
    	else 
    		throw new IllegalArgumentException();
    }
    
    private void connectNxt()
    {
    	if(mode!=MODE_CONNECT_NXT) //檢查模式
    		throw new IllegalArgumentException();
    	
    	String name;
    	BluetoothDevice nxt = null;
    	
    	if((name = ((EditText) findViewById(R.id.editNxtName)).getText().toString()).equals("")) //檢查是否為空字串
    	{
    		Toast.makeText(this, "Please provide the name of your NXT", Toast.LENGTH_SHORT).show();
    		return;
    	}
    	
        Set<BluetoothDevice> devicesSet = adapter.getBondedDevices(); //取得裝置清單
        
        if(devicesSet.size()==0) //找不到裝置
        {
        	Toast.makeText(this, "No devices found", Toast.LENGTH_SHORT).show();
        	return;
        }
        
        for (BluetoothDevice device : devicesSet) //搜尋裝置
        {
            if (device.getName().equals(name))
            {
            	nxt = device;
                break;
            }
        }
        
        if(nxt==null) //找不到裝置
        {
        	Toast.makeText(this, "NXT not found", Toast.LENGTH_SHORT).show();
        	return;
        }
        
        try
        {
        	//建立nxt socket
			nxtSocket = nxt.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
			nxtSocket.connect();
			nxtDataOut = new DataOutputStream(nxtSocket.getOutputStream());
			nxtDataIn = new DataInputStream(nxtSocket.getInputStream());
		}
        catch(IOException e)
		{
        	Toast.makeText(this, "Connection failure", Toast.LENGTH_SHORT).show();
        	return;
		}

    	Toast.makeText(this, "Connected", Toast.LENGTH_SHORT).show();
		try {
			setMode(MODE_CONTROL);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    }




   

}
Code. Android receive ultrasonic value from Lego robot

Code 機器人端使用 leJOS

import lejos.nxt.*;
import lejos.nxt.comm.*;
import lejos.util.Delay;

import java.io.*;
public class motor_control{
	public static void main(String args[]) throws IOException{
		Button.ESCAPE.addButtonListener(new ButtonListener(){
				public void buttonPressed(Button b){
					System.exit(0);
					}
				public void buttonReleased(Button b){
					
				}
		});
		System.out.println("Waiting");
		BTConnection btc = Bluetooth.waitForConnection(0, NXTConnection.RAW);
		DataInputStream dis = btc.openDataInputStream();
		DataOutputStream dos = btc.openDataOutputStream();
		System.out.println("Connected");
		UltrasonicSensor ultrasonic1 = new UltrasonicSensor(SensorPort.S2);
		int Ultrasonicvalue1;
		while(true){
			int a=dis.readInt();
			if(a==1){
				for(int i=0;i<7;i++){
					Motor.A.setSpeed(100);
					Motor.B.setSpeed(100);
					Motor.A.backward();
					Motor.B.forward();
					Delay.msDelay(822);
					Ultrasonicvalue1=ultrasonic1.getDistance();
		//	System.out.println(Ultrasonicvalue1);
			
				try {
					dos.writeInt(Ultrasonicvalue1);
					dos.flush();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			
			}
				Motor.A.setSpeed(0);
				Motor.B.setSpeed(0);
				Motor.A.backward();
				Motor.B.backward();
			}
		}
			}
}
Code. Robot send out ultrasonic value to Android (leJOS)

140418 淡江電機 Android 行動裝置整合機器人控制課程,期中專題展示

今天阿吉老師到淡江大學上課,而今天的課程是期中的專題展示,主要的內容是於 Eclipse IDE 中使用 Java程式語言來開發各種機器人控制專題。

這次的主題有保全機器人、路面避障機器人、發射機器人、生活便利機器人、繪圖機器人、堆高機、分類機器人、測速照相機器人、搬運機器人、搖擺彈彈球、搬運機器人、大嘴鱷魚、全景拍照機器人等等。

既然是Android,同學們就要思考Android手機資源要如何與機器人整合,而不是換個程式語言玩碰碰車而已。手機上的動作感測器、GPS、照相機、網路連線、麥克風等等,對機器人來說都是很關鍵的功能啊!

另一方面,各種 Google API例如 Google Map、Google Chart、Google Application Engine 或是較簡單的語音輸入/輸出等,都能讓機器人更豐富。

來看看CAVEDU之前做的 Google Map 結合機器人的概念影片以及 openCV / Android 實作,讓機器人可以透過手機的影像辨識結果來移動。

[youtube=https://www.youtube.com/watch?v=qvI04snZRuU] [youtube=https://www.youtube.com/watch?v=k6Lelf7SNWI]

軟滑小編也有修這堂課喔!會修這堂課是因為,之前大三有修過 leJOS 樂高機器人的課程。想說要更進一步的學更多相關的課程才修的。不過本學期的課程內容比起以往難度提高很多很多,許多同學都為了把專題做的更好,準備了很久。

寫app的方式有很多種,相較起來App Inventer,java的變化更多更複雜,也代表著程式碼更多更難。所以App Inventer真的是很適合學習Android入門的程式語言喔!但換個角度來說,如果要做到更完整的功能,還是要回到 Android 正規開發環境哩,真難抉擇啊

同學們都很認真的上台報告

這組的目標是藉由手機的姿態感測器讓機器人保持在翹翹板中央,做到簡易的動態平衡控制。

來看一下同學們的作品有哪些,有些的機構設計的很不錯!

分類機器人
分類機器人Demo
大嘴鱷魚
全景拍照機器人
推高機

最後由老師講評整個專題展示結果,大家的表現… 還有很大的進步空間啦,希望學期末報告的時候都可以更棒!

阿吉老師講評

閱讀延伸

AppInventor – 圖形化Android

Android相關文章

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

 2014-02-25 18.45.34

本文是根據 leJOS 官方文件說明來 建置能讓 EV3 藉由 leJOS 來開機的 micro SD卡。這張SD卡是用來讓 EV3 得以執行 Java /leJOS  程式,是根據標準樂高韌體來建置,但提供了以下進階功能:

1. 不需執行 Lego 虛擬機即可載入 Lego device access Kernel module。
2. 藉由支援的USB WiFi 轉接器來載入 WiFi 模組來建立連線。這讓您得以藉由Telnet, SSH 與 NFS 來連接EV3,讓程式開發更容易。.
3. 已包含了使用  Atheros ATH9K 與 Realtex 8192cu 晶片組的裝置驅動模組,會根據偵測到的晶片組來自動載入。 EV3 已確認可使用 NetGear WNA1100 與 EDIMAX EW-7811Un 這兩種無線網路轉接器,其它使用相同晶片組的轉接器應該也可使用。
4. 需安裝 Oracle Java SE Embedded JVM, JNA 與 leJOS 等class,才可在 EV3 上執行各種 Java 程式
5. 包含了可在 Linux 關閉時,一併關閉EV3主機電源的scripts 。


 

我需要什麼?

        1. 一張容量至少為1 Gb (大一點比較好) 的 micro SD卡。

2. (非必要), 一隻支援 EV3 的WiFi 無線網路 USB轉接器。截至目前為止,可用的只有NetGear WNA1100 與 EDIMAX EW-7811Un 這兩款,但日後應該會有更多。

3. 可執行 Linux 或 Microsoft Windows 作業系統的電腦,或任何可執行 Eclipse 以及完整 Java JDK 的電腦。

4. (非必要), 一個可供連線的 WiFi 網路(當然也要知道網路的 SSID 與 金鑰等資訊)

5.下載 Oracle Java SE Embedded JVM

6. (非必要), 可連接 EV3的 Telnet 或 ssh 程式。

7. (非必要), 一個可執行在PC 端所開發程式的 NFS server。

8. 在Microsoft Windows, 您需要像 win32DiskImager 這樣的燒錄軟體好將映像檔燒錄到SD卡中。


開始動工:

1. 請下載 EV3開機映像檔的壓縮檔(sd.zip,17.6mb),解壓縮之後即為單一個映像檔,檔名為 sd.img 。
2. 請下載 Oracle Java SE Embedded JVM
3. 如果您是使用 Micrsoft Windows,請下載win32DiskImager 或類似的燒錄軟體。

OK,接著來燒錄SD卡吧!

本文假設您使用的是Microsoft Windows,您可使用 win32DiskImager 這樣的燒錄軟體,將映像檔燒錄到SD卡中。操作很容易,只要指定好檔案來源,燒錄位置就可以了,在此就不多說了。

Linux 與 MAC 的朋友,請自行參閱官網說明來執行,不過基本步驟是差不多的。

檔案燒錄完成之後,將記憶卡移除之後再次插入電腦。這時候,記憶卡的名稱會變成 LMS2012 。請將剛才下載的 Oracle Java SE Embedded JVM 的 .gz  壓縮檔(檔名:ejre-7u51-fcs-b13-linux-arm-sflt-headless-18_dec_2013.tar.gz)放到 LMS2012 資料夾中,最後移除記憶卡。

螢幕快照 2014-02-28 上午12.56.01

現在開機SD卡已經完工了,將 micro SD卡插入 EV3之後啟動,就會由 SD卡來開機囉!

[leJOS] 在Eclispe 中寫完第一個 leJOS 程式

本篇要告訴您如何在Eclispe 中寫完第一個 leJOS 程式,請參考 [leJOS 用於樂高的 Java 程式語言] 把 Eclispe 的 leJOS 環境漸好,就可以一起來用 Java 控制樂高機器人囉!
1. 啓動 Eclispe 整合式開發環境,File -> New -> LeJOS NXT Project

[文章更新] leJOS 用於樂高的 Java 程式語言

leJOS是什麼?


leJOS NXJ 是可執行於樂高NXT主機上的JVM (Java Virtual Machine)。在2000年,一些程式愛好者提出一個名為「TinyVM」的計畫並推出第一版的leJOS,因此可以在RCX上進行物件導向程式設計,並可以用標準的Java語法來發展機器人應用程式。leJOS的穩定成長可歸功於整合了許多特色和Java的功能,leJOS可以在各種作業系統上開發,例如Windows、Linux 和Mac OS,除此之外,您也可以在Android裝置上來開發機器人應用程式。
 
  時至今日 leJOS 已經是相當成熟的機器人開發環境,除了使用正規Java語法之外,也針對機器人開發出許多對應的套件,例如各種特殊感應器存取,定位與導航等等。另外用於新一代樂高機器人的 leJOS 也即將推出囉,敬請期待,並請參考本團隊出版的 [機器人程式設計與實作   使用JAVA]第二版。

 
從leJOS可以學到什麼?
 

   1. 使用正規Java語法

2. 網路通訊與 Android 行動裝置

 
   3. 藍牙通訊與I2C傳輸應用4. 定位與導航

如何安裝leJOS環境?

[leJOS教學] 感測器資訊顯示在NXT螢幕, 結合按鈕換頁

將4種感測器資訊顯示在NXT螢幕上, 結合按鈕換頁。 加入了Button.LEFT 與 Button.RIGHT的點擊事件監聽器, 藉此達到換頁的功能。

每一頁(case0~case3) 就是獨一的感測器資訊, 這樣就不用把所有的資訊都擠在同一頁了。 覺得NXT螢幕很擠的朋友可以參考本份程式碼。

螢幕截圖稍後補上~

=======================================================

import javax.microedition.lcdui.Graphics;

import lejos.nxt.Button;
import lejos.nxt.ButtonListener;
import lejos.nxt.LCD;
import lejos.nxt.LightSensor;
import lejos.nxt.SensorPort;
import lejos.nxt.SoundSensor;
import lejos.nxt.TouchSensor;
import lejos.nxt.UltrasonicSensor;
import lejos.util.Delay;

public class draw {
static int type=0;
public static void main(String[] arg){

LightSensor light=new LightSensor(SensorPort.S2);
UltrasonicSensor ultra=new UltrasonicSensor(SensorPort.S4);
TouchSensor touch=new TouchSensor(SensorPort.S1);
SoundSensor sound=new SoundSensor(SensorPort.S3);
Graphics gra=new Graphics();
int Lvalue,Uvalue,Svalue;
boolean Tvalue;
Button.ESCAPE.addButtonListener(new ButtonListener() {
public void buttonReleased(Button arg0) {System.exit(1);}
public void buttonPressed(Button arg0) {}
});

Button.LEFT.addButtonListener(new ButtonListener() {
public void buttonReleased(Button arg0) {
type–;
if(type<0){type=3;}
}
public void buttonPressed(Button arg0) {}
});

Button.RIGHT.addButtonListener(new ButtonListener() {
public void buttonReleased(Button arg0) {
type++;
if(type>3){type=0;}
}
public void buttonPressed(Button arg0) {}
});

while(true){
switch(type){
case 0:
Lvalue=light.readValue();
gra.clear();
LCD.drawString(“Light”, 1, 1);
gra.drawRect(40, 10, 10, 50);
gra.fillRect(41, 60-(int)(Lvalue/1.6), 9, (int)(Lvalue/1.6));
break;
case 1:
Uvalue=ultra.getDistance();
gra.clear();
LCD.drawString(“Ultra”, 1, 1);
gra.drawArc(25, 10, 50, 50, 0, 180);
gra.drawLine(25, 35, 75, 35);
if(Uvalue>0&&Uvalue<180){
gra.drawLine(50, 35, 50+(int)(Math.cos(Math.toRadians(Uvalue))*25), 35-(int)(Math.sin(Math.toRadians(Uvalue))*25));
}
break;
case 2:
Svalue=sound.readValue();
gra.clear();
LCD.drawString(“Sound”, 1, 1);
gra.drawLine(40, 50, 60, 50);
gra.drawLine(45, 60, 55, 60);
gra.drawLine(40, 50, 45, 60);
gra.drawLine(60, 50, 55, 60);
if(Svalue>25){gra.drawArc(40, 40, 20, 20, 45, 90);}
if(Svalue>50){gra.drawArc(35, 35, 30, 30, 45, 90);}
if(Svalue>75){gra.drawArc(30, 30, 40, 40, 45, 90);}
if(Svalue>100){gra.drawArc(25, 25, 50, 50, 45, 90);}
break;
case 3:
Tvalue=touch.isPressed();
gra.clear();
gra.drawRect(25, 10, 50, 50);
gra.drawRect(32, 17, 36, 36);
gra.drawLine(25, 10, 32, 17);
gra.drawLine(75, 60, 68, 53);
LCD.drawString(“Touch”, 1, 1);
if(Tvalue){

gra.drawLine(26,11,74,11);
gra.drawLine(27,12,73,12);
gra.drawLine(28,13,72,13);
gra.drawLine(29,14,71,14);

gra.drawLine(30,15,70,15);
gra.drawLine(31,16,69,16);

gra.drawLine(26,11,26,59);
gra.drawLine(27,12,27,58);
gra.drawLine(28,13,28,57);
gra.drawLine(29,14,29,56);
gra.drawLine(30,15,30,55);
gra.drawLine(31,16,31,54);

}
else{

gra.drawLine(74,11,74,59);
gra.drawLine(73,12,73,58);
gra.drawLine(72,13,72,57);
gra.drawLine(71,14,71,56);
gra.drawLine(70,15,70,55);
gra.drawLine(69,16,69,54);

gra.drawLine(74,59,26,59);
gra.drawLine(73,58,27,58);
gra.drawLine(72,57,28,57);
gra.drawLine(71,56,29,56);
gra.drawLine(70,55,30,55);
gra.drawLine(69,54,31,54);
}
break;
}
Delay.msDelay(500);
}

}
}

[溫故知新] Java 版的P 控制循跡機器人

Java (leJOS) 的 P控制循跡機器人, 可以得到相當順暢的循跡效果。

參考資料: http://www.inpharmix.com/jps/PID_Controller_For_Lego_Mindstorms_Robots.html

關鍵就在這兩個式子:

        vl = 200 + kp*(light.readValue() – light_initial);

vr = 200 – kp*(light.readValue() – light_initial);

1. light_initial是目標值, 也就是大約光感測器打出小紅圓圈一半位於白色場地, 一半位於黑色膠帶之上的光感測器數值。

2. light.readValue()是當下的光感測器數值, 兩者差值>0 會讓vl變小但vr變大, 機器人此時左轉, 差值愈大則機器人轉彎幅度愈大(愈接近原地旋轉)。

3. kp: 比例控制常數, 用來調整修正幅度, kp愈大機器人愈敏感。

4. 200 是基礎轉速, 就是當 light.readValue() 等於 light_initial 時, 機器人會以 200 前進。

如此機器人就能在直線是大幅減少偵測修正的次數, 並有效得知彎道的曲率。 是不是很棒呢?

來看看影片吧( NXC版本)。

============================================

import lejos.nxt.*;

public class Hy {

public static void main(String args[])
{
Button.ESCAPE.addButtonListener(new ButtonListener()
{
public void buttonPressed(Button b){System.exit(1);}
public void buttonReleased(Button b){}
});
int kp=20;
int light_initial = (30+60)/2; //half
LightSensor light = new LightSensor(SensorPort.S1);
int vl,vr;
while(true)
{

vl = 200 + kp*(light.readValue() – light_initial);
vr = 200 – kp*(light.readValue() – light_initial);
Motor.B.setSpeed(vl);
Motor.C.setSpeed(vr);
Motor.B.forward();
Motor.C.forward();
}
}
}

[leJOS] 讓機器人前進指定距離(公分) – 多執行緒

在程式中以一個變數 walk 來指定機器人行走距離, 單位為公分. 程式環境使用 leJOS

本範例重點在於將移動距離根據輪徑(5.6 cm) 轉換為馬達轉動角度, 使用 Motor.B.rotate(角度) 指令. 由於該指令屬於閉區間指令, 所以

Motor.B.rotate(180);
Motor.C.rotate(180);

會讓B馬達轉完180度之後再換C馬達旋轉180度, 機器人無法直走. 所以本範例透過兩個執行緒t1, t2分別執行 Motor.X.rotate指令, 這樣就可以讓機器人順利移動指定角度了

CAVE的 leJOS實驗室: http://lab.cavedu.com/lejos

淡江機器人Java教學網: lejosrobot2012.cavedu.com

===========================================

import lejos.nxt.*;

import lejos.util.Delay;

 

class walk

{

        public static void main(String arg[])

        {

               final double angle;

               double walk=10;

               int speed=360;

               Thread t1,t2;

               Runnable runR,runL;

 

        Button.ESCAPE.addButtonListener(new ButtonListener()

        {

        public void buttonPressed(Button b){System.exit(0);}

        public void buttonReleased(Button b){}

        });

 

        angle=(walk/(5.6d*Math.PI))*360d;

        runR=new Runnable() {

 

        public void run() {

               Motor.B.rotate((int)angle);

               }

        };

        runL=new Runnable() {

 

        public void run() {

               Motor.C.rotate((int)angle);

                }

        };

        t1=new Thread(runR);

        t2=new Thread(runL);

        t1.start();

        t2.start();

   }//main

}//class

leJOS 的Eclipse plugin 安裝教學

在 leJOS 0.8.5 的年代, Eclipse plugin是不能用的. 到了 leJOS 0.9 發布之後, 才又能在 Eclipse 中來編寫 leJOS 的NXT or PC專案. 不然只能用txt 或是例如 PsPad 或是 Notepad++ 這種泛用型編輯程式. 有點不方便呢!

在Eclipse中當然就可以享受各種自動完成, 錯誤提示以及自動載入相關套件的好處囉!

下周就是新一期的淡江機器人Java 課程開學, 有興趣的朋友歡迎和我們一同學習 –> 2011 Java機器人學習網

簡單來說您需要:

1. 安裝JDK

2. 下載Eclipse, 各個版本都可以

3. 在Eclipse中安裝 leJOS Eclipse plugin.

完整的中文安裝手冊請由 D.I.V.E團隊處下載, http://diveduino.blogspot.tw/2011/09/eclipse-37-lejos-09.html

[leJOS] 彈跳球

力維用leJOS改寫宗翰老師的NXC彈跳球

http://tw.myblog.yahoo.com/lego-caveschool/article?mid=4137&prev=4145&next=4103&l=f&fid=18

程式可直接複製或按我下載

=============================================

import lejos.nxt.*;
import lejos.util.Delay;
import javax.microedition.lcdui.Graphics;
class ball
{
 public static void main(String args[])
 {
  Button.ESCAPE.addButtonListener(new ButtonListener()
  {
   public void buttonPressed(Button b){System.exit(1);}
   public void buttonReleased(Button b){}
  });
  Graphics g = new Graphics();
     int ball_x = (int)(Math.random()*61)+20;
        int ball_y = (int)(Math.random()*31)+20;
        int del_x = 1;
        int del_y = 1;
  int r = 2;
  g.fillArc(ball_x,ball_y,r,r,0,360);
  while(true)
        {
         LCD.clear();
      ball_x = ball_x + del_x;
            ball_y = ball_y + del_y;
   g.fillArc(ball_x,ball_y,r,r,0,360);
   if( ball_x <= 2 || ball_x>=96)
   {
       del_x = del_x*-1;
                Sound.playTone(440,1);
         }
            if( ball_y <= 5 || ball_y >= 61)
            {
       del_y = del_y*-1;
                Sound.playTone(440,1);
   }
   Delay.msDelay(40);
  }
 }
}

JAVA book ch8 修正

各位朋友如果在執行 Sample8_1 與 Sample8_3 的時候遇到不能下載的問題時, 這邊提供兩種解決方式:

根據阿吉實地測試10組master/slave中約有1~2組會出現無法正確傳輸的問題。

1. Sample8_3.java中的第8行, 請指定Slave NXT的名稱 (把 NAME 改成您的Slave NXT名稱即可)。

if(!conn.connectTo(“NAME“, “”, NXTCommFactory.BLUETOOTH)) //連線到NXT

2. 如果還是不行請使用較低階的執行指令, 在cmd 下以 nxjpcc 編譯完 Sample8_3.java後輸入以下指令。 這是因為有些電腦的leJOS無法正確帶入pccomm.jar導致通訊出問題。

java -cp .;”%NXJ_HOME%”\lib\pccomm.jar Sample8_3

以上方法感謝許多熱心的老師們提供。

 

 

具備濾波功能的倒車雷達(使用陣列) ver1.2 by 力維

上一個範例中我們使用了移動平滑法來降低超音波感應器本身的雜訊, 今天我們改用陣列來處理。 整個程式就會變得簡潔多囉! 程式可直接複製或按我下載

==============================================

import lejos.nxt.*;
import lejos.util.Delay;
class radar_v1_2
{
   public static void main(String args[])
   {
      Button.ESCAPE. addButtonListener(new ButtonListener()
      {
            public void buttonPressed(Button b){System.exit(1);}
            public void buttonReleased(Button b){}
       });
      
       UltrasonicSensor us = new UltrasonicSensor(SensorPort. S1);
       int[] distance = new int[8]; //宣告一個長度8的整數陣列來存放超音波感應器值
       int fre=0;  //指定頻率初始值為0
       while(true)
       {
          for(int i=distance.length-1;i>0;i–)
          {
              distance[i]= distance[i-1];
          }
        distance[0] = us.getDistance();
       
        for(int j=0; j<distance.length-1;j++)

        {
                            fre += distance[i];
        }
       Sound.playTone(fre/distance. length*10,100);
       LCD.drawInt(fre/distance. length,0,0);
       Delay.msDelay(100);
       LCD.clear();
       fre=0;
      }
   }
}

具備濾波功能的倒車雷達 ver1.0 by 力維

程式下載請按我

這是力維寫的簡易倒車雷達程式, 將超音波感應器值轉換為發音頻率, 和NXT內建的 Try Ultrasonic功能是一樣的。
但在距離極小以及極大的時候, 超音波感應器本身的雜訊還不小, 所以我們用8個變數來存放距離值後取平均, 這樣可以讓整組data圖表平滑一點。 各位朋友可以實際操作看看。
還有第二個使用陣列的版本, 請按我
============================================
import lejos.nxt.*;
import lejos.util.Delay;
class radar_v1_0
{
   public static void main(String args[])
   {
   Button.ESCAPE. addButtonListener(new ButtonListener()
     {
     public void buttonPressed(Button b){System.exit(1);}
     public void buttonReleased(Button b){}
     }); //定義NXT灰色鍵可中斷程式
  
   UltrasonicSensor us = new UltrasonicSensor(SensorPort. S1); //宣告1號超音波感應器
   int distance1=0,distance2=0, distance3=0,distance4=0, distance5=0,distance6=0,  distance7=0,distance8=0;     
   while(true)
   {
    distance8 = distance7;
    distance7 = distance6;
    distance6 = distance5;
    distance5 = distance4;
    distance4 = distance3;
    distance3 = distance2;
    distance2 = distance1;
    distance1 = us.getDistance();
    Sound.playTone((distance1+ distance2+distance3+distance4+ distance5+distance6+distance7+ distance8)/8*10,100); //將8個距離值平均後轉為發音頻率
    LCD.drawInt((distance1+ distance2+distance3+distance4+ distance5+distance6+distance7+ distance8)/8,0,0); //顯示平均值
    Delay.msDelay(100);
    LCD.clear();
    }
  }
}