First version.

This commit is contained in:
rics
2011-10-12 13:16:02 +02:00
commit f404937f84
272 changed files with 6694 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
/**
* Copyright 2010 Guenther Hoelzl, Shawn Brown
*
* This file is part of MINDdroid.
*
* MINDdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MINDdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MINDdroid. If not, see <http://www.gnu.org/licenses/>.
**/
package com.lego.minddroid;
import android.app.Activity;
import android.app.Dialog;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.Button;
class About {
private Dialog dialog;
public void show(Activity myActivity) {
dialog = new Dialog(myActivity);
dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(R.layout.aboutbox);
Button buttonOK = (Button) dialog.findViewById(R.id.AboutOKbutton);
buttonOK.setOnClickListener(new OnClickListener() {
public void onClick(View v)
{
dialog.dismiss();
}
});
dialog.show();
}
}

View File

@@ -0,0 +1,478 @@
/**
* Copyright 2010 Guenther Hoelzl, Shawn Brown
*
* This file is part of MINDdroid.
*
* MINDdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MINDdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MINDdroid. If not, see <http://www.gnu.org/licenses/>.
**/
package com.lego.minddroid;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.UUID;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
/**
* This class is for talking to a LEGO NXT robot via bluetooth.
* The communciation to the robot is done via LCP (LEGO communication protocol).
* Objects of this class can either be run as standalone thread or controlled
* by the owners, i.e. calling the send/recive methods by themselves.
*/
public class BTCommunicator extends Thread {
public static final int MOTOR_A = 0;
public static final int MOTOR_B = 1;
public static final int MOTOR_C = 2;
public static final int MOTOR_B_ACTION = 40;
public static final int MOTOR_RESET = 10;
public static final int DO_BEEP = 51;
public static final int DO_ACTION = 52;
public static final int READ_MOTOR_STATE = 60;
public static final int GET_FIRMWARE_VERSION = 70;
public static final int DISCONNECT = 99;
public static final int DISPLAY_TOAST = 1000;
public static final int STATE_CONNECTED = 1001;
public static final int STATE_CONNECTERROR = 1002;
public static final int STATE_CONNECTERROR_PAIRING = 1022;
public static final int MOTOR_STATE = 1003;
public static final int STATE_RECEIVEERROR = 1004;
public static final int STATE_SENDERROR = 1005;
public static final int FIRMWARE_VERSION = 1006;
public static final int FIND_FILES = 1007;
public static final int START_PROGRAM = 1008;
public static final int STOP_PROGRAM = 1009;
public static final int GET_PROGRAM_NAME = 1010;
public static final int PROGRAM_NAME = 1011;
public static final int SAY_TEXT = 1030;
public static final int VIBRATE_PHONE = 1031;
public static final int NO_DELAY = 0;
private static final UUID SERIAL_PORT_SERVICE_CLASS_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
// this is the only OUI registered by LEGO, see http://standards.ieee.org/regauth/oui/index.shtml
public static final String OUI_LEGO = "00:16:53";
private Resources mResources;
private BluetoothAdapter btAdapter;
private BluetoothSocket nxtBTsocket = null;
private OutputStream nxtOutputStream = null;
private InputStream nxtInputStream = null;
private boolean connected = false;
private Handler uiHandler;
private String mMACaddress;
private BTConnectable myOwner;
private byte[] returnMessage;
public BTCommunicator(BTConnectable myOwner, Handler uiHandler, BluetoothAdapter btAdapter, Resources resources) {
this.myOwner = myOwner;
this.uiHandler = uiHandler;
this.btAdapter = btAdapter;
this.mResources = resources;
}
public Handler getHandler() {
return myHandler;
}
public byte[] getReturnMessage() {
return returnMessage;
}
public void setMACAddress(String mMACaddress) {
this.mMACaddress = mMACaddress;
}
/**
* @return The current status of the connection
*/
public boolean isConnected() {
return connected;
}
/**
* Creates the connection, waits for incoming messages and dispatches them. The thread will be terminated
* on closing of the connection.
*/
@Override
public void run() {
try {
createNXTconnection();
}
catch (IOException e) { }
while (connected) {
try {
returnMessage = receiveMessage();
if ((returnMessage.length >= 2) && ((returnMessage[0] == LCPMessage.REPLY_COMMAND) ||
(returnMessage[0] == LCPMessage.DIRECT_COMMAND_NOREPLY)))
dispatchMessage(returnMessage);
} catch (IOException e) {
// don't inform the user when connection is already closed
if (connected)
sendState(STATE_RECEIVEERROR);
return;
}
}
}
/**
* Create a bluetooth connection with SerialPortServiceClass_UUID
* @see <a href=
* "http://lejos.sourceforge.net/forum/viewtopic.php?t=1991&highlight=android"
* />
* On error the method either sends a message to it's owner or creates an exception in the
* case of no message handler.
*/
public void createNXTconnection() throws IOException {
try {
BluetoothSocket nxtBTSocketTemporary;
BluetoothDevice nxtDevice = null;
nxtDevice = btAdapter.getRemoteDevice(mMACaddress);
if (nxtDevice == null) {
if (uiHandler == null)
throw new IOException();
else {
sendToast(mResources.getString(R.string.no_paired_nxt));
sendState(STATE_CONNECTERROR);
return;
}
}
nxtBTSocketTemporary = nxtDevice.createRfcommSocketToServiceRecord(SERIAL_PORT_SERVICE_CLASS_UUID);
try {
nxtBTSocketTemporary.connect();
}
catch (IOException e) {
if (myOwner.isPairing()) {
if (uiHandler != null) {
sendToast(mResources.getString(R.string.pairing_message));
sendState(STATE_CONNECTERROR_PAIRING);
}
else
throw e;
return;
}
// try another method for connection, this should work on the HTC desire, credits to Michael Biermann
try {
Method mMethod = nxtDevice.getClass().getMethod("createRfcommSocket", new Class[] { int.class });
nxtBTSocketTemporary = (BluetoothSocket) mMethod.invoke(nxtDevice, Integer.valueOf(1));
nxtBTSocketTemporary.connect();
}
catch (Exception e1){
if (uiHandler == null)
throw new IOException();
else
sendState(STATE_CONNECTERROR);
return;
}
}
nxtBTsocket = nxtBTSocketTemporary;
nxtInputStream = nxtBTsocket.getInputStream();
nxtOutputStream = nxtBTsocket.getOutputStream();
connected = true;
} catch (IOException e) {
if (uiHandler == null)
throw e;
else {
if (myOwner.isPairing())
sendToast(mResources.getString(R.string.pairing_message));
sendState(STATE_CONNECTERROR);
return;
}
}
// everything was OK
if (uiHandler != null)
sendState(STATE_CONNECTED);
}
/**
* Closes the bluetooth connection. On error the method either sends a message
* to it's owner or creates an exception in the case of no message handler.
*/
public void destroyNXTconnection() throws IOException {
try {
if (nxtBTsocket != null) {
connected = false;
nxtBTsocket.close();
nxtBTsocket = null;
}
nxtInputStream = null;
nxtOutputStream = null;
} catch (IOException e) {
if (uiHandler == null)
throw e;
else
sendToast(mResources.getString(R.string.problem_at_closing));
}
}
/**
* Sends a message on the opened OutputStream
* @param message, the message as a byte array
*/
public void sendMessage(byte[] message) throws IOException {
if (nxtOutputStream == null)
throw new IOException();
// send message length
int messageLength = message.length;
nxtOutputStream.write(messageLength);
nxtOutputStream.write(messageLength >> 8);
nxtOutputStream.write(message, 0, message.length);
}
/**
* Receives a message on the opened InputStream
* @return the message
*/
public byte[] receiveMessage() throws IOException {
if (nxtInputStream == null)
throw new IOException();
int length = nxtInputStream.read();
length = (nxtInputStream.read() << 8) + length;
byte[] returnMessage = new byte[length];
nxtInputStream.read(returnMessage);
return returnMessage;
}
/**
* Sends a message on the opened OutputStream. In case of
* an error the state is sent to the handler.
* @param message, the message as a byte array
*/
private void sendMessageAndState(byte[] message) {
if (nxtOutputStream == null)
return;
try {
sendMessage(message);
}
catch (IOException e) {
sendState(STATE_SENDERROR);
}
}
private void dispatchMessage(byte[] message) {
switch (message[1]) {
case LCPMessage.GET_OUTPUT_STATE:
if (message.length >= 25)
sendState(MOTOR_STATE);
break;
case LCPMessage.GET_FIRMWARE_VERSION:
if (message.length >= 7)
sendState(FIRMWARE_VERSION);
break;
case LCPMessage.FIND_FIRST:
case LCPMessage.FIND_NEXT:
if (message.length >= 28) {
// Success
if (message[2] == 0)
sendState(FIND_FILES);
}
break;
case LCPMessage.GET_CURRENT_PROGRAM_NAME:
if (message.length >= 23) {
sendState(PROGRAM_NAME);
}
break;
case LCPMessage.SAY_TEXT:
if (message.length == 22) {
sendState(SAY_TEXT);
}
case LCPMessage.VIBRATE_PHONE:
if (message.length == 3) {
sendState(VIBRATE_PHONE);
}
}
}
private void doBeep(int frequency, int duration) {
byte[] message = LCPMessage.getBeepMessage(frequency, duration);
sendMessageAndState(message);
waitSomeTime(20);
}
private void doAction(int actionNr) {
byte[] message = LCPMessage.getActionMessage(actionNr);
sendMessageAndState(message);
}
private void startProgram(String programName) {
byte[] message = LCPMessage.getStartProgramMessage(programName);
sendMessageAndState(message);
}
private void stopProgram() {
byte[] message = LCPMessage.getStopProgramMessage();
sendMessageAndState(message);
}
private void getProgramName() {
byte[] message = LCPMessage.getProgramNameMessage();
sendMessageAndState(message);
}
private void changeMotorSpeed(int motor, int speed) {
if (speed > 100)
speed = 100;
else if (speed < -100)
speed = -100;
byte[] message = LCPMessage.getMotorMessage(motor, speed);
sendMessageAndState(message);
}
private void rotateTo(int motor, int end) {
byte[] message = LCPMessage.getMotorMessage(motor, -80, end);
sendMessageAndState(message);
}
private void reset(int motor) {
byte[] message = LCPMessage.getResetMessage(motor);
sendMessageAndState(message);
}
private void readMotorState(int motor) {
byte[] message = LCPMessage.getOutputStateMessage(motor);
sendMessageAndState(message);
}
private void getFirmwareVersion() {
byte[] message = LCPMessage.getFirmwareVersionMessage();
sendMessageAndState(message);
}
private void findFiles(boolean findFirst, int handle) {
byte[] message = LCPMessage.getFindFilesMessage(findFirst, handle, "*.*");
sendMessageAndState(message);
}
private void waitSomeTime(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
}
}
private void sendToast(String toastText) {
Bundle myBundle = new Bundle();
myBundle.putInt("message", DISPLAY_TOAST);
myBundle.putString("toastText", toastText);
sendBundle(myBundle);
}
private void sendState(int message) {
Bundle myBundle = new Bundle();
myBundle.putInt("message", message);
sendBundle(myBundle);
}
private void sendBundle(Bundle myBundle) {
Message myMessage = myHandler.obtainMessage();
myMessage.setData(myBundle);
uiHandler.sendMessage(myMessage);
}
// receive messages from the UI
final Handler myHandler = new Handler() {
@Override
public void handleMessage(Message myMessage) {
int message;
switch (message = myMessage.getData().getInt("message")) {
case MOTOR_A:
case MOTOR_B:
case MOTOR_C:
changeMotorSpeed(message, myMessage.getData().getInt("value1"));
break;
case MOTOR_B_ACTION:
rotateTo(MOTOR_B, myMessage.getData().getInt("value1"));
break;
case MOTOR_RESET:
reset(myMessage.getData().getInt("value1"));
break;
case START_PROGRAM:
startProgram(myMessage.getData().getString("name"));
break;
case STOP_PROGRAM:
stopProgram();
break;
case GET_PROGRAM_NAME:
getProgramName();
break;
case DO_BEEP:
doBeep(myMessage.getData().getInt("value1"), myMessage.getData().getInt("value2"));
break;
case DO_ACTION:
doAction(0);
break;
case READ_MOTOR_STATE:
readMotorState(myMessage.getData().getInt("value1"));
break;
case GET_FIRMWARE_VERSION:
getFirmwareVersion();
break;
case FIND_FILES:
findFiles(myMessage.getData().getInt("value1") == 0, myMessage.getData().getInt("value2"));
break;
case DISCONNECT:
// send stop messages before closing
changeMotorSpeed(MOTOR_A, 0);
changeMotorSpeed(MOTOR_B, 0);
changeMotorSpeed(MOTOR_C, 0);
waitSomeTime(500);
try {
destroyNXTconnection();
}
catch (IOException e) { }
break;
}
}
};
}

View File

@@ -0,0 +1,29 @@
/**
* Copyright 2010 Guenther Hoelzl
*
* This file is part of MINDdroid.
*
* MINDdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MINDdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MINDdroid. If not, see <http://www.gnu.org/licenses/>.
**/
package com.lego.minddroid;
public interface BTConnectable {
/**
* @return true, when currently pairing
*/
public boolean isPairing();
}

View File

@@ -0,0 +1,219 @@
/**
* (Changes from original are) Copyright 2010 Guenther Hoelzl, Shawn Brown
*
* This file is part of MINDdroid.
*
* MINDdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MINDdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MINDdroid. If not, see <http://www.gnu.org/licenses/>.
*
*
* (original work is) Copyright (C) 2009 The Android Open Source Project
**/
package com.lego.minddroid;
import java.util.Set;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
/**
* This Activity appears as a dialog. It lists any paired devices and
* devices detected in the area after discovery. When a device is chosen
* by the user, the MAC address of the device is sent back to the parent
* Activity in the result Intent.
*/
public class DeviceListActivity extends Activity {
static final String PAIRING = "pairing";
// Return Intent extra
public static String DEVICE_NAME_AND_ADDRESS = "device_infos";
public static String EXTRA_DEVICE_ADDRESS = "device_address";
// Member fields
private BluetoothAdapter mBtAdapter;
private ArrayAdapter<String> mPairedDevicesArrayAdapter;
private ArrayAdapter<String> mNewDevicesArrayAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Setup the window
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.device_list);
// Set result CANCELED incase the user backs out
setResult(Activity.RESULT_CANCELED);
// Initialize the button to perform device discovery
Button scanButton = (Button) findViewById(R.id.button_scan);
scanButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
doDiscovery();
v.setVisibility(View.GONE);
}
});
// Initialize array adapters. One for already paired devices and
// one for newly discovered devices
mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
mNewDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
// Find and set up the ListView for paired devices
ListView pairedListView = (ListView) findViewById(R.id.paired_devices);
pairedListView.setAdapter(mPairedDevicesArrayAdapter);
pairedListView.setOnItemClickListener(mDeviceClickListener);
// Find and set up the ListView for newly discovered devices
ListView newDevicesListView = (ListView) findViewById(R.id.new_devices);
newDevicesListView.setAdapter(mNewDevicesArrayAdapter);
newDevicesListView.setOnItemClickListener(mDeviceClickListener);
// Register for broadcasts when a device is discovered
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
this.registerReceiver(mReceiver, filter);
// Register for broadcasts when discovery has finished
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
this.registerReceiver(mReceiver, filter);
// Get the local Bluetooth adapter
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
// Get a set of currently paired devices
Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices();
// If there are paired devices, add each one to the ArrayAdapter
boolean legoDevicesFound = false;
if (pairedDevices.size() > 0) {
findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE);
for (BluetoothDevice device : pairedDevices) {
// only add LEGO devices
if (device.getAddress().startsWith(BTCommunicator.OUI_LEGO)) {
legoDevicesFound = true;
mPairedDevicesArrayAdapter.add(device.getName() + "-" + device.getAddress());
}
}
}
if (legoDevicesFound == false) {
String noDevices = getResources().getText(R.string.none_paired).toString();
mPairedDevicesArrayAdapter.add(noDevices);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// Make sure we're not doing discovery anymore
if (mBtAdapter != null) {
mBtAdapter.cancelDiscovery();
}
// Unregister broadcast listeners
this.unregisterReceiver(mReceiver);
}
/**
* Start device discover with the BluetoothAdapter
*/
private void doDiscovery() {
// Indicate scanning in the title
setProgressBarIndeterminateVisibility(true);
setTitle(R.string.scanning);
// Turn on sub-title for new devices
findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE);
// If we're already discovering, stop it
if (mBtAdapter.isDiscovering()) {
mBtAdapter.cancelDiscovery();
}
// Request discover from BluetoothAdapter
mBtAdapter.startDiscovery();
}
// The on-click listener for all devices in the ListViews
private OnItemClickListener mDeviceClickListener = new OnItemClickListener() {
public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) {
String info = ((TextView) v).getText().toString();
// did we choose a correct name and address?
if (info.lastIndexOf('-') != info.length()-18)
return;
// Cancel discovery because it's costly and we're about to connect
mBtAdapter.cancelDiscovery();
// Get the device MAC address, this is the text after the last '-' character
String address = info.substring(info.lastIndexOf('-')+1);
// Create the result Intent and include the infos
Intent intent = new Intent();
Bundle data = new Bundle();
data.putString(DEVICE_NAME_AND_ADDRESS, info);
data.putString(EXTRA_DEVICE_ADDRESS, address);
data.putBoolean(PAIRING,av.getId()==R.id.new_devices);
intent.putExtras(data);
// Set result and finish this Activity
setResult(RESULT_OK, intent);
finish();
}
};
// The BroadcastReceiver that listens for discovered devices and
// changes the title when discovery is finished
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// When discovery finds a device
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// Get the BluetoothDevice object from the Intent
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// If it's already paired, skip it, because it's been listed already
if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
mNewDevicesArrayAdapter.add(device.getName() + "-" + device.getAddress());
}
// When discovery is finished, change the Activity title
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
setProgressBarIndeterminateVisibility(false);
setTitle(R.string.select_device);
if (mNewDevicesArrayAdapter.getCount() == 0) {
String noDevices = getResources().getText(R.string.none_found).toString();
mNewDevicesArrayAdapter.add(noDevices);
}
}
}
};
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright 2010 Guenther Hoelzl
*
* This file is part of MINDdroid.
*
* MINDdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MINDdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MINDdroid. If not, see <http://www.gnu.org/licenses/>.
**/
package com.lego.minddroid;
public interface DialogListener {
public void dialogUpdate(String fileName);
}

View File

@@ -0,0 +1,93 @@
package com.lego.minddroid;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
public class EnableBT extends Activity { //currently unused. Will be implemented to allow connection without user having to say "Yes turn bt on" (when it isn't)
boolean processStarted = false;
StatusReciever statusReciever;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setResult(Activity.RESULT_CANCELED);
statusReciever = new StatusReciever();
registerReceiver(statusReciever, new IntentFilter("android.bluetooth.adapter.action.STATE_CHANGED"));
processStarted = turnOnBt();
if (!processStarted) {
sendFailureStatus();
}
}
private boolean turnOnBt() {
return BluetoothAdapter.getDefaultAdapter().enable();
}
public void sendFailureStatus() {
Log.d("EnableBT sendFailureStatus", "RESULT_CANCELED");
this.setResult(Activity.RESULT_CANCELED);
finish();
}
public void sendSuccessStatus() {
Log.d("EnableBT sendSuccessStatus", "RESULT_OK");
this.setResult(Activity.RESULT_OK);
finish();
}
@Override
protected void onPause() {
super.onPause();
try {
unregisterReceiver(statusReciever);
} catch (Exception e) {
// not registered
}
}
@Override
protected void onDestroy() {
try {
unregisterReceiver(statusReciever);
} catch (Exception e) {
// not registered
}
super.onDestroy();
}
public class StatusReciever extends BroadcastReceiver {
/**
*
*/
public final static String STATE_CHANGED = "android.bluetooth.adapter.action.STATE_CHANGED";
@Override
public void onReceive(Context context, Intent intent) {
Log.d("EnableBT statusReciever", "onReceive");
if (intent.getAction().equals(STATE_CHANGED)) {
Log.d("EnableBT statusReciever", "ACTION_STATE_CHANGED");
sendSuccessStatus();
} else {
Log.d("EnableBT statusReciever", "fail: " + intent.getAction());
sendFailureStatus();
}
}
}
}

View File

@@ -0,0 +1,67 @@
/**
* Copyright 2010 Guenther Hoelzl, Shawn Brown
*
* This file is part of MINDdroid.
*
* MINDdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MINDdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MINDdroid. If not, see <http://www.gnu.org/licenses/>.
**/
package com.lego.minddroid;
import java.util.Iterator;
import java.util.List;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
class FileDialog {
private Activity myActivity;
private List<String> myList;
private int programNr = -1;
private CharSequence[] programs;
public FileDialog(Activity activity, List<String> list) {
myActivity = activity;
myList = list;
// copy Strings from list to CharSequence array
programs = new CharSequence[myList.size()];
Iterator<String> iterator = myList.iterator();
int position = 0;
while(iterator.hasNext()) {
programs[position++] = iterator.next();
}
}
/**
* Shows the dialog
* @param startStop when true shows another title (for leJOSMINDdroid)
*/
public void show(boolean startStop) {
AlertDialog.Builder builder = new AlertDialog.Builder(myActivity);
builder.setTitle(myActivity.getResources().getString(startStop ? R.string.file_dialog_title_1 : R.string.file_dialog_title_2));
builder.setItems(programs, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
startProgram(item);
}
});
builder.create().show();
}
private void startProgram(int number) {
((MINDdroidCV) myActivity).startProgram((String) programs[number]);
}
}

View File

@@ -0,0 +1,797 @@
/**
* Copyright 2010 Guenther Hoelzl, Shawn Brown
*
* This file is part of MINDdroid.
*
* MINDdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MINDdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MINDdroid. If not, see <http://www.gnu.org/licenses/>.
**/
package com.lego.minddroid;
import java.util.List;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class GameView extends SurfaceView implements SurfaceHolder.Callback {
private static final int SHORT_PRESS_MAX_DURATION = 750;
class GameThread extends Thread {
/** time between each redraw */
private static final int REDRAW_SCHED = 100;
private int ICON_MAX_SIZE;
private int ICON_MIN_SIZE;
private int GOAL_HEIGHT;
private int GOAL_WIDTH;
private static final int HAPTIC_FEEDBACK_LENGTH = 30;
/**
* is tilt icon in goal
*/
boolean mInGoal = true;
/**
* to notify users when leaving goal
*/
Vibrator mHapticFeedback;
/** The drawable to use as the background of the animation canvas */
private Bitmap mBackgroundImage;
private Drawable mIconOrange;
private Drawable mIconWhite;
private Bitmap mTarget;
private Bitmap mTargetInactive;
private Bitmap mActionButton;
private Bitmap mActionDownButton;
/**
* Current height of the surface/canvas.
*
* @see #setSurfaceSize
*/
private int mCanvasHeight = 1;
/**
* Current width of the surface/canvas.
*
* @see #setSurfaceSize
*/
private int mCanvasWidth = 1;
/** Message handler used by thread to interact with TextView */
private Handler mHandler;
/** Used to figure out elapsed time between frames */
private long mLastTime;
/** Indicate whether the surface has been created & is ready to draw */
private boolean mRun = false;
/** Handle to the surface manager object we interact with */
private SurfaceHolder mSurfaceHolder;
/** X of motion indicator */
private float mX;
/** Y of motion indicator */
private float mY;
/**
* mIconSize grows within target between ICON_MIN_SIZE and ICON_MAX_SIZE
*/
private int mGrowAdjust;
/**
* time when haptic feedback will stop - needed to ensure we don't take
* tilt measurements while handset if vibrating
*/
//private long mFeedbackEnd = 0;
/**
* track how long since we redrew screen
*/
long mElapsedSinceDraw = 0;
/**
* track how long since we redrew screen
*/
long mElapsedSinceNXTCommand = 0;
/**
* count how many times we took tilt readings in 100ms so we can average
* position
*/
int mAvCount = 0;
/**
* time when tilt icon should change color
*/
long mNextPulse = 0;
/* holder for current color in pulse effect* */
Drawable mPulsingTiltIcon;
/** was action button just pressed */
boolean mActionPressed = false;
/** */
boolean mToNXT = false;
/** buffers to hold tilt readings for averaging */
float mNumAcX;
float mNumAcY;
/** digital filtering variables **/
private float xX0 = 0;
private float xX1 = 0;
private float xY0 = 0;
private float xY1 = 0;
private float yX0 = 0;
private float yX1 = 0;
private float yY0 = 0;
private float yY1 = 0;
public boolean longPressCancel;
public GameThread(SurfaceHolder surfaceHolder, Context context, Vibrator vibrator, Handler handler) {
// get handles to some important objects
mHapticFeedback = vibrator;
mSurfaceHolder = surfaceHolder;
mHandler = handler;
Resources res = context.getResources();
mIconOrange = context.getResources().getDrawable(R.drawable.orange);
// load background image as a Bitmap instead of a Drawable b/c
// we don't need to transform it and it's faster to draw this way
mIconWhite = context.getResources().getDrawable(R.drawable.white);
mTarget = BitmapFactory.decodeResource(res, R.drawable.target_no_orange_dot);
mTargetInactive = BitmapFactory.decodeResource(res, R.drawable.target);
mActionButton = BitmapFactory.decodeResource(res, R.drawable.action_btn_up);
mActionDownButton = BitmapFactory.decodeResource(res, R.drawable.action_btn_down);
mBackgroundImage = BitmapFactory.decodeResource(res, R.drawable.background_2);
}
private int calcGrowAdjust(float mX2, float mY2) {
int xDistanceFromCenter = (int) Math.abs((mCanvasWidth / 2) - mX2);
int yDistanceFromCenter = (int) Math.abs(((mCanvasHeight - mActionButton.getHeight()) / 2) - mY2);
if (xDistanceFromCenter > ICON_MAX_SIZE || yDistanceFromCenter > ICON_MAX_SIZE) {
return ICON_MAX_SIZE;
}
if (xDistanceFromCenter > yDistanceFromCenter) {
return (xDistanceFromCenter > ICON_MIN_SIZE ? xDistanceFromCenter : ICON_MIN_SIZE);
}
return (yDistanceFromCenter > ICON_MIN_SIZE ? yDistanceFromCenter : ICON_MIN_SIZE);
}
private int calcNextPulse() {
int xDistanceFromGoal = 0;
int yDistanceFromGoal = 0;
if (mX > mCanvasWidth / 2) {
xDistanceFromGoal = (int) ((mX - (mCanvasWidth / 2)) - (GOAL_WIDTH / 2));
} else {
xDistanceFromGoal = (int) ((mCanvasWidth / 2) - mX) - (GOAL_WIDTH / 2);
}
xDistanceFromGoal += ICON_MAX_SIZE / 2;//adjust for icon width so that when icon touches outer edge, it will be at 100%.
if (mY > ((mCanvasHeight - mActionButton.getHeight()) / 2)) {
yDistanceFromGoal = (int) ((mY - ((mCanvasHeight - mActionButton.getHeight()) / 2)) - (GOAL_WIDTH / 2));//GOAL_WIDTH ok for y when square
} else {
yDistanceFromGoal = (int) (((mCanvasHeight - mActionButton.getHeight()) / 2) - mY - (GOAL_WIDTH / 2));
}
yDistanceFromGoal += ICON_MAX_SIZE / 2;//adjust for icon width so that when icon touches outer edge, it will be at 100%.
double mOneSideGameWidth = (mCanvasWidth - GOAL_WIDTH) / 2;//
double mOneSideGameHeight = ((mCanvasHeight - mActionButton.getHeight()) / 2) - (GOAL_WIDTH / 2);// if it's square --OK
double mPercentToXEdge = (xDistanceFromGoal / (mOneSideGameWidth)) * 100;
double mPercentToYEdge = (yDistanceFromGoal / mOneSideGameHeight) * 100;
float closeEdge = (float) (mPercentToXEdge > mPercentToYEdge ? mPercentToXEdge : mPercentToYEdge);
return (int) (800 - ((closeEdge * 8)));
}
/**
* Draws move indicator, button and background to the provided Canvas.
*/
private void doDraw(Canvas mCanvas) {
if (!mActivity.isConnected()) {
// draw the background
mCanvas.drawBitmap(mBackgroundImage, 0, 0, null);
//draw pressed action button
mCanvas.drawBitmap(mActionDownButton, 0, mCanvasHeight - mActionButton.getHeight(), null);
//draw icon in goal
// draw the goal
mCanvas.drawBitmap(mTargetInactive, (mCanvasWidth - mTarget.getWidth()) / 2, ((mCanvasHeight - mActionButton.getHeight()) / 2)
- (mTarget.getHeight() / 2), null);
} else {
// Draw the background image. Operations on the Canvas accumulate
if (thread.isInGoal()) { // icon is in goal
mInGoal = true;
mGrowAdjust = calcGrowAdjust(mX, mY);
} else {
mGrowAdjust = ICON_MAX_SIZE;
if (mInGoal) {// was in goal before
mInGoal = false;
vibrate();
}
}
// draw the background
mCanvas.drawBitmap(mBackgroundImage, 0, 0, null);
// draw the action button
mCanvas.drawBitmap(mActionPressed ? mActionDownButton : mActionButton, 0, mCanvasHeight - mActionButton.getHeight(), null);
mActionPressed = false;
// draw the goal
mCanvas.drawBitmap(mTarget, (mCanvasWidth - mTarget.getWidth()) / 2,
((mCanvasHeight - mActionButton.getHeight()) / 2) - (mTarget.getHeight() / 2), null);
// update the icon location and draw (or blink) it
if (mInGoal) {
mIconOrange.setBounds((int) mX - (mGrowAdjust / 2), (int) mY - ((mGrowAdjust / 2)), ((int) mX + (mGrowAdjust / 2)), (int) mY
+ (mGrowAdjust / 2));
mIconOrange.draw(mCanvas);
} else {
// boundary checking, don't want the move_icon going off-screen.
if (mX + ICON_MAX_SIZE / 2 >= mCanvasWidth) {// set at outer edge
mX = mCanvasWidth - (ICON_MAX_SIZE / 2);
} else if (mX - (ICON_MAX_SIZE / 2) < 0) {
mX = ICON_MAX_SIZE / 2;
}
// boundary checking, don't want the move_icon rolling
// off-screen.
if (mY + ICON_MAX_SIZE / 2 >= (mCanvasHeight - mActionButton.getHeight())) {// set at outer edge
mY = mCanvasHeight - mActionButton.getHeight() - ICON_MAX_SIZE / 2;
} else if (mY - ICON_MAX_SIZE / 2 < 0) {
mY = ICON_MAX_SIZE / 2;
}
if (mLastTime > mNextPulse) {
mPulsingTiltIcon = mPulsingTiltIcon == mIconOrange ? mIconWhite : mIconOrange;
mNextPulse = mPulsingTiltIcon == mIconOrange ? mLastTime + calcNextPulse() : mLastTime + 90;
}
mPulsingTiltIcon.setBounds((int) mX - (mGrowAdjust / 2), (int) mY - (mGrowAdjust / 2), ((int) mX + mGrowAdjust / 2),
((int) mY + mGrowAdjust / 2));
mPulsingTiltIcon.draw(mCanvas);
}
}
}
/**
* Starts the game, setting parameters for the current difficulty.
*/
public void doStart() {
synchronized (mSurfaceHolder) {
mX = mCanvasWidth / 2;
mY = (mCanvasHeight - mActionButton.getHeight()) / 2;
}
}
/**
* Pauses the animation.
*/
public void pause() {
thread.setRunning(false);
synchronized (mSurfaceHolder) {
}
boolean retry = true;
getThread().setRunning(false);
while (retry) {
try {
getThread().join();
retry = false;
} catch (InterruptedException e) {
}
}
}
/**
* Restores game state from the indicated Bundle. Typically called when
* the Activity is being restored after having been previously
* destroyed.
*
* @param savedState
* Bundle containing the game state
*/
public synchronized void restoreState(Bundle savedState) {
synchronized (mSurfaceHolder) {
}
}
@Override
public void run() {
// Log.d(TAG, "--run--");
while (mRun) {
// sleep some time
try {
Thread.sleep(30);
}
catch (InterruptedException e) {
}
updateTime();
updateMoveIndicator(mAccelX, mAccelY);
doActionButtonFeedback();
// is it time to update the screen?
if (mElapsedSinceDraw > REDRAW_SCHED) {
//is it time to update motor movement?
if (mElapsedSinceNXTCommand > MINDdroidCV.UPDATE_TIME) {
//calculate and send command to move motors
doMotorMovement(-mNumAcY, -mNumAcX);
}
lockCanvasAndDraw();
}
}
}
private void doActionButtonFeedback() {
if((mLastTime-mTimeActionDown)>SHORT_PRESS_MAX_DURATION && longPressCancel!=true) {
vibrate();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
vibrate();
longPressCancel=true;
}
}
public void lockCanvasAndDraw() {
Canvas c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
doDraw(c);
}
} finally {
// do this in a finally so that if an exception is
// thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
mElapsedSinceDraw = 0;// mLastTime set to current
// moment in updateTime
}
}
}
/**
* Dump game state to the provided Bundle. Typically called when the
* Activity is being suspended.
*
* @return Bundle with this view's state
*/
public Bundle saveState(Bundle map) {
synchronized (mSurfaceHolder) {
if (map != null) {
}
}
return map;
}
/**
* Used to signal the thread whether it should be running or not.
* Passing true allows the thread to run; passing false will shut it
* down if it's already running. Calling start() after this was most
* recently called with false will result in an immediate shutdown.
*
* @param b
* true to run, false to shut down
*/
public void setRunning(boolean b) {
mRun = b;
}
public void doMotorMovement(float pitch, float roll) {
int left=0;
int right=0;
// only when phone is little bit tilted
if ((Math.abs(pitch) > 10.0) || (Math.abs(roll) > 10.0)) {
// limit pitch and roll
if (pitch > 33.3){
pitch = (float) 33.3;
}else if (pitch < -33.3){
pitch = (float) -33.3;}
if (roll > 33.3){
roll = (float) 33.3;
}else if (roll < -33.3){
roll = (float) -33.3;
}
// when pitch is very small then do a special turning function
if (Math.abs(pitch) > 10.0) {
left = (int) Math.round(3.3 * pitch * (1.0 + roll / 60.0));
right = (int) Math.round(3.3 * pitch * (1.0 - roll / 60.0));
} else {
left = (int) Math.round(3.3 * roll - Math.signum(roll) * 3.3 * Math.abs(pitch));
right = -left;
}
// limit the motor outputs
if (left > 100)
left = 100;
else if (left < -100)
left = -100;
if (right > 100)
right = 100;
else if (right < -100)
right = -100;
// Reverse the motors when driving back at the shooterbot and the NXJ model
if (pitch < -10 && (mActivity.mRobotType==R.id.robot_type_1 || mActivity.mRobotType==R.id.robot_type_4)) {
int back_left=right;
int back_right=left;
left=back_left;
right=back_right;
}
}
mActivity.updateMotorControl(left, right);
}
/**
* Sets the game mode. That is, whether we are running, paused, etc.
*
* @see #setState(int, CharSequence)
* @param mode
* one of the STATE_* constants
*/
public void setState(int mode) {
synchronized (mSurfaceHolder) {
setState(mode, null);
}
}
/**
* Sets the game mode. That is, whether we are running, paused, in the
* failure state, in the victory state, etc.
*
* @param mode
* one of the STATE
* @param message
* string to add to screen or null
*/
public void setState(int mode, CharSequence message) {
synchronized (mSurfaceHolder) {
}
}
/* Callback invoked when the surface dimensions change. */
public void setSurfaceSize(int width, int height) {
// synchronized to make sure these all change atomically
synchronized (mSurfaceHolder) {
mCanvasWidth = width;
mCanvasHeight = height;
float mAHeight = mActionButton.getHeight();
float mAWidth = mActionButton.getWidth();
mActionButton = Bitmap.createScaledBitmap(mActionButton, width, (Math.round((width * (mAHeight / mAWidth)))), true);
mActionDownButton = Bitmap.createScaledBitmap(mActionDownButton, width, (Math.round((width * (mAHeight / mAWidth)))), true);
// don't forget to resize the background image
mBackgroundImage = Bitmap.createScaledBitmap(mBackgroundImage, width, height, true);
int temp_ratio = mCanvasWidth / 64;
GOAL_WIDTH = mCanvasWidth / temp_ratio;
ICON_MAX_SIZE = (GOAL_WIDTH / 8) * 6;
ICON_MIN_SIZE = (GOAL_WIDTH / 4);
temp_ratio = mCanvasHeight / 64;
GOAL_HEIGHT = mCanvasHeight / temp_ratio;
mTarget = Bitmap.createScaledBitmap(mTarget, GOAL_WIDTH, GOAL_HEIGHT, true);
mTargetInactive = Bitmap.createScaledBitmap(mTargetInactive, GOAL_WIDTH, GOAL_HEIGHT, true);
}
}
/**
* Resumes from a pause.
*/
public void unpause() {
// Move the real time clock up to now
synchronized (mSurfaceHolder) {
mLastTime = System.currentTimeMillis() + 100;
}
}
private void updateTime() {// use for blinking
long now = System.currentTimeMillis();
// Do nothing if mLastTime is in the future.
// This allows the game-start to delay the start
// by 100ms or whatever.
if (mLastTime > now)
return;
// double elapsed = (now - mLastTime) / 1000.0;
long elapsed = now - mLastTime;
// elapsedSincePulse += elapsed;
mElapsedSinceDraw += elapsed;
mElapsedSinceNXTCommand += elapsed;
mLastTime = now;
}
public void vibrate() {
mHapticFeedback.vibrate(HAPTIC_FEEDBACK_LENGTH);
//mFeedbackEnd = System.currentTimeMillis() + HAPTIC_FEEDBACK_LENGTH + 15;
}
void updateMoveIndicator(float mAcX, float mAcY) {
// IIR filtering for x direction
xX1 = xX0;
xX0 = mAcX;
xY1 = xY0;
xY0 = (float) 0.07296293 * xX0 + (float) 0.07296293 * xX1 + (float) 0.8540807 * xY1;
mAcX = xY0;
// IIR filtering for y direction
yX1 = yX0;
yX0 = mAcY;
yY1 = yY0;
yY0 = (float) 0.07296293 * yX0 + (float) 0.07296293 * yX1 + (float) 0.8540807 * yY1;
mAcY = yY0;
mX = ((mCanvasWidth / 2)) + (int) ((mAcX / 10) * (mCanvasWidth / 10));
mNumAcX = mAcX;
mY = (((mCanvasHeight - mActionButton.getHeight()) / 2)) + (int) ((mAcY / 10) * ((mCanvasHeight - mActionButton.getHeight()) / 10));
mNumAcY = mAcY;
}
public boolean isInGoal() {
if ((mCanvasWidth - mTarget.getWidth()) / 2 > mX || (mCanvasWidth + mTarget.getWidth()) / 2 < mX) {// x is not within goal
return false;
}
if (((mCanvasHeight - (mActionButton.getHeight() + (GOAL_HEIGHT))) / 2 > mY || ((mCanvasHeight - mActionButton.getHeight()) + (GOAL_HEIGHT)) / 2 < mY)) {
return false;
}
return true;
}
}
/** used for logging */
private static final String TAG = GameView.class.getName();;
private MINDdroidCV mActivity;
/** The thread that actually draws the animation */
private GameThread thread;
private SensorManager mSensorManager;
/** orientation (tilt) readings */
private float mAccelX = 0;
private float mAccelY = 0;
private float mAccelZ = 0; // heading
/**time that action button was pressed - used to calc long or short press */
long mTimeActionDown=0;
private final SensorEventListener mSensorAccelerometer = new SensorEventListener() {
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// TODO Auto-generated method stub
}
@Override
public void onSensorChanged(SensorEvent event) {
mAccelX = 0 - event.values[2];
mAccelY = 0 - event.values[1];
mAccelZ = event.values[0];
}
};
Context context;
public GameView(Context context, MINDdroidCV uiActivity) {
super(context);
mActivity = uiActivity;
mSensorManager = (SensorManager) mActivity.getSystemService(Context.SENSOR_SERVICE);
// register our interest in hearing about changes to our surface
SurfaceHolder holder = getHolder();
holder.setKeepScreenOn(true);
holder.addCallback(this);
this.context=context;
// create thread only; it's started in surfaceCreated()
thread = new GameThread(holder, context, (Vibrator) uiActivity.getSystemService(Context.VIBRATOR_SERVICE), new Handler() {
@Override
public void handleMessage(Message m) {
}
});
setFocusable(true); // make sure we get key events
}
public GameThread getThread() {
return thread;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getY() > this.getHeight() - getThread().mActionButton.getHeight()) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mTimeActionDown=System.currentTimeMillis();
getThread().longPressCancel=false;
break;
case MotionEvent.ACTION_UP:
long mTimeActionUp=System.currentTimeMillis();
// short press
if (mTimeActionUp-mTimeActionDown<SHORT_PRESS_MAX_DURATION) {
getThread().longPressCancel=true;
mActivity.actionButtonPressed();
}
// long press
else {
mActivity.actionButtonLongPress();
}
break;
}
}
return true;
}
public void registerListener() {
List<Sensor> sensorList;
// register orientation sensor
sensorList = mSensorManager.getSensorList(Sensor.TYPE_ORIENTATION);
mSensorManager.registerListener(mSensorAccelerometer, sensorList.get(0), SensorManager.SENSOR_DELAY_GAME);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
getThread().setSurfaceSize(width, height);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (getThread().getState()!=Thread.State.TERMINATED){
getThread().setRunning(true);
getThread().start();
} else{
thread = new GameThread(holder, context , (Vibrator) mActivity.getSystemService(Context.VIBRATOR_SERVICE), new Handler() {
@Override
public void handleMessage(Message m) {
}
});
getThread().setRunning(true);
getThread().start();
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.i (TAG,"surfaceDestroyed");
boolean retry = true;
getThread().setRunning(false);
while (retry) {
try {
getThread().join();
retry = false;
} catch (InterruptedException e) {
}
}
}
public void unregisterListener() {
mSensorManager.unregisterListener(mSensorAccelerometer);
}
}

View File

@@ -0,0 +1,317 @@
/**
* Copyright 2010, 2011 Guenther Hoelzl, Shawn Brown
*
* This file is part of MINDdroid.
*
* MINDdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MINDdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MINDdroid. If not, see <http://www.gnu.org/licenses/>.
**/
package com.lego.minddroid;
/**
* Class for composing the proper messages for simple
* communication over bluetooth
*/
public class LCPMessage {
// the folowing constants were taken from the leJOS project (http://www.lejos.org)
// Command types constants. Indicates type of packet being sent or received.
public static byte DIRECT_COMMAND_REPLY = 0x00;
public static byte SYSTEM_COMMAND_REPLY = 0x01;
public static byte REPLY_COMMAND = 0x02;
public static byte DIRECT_COMMAND_NOREPLY = (byte)0x80; // Avoids ~100ms latency
public static byte SYSTEM_COMMAND_NOREPLY = (byte)0x81; // Avoids ~100ms latency
// Direct Commands
public static final byte START_PROGRAM = 0x00;
public static final byte STOP_PROGRAM = 0x01;
public static final byte PLAY_SOUND_FILE = 0x02;
public static final byte PLAY_TONE = 0x03;
public static final byte SET_OUTPUT_STATE = 0x04;
public static final byte SET_INPUT_MODE = 0x05;
public static final byte GET_OUTPUT_STATE = 0x06;
public static final byte GET_INPUT_VALUES = 0x07;
public static final byte RESET_SCALED_INPUT_VALUE = 0x08;
public static final byte MESSAGE_WRITE = 0x09;
public static final byte RESET_MOTOR_POSITION = 0x0A;
public static final byte GET_BATTERY_LEVEL = 0x0B;
public static final byte STOP_SOUND_PLAYBACK = 0x0C;
public static final byte KEEP_ALIVE = 0x0D;
public static final byte LS_GET_STATUS = 0x0E;
public static final byte LS_WRITE = 0x0F;
public static final byte LS_READ = 0x10;
public static final byte GET_CURRENT_PROGRAM_NAME = 0x11;
public static final byte MESSAGE_READ = 0x13;
// NXJ additions
public static byte NXJ_DISCONNECT = 0x20;
public static byte NXJ_DEFRAG = 0x21;
// MINDdroidConnector additions
public static final byte SAY_TEXT = 0x30;
public static final byte VIBRATE_PHONE = 0x31;
public static final byte ACTION_BUTTON = 0x32;
// System Commands:
public static final byte OPEN_READ = (byte)0x80;
public static final byte OPEN_WRITE = (byte)0x81;
public static final byte READ = (byte)0x82;
public static final byte WRITE = (byte)0x83;
public static final byte CLOSE = (byte)0x84;
public static final byte DELETE = (byte)0x85;
public static final byte FIND_FIRST = (byte)0x86;
public static final byte FIND_NEXT = (byte)0x87;
public static final byte GET_FIRMWARE_VERSION = (byte)0x88;
public static final byte OPEN_WRITE_LINEAR = (byte)0x89;
public static final byte OPEN_READ_LINEAR = (byte)0x8A;
public static final byte OPEN_WRITE_DATA = (byte)0x8B;
public static final byte OPEN_APPEND_DATA = (byte)0x8C;
public static final byte BOOT = (byte)0x97;
public static final byte SET_BRICK_NAME = (byte)0x98;
public static final byte GET_DEVICE_INFO = (byte)0x9B;
public static final byte DELETE_USER_FLASH = (byte)0xA0;
public static final byte POLL_LENGTH = (byte)0xA1;
public static final byte POLL = (byte)0xA2;
public static final byte NXJ_FIND_FIRST = (byte)0xB6;
public static final byte NXJ_FIND_NEXT = (byte)0xB7;
public static final byte NXJ_PACKET_MODE = (byte)0xff;
// Error codes
public static final byte MAILBOX_EMPTY = (byte)0x40;
public static final byte FILE_NOT_FOUND = (byte)0x86;
public static final byte INSUFFICIENT_MEMORY = (byte) 0xFB;
public static final byte DIRECTORY_FULL = (byte) 0xFC;
public static final byte UNDEFINED_ERROR = (byte) 0x8A;
public static final byte NOT_IMPLEMENTED = (byte) 0xFD;
// Firmware codes
public static byte[] FIRMWARE_VERSION_LEJOSMINDDROID = { 0x6c, 0x4d, 0x49, 0x64 };
public static byte[] getBeepMessage(int frequency, int duration) {
byte[] message = new byte[6];
message[0] = DIRECT_COMMAND_NOREPLY;
message[1] = PLAY_TONE;
// Frequency for the tone, Hz (UWORD); Range: 200-14000 Hz
message[2] = (byte) frequency;
message[3] = (byte) (frequency >> 8);
// Duration of the tone, ms (UWORD)
message[4] = (byte) duration;
message[5] = (byte) (duration >> 8);
return message;
}
public static byte[] getActionMessage(int actionNr) {
byte[] message = new byte[3];
message[0] = DIRECT_COMMAND_NOREPLY;
message[1] = ACTION_BUTTON;
message[2] = (byte) actionNr;
return message;
}
public static byte[] getMotorMessage(int motor, int speed) {
byte[] message = new byte[12];
message[0] = DIRECT_COMMAND_NOREPLY;
message[1] = SET_OUTPUT_STATE;
// Output port
message[2] = (byte) motor;
if (speed == 0) {
message[3] = 0;
message[4] = 0;
message[5] = 0;
message[6] = 0;
message[7] = 0;
} else {
// Power set option (Range: -100 - 100)
message[3] = (byte) speed;
// Mode byte (Bit-field): MOTORON + BREAK
message[4] = 0x03;
// Regulation mode: REGULATION_MODE_MOTOR_SPEED
message[5] = 0x01;
// Turn Ratio (SBYTE; -100 - 100)
message[6] = 0x00;
// RunState: MOTOR_RUN_STATE_RUNNING
message[7] = 0x20;
}
// TachoLimit: run forever
message[8] = 0;
message[9] = 0;
message[10] = 0;
message[11] = 0;
return message;
}
public static byte[] getMotorMessage(int motor, int speed, int end) {
byte[] message = getMotorMessage(motor, speed);
// TachoLimit
message[8] = (byte) end;
message[9] = (byte) (end >> 8);
message[10] = (byte) (end >> 16);
message[11] = (byte) (end >> 24);
return message;
}
public static byte[] getResetMessage(int motor) {
byte[] message = new byte[4];
message[0] = DIRECT_COMMAND_NOREPLY;
message[1] = RESET_MOTOR_POSITION;
// Output port
message[2] = (byte) motor;
// absolute position
message[3] = 0;
return message;
}
public static byte[] getStartProgramMessage(String programName) {
byte[] message = new byte[22];
message[0] = DIRECT_COMMAND_NOREPLY;
message[1] = START_PROGRAM;
// copy programName and end with 0 delimiter
for (int pos=0; pos<programName.length(); pos++)
message[2+pos] = (byte) programName.charAt(pos);
message[programName.length()+2] = 0;
return message;
}
public static byte[] getStopProgramMessage() {
byte[] message = new byte[2];
message[0] = DIRECT_COMMAND_NOREPLY;
message[1] = STOP_PROGRAM;
return message;
}
public static byte[] getProgramNameMessage() {
byte[] message = new byte[2];
message[0] = DIRECT_COMMAND_REPLY;
message[1] = GET_CURRENT_PROGRAM_NAME;
return message;
}
public static byte[] getOutputStateMessage(int motor) {
byte[] message = new byte[3];
message[0] = DIRECT_COMMAND_REPLY;
message[1] = GET_OUTPUT_STATE;
// Output port
message[2] = (byte) motor;
return message;
}
public static byte[] getFirmwareVersionMessage() {
byte[] message = new byte[2];
message[0] = SYSTEM_COMMAND_REPLY;
message[1] = GET_FIRMWARE_VERSION;
return message;
}
public static byte[] getFindFilesMessage(boolean findFirst, int handle, String searchString) {
byte[] message;
if (findFirst)
message = new byte[22];
else
message = new byte[3];
message[0] = SYSTEM_COMMAND_REPLY;
if (findFirst) {
message[1] = FIND_FIRST;
// copy searchString and end with 0 delimiter
for (int pos=0; pos<searchString.length(); pos++)
message[2+pos] = (byte) searchString.charAt(pos);
message[searchString.length()+2] = 0;
} else {
message[1] = FIND_NEXT;
message[2] = (byte) handle;
}
return message;
}
public static byte[] getOpenWriteMessage(String fileName, int fileLength) {
byte[] message = new byte[26];
message[0] = SYSTEM_COMMAND_REPLY;
message[1] = OPEN_WRITE;
// copy programName and end with 0 delimiter
for (int pos=0; pos<fileName.length(); pos++)
message[2+pos] = (byte) fileName.charAt(pos);
message[fileName.length()+2] = 0;
// copy file size
message[22] = (byte) fileLength;
message[23] = (byte) (fileLength >>> 8);
message[24] = (byte) (fileLength >>> 16);
message[25] = (byte) (fileLength >>> 24);
return message;
}
public static byte[] getWriteMessage(int handle, byte[] data, int dataLength) {
byte[] message = new byte[dataLength + 3];
message[0] = SYSTEM_COMMAND_REPLY;
message[1] = WRITE;
// copy handle
message[2] = (byte) handle;
// copy data
System.arraycopy(data, 0, message, 3, dataLength);
return message;
}
public static byte[] getCloseMessage(int handle) {
byte[] message = new byte[3];
message[0] = SYSTEM_COMMAND_REPLY;
message[1] = CLOSE;
// copy handle
message[2] = (byte) handle;
return message;
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.lego.minddroid;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStreamReader;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.SharedPreferences;
/**
* Displays an LAMA ("LEGO Application MINDdroid Agreement") that the user has to accept before
* using the application. Your application should call {@link Lama#show(android.app.Activity)}
* in the onCreate() method of the first activity. If the user accepts the LAMA, it will never
* be shown again. If the user refuses, {@link android.app.Activity#finish()} is invoked
* on your activity.
*/
class Lama {
private static final String ASSET_LAMA = "LAMA";
private static final String PREFERENCE_LAMA_ACCEPTED = "lama.accepted";
private static final String PREFERENCES_LAMA = "lama";
/**
* callback to let the activity know when the user has accepted the LAMA.
*/
static interface OnLamaAgreedTo {
/**
* Called when the user has accepted the lama and the dialog closes.
*/
void onLamaAgreedTo();
}
/**
* Displays the LAMA if necessary. This method should be called from the onCreate()
* method of your main Activity.
*
* @param activity The Activity to finish if the user rejects the LAMA.
* @return Whether the user has agreed already.
*/
static boolean show(final Activity activity) {
final SharedPreferences preferences = activity.getSharedPreferences(PREFERENCES_LAMA,
Activity.MODE_PRIVATE);
if (!preferences.getBoolean(PREFERENCE_LAMA_ACCEPTED, false)) {
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.lama_title);
builder.setCancelable(true);
builder.setPositiveButton(R.string.lama_accept, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
accept(preferences);
if (activity instanceof OnLamaAgreedTo) {
((OnLamaAgreedTo) activity).onLamaAgreedTo();
}
}
});
builder.setNegativeButton(R.string.lama_refuse, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
refuse(activity);
}
});
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
refuse(activity);
}
});
builder.setMessage(readLama(activity));
builder.create().show();
return false;
}
return true;
}
private static void accept(SharedPreferences preferences) {
preferences.edit().putBoolean(PREFERENCE_LAMA_ACCEPTED, true).commit();
}
private static void refuse(Activity activity) {
activity.finish();
}
private static CharSequence readLama(Activity activity) {
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(activity.getAssets().open(ASSET_LAMA)));
String line;
StringBuilder buffer = new StringBuilder();
while ((line = in.readLine()) != null) buffer.append(line).append('\n');
return buffer;
} catch (IOException e) {
return "";
} finally {
closeStream(in);
}
}
/**
* Closes the specified stream.
*
* @param stream The stream to close.
*/
private static void closeStream(Closeable stream) {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
// Ignore
}
}
}
}

View File

@@ -0,0 +1,787 @@
/**
* Copyright 2010, 2011 Guenther Hoelzl, Shawn Brown
*
* This file is part of MINDdroid.
*
* MINDdroid is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* MINDdroid is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* MINDdroid. If not, see <http://www.gnu.org/licenses/>.
*
* Modified by Richard Szabo to integrate OpenCV computer vision library functionality.
* http://opencv.willowgarage.com/wiki/
*
* 2011
**/
package com.lego.minddroid;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
import android.speech.tts.TextToSpeech;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Toast;
/**
* This class is for talking to a LEGO NXT robot and controlling it
* via bluetooth and the built in acceleration sensor.
* The communication to the robot is done via LCP (LEGO communication protocol),
* so no special software has to be installed on the robot.
*/
public class MINDdroidCV extends Activity implements BTConnectable, TextToSpeech.OnInitListener {
public static final int UPDATE_TIME = 200;
public static final int MENU_TOGGLE_CONNECT = Menu.FIRST;
public static final int MENU_QUIT = Menu.FIRST + 1;
private static final int REQUEST_CONNECT_DEVICE = 1000;
private static final int REQUEST_ENABLE_BT = 2000;
private BTCommunicator myBTCommunicator = null;
private boolean connected = false;
private ProgressDialog connectingProgressDialog;
private Handler btcHandler;
private Menu myMenu;
private GameView mView;
private Activity thisActivity;
private boolean btErrorPending = false;
private boolean pairing;
private static boolean btOnByUs = false;
int mRobotType;
int motorLeft;
private int directionLeft; // +/- 1
int motorRight;
private boolean stopAlreadySent = false;
private int directionRight; // +/- 1
private int motorAction;
private int directionAction; // +/- 1
private List<String> programList;
private static final int MAX_PROGRAMS = 20;
private String programToStart;
private Toast reusableToast;
// experimental TTS support
private TextToSpeech mTts;
private final int TTS_CHECK_CODE = 9991;
/**
* Asks if bluetooth was switched on during the runtime of the app. For saving
* battery we switch it off when the app is terminated.
* @return true, when bluetooth was switched on by the app
*/
public static boolean isBtOnByUs() {
return btOnByUs;
}
/**
* Sets a flag when bluetooth was switched on durin runtime
* @param btOnByUs true, when bluetooth was switched on by the app
*/
public static void setBtOnByUs(boolean btOnByUs) {
MINDdroidCV.btOnByUs = btOnByUs;
}
/**
* @return true, when currently pairing
*/
@Override
public boolean isPairing() {
return pairing;
}
/**
* Called when the activity is first created. Inititializes all the
* graphical views.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
thisActivity = this;
mRobotType = this.getIntent().getIntExtra(SplashMenu.MINDDROID_ROBOT_TYPE, R.id.robot_type_1);
setUpByType();
requestWindowFeature(Window.FEATURE_NO_TITLE);
StartSound mySound = new StartSound(this);
mySound.start();
reusableToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
if( mRobotType == R.id.robot_type_5 ) { // opencv usage
showToast("opencv", Toast.LENGTH_SHORT);
setContentView(new SampleView(getApplicationContext(), this));
} else {
showToast("no opencv", Toast.LENGTH_SHORT);
// setup our view, give it focus and display.
mView = new GameView(getApplicationContext(), this);
mView.setFocusable(true);
setContentView(mView);
}
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
// experimental TTS support for the lejosMINDdroid project
mTts = new TextToSpeech(this,
this // TextToSpeech.OnInitListener
);
}
/**
* Initialization of the motor commands for the different robot types.
*/
private void setUpByType() {
switch (mRobotType) {
case R.id.robot_type_2:
motorLeft = BTCommunicator.MOTOR_B;
directionLeft = 1;
motorRight = BTCommunicator.MOTOR_C;
directionRight = 1;
motorAction = BTCommunicator.MOTOR_A;
directionAction = 1;
break;
case R.id.robot_type_3:
motorLeft = BTCommunicator.MOTOR_C;
directionLeft = -1;
motorRight = BTCommunicator.MOTOR_B;
directionRight = -1;
motorAction = BTCommunicator.MOTOR_A;
directionAction = 1;
break;
case R.id.robot_type_5:
motorLeft = BTCommunicator.MOTOR_C;
directionLeft = 1;
motorRight = BTCommunicator.MOTOR_B;
directionRight = 1;
motorAction = BTCommunicator.MOTOR_A;
directionAction = 1;
break;
default:
// default
motorLeft = BTCommunicator.MOTOR_B;
directionLeft = 1;
motorRight = BTCommunicator.MOTOR_C;
directionRight = 1;
motorAction = BTCommunicator.MOTOR_A;
directionAction = 1;
break;
}
}
/**
* Updates the menus and possible buttons when connection status changed.
*/
private void updateButtonsAndMenu() {
if (myMenu == null)
return;
myMenu.removeItem(MENU_TOGGLE_CONNECT);
if (connected) {
myMenu.add(0, MENU_TOGGLE_CONNECT, 1, getResources().getString(R.string.disconnect)).setIcon(R.drawable.ic_menu_connected);
} else {
myMenu.add(0, MENU_TOGGLE_CONNECT, 1, getResources().getString(R.string.connect)).setIcon(R.drawable.ic_menu_connect);
}
}
/**
* Creates a new object for communication to the NXT robot via bluetooth and fetches the corresponding handler.
*/
private void createBTCommunicator() {
// interestingly BT adapter needs to be obtained by the UI thread - so we pass it in in the constructor
myBTCommunicator = new BTCommunicator(this, myHandler, BluetoothAdapter.getDefaultAdapter(), getResources());
btcHandler = myBTCommunicator.getHandler();
}
/**
* Creates and starts the a thread for communication via bluetooth to the NXT robot.
* @param mac_address The MAC address of the NXT robot.
*/
private void startBTCommunicator(String mac_address) {
connected = false;
connectingProgressDialog = ProgressDialog.show(this, "", getResources().getString(R.string.connecting_please_wait), true);
if (myBTCommunicator != null) {
try {
myBTCommunicator.destroyNXTconnection();
}
catch (IOException e) { }
}
createBTCommunicator();
myBTCommunicator.setMACAddress(mac_address);
myBTCommunicator.start();
updateButtonsAndMenu();
}
/**
* Sends a message for disconnecting to the communcation thread.
*/
public void destroyBTCommunicator() {
if (myBTCommunicator != null) {
sendBTCmessage(BTCommunicator.NO_DELAY, BTCommunicator.DISCONNECT, 0, 0);
myBTCommunicator = null;
}
connected = false;
updateButtonsAndMenu();
}
/**
* Gets the current connection status.
* @return the current connection status to the robot.
*/
public boolean isConnected() {
return connected;
}
/**
* Method for performing the appropriate action when the ACTION button is pressed shortly.
*/
public void actionButtonPressed() {
if (myBTCommunicator != null) {
if( mView != null ) {
mView.getThread().mActionPressed = true;
}
// Wolfgang Amadeus Mozart "Zauberfloete - Der Vogelfaenger bin ich ja"
if (mRobotType != R.id.robot_type_4) {
sendBTCmessage(BTCommunicator.NO_DELAY, BTCommunicator.DO_BEEP, 392, 100);
sendBTCmessage(200, BTCommunicator.DO_BEEP, 440, 100);
sendBTCmessage(400, BTCommunicator.DO_BEEP, 494, 100);
sendBTCmessage(600, BTCommunicator.DO_BEEP, 523, 100);
sendBTCmessage(800, BTCommunicator.DO_BEEP, 587, 300);
sendBTCmessage(1200, BTCommunicator.DO_BEEP, 523, 300);
sendBTCmessage(1600, BTCommunicator.DO_BEEP, 494, 300);
}
// MOTOR ACTION: forth an back
switch (mRobotType) {
case R.id.robot_type_3:
// Robogator: bite the user ;-)
for (int bite=0; bite<3; bite++) {
sendBTCmessage(bite*400, motorAction, 75 * directionAction, 0);
sendBTCmessage(bite*400+200, motorAction, -75 * directionAction, 0);
}
sendBTCmessage(3*400, motorAction, 0, 0);
break;
case R.id.robot_type_4:
// lejosMINDdroid: just send the message for button press
sendBTCmessage(BTCommunicator.NO_DELAY, BTCommunicator.DO_ACTION, 0, 0);
break;
case R.id.robot_type_5:
//
sendBTCmessage(BTCommunicator.NO_DELAY, BTCommunicator.DO_ACTION, 0, 0);
break;
default:
// other robots: 180 degrees forth and back
sendBTCmessage(BTCommunicator.NO_DELAY, motorAction, 75 * directionAction, 0);
sendBTCmessage(500, motorAction, -75 * directionAction, 0);
sendBTCmessage(1000, motorAction, 0, 0);
break;
}
}
}
/**
* Method for performing the appropriate action when the ACTION button is long pressed.
*/
public void actionButtonLongPress() {
if (myBTCommunicator != null) {
if( mView != null ) {
mView.getThread().mActionPressed = true;
}
if (programList.size() == 0)
showToast(R.string.no_files_found, Toast.LENGTH_SHORT);
FileDialog myFileDialog = new FileDialog(this, programList);
myFileDialog.show(mRobotType == R.id.robot_type_4);
}
}
/**
* Starts a program on the NXT robot.
* @param name The program name to start. Has to end with .rxe on the LEGO firmware and with .nxj on the
* leJOS NXJ firmware.
*/
public void startProgram(String name) {
// for .rxe programs: get program name, eventually stop this and start the new one delayed
// is handled in startRXEprogram()
if (name.endsWith(".rxe")) {
programToStart = name;
sendBTCmessage(BTCommunicator.NO_DELAY, BTCommunicator.GET_PROGRAM_NAME, 0, 0);
return;
}
// for .nxj programs: stop bluetooth communication after starting the program
if (name.endsWith(".nxj")) {
sendBTCmessage(BTCommunicator.NO_DELAY, BTCommunicator.START_PROGRAM, name);
destroyBTCommunicator();
return;
}
// for all other programs: just start the program
sendBTCmessage(BTCommunicator.NO_DELAY, BTCommunicator.START_PROGRAM, name);
}
/**
* Depending on the status (whether the program runs already) we stop it, wait and restart it again.
* @param status The current status, 0x00 means that the program is already running.
*/
public void startRXEprogram(byte status) {
if (status == 0x00) {
sendBTCmessage(BTCommunicator.NO_DELAY, BTCommunicator.STOP_PROGRAM, 0, 0);
sendBTCmessage(1000, BTCommunicator.START_PROGRAM, programToStart);
}
else {
sendBTCmessage(BTCommunicator.NO_DELAY, BTCommunicator.START_PROGRAM, programToStart);
}
}
/**
* Sends the motor control values to the communcation thread.
* @param left The power of the left motor from 0 to 100.
* @param rigth The power of the right motor from 0 to 100.
*/
public void updateMotorControl(int left, int right) {
if (myBTCommunicator != null) {
// don't send motor stop twice
if ((left == 0) && (right == 0)) {
if (stopAlreadySent)
return;
else
stopAlreadySent = true;
}
else
stopAlreadySent = false;
// send messages via the handler
sendBTCmessage(BTCommunicator.NO_DELAY, motorLeft, left * directionLeft, 0);
sendBTCmessage(BTCommunicator.NO_DELAY, motorRight, right * directionRight, 0);
}
}
/**
* Sends the message via the BTCommuncator to the robot.
* @param delay time to wait before sending the message.
* @param message the message type (as defined in BTCommucator)
* @param value1 first parameter
* @param value2 second parameter
*/
void sendBTCmessage(int delay, int message, int value1, int value2) {
Bundle myBundle = new Bundle();
myBundle.putInt("message", message);
myBundle.putInt("value1", value1);
myBundle.putInt("value2", value2);
Message myMessage = myHandler.obtainMessage();
myMessage.setData(myBundle);
if (delay == 0)
btcHandler.sendMessage(myMessage);
else
btcHandler.sendMessageDelayed(myMessage, delay);
}
/**
* Sends the message via the BTCommuncator to the robot.
* @param delay time to wait before sending the message.
* @param message the message type (as defined in BTCommucator)
* @param String a String parameter
*/
void sendBTCmessage(int delay, int message, String name) {
Bundle myBundle = new Bundle();
myBundle.putInt("message", message);
myBundle.putString("name", name);
Message myMessage = myHandler.obtainMessage();
myMessage.setData(myBundle);
if (delay == 0)
btcHandler.sendMessage(myMessage);
else
btcHandler.sendMessageDelayed(myMessage, delay);
}
@Override
public void onResume() {
super.onResume();
if( mView != null ) {
mView.registerListener();
}
}
@Override
protected void onStart() {
super.onStart();
if (!BluetoothAdapter.getDefaultAdapter().isEnabled()) {
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
} else {
selectNXT();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
destroyBTCommunicator();
}
@Override
public void onPause() {
if( mView != null ) {
mView.unregisterListener();
}
destroyBTCommunicator();
super.onStop();
}
@Override
public void onSaveInstanceState(Bundle icicle) {
super.onSaveInstanceState(icicle);
if( mView != null ) {
mView.unregisterListener();
}
}
/**
* Creates the menu items
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
myMenu = menu;
myMenu.add(0, MENU_TOGGLE_CONNECT, 1, getResources().getString(R.string.connect)).setIcon(R.drawable.ic_menu_connect);
myMenu.add(0, MENU_QUIT, 2, getResources().getString(R.string.quit)).setIcon(R.drawable.ic_menu_exit);
updateButtonsAndMenu();
return true;
}
/**
* Handles item selections
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_TOGGLE_CONNECT:
if (myBTCommunicator == null || connected == false) {
selectNXT();
} else {
destroyBTCommunicator();
updateButtonsAndMenu();
}
return true;
case MENU_QUIT:
destroyBTCommunicator();
finish();
if (btOnByUs)
showToast(R.string.bt_off_message, Toast.LENGTH_SHORT);
SplashMenu.quitApplication();
return true;
}
return false;
}
/**
* Displays a message as a toast
* @param textToShow the message
* @param length the length of the toast to display
*/
private void showToast(String textToShow, int length) {
reusableToast.setText(textToShow);
reusableToast.setDuration(length);
reusableToast.show();
}
/**
* Displays a message as a toast
* @param resID the ressource ID to display
* @param length the length of the toast to display
*/
private void showToast(int resID, int length) {
reusableToast.setText(resID);
reusableToast.setDuration(length);
reusableToast.show();
}
/**
* Receive messages from the BTCommunicator
*/
final Handler myHandler = new Handler() {
@Override
public void handleMessage(Message myMessage) {
switch (myMessage.getData().getInt("message")) {
case BTCommunicator.DISPLAY_TOAST:
showToast(myMessage.getData().getString("toastText"), Toast.LENGTH_SHORT);
break;
case BTCommunicator.STATE_CONNECTED:
connected = true;
programList = new ArrayList<String>();
connectingProgressDialog.dismiss();
updateButtonsAndMenu();
sendBTCmessage(BTCommunicator.NO_DELAY, BTCommunicator.GET_FIRMWARE_VERSION, 0, 0);
break;
case BTCommunicator.MOTOR_STATE:
if (myBTCommunicator != null) {
byte[] motorMessage = myBTCommunicator.getReturnMessage();
int position = byteToInt(motorMessage[21]) + (byteToInt(motorMessage[22]) << 8) + (byteToInt(motorMessage[23]) << 16)
+ (byteToInt(motorMessage[24]) << 24);
showToast(getResources().getString(R.string.current_position) + position, Toast.LENGTH_SHORT);
}
break;
case BTCommunicator.STATE_CONNECTERROR_PAIRING:
connectingProgressDialog.dismiss();
destroyBTCommunicator();
break;
case BTCommunicator.STATE_CONNECTERROR:
connectingProgressDialog.dismiss();
case BTCommunicator.STATE_RECEIVEERROR:
case BTCommunicator.STATE_SENDERROR:
destroyBTCommunicator();
if (btErrorPending == false) {
btErrorPending = true;
// inform the user of the error with an AlertDialog
AlertDialog.Builder builder = new AlertDialog.Builder(thisActivity);
builder.setTitle(getResources().getString(R.string.bt_error_dialog_title))
.setMessage(getResources().getString(R.string.bt_error_dialog_message)).setCancelable(false)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
btErrorPending = false;
dialog.cancel();
selectNXT();
}
});
builder.create().show();
}
break;
case BTCommunicator.FIRMWARE_VERSION:
if (myBTCommunicator != null) {
byte[] firmwareMessage = myBTCommunicator.getReturnMessage();
// check if we know the firmware
boolean isLejosMindDroid = true;
for (int pos=0; pos<4; pos++) {
if (firmwareMessage[pos + 3] != LCPMessage.FIRMWARE_VERSION_LEJOSMINDDROID[pos]) {
isLejosMindDroid = false;
break;
}
}
if (isLejosMindDroid) {
mRobotType = R.id.robot_type_4;
setUpByType();
}
// afterwards we search for all files on the robot
sendBTCmessage(BTCommunicator.NO_DELAY, BTCommunicator.FIND_FILES, 0, 0);
}
break;
case BTCommunicator.FIND_FILES:
if (myBTCommunicator != null) {
byte[] fileMessage = myBTCommunicator.getReturnMessage();
String fileName = new String(fileMessage, 4, 20);
fileName = fileName.replaceAll("\0","");
if (mRobotType == R.id.robot_type_4 || fileName.endsWith(".nxj") || fileName.endsWith(".rxe")) {
programList.add(fileName);
}
// find next entry with appropriate handle,
// limit number of programs (in case of error (endless loop))
if (programList.size() <= MAX_PROGRAMS)
sendBTCmessage(BTCommunicator.NO_DELAY, BTCommunicator.FIND_FILES,
1, byteToInt(fileMessage[3]));
}
break;
case BTCommunicator.PROGRAM_NAME:
if (myBTCommunicator != null) {
byte[] returnMessage = myBTCommunicator.getReturnMessage();
startRXEprogram(returnMessage[2]);
}
break;
case BTCommunicator.SAY_TEXT:
if (myBTCommunicator != null) {
byte[] textMessage = myBTCommunicator.getReturnMessage();
// evaluate control byte
byte controlByte = textMessage[2];
// BIT7: Language
if ((controlByte & 0x80) == 0x00)
mTts.setLanguage(Locale.US);
else
mTts.setLanguage(Locale.getDefault());
// BIT6: Pitch
if ((controlByte & 0x40) == 0x00)
mTts.setPitch(1.0f);
else
mTts.setPitch(0.75f);
// BIT0-3: Speech Rate
switch (controlByte & 0x0f) {
case 0x01:
mTts.setSpeechRate(1.5f);
break;
case 0x02:
mTts.setSpeechRate(0.75f);
break;
default: mTts.setSpeechRate(1.0f);
break;
}
String ttsText = new String(textMessage, 3, 19);
ttsText = ttsText.replaceAll("\0","");
showToast(ttsText, Toast.LENGTH_SHORT);
mTts.speak(ttsText, TextToSpeech.QUEUE_FLUSH, null);
}
break;
case BTCommunicator.VIBRATE_PHONE:
if (myBTCommunicator != null) {
byte[] vibrateMessage = myBTCommunicator.getReturnMessage();
Vibrator myVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
myVibrator.vibrate(vibrateMessage[2]*10);
}
break;
}
}
};
private int byteToInt(byte byteValue) {
int intValue = (byteValue & (byte) 0x7f);
if ((byteValue & (byte) 0x80) != 0)
intValue |= 0x80;
return intValue;
}
void selectNXT() {
Intent serverIntent = new Intent(this, DeviceListActivity.class);
startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CONNECT_DEVICE:
// When DeviceListActivity returns with a device to connect
if (resultCode == Activity.RESULT_OK) {
// Get the device MAC address and start a new bt communicator thread
String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
pairing = data.getExtras().getBoolean(DeviceListActivity.PAIRING);
startBTCommunicator(address);
}
break;
case REQUEST_ENABLE_BT:
// When the request to enable Bluetooth returns
switch (resultCode) {
case Activity.RESULT_OK:
btOnByUs = true;
selectNXT();
break;
case Activity.RESULT_CANCELED:
showToast(R.string.bt_needs_to_be_enabled, Toast.LENGTH_SHORT);
finish();
break;
default:
showToast(R.string.problem_at_connecting, Toast.LENGTH_SHORT);
finish();
break;
}
break;
// will not be called now, since the check intent is not generated
case TTS_CHECK_CODE:
if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
// success, create the TTS instance
mTts = new TextToSpeech(this, this);
}
else {
// missing data, install it
Intent installIntent = new Intent();
installIntent.setAction(
TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
startActivity(installIntent);
}
break;
}
}
/**
* Initializing of the TTS engine.
*/
public void onInit(int status) {
// status can be either TextToSpeech.SUCCESS or TextToSpeech.ERROR.
if (status == TextToSpeech.SUCCESS) {
// Set preferred language to US english.
// Note that a language may not be available, and the result will indicate this.
int result = mTts.setLanguage(Locale.US);
// Try this someday for some interesting results.
if (result == TextToSpeech.LANG_MISSING_DATA ||
result == TextToSpeech.LANG_NOT_SUPPORTED) {
// Language data is missing or the language is not supported.
if (mRobotType == R.id.robot_type_4)
showToast(R.string.tts_language_not_supported, Toast.LENGTH_LONG);
}
} else {
// Initialization failed.
if (mRobotType == R.id.robot_type_4)
showToast(R.string.tts_initialization_failure, Toast.LENGTH_LONG);
}
}
}

View File

@@ -0,0 +1,103 @@
/**
* Copyright 2011 Guenther Hoelzl
*
* This file is part of MINDdroid.
*
* MINDdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MINDdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MINDdroid. If not, see <http://www.gnu.org/licenses/>.
**/
package com.lego.minddroid;
import java.io.File;
import java.util.ArrayList;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
/**
* Builds and shows a file dialog for selecting nxj-files
* @return number of nxj-files in the directory
*/
class NXJFileDialog {
private final static String DIR_PATH = "/sdcard/download/";
private final static String EXTENSION = ".nxj";
private Activity mActivity;
private DialogListener mDialogListener;
private String[] programs;
private int preinstalledFiles;
public NXJFileDialog(Activity activity, DialogListener dialogListener) {
mActivity = activity;
mDialogListener = dialogListener;
}
/**
* Refreshes the file list of the directory in DIR_PATH and builds
* an array.
* @param preinstalledList list of preinstalled nxj-files
* @return number of nxj-files in the directory
*/
public int refreshFileList(String[] preinstalledList) {
ArrayList<String> nxjPrograms = new ArrayList();
// internal nxj-files
preinstalledFiles = preinstalledList.length;
for (int index = 0; index < preinstalledFiles; index++)
nxjPrograms.add(preinstalledList[index]);
// external nxj-files
File file = new File(DIR_PATH);
if (file != null) {
File[] files = file.listFiles();
if (files != null) {
for (int fileNr = 0; fileNr < files.length; fileNr++) {
if (files[fileNr].getName().toLowerCase().endsWith(EXTENSION))
nxjPrograms.add(files[fileNr].getName());
}
}
}
programs = new String[nxjPrograms.size()];
programs = nxjPrograms.toArray(programs);
return programs.length;
}
/**
* Shows the dialog
*/
public void show() {
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
builder.setTitle(mActivity.getResources().getString(R.string.nxj_file_dialog_title));
builder.setItems(programs, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
informDialogListener(item, programs[item]);
}
});
builder.create().show();
}
/**
* Informs the calling activity about the new selected file name
* @param item nr in the list of the selected item
* @param fileName the name of the file
*/
public void informDialogListener(int item, String fileName) {
if (item >= preinstalledFiles)
fileName = DIR_PATH.concat(fileName);
mDialogListener.dialogUpdate(fileName);
}
}

View File

@@ -0,0 +1,399 @@
/**
* Copyright 2011 Guenther Hoelzl
*
* This file is part of MINDdroid.
*
* MINDdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MINDdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MINDdroid. If not, see <http://www.gnu.org/licenses/>.
**/
package com.lego.minddroid;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.bluetooth.BluetoothAdapter;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
/**
* This class is for uploading programs to the NXT brick via Bluetooth.
* Special programs will be able to communicate with MINDdroid, so no PC
* is required for playing with a robot.
*/
public class NXJUploader extends Activity implements UploadThreadListener, DialogListener, BTConnectable
{
private static final int DIALOG_NXT = 0;
private static final int DIALOG_FILE = 1;
// preinstalled modules on res/raw/directoy
private static String[] preinstalledNXJstring = new String[]
{ "AlphaRex.nxj",
"MINDGameZ.nxj"
};
private static final int REQUEST_CONNECT_DEVICE = 1000;
private static final int REQUEST_ENABLE_BT = 2000;
private BTCommunicator mNXT;
private UploadThread uploadThread;
private Handler handler;
private ProgressDialog progressDialog;
private int uploadStatus;
private int runningDialog;
private boolean pairing;
private boolean btErrorPending = false;
private static boolean btOnByUs = false;
/**
* Called when the activity is first created.
*/
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.nxjuploader);
initLayout();
// Create objects for communication
mNXT = new BTCommunicator(this, null, BluetoothAdapter.getDefaultAdapter(), getResources());
handler = new Handler();
// Create and launch the upload thread
uploadThread = new UploadThread(this, getResources());
uploadThread.setBluetoothCommunicator(mNXT);
uploadThread.start();
}
@Override
protected void onStart() {
super.onStart();
if (!BluetoothAdapter.getDefaultAdapter().isEnabled()) {
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
}
}
/**
* Called when the activity is destroyed.
*/
@Override
protected void onDestroy() {
super.onDestroy();
// request the uploadthread to stop
uploadThread.requestStop();
}
/**
* Asks if bluetooth was switched on during the runtime of the app. For saving
* battery we switch it off when the app is terminated.
* @return true, when bluetooth was switched on by the app
*/
public static boolean isBtOnByUs() {
return btOnByUs;
}
/**
* Sets a flag when bluetooth was switched on durin runtime
* @param btOnByUs true, when bluetooth was switched on by the app
*/
public static void setBtOnByUs(boolean btOnByUs) {
NXJUploader.btOnByUs = btOnByUs;
}
/**
* @return true, when currently pairing
*/
@Override
public boolean isPairing() {
return pairing;
}
/**
* Displays a message as a toast
*/
public void showToast(String message) {
Toast toast = Toast.makeText(this, message, Toast.LENGTH_SHORT);
toast.show();
return;
}
/**
* Displays a message from resID as a toast
*/
public void showToast(int resID) {
Toast toast = Toast.makeText(this, resID, Toast.LENGTH_SHORT);
toast.show();
return;
}
/**
* Displays resp. updates a progress dialog
*/
public void showProgressDialog(String message, int maxProgress, int currentProgress) {
boolean initialized = false;
if (progressDialog == null) {
initialized = true;
progressDialog = new ProgressDialog(this);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setCancelable(false);
progressDialog.setMessage(message);
}
progressDialog.setMax(maxProgress);
progressDialog.setProgress(currentProgress);
if (initialized)
progressDialog.show();
}
/**
* Displays resp. updates a progress dialog
*/
public void showProgressDialog(String message) {
if (progressDialog == null) {
progressDialog = new ProgressDialog(this);
progressDialog.setCancelable(false);
progressDialog.setMessage(message);
progressDialog.show();
}
}
/**
* Dismisses an existing progress dialog
*/
public void dismissProgressDialog() {
if (progressDialog != null) {
progressDialog.dismiss();
progressDialog = null;
}
}
/**
* Initializes the values on the main screen
*/
private void initLayout() {
initNXTButton();
initFileButton();
initUploadButton();
}
/**
* Initializes the "SELECT NXT" button
*/
private void initNXTButton() {
Button fileButton = (Button) findViewById(R.id.nxt_button);
fileButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
selectNXT();
}
});
}
/**
* Initializes the "SELECT FILE" button
*/
private void initFileButton() {
Button fileButton = (Button) findViewById(R.id.file_button);
fileButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
showFileDialog();
}
});
}
/**
* Starts the NXT selection activity
*/
private void selectNXT() {
Intent serverIntent = new Intent(this, DeviceListActivity.class);
startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
}
/**
* Shows the file dialog
*/
private void showFileDialog() {
NXJFileDialog fileDialog = new NXJFileDialog(this, this);
if (fileDialog.refreshFileList(preinstalledNXJstring) == 0)
showToast(R.string.nxj_no_files);
else {
runningDialog = DIALOG_FILE;
fileDialog.show();
}
}
/*
* This is the method for returning values of dialogs
* @param the selected text
*/
@Override
public void dialogUpdate(String text) {
TextView textView;
switch (runningDialog) {
case DIALOG_NXT:
textView = (TextView) findViewById(R.id.nxt_name);
textView.setText(text);
break;
case DIALOG_FILE:
textView = (TextView) findViewById(R.id.nxj_file_name);
textView.setText(text);
break;
}
}
/**
* Initializes the "UPLOAD" button
*/
private void initUploadButton() {
Button uploadButton = (Button) findViewById(R.id.upload_button);
uploadButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
TextView nxtTextView = (TextView) findViewById(R.id.nxt_name);
String macAddress = nxtTextView.getText().toString();
if (macAddress.compareTo("") == 0) {
showToast(R.string.nxj_please_select_nxt);
return;
}
macAddress = macAddress.substring(macAddress.lastIndexOf('-')+1);
TextView nxjTextView = (TextView) findViewById(R.id.nxj_file_name);
String fileName = nxjTextView.getText().toString();
if (fileName.compareTo("") == 0) {
showToast(R.string.nxj_please_select_file);
return;
}
uploadThread.enqueueUpload(macAddress, fileName);
}
});
}
/**
* This will be called by the UploadThread to signal an update of the
* current status.
* @param status The current state of the UploadThread
*/
@Override
public void handleUploadThreadUpdate(final int status) {
handler.post(new Runnable() {
@Override
public void run() {
if (status != uploadStatus) {
dismissProgressDialog();
uploadStatus = status;
}
showUploadStatus();
}
});
}
/**
* Shows the current status of the uploader either in
* a progress bar or in toast in case of an error.
*/
private void showUploadStatus() {
switch (uploadStatus) {
case UploadThread.CONNECTING:
showProgressDialog(getResources().getString(R.string.nxj_connecting));
break;
case UploadThread.UPLOADING:
showProgressDialog(getResources().getString(R.string.nxj_uploading),
uploadThread.getFileLength(),
uploadThread.getBytesUploaded());
break;
default:
dismissProgressDialog();
}
switch (uploadThread.getErrorCode()) {
case UploadThread.NO_ERROR:
break;
case UploadThread.OPEN_BT_ERROR:
if (pairing)
showToast(R.string.nxj_bluetooth_pairing);
else
showBTErrorDialog();
break;
case UploadThread.CLOSE_BT_ERROR:
showBTErrorDialog();
break;
case UploadThread.OPEN_FILE_ERROR:
showToast(R.string.nxj_file_open_error);
break;
case UploadThread.UPLOAD_ERROR:
showBTErrorDialog();
break;
default:
showToast(R.string.nxj_other_error);
}
uploadThread.resetErrorCode();
}
/**
* Shows an error dialog when there's an error regarding
* bluettooth transfer.
*/
private void showBTErrorDialog() {
if (btErrorPending == false) {
btErrorPending = true;
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getResources().getString(R.string.bt_error_dialog_title))
.setMessage(getResources().getString(R.string.bt_error_dialog_message)).setCancelable(false)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
btErrorPending = false;
dialog.cancel();
}
});
builder.create().show();
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CONNECT_DEVICE:
// When DeviceListActivity returns with a device to connect
if (resultCode == Activity.RESULT_OK) {
// Get the device infos
String infos = data.getExtras().getString(DeviceListActivity.DEVICE_NAME_AND_ADDRESS);
pairing = data.getExtras().getBoolean(DeviceListActivity.PAIRING);
TextView textView = (TextView) findViewById(R.id.nxt_name);
textView.setText(infos);
}
break;
case REQUEST_ENABLE_BT:
// When the request to enable Bluetooth returns
switch (resultCode) {
case Activity.RESULT_OK:
btOnByUs = true;
break;
case Activity.RESULT_CANCELED:
showToast(R.string.bt_needs_to_be_enabled);
finish();
break;
}
break;
}
}
}

View File

@@ -0,0 +1,97 @@
/**
* Copyright 2010 Guenther Hoelzl, Shawn Brown
*
* This file is part of MINDdroid.
*
* MINDdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MINDdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MINDdroid. If not, see <http://www.gnu.org/licenses/>.
**/
package com.lego.minddroid;
import android.app.Activity;
import android.app.Dialog;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.RadioButton;
import android.widget.Toast;
public class Options {
private Dialog mDialog;
String mSelectionMessage;
SplashMenu splashMenu;
public Options(Activity myActivity) {
this.splashMenu=(SplashMenu) myActivity;
mDialog = new Dialog(myActivity);
mDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
mDialog.setContentView(R.layout.options);
mSelectionMessage = myActivity.getString(R.string.model_type_selected);
final RadioButton robot_type_1 = (RadioButton) mDialog.findViewById(R.id.robot_type_1);
final RadioButton robot_type_2 = (RadioButton) mDialog.findViewById(R.id.robot_type_2);
final RadioButton robot_type_3 = (RadioButton) mDialog.findViewById(R.id.robot_type_3);
final RadioButton robot_type_4 = (RadioButton) mDialog.findViewById(R.id.robot_type_4);
final RadioButton robot_type_5 = (RadioButton) mDialog.findViewById(R.id.robot_type_5);
switch (splashMenu.getRobotType()) {
case R.id.robot_type_2:
robot_type_2.setChecked(true);
break;
case R.id.robot_type_3:
robot_type_3.setChecked(true);
break;
case R.id.robot_type_4:
robot_type_4.setChecked(true);
break;
case R.id.robot_type_5:
robot_type_5.setChecked(true);
Toast toast = Toast.makeText(myActivity, "version 90", Toast.LENGTH_SHORT);
toast.show();
break;
default:
robot_type_1.setChecked(true);
break;
}
robot_type_1.setOnClickListener(radio_listener);
robot_type_2.setOnClickListener(radio_listener);
robot_type_3.setOnClickListener(radio_listener);
robot_type_4.setOnClickListener(radio_listener);
robot_type_5.setOnClickListener(radio_listener);
}
public void show() {
mDialog.show();
}
private OnClickListener radio_listener = new OnClickListener() {
@Override
public void onClick(View v) {
// Perform action on clicks
RadioButton rb = (RadioButton) v;
rb.setChecked(true);
splashMenu.setRobotType(rb.getId());
Toast.makeText(mDialog.getContext(), mSelectionMessage + " " + rb.getText(), Toast.LENGTH_SHORT).show();
mDialog.dismiss();
}
};
}

View File

@@ -0,0 +1,29 @@
package com.lego.minddroid;
import android.content.Context;
import android.graphics.Bitmap;
class SampleView extends SampleViewBase {
public SampleView(Context context, MINDdroidCV uiActivity) {
super(context,uiActivity);
}
@Override
protected Bitmap processFrame(byte[] data) {
int frameSize = getFrameWidth() * getFrameHeight();
int[] rgba = new int[frameSize];
FindLight(getFrameWidth(), getFrameHeight(), data, rgba,buffer);
Bitmap bmp = Bitmap.createBitmap(getFrameWidth(), getFrameHeight(), Bitmap.Config.ARGB_8888);
bmp.setPixels(rgba, 0/* offset */, getFrameWidth() /* stride */, 0, 0, getFrameWidth(), getFrameHeight());
return bmp;
}
public native void FindLight(int width, int height, byte yuv[], int[] rgba,double[] array);
static {
System.loadLibrary("mixed_sample");
}
}

View File

@@ -0,0 +1,182 @@
package com.lego.minddroid;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.List;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public abstract class SampleViewBase extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private static final String TAG = "Sample::SurfaceView";
private Camera mCamera;
private SurfaceHolder mHolder;
private int mFrameWidth;
private int mFrameHeight;
private byte[] mFrame;
private boolean mThreadRun;
protected MINDdroidCV mActivity;
protected double[]buffer;
protected int left, right;
public SampleViewBase(Context context, MINDdroidCV uiActivity) {
super(context);
mActivity = uiActivity;
mHolder = getHolder();
mHolder.addCallback(this);
buffer = new double[10];
Log.i(TAG, "Instantiated new " + this.getClass());
}
public int getFrameWidth() {
return mFrameWidth;
}
public int getFrameHeight() {
return mFrameHeight;
}
public void surfaceChanged(SurfaceHolder _holder, int format, int width, int height) {
Log.i(TAG, "surfaceCreated");
if (mCamera != null) {
Camera.Parameters params = mCamera.getParameters();
List<Camera.Size> sizes = params.getSupportedPreviewSizes();
mFrameWidth = width;
mFrameHeight = height;
// selecting optimal camera preview size
{
double minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - height) < minDiff) {
mFrameWidth = size.width;
mFrameHeight = size.height;
minDiff = Math.abs(size.height - height);
}
}
}
params.setPreviewSize(getFrameWidth(), getFrameHeight());
mCamera.setParameters(params);
//mCamera.setDisplayOrientation(180);
try {
mCamera.setPreviewDisplay(null);
} catch (IOException e) {
Log.e(TAG, "mCamera.setPreviewDisplay fails: " + e);
}
mCamera.startPreview();
}
}
public void surfaceCreated(SurfaceHolder holder) {
Log.i(TAG, "surfaceCreated");
mCamera = Camera.open();
mCamera.setPreviewCallback(new PreviewCallback() {
public void onPreviewFrame(byte[] data, Camera camera) {
synchronized (SampleViewBase.this) {
mFrame = data;
SampleViewBase.this.notify();
}
}
});
(new Thread(this)).start();
}
public void surfaceDestroyed(SurfaceHolder holder) {
Log.i(TAG, "surfaceDestroyed");
mThreadRun = false;
if (mCamera != null) {
synchronized (this) {
mCamera.stopPreview();
mCamera.setPreviewCallback(null);
mCamera.release();
mCamera = null;
}
}
}
protected abstract Bitmap processFrame(byte[] data);
public void run() {
mThreadRun = true;
Log.i(TAG, "Starting processing thread");
while (mThreadRun) {
Bitmap bmp = null;
synchronized (this) {
try {
this.wait();
bmp = processFrame(mFrame);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (bmp != null) {
Canvas canvas = mHolder.lockCanvas();
calculateMove();
if (canvas != null) {
canvas.drawBitmap(bmp,0.0f,0.0f,null); //(canvas.getWidth() - getFrameWidth()) / 2, (canvas.getHeight() - getFrameHeight()) / 2, null);
//drawText(canvas,buffer);
mHolder.unlockCanvasAndPost(canvas);
}
bmp.recycle();
}
}
}
void drawText(Canvas canvas,double[] buffer) {
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE);
DecimalFormat twoPlaces = new DecimalFormat("0.00");
String todraw = ":" + twoPlaces.format(buffer[0]) + ":" +
twoPlaces.format(buffer[1]) + ":" +
twoPlaces.format(buffer[2]) + ":" +
left + ":" + right + ":";
Paint bpaint = new Paint();
bpaint.setStyle(Paint.Style.FILL);
bpaint.setColor(Color.BLUE);
Rect rect = new Rect(0,0,250,50);
canvas.drawRect(rect , bpaint);
canvas.drawText(todraw, 0, todraw.length(), 10.0f, 10.0f, paint);
}
void calculateMove() {
// buffer[1] holds the light direction info if the phone is in landscape format
// small values -> turn left
// large values -> turn right
// in portrait mode buffer[2] should be used
// large values -> turn right
// small values -> turn left
if( buffer[0] > 100 ) { // light is visible
int forwardSpeed = 50;
double upScale = 40;
//double direction = (buffer[1] - getFrameWidth()/2)/getFrameWidth();
double direction = -1.0 * (buffer[2] - getFrameHeight()/2)/getFrameHeight();
left = (int)(upScale * direction) + forwardSpeed;
right = (int)(-1.0 * upScale * direction) + forwardSpeed;
} else {
left = 0;
right = 0;
}
left = Math.min(Math.max(left,0),100);
right = Math.min(Math.max(right,0),100);
mActivity.updateMotorControl(left,right);
}
}

View File

@@ -0,0 +1,160 @@
/**
* Copyright 2010 Guenther Hoelzl, Shawn Brown
*
* This file is part of MINDdroid.
*
* MINDdroid is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* MINDdroid is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* MINDdroid. If not, see <http://www.gnu.org/licenses/>.
**/
package com.lego.minddroid;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
public class SplashMenu extends Activity {
public static final int MENU_OPTIONS = Menu.FIRST;
public static final int MENU_UPLOAD = Menu.FIRST + 1;
public static final int MENU_ABOUT = Menu.FIRST + 2;
public static final int MENU_QUIT = Menu.FIRST + 3;
public static final String MINDDROID_PREFS = "Mprefs";
public static final String MINDDROID_ROBOT_TYPE = "MrobotType";
private int mRobotType;
public static void quitApplication() {
if (MINDdroidCV.isBtOnByUs() || NXJUploader.isBtOnByUs()) {
BluetoothAdapter.getDefaultAdapter().disable();
MINDdroidCV.setBtOnByUs(false);
NXJUploader.setBtOnByUs(false);
}
splashMenu.finish();
}
private View splashMenuView;
private static Activity splashMenu;
@Override
protected void onCreate(Bundle savedInstanceState) {
// show Lama and write nxj-files to SD-card
Lama.show(this);
super.onCreate(savedInstanceState);
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
mRobotType=lookupRobotType();
splashMenuView = new SplashMenuView(getApplicationContext(), this);
setContentView(splashMenuView);
splashMenu = this;
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
protected void onPause() {
if (MINDdroidCV.isBtOnByUs() || NXJUploader.isBtOnByUs()) {
BluetoothAdapter.getDefaultAdapter().disable();
MINDdroidCV.setBtOnByUs(false);
NXJUploader.setBtOnByUs(false);
}
super.onPause();
}
@Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
}
/**
* Creates the menu items
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(0, MENU_OPTIONS, 1, getResources().getString(R.string.options)).setIcon(R.drawable.ic_menu_preferences);
menu.add(0, MENU_UPLOAD, 2, getResources().getString(R.string.upload)).setIcon(R.drawable.ic_menu_nxj);
menu.add(0, MENU_ABOUT, 3, getResources().getString(R.string.about)).setIcon(R.drawable.ic_menu_about);
menu.add(0, MENU_QUIT, 4, getResources().getString(R.string.quit)).setIcon(R.drawable.ic_menu_exit);
return true;
}
/**
* Enables/disables the menu items
*/
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
boolean displayMenu;
displayMenu = super.onPrepareOptionsMenu(menu);
if (displayMenu) {
menu.getItem(1).setEnabled(getRobotType() == R.id.robot_type_4);
}
return displayMenu;
}
/**
* Handles item selections
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_OPTIONS:
Options options = new Options(this);
options.show();
return true;
case MENU_UPLOAD:
Intent nxjUpload = new Intent(this.getBaseContext(), NXJUploader.class);
this.startActivity(nxjUpload);
return true;
case MENU_ABOUT:
About about = new About();
about.show(this);
return true;
case MENU_QUIT:
finish();
return true;
}
return false;
}
public void setRobotType(int mRobotType) {
SharedPreferences mUserPreferences = getSharedPreferences(MINDDROID_PREFS, Context.MODE_PRIVATE);
Editor mPrefsEditor = mUserPreferences.edit();
mPrefsEditor.putInt(MINDDROID_ROBOT_TYPE, mRobotType);
mPrefsEditor.commit();
this.mRobotType = mRobotType;
}
public int lookupRobotType() {
SharedPreferences mUserPreferences;
mUserPreferences = getSharedPreferences(MINDDROID_PREFS, Context.MODE_PRIVATE);
return mUserPreferences.getInt(MINDDROID_ROBOT_TYPE, R.id.robot_type_1);
}
public int getRobotType() {
return mRobotType;
}
}

View File

@@ -0,0 +1,143 @@
/**
* Copyright 2010 Guenther Hoelzl, Shawn Brown
*
* This file is part of MINDdroid.
*
* MINDdroid is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* MINDdroid is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* MINDdroid. If not, see <http://www.gnu.org/licenses/>.
**/
package com.lego.minddroid;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.view.View;
public class SplashMenuView extends View {
int mScreenWidth;
int mScreenHeight;
int startButtonYStart;
int tutorialButtonYStart;
Activity splashMenuActivity;
Resources res;
Bitmap ic_splash_tutorial;
Bitmap ic_splash_start;
Bitmap ic_splash_legal;
Bitmap logo_splash_minddroid;
Bitmap mBackgroundImage;
public SplashMenuView(Context context, Activity splashMenuActivity) {
super(context);
this.splashMenuActivity = splashMenuActivity;
res = context.getResources();
}
private int calcImgHeight(float originalImageHeight, float originalImageWidth) {
float screenWidth = mScreenWidth;
return (int) (originalImageHeight * (screenWidth / originalImageWidth));
}
private float calcStartPos() {
float remainingSpace;
remainingSpace = mScreenHeight - logo_splash_minddroid.getHeight() - ic_splash_legal.getHeight() - ic_splash_start.getHeight()
- ic_splash_tutorial.getHeight();
float divider = remainingSpace / 5;
startButtonYStart = (int) getStartButtonYPos(divider);
return getStartButtonYPos(divider);
}
private float calcTutorialPos() {
float remainingSpace;
remainingSpace = mScreenHeight - logo_splash_minddroid.getHeight() - ic_splash_legal.getHeight() - ic_splash_start.getHeight()
- ic_splash_tutorial.getHeight();
float divider = remainingSpace / 5;
tutorialButtonYStart = (int) getTutorialButtonYPos(divider);
return getTutorialButtonYPos(divider);
}
public float getStartButtonYPos(float divider) {
return (logo_splash_minddroid.getHeight() + ic_splash_start.getHeight() + (divider * 3));
}
public float getTutorialButtonYPos(float divider) {
return (logo_splash_minddroid.getHeight() + (divider * 2));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(mBackgroundImage, 0, 0, null);
canvas.drawBitmap(logo_splash_minddroid, 0, 0, null);
canvas.drawBitmap(ic_splash_start, 0, calcStartPos(), null);
canvas.drawBitmap(ic_splash_tutorial, 0, calcTutorialPos(), null);
canvas.drawBitmap(ic_splash_legal, 0, mScreenHeight - ic_splash_legal.getHeight(), null);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mScreenHeight = h;
mScreenWidth = w;
setupBitmaps();
invalidate();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (event.getY() > tutorialButtonYStart && event.getY() <= tutorialButtonYStart + ic_splash_tutorial.getHeight()) {
Tutorial tutorial = new Tutorial(mScreenWidth, mScreenWidth);
tutorial.show(splashMenuActivity);
} else if (event.getY() > startButtonYStart && event.getY() <= startButtonYStart + ic_splash_start.getHeight()) {
Intent playGame = new Intent(splashMenuActivity.getBaseContext(), MINDdroidCV.class);
playGame.putExtra(SplashMenu.MINDDROID_ROBOT_TYPE, ((SplashMenu)splashMenuActivity).getRobotType());
splashMenuActivity.startActivity(playGame);
}
}
return true;
}
private void setupBitmaps() {
ic_splash_tutorial = BitmapFactory.decodeResource(res, R.drawable.ic_splash_tutorial);
ic_splash_tutorial = Bitmap.createScaledBitmap(ic_splash_tutorial, mScreenWidth,
calcImgHeight(ic_splash_tutorial.getHeight(), ic_splash_tutorial.getWidth()), true);
ic_splash_start = BitmapFactory.decodeResource(res, R.drawable.ic_splash_start);
ic_splash_start = Bitmap.createScaledBitmap(ic_splash_start, mScreenWidth,
calcImgHeight(ic_splash_start.getHeight(), ic_splash_start.getWidth()), true);
ic_splash_legal = BitmapFactory.decodeResource(res, R.drawable.ic_splash_legal);
ic_splash_legal = Bitmap.createScaledBitmap(ic_splash_legal, mScreenWidth,
calcImgHeight(ic_splash_legal.getHeight(), ic_splash_legal.getWidth()), true);
logo_splash_minddroid = BitmapFactory.decodeResource(res, R.drawable.logo_splash_minddroid);
logo_splash_minddroid = Bitmap.createScaledBitmap(logo_splash_minddroid, mScreenWidth,
calcImgHeight(logo_splash_minddroid.getHeight(), logo_splash_minddroid.getWidth()), true);
mBackgroundImage = BitmapFactory.decodeResource(res, R.drawable.background_1);
mBackgroundImage = Bitmap.createScaledBitmap(mBackgroundImage, mScreenWidth, mScreenHeight, true);
}
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright 2010 Guenther Hoelzl, Shawn Brown
*
* This file is part of MINDdroid.
*
* MINDdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MINDdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MINDdroid. If not, see <http://www.gnu.org/licenses/>.
**/
package com.lego.minddroid;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
public class StartSound extends Thread {
private Context myContext;
AudioManager myAudioManager;
public StartSound(Context myContext) {
this.myContext = myContext;
myAudioManager = (AudioManager) myContext.getSystemService(Context.AUDIO_SERVICE);
}
@Override
public void run() {
if (myAudioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL) {
int ringVolume = myAudioManager.getStreamVolume(AudioManager.STREAM_RING);
MediaPlayer myMediaPlayer = MediaPlayer.create(myContext, R.raw.startdroid);
myMediaPlayer.start();
myMediaPlayer.setVolume( ((float) ringVolume)/10f, ((float) ringVolume)/10f);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
myMediaPlayer.stop();
}
}
}

View File

@@ -0,0 +1,123 @@
/**
* Copyright 2010 Guenther Hoelzl, Shawn Brown
*
* This file is part of MINDdroid.
*
* MINDdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MINDdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MINDdroid. If not, see <http://www.gnu.org/licenses/>.
**/
package com.lego.minddroid;
import android.app.Activity;
import android.app.Dialog;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
class Tutorial {
private Dialog dialog;
private int currentScene = 0;
private Activity myActivity;
private int myScreenWidth;
private int myScreenHeight;
// the following array holds the scenes, each scene consists of the following properties
// layout, text/image#1, text/image#2, text/image#3
private int[] sceneProperties = new int[] {
R.layout.tutorial_01, R.string.tutorial_welcome_droid, 0, 0,
R.layout.tutorial_02, R.drawable.tutorial_01, R.string.tutorial_bubble_01, 0,
R.layout.tutorial_03, R.string.tutorial_a, 0, 0,
R.layout.tutorial_02, R.drawable.tutorial_02, R.string.tutorial_bubble_02, 0,
R.layout.tutorial_04, 0, 0, 0,
R.layout.tutorial_02, R.drawable.tutorial_03, R.string.tutorial_bubble_03, 0,
R.layout.tutorial_03, R.string.tutorial_e, 0, 0,
R.layout.tutorial_02, R.drawable.tutorial_04, R.string.tutorial_bubble_04, 0,
R.layout.tutorial_03, R.string.tutorial_f, 0, 0,
R.layout.tutorial_02, R.drawable.tutorial_05, R.string.tutorial_bubble_05, 0,
};
public Tutorial(int screenWidth, int screenHeight) {
myScreenWidth = screenWidth;
myScreenHeight = screenHeight;
}
public void show(final Activity myActivity) {
this.myActivity = myActivity;
dialog = new Dialog(myActivity);
dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
setNewContent();
dialog.show();
}
public void setNewContent() {
// end of the show
if (currentScene >= (sceneProperties.length / 4)) {
dialog.dismiss();
return;
}
TextView myTextView;
ImageView myImageView;
int layout = sceneProperties[currentScene*4];
int resource0 = sceneProperties[currentScene*4+1];
int resource1 = sceneProperties[currentScene*4+2];
int resource2 = sceneProperties[currentScene*4+3];
// rehide the current dialog shortly
if (dialog.isShowing())
dialog.dismiss();
dialog.setContentView(layout);
switch (layout) {
case R.layout.tutorial_01:
case R.layout.tutorial_03:
myTextView = (TextView) dialog.findViewById(R.id.TutorialTextView);
myTextView.setText(myActivity.getResources().getString(resource0));
case R.layout.tutorial_04:
Button buttonOK = (Button) dialog.findViewById(R.id.nextButton);
buttonOK.setOnClickListener(new OnClickListener() {
public void onClick(View v)
{
currentScene++;
setNewContent();
}
});
break;
case R.layout.tutorial_02:
myImageView = (ImageView) dialog.findViewById(R.id.TutorialImageView);
myImageView.setImageResource(resource0);
myImageView.setOnClickListener(new OnClickListener() {
public void onClick(View v)
{
currentScene++;
setNewContent();
}
});
myTextView = (TextView) dialog.findViewById(R.id.TutorialTextView);
myTextView.setTextSize(20);
myTextView.setText(myActivity.getResources().getString(resource1));
break;
}
dialog.show();
}
}

View File

@@ -0,0 +1,254 @@
/**
* Copyright 2011 Guenther Hoelzl
*
* This file is part of MINDdroid.
*
* MINDdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MINDdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MINDdroid. If not, see <http://www.gnu.org/licenses/>.
**/
package com.lego.minddroid;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Looper;
/**
* The tasks have to be done in this thread, so the user interface
* isn't blocked.
*/
public final class UploadThread extends Thread {
// errorcodes during the task
public static final int NO_ERROR = 0;
public static final int OPEN_BT_ERROR = 1;
public static final int CLOSE_BT_ERROR = 2;
public static final int OPEN_FILE_ERROR = 3;
public static final int UPLOAD_ERROR = 4;
// status of the thread
public static final int IDLE = 0;
public static final int CONNECTING = 1;
public static final int UPLOADING = 2;
private static final int MAX_BUFFER_SIZE = 58;
private Handler handler;
private UploadThreadListener listener;
private Resources resources;
private BTCommunicator mBTCommunicator;
private int mFileLength;
private int mUploaded;
private int errorCode;
public UploadThread(UploadThreadListener listener, Resources resources) {
this.listener = listener;
this.resources = resources;
}
public void setBluetoothCommunicator(BTCommunicator communicator) {
mBTCommunicator = communicator;
}
/**
* Prepares and starts the looper of the thread
*/
@Override
public void run() {
try {
Looper.prepare();
handler = new Handler();
Looper.loop();
} catch (Throwable t) {
}
}
/**
* Puts a new request for stopping the looper into the queue
*/
public synchronized void requestStop() {
handler.post(new Runnable() {
@Override
public void run() {
Looper.myLooper().quit();
}
});
}
/**
* Puts a new request for uploading into the queue handled by the looper
* @param fileName the name of the file to upload including the path
*/
public synchronized void enqueueUpload(final String nxtAddress, final String fileName) {
handler.post(new Runnable() {
@Override
public void run() {
boolean uploading = false;
try {
signalUpdate(CONNECTING);
mBTCommunicator.setMACAddress(nxtAddress);
mBTCommunicator.createNXTconnection();
signalUpdate(UPLOADING);
uploading = true;
uploadFile(fileName);
signalUpdate(IDLE);
} catch (FileNotFoundException e) {
errorCode = OPEN_FILE_ERROR;
} catch (IOException e) {
errorCode = uploading ? UPLOAD_ERROR : OPEN_BT_ERROR;
} finally {
try {
mBTCommunicator.destroyNXTconnection();
} catch (IOException e) {
errorCode = CLOSE_BT_ERROR;
}
signalUpdate(IDLE);
}
}
});
}
/**
* @return the maximum number of bytes to be uploaded
*/
public int getFileLength() {
return mFileLength;
}
/**
* @return the number of bytes already uploaded
*/
public int getBytesUploaded() {
return mUploaded;
}
/**
* @return the error after an action
*/
public int getErrorCode() {
return errorCode;
}
/**
* Resets the error status
*/
public void resetErrorCode() {
errorCode = NO_ERROR;
}
/**
* Opens a file with the given filename and uplodads it to the robot
* @param fileName the name of the file to upload including the path
*/
private void uploadFile(String fileName) throws FileNotFoundException, IOException {
byte[] data = new byte[MAX_BUFFER_SIZE];
int readLength;
byte handle;
InputStream inputStream = null;
byte[] message;
// internal file: no path given
if (fileName.indexOf('/') == -1) {
int dotPosition = fileName.lastIndexOf('.');
String resourceName = fileName.substring(0, dotPosition).toLowerCase();
int id = resources.getIdentifier(resourceName, "raw", "com.lego.minddroid");
inputStream = resources.openRawResource(id);
// read stream once for getting it's size and reopen it afterwards
mFileLength = 0;
while ((readLength = inputStream.read(data)) > 0)
mFileLength += readLength;
inputStream = resources.openRawResource(id);
}
// external file: with full path
else {
File file = new File(fileName);
if (!file.exists())
throw new FileNotFoundException();
inputStream = new FileInputStream(file);
mFileLength = (int) file.length();
}
mUploaded = 0;
// extract fileName without path
int lastSlashPos = fileName.lastIndexOf('/');
fileName = fileName.substring(lastSlashPos + 1, fileName.length());
// send OpenWriteMessage
message = LCPMessage.getOpenWriteMessage(fileName, mFileLength);
mBTCommunicator.sendMessage(message);
// get reply message with handle
message = mBTCommunicator.receiveMessage();
// check message and get handle
if (message == null ||
message.length != 4 ||
message[0] != LCPMessage.REPLY_COMMAND ||
message[1] != LCPMessage.OPEN_WRITE ||
message[2] != 0)
throw new IOException();
handle = message[3];
while ((readLength = inputStream.read(data)) > 0) {
// send WriteMessage and receive reply with next handle
message = LCPMessage.getWriteMessage(handle, data, readLength);
mBTCommunicator.sendMessage(message);
// get reply message and with handle
message = mBTCommunicator.receiveMessage();
// check message and get handle
if (message == null ||
message.length != 6 ||
message[0] != LCPMessage.REPLY_COMMAND ||
message[1] != LCPMessage.WRITE ||
message[2] != 0)
throw new IOException();
handle = message[3];
mUploaded += readLength;
signalUpdate(UPLOADING);
}
// send CloseFile(handle);
message = LCPMessage.getCloseMessage(handle);
mBTCommunicator.sendMessage(message);
// get reply message with handle
message = mBTCommunicator.receiveMessage();
// check message
if (message == null ||
message.length != 4 ||
message[0] != LCPMessage.REPLY_COMMAND ||
message[1] != LCPMessage.CLOSE ||
message[2] != 0)
throw new IOException();
}
/**
* Informs the listener activity to make an update at the screen.
* @param status the current status of the thread
*/
private void signalUpdate(int status) {
if (listener != null)
listener.handleUploadThreadUpdate(status);
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright 2011 Guenther Hoelzl
*
* This file is part of MINDdroid.
*
* MINDdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MINDdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MINDdroid. If not, see <http://www.gnu.org/licenses/>.
**/
package com.lego.minddroid;
public interface UploadThreadListener {
/**
* This will be called by the UploadThread to signal an update of the
* current status.
* @param status The current state of the UploadThread
*/
void handleUploadThreadUpdate(final int status);
}