Tag Archives: lejos

[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)

[溫故知新] 實做比例控制方法之循跡機器人

比例(proportional)控制方法是一種控制理論,可用來讓一個自動控制系統能夠對不同的環境狀況做出不同的反應,誤差愈大時,修正幅度也愈大,反之則只要小幅度修正即可,盡量讓。當然 P控制不是萬能,所以還有後續的積分(Integral)與微分(Differential)控制法,但本文先不介紹。

一般來說, 我們都是機器人循跡這個範例中來應用比例控制方法,對於學生來說是個非常好的練習,可以充分體會理論與現實狀況的差異(ㄎㄎ)

本文內容是發想自[A PID Controller For Lego Mindstorms Robots] 一文,歡迎延伸閱讀,本文中的示意圖也是引用自該網頁,您也可以參考維基百科對於 PID 控制器的定義

傳統的循線方法一言以蔽之就是二分法:向右前方移動,直到偵測到黑線之後向左修正。(白場地黑線,機器人置於左邊)

Basic_bot.

從這裡我們可以看出許多理想性的假設:

1. 每次修正都要能夠回到白色場地,這就是為什麼當軌跡曲率變化太大(例如許多左彎後接一個右彎、髮夾彎甚至直角灣)時,都會在此陣亡…

2. 機器人根本不會直走,因為我們的程式就是這樣寫的,左前方右前方一直重複移動。這樣會造成明明是大直線,但是機器人還在那邊左右左右修正,太慢了。

3. 機器人不知道彎道的變化程度,每次修正的動作皆相同,傳統的兩分法是取黑色軌跡線與白色場地的光值之中間值作為判斷邊界,例如 (30+50) / 2 = 40。小於40視為偵測到黑線,反之則代表在白色場地上。如果光值是46或47,代表此時只有偏離一點點,應該進行小幅度修正就好。但是二分法就是無法作到。

4. 黑就是均勻的黑,白就是均勻的白,怎麼可能嘛!這要考慮到光照,桌面乾淨程度等等…  實際上是不可能有均勻顏色這種事的。

所以由此可以延伸出非常多東西可以深入去玩,您的機器人也會愈來愈聰明:

1. 導入比例控制方法,讓機器人碰到劇烈彎道時可以修正得更快速

2. 導入比例控制方法,當誤差為零時,機器人就是直走!

3. 導入比例控制方法,以本文的設定來說,光值會在30~50之間連續變化,誤差產生時會影響機器人的馬達轉速。以髮夾彎來說,機器人一定會瞬間切入黑色軌跡線較深,這時光值會下降比較多,機器人便是藉由這種方法來得知彎道的曲率,作出更適合的修正。

4. 每次都重新更新判斷邊界值,機器人對於環境的適應力會更高

下圖中我們可以看到二分法就是很簡單的 if / else,機器人只有兩個動作。

到了中間的三階段之後,我們設計一個區間(光值43~47),這時代表機器人略偏但是不算太偏,所以還是直走。直到光值小於43或是大於47代表篇很多了才進行修正。

有三階段就有四階段五階段,最後這麼多階段就會變成一條直線,比例控制方法的比例事實上就是這條線的斜率。斜率愈大代表機器人對於誤差愈敏感,修正幅度也愈大。這在程式中我們是用一個參數 kp 來調整。kp 不是愈大愈好唷!kp過大會讓機器人變得神經兮兮,一點點誤差也一直在修正反而效果不好。這裡都需要反覆地調整,請加油~

light_number_line_3

簡單用 EV3 程式寫一個來玩玩看,請於CAVE實驗室下載唷!

ev3

阿吉老師在淡江電機所開的[機器人程式模擬與開發],就是使用 leJOS Java程式語言來控制樂高NXT機器人,歡迎跟著我們的教學網站一同學習! http://lejosrobot2013.cavedu.com/。以下是上周四的 P控制機器人上課影片與照片

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

2013-10-24 11.15.56 2013-10-24 12.33.34

這是我們另一個使用 NXC 寫的雙光感測器循跡程式,是不是有如刀切豆腐一樣順暢呢?

[youtube=http://youtu.be/0ZMz-CS0bPY]

附上NXC程式碼:

//我是p控制
task main()
{
SetSensorLight(S3);
int kp=2; //比例參數
int light_initial = Sensor(S3); //程式啟動時先抓一次光值,這時請將光感測器所投射出的光圈擺在黑線左測一半處
int light_realtime;                      //即時光值
int vl,vr;                                         //左右輪速
while(true)
{
light_realtime = Sensor(S3);    //更新即時光值
vl = 40 + kp*(light_realtime-light_initial);    //計算左輪速,40 是基礎速度,代表當誤差為0時,機器人將以此速度直走
vr = 40 + kp*(light_realtime+light_initial); //計算右輪速, +/- 是代表進彎時的修正方向,可根據實際狀況修改
OnFwd(OUT_B,vl);                      //出發!
OnFwd(OUT_C,vr);
}

}

[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

0912 淡江Java機器人課程

這是第二年在淡江電機開設「機器人程式模擬與開發」課程,課程內容使用leJOS (Java for Lego NXT)來控制樂高機器人。人數一樣是爆滿的65人,看來大家都很有幹勁。一路維持到學期末吧。

上課輕鬆但不隨便,是阿吉老師的原則喲

官方網站:http://lejosrobot2012.cavedu.com/

2011的教學網站:https://sites.google.com/site/javanxt

安裝leJOS 0.9.0 與 leJOS NXJ Eclipse plugin

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

國際機器人研討會-International Symposium on Robotics 即將開始!

ISR 2012即將於下週開始,本團隊宗翰老師以及阿吉老師等分別投稿了兩篇論文,都是與機器人教育有關:

1. The Programming Software for Hands-on Robot Education
作者:Tsung-Han Hsieh, Chi-Hung Tseng

 Abstract—LEGO MINDSTORMS intelligent robot has played a leading role in robot education for more than a decade; it brought math, science and engineering learning combined with hands-on opportunities to form a prob-lem-solving atmosphere in the classroom. The underlying concept has proved its effectiveness to motivate students to learn related subjects. Furthermore, the hands-on nature of the LEGO set also increases students’ creativity and origi-nality; although it was initially aimed to appeal elementary school students, its versatility and multi-functionalities led to a much wider range of applications. Since then, there have been many computer languages or software designed to be compatible with LEGO brick, in order to support different needs. In regard to this topic, we are going to ex-plore and discuss the variety of software tools that were used, their capability and the adaptation of each tool for different levels of students from primary schools to univer-sities. Benefits and drawbacks in using text-based and graphical programming environments will be discussed in relation to this topic, including NXT-G, app inventor, LabVIEW, NXC, RobotC and LeJOS.

Keywords: robot education; app inventor; LabVIEW; NXC

2. Combination Curriculum of Robotics and Mobile Phone in Primary Education Level with Graphical Programming Environment
作者:Chi-Hung Tseng, Gi-Ming Liu, Wen-You Lu

Abstract—Robots have proved effective in support of play-based teaching activities, especially at the primary education level. With the rapid improve-ment of the smart phone, we found many useful functions which robots can benefit from smart phones. For instance, robot can know where it is and where it is going from the information of the smart phone’s GPS sensor; it can also realize its motion through the orientation sensor; Moreover, with the help of the large touch screen on the phone, user can easily view various kind of robot’s information on the screen; Finally, robot can take advantage of the camera as its eyes for object tracking. All these fea-tures can be implemented with Android devices and LEGO NXT robots. At the same time, we realized that it is not practical to teach users become a skilled Java programmer, which is the official programming language of Android development. Thanks for App Inventor provided by Google, it is a graphical pro-gramming environment on the base of Google Ap-plication Engine. User with relatively no program-ming skills can easily build up there smart phone apps in the first run, therefore App Inventor gains worldwide received from elementary to high school teachers and non-computer-science related college student who intend to design smart phone applica-tions. This topic is going to briefly discuss about the combination of robots and mobile phone applications in primary Education Level and as well how graph-ical programming environment is employed to im-prove students’ learning performance.

Keywords: primary education; robotics; Android; App Inventor; graphical programming

此兩篇都將由阿吉老師報告,衝阿~阿吉老師!

0726 Java機器人課程 – 雙光源感測器循跡

用C or Java 來控制樂高機器人可以做到比NXT-G更細節的控制。 數騎在機器人王國開的Java機器人程式設計班, 連阿姨也來學唷!!

使用的書當然是CAVE出版的[機器人程式設計與實作   使用Java]

也歡迎大家參考阿吉老師在淡江大學的[機器人程式模擬與開發]網站

頁面最下方有範例code



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

import lejos.nxt.*; 
import lejos.util.Delay;
class light 
{
public static void main(String args[])
{
Button.ESCAPE.addButtonListener(new ButtonListener()
{
public void buttonPressed(Button b){System.exit(1);}
public void buttonReleased(Button b){}
});

LightSensor light1 = new LightSensor(SensorPort.S1);
LightSensor light2 = new LightSensor(SensorPort.S2);
Motor.B.setSpeed(450);
Motor.C.setSpeed(450);
while(true)
{

if(light1.readValue()<45 && light2.readValue()<45)
{
Motor.B.stop();
Motor.C.stop();
Delay.msDelay(3000);
}
else if(light1.readValue()<45 && light2.readValue()>45)
{
Motor.B.stop();
Motor.C.forward();
}
else if(light2.readValue()<45 && light1.readValue()>45)
{
Motor.C.stop(); 
Motor.B.forward();
}
else
{
Motor.B.forward();
Motor.C.forward();
}
}
}
}

[淡江leJOS] 機器人程式模擬與開發期末發表,放寒假啦!

淡江機器人教學網請按我    <-  請大家多多利用,和我們一起學習!照片影片還有學生的期末報告檔案都會在近日上線,希望給有心學習機器人的朋友更多有用的資源。

淡江電機系的「機器人程式模擬與開發」,於課程中使用 leJOS 控制樂高機器人,並加入Android 行動裝置來說明藍牙與嵌入式系統等基礎概念。

1/6 是期末成果發表,這些都是阿吉老師的學生唷!上課雖然有點調皮,但都是好孩子。

幫學生留下相關書面影音記錄,對同學要申請學校時有幫助。

在 Visual Studio 中將 Kinect 影像回傳到電腦,看看機器人的狀況

手機遙控乒乓球發球機,這一組的機構設計很棒!一個關節就能控制發球嘴上下左右擺動。

變形金剛爬樓梯機器人,可以根據坡度來決定爬坡的模式,機器人會變形。

迷宮,機器人可以走完隨機擺設的迷宮,且會對路徑進行最佳化。

透過陀螺儀感測器的自我平衡摩托車,可以持續走很遠都不會跌倒

智慧型行車系統。可以做到像VOLVO一樣的自動減速並煞車的功能

顏色分類機#1

顏色分類機#2,同學說最困難的地方在於找到合適大小的球!?

[淡江Java] 73人大爆滿…

全班73人大爆滿, 不過這是初選啦, 名單還沒有完全確定。

還有人拿加簽單找阿吉老師簽, 也有外系跑來修我開的這門 “機器人程式模擬與開發”,  for 電機系三年級。 課程內容是用leJOS (可用於樂高機器人上的Java) 來教機器人控制相關技巧。

我看到這麼多人心中就煩惱不知道期末專題的成效會不會受限, 原本想說20人左右是比較理想的人數。 以我上學期在師大應用電子系教課的經驗來看, 我必須和同學說明這門課是實作導向, 雖然不用考試, 但也不會輕鬆到哪裡去。 所以想混學分的不要來。就看下次上課有多少人留下來囉…

上課人數多或少對我來說沒有什麼差別, 只是我希望來的都是真的想學的, 有人想休卻選不到, 所以要珍惜啊。 我一定會好好磨練這些小朋友們的 (摩拳擦掌)。

下課後到游泳館游個泳再坐捷運回家, 這樣的周五, 挺好的。