/**
* Catroid: An on-device graphical programming language for Android devices
* Copyright (C) 2010-2011 The Catroid Team
* (<http://code.google.com/p/catroid/wiki/Credits>)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* An additional term exception under section 7 of the GNU Affero
* General Public License, version 3, is available at
* http://www.catroid.org/catroid_license_additional_term
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* 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 Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with MINDdroid. If not, see <http://www.gnu.org/licenses/>.
*/
package at.tugraz.ist.catroid.LegoNXT;
import java.io.IOException;
import java.util.ArrayList;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
/**
* 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 abstract class LegoNXTCommunicator 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 RECEIVED_MESSAGE = 1111;
public static final int NO_DELAY = 0;
public static final int GENERAL_COMMAND = 100;
public static final int MOTOR_COMMAND = 102;
public static final int TONE_COMMAND = 101;
protected boolean connected = false;
protected Handler uiHandler;
private static boolean requestConfirmFromDevice = false;
protected static ArrayList<byte[]> receivedMessages = new ArrayList<byte[]>();
protected byte[] returnMessage;
protected Resources mResources;
public LegoNXTCommunicator(Handler uiHandler, Resources resources) {
this.uiHandler = uiHandler;
this.mResources = resources;
}
public static ArrayList<byte[]> getReceivedMessageList() {
return receivedMessages;
}
public static void enableRequestConfirmFromDevice(boolean cfd) {
requestConfirmFromDevice = cfd;
}
public Handler getHandler() {
return myHandler;
}
public byte[] getReturnMessage() {
return returnMessage;
}
/**
* @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 abstract void run();
/**
* 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 abstract void createNXTconnection() throws IOException;
/**
* 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 abstract void destroyNXTconnection() throws IOException;
/**
* Sends a message on the opened OutputStream
*
* @param message
* , the message as a byte array
*/
public abstract void sendMessage(byte[] message) throws IOException;
/**
* Receives a message on the opened InputStream
*
* @return the message
*/
public abstract byte[] receiveMessage() throws IOException;
public abstract void stopAllNXTMovement();
/**
* 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
*/
protected void sendState(int message) {
Bundle myBundle = new Bundle();
myBundle.putInt("message", message);
sendBundle(myBundle);
}
protected void sendMessageAndState(byte[] message) {
try {
sendMessage(message);
} catch (IOException e) {
sendState(STATE_SENDERROR);
}
}
protected void sendState(int message, byte[] data) {
Bundle myBundle = new Bundle();
myBundle.putInt("message", message);
myBundle.putByteArray("received_message", data);
sendBundle(myBundle);
}
protected void sendBundle(Bundle myBundle) {
Message myMessage = myHandler.obtainMessage();
myMessage.setData(myBundle);
uiHandler.sendMessage(myMessage);
}
protected void dispatchMessage(byte[] message) {
//Log.i("bt", "Received response, length: " + message.length);
// for (int i = 0; i < message.length; i++) {
// Log.i("bt", " " + (0x000000FF & message[i]));
// }
switch (message[1]) {
case LCPMessage.SET_OUTPUT_STATE:
//sendState(RECEIVED_MESSAGE, message);
analyzeMessageSetOutputState(message);
break;
case LCPMessage.GET_OUTPUT_STATE:
//sendState(RECEIVED_MESSAGE, message);
receivedMessages.add(message);
analyzeMessageGetOutputState(message);
break;
default:
Log.i("bt", "Unknown Message received by LegoNXTCommunicator over bluetooth " + message.length);
receivedMessages.add(message);
break;
}
}
protected void analyzeMessageSetOutputState(byte[] message) {
//change command byte0 to DIRECT_COMMAND_REPLY to use!
Log.i("bt", "Direct command executed: " + (int) message[0]);
Log.i("bt", "executed Command was: " + (int) message[1]);
Log.i("bt", "Status: " + (int) message[2]);
Log.i("bt", "Length: " + message.length);
}
protected void analyzeMessageGetOutputState(byte[] message) {
//See Lego NXT Docu or LCPMessage class for info on numbers!
Log.i("bt", "Message Length: " + message.length);
Log.i("bt", "GetOutputState executed: " + (int) message[0]);
// Log.i("bt", "----- executed Command: " + (int) message[1]);
// Log.i("bt", "Status: " + (int) message[2]);
// Log.i("bt", "Used Motor: " + (int) message[3]);
// Log.i("bt", "Used Power: " + (int) message[4]);
//Log.i("bt", "Mode: " + (int) message[5]);
//Log.i("bt", "Regulation: " + (int) message[6]);
//Log.i("bt", "Turn Ratio: " + (int) message[7]);
//Log.i("bt", "Run State: " + (int) message[8]);
// int tacholimit = (0x000000FF & message[9]); //unsigned types would be too smart for java, sorry no chance mate!
// tacholimit += ((0x000000FF & message[10]) << 8);
// tacholimit += ((0x000000FF & message[11]) << 16);
// tacholimit += ((0x000000FF & message[12]) << 24);
//Log.i("bt", "Tacholimit " + tacholimit);
/*
* int tachocount = message[13];
* tachocount += (message[14] << 8);
* tachocount += (message[15] << 16);
* tachocount += (message[16] << 24);
*
* Log.i("bt", "Tachocount " + tachocount);
*/
}
protected void doBeep(int frequency, int duration) {
byte[] message = LCPMessage.getBeepMessage(frequency, duration);
sendMessageAndState(message);
waitSomeTime(20);
}
protected void waitSomeTime(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
}
}
protected void sendToast(String toastText) {
Bundle myBundle = new Bundle();
myBundle.putInt("message", DISPLAY_TOAST);
myBundle.putString("toastText", toastText);
sendBundle(myBundle);
}
protected synchronized void moveMotor(int motor, int speed, int end) {
byte[] message = LCPMessage.getMotorMessage(motor, speed, end);
sendMessageAndState(message);
//Log.i("bto", "Motor " + motor + " speed " + speed);
if (requestConfirmFromDevice) {
byte[] test = LCPMessage.getOutputStateMessage(motor);
sendMessageAndState(test);
}
}
// receive messages from the UI
final Handler myHandler = new Handler() {
@Override
public void handleMessage(Message myMessage) {
switch (myMessage.what) {
case TONE_COMMAND:
doBeep(myMessage.getData().getInt("frequency"), myMessage.getData().getInt("duration"));
break;
case DISCONNECT:
break;
default:
int motor;
int speed;
int angle;
motor = myMessage.getData().getInt("motor");
speed = myMessage.getData().getInt("speed");
angle = myMessage.getData().getInt("angle");
moveMotor(motor, speed, angle);
break;
}
}
};
}