/*
* roombacomm.ArduinoClient -- test out the Arduino subsystem without robot motion
*
* Copyright (c) 2009 Paul Bouchier, bouchier@at@classicnet.net
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*
*/
package com.hackingroomba.roombacomm;
import com.hackingroomba.roombacomm.RobotConnection.ReadTerminator;
import com.hackingroomba.roombacomm.RobotType.robotTypes;
import java.io.*;
/**
* The extensions to roombacomm for ArduinoBot operations.
*
*
Overview
* This class contains the operations available from an ArduinoBot
**/
public class ArduinoBot extends RoombaComm {
// connection variables
private Integer compassHeading;
private String arduinoString;
private RobotConnection robotConnection;
private RobotType robotType;
// compass variables
private int compassOffset = 0;
private boolean compassOffsetInitialized = false;
// encoder variables
private int encoder;
private long startTime;
public long elapsedTime;
public ArduinoBot() {
super();
}
public ArduinoBot(RobotConnection robotConnection) {
super(robotConnection);
this.robotConnection = robotConnection;
}
/**
* Initialize an Arduino robot controller
*/
public boolean initArduinoBot(RobotType rt)
{
byte[] robotTypeMsg = "robot m\r".getBytes(); // message to Arduino to tell it the robot type
robotType = rt; // record robotType for later use
System.out.println("checking for ArduinoBot... \n");
try {
Thread.sleep(2000);
} catch (Exception e) {System.out.println("Exception in Thread.sleep()"); }
// send the robot type message to Arduino, so it knows how to read the encoders, and any other robot-specific things
if (robotType.robotType == RobotType.robotTypes.frankenRoomba) {
System.out.println("Sending robot r to Arduino");
robotTypeMsg = "robot r\r".getBytes();
} else if (robotType.robotType == robotTypes.tankbot) {
System.out.println("Sending robot t to Arduino");
robotTypeMsg = "robot t\r".getBytes();
}
send(robotTypeMsg);
printCompass();
printEncoders();
return true;
}
/**
* Send a single byte to the ArduinoBot
* (defined as int because of stupid java signed bytes). SendToArduino method of RobotConnection
* is used because this is an ardunobot, which requires a leading m and trailing to make
* the parser work right
* @param b byte of an Arduino command to send
* @return true on successful send
*/
public boolean send(int b) {
return(robotConnection.sendToArduino(b));
}
/**
* Send a byte array to the ArduinoBot. If the first byte is 128 or higher, preface the byte string with m
* so it's treated by Arduino as a roomba command
*/
public boolean send(byte[] bytes) {
if (bytes[0] < 0) // must be a roomba command
return(robotConnection.sendToArduino(bytes));
else
return(robotConnection.send(bytes));
}
/**
* Read a string from Arduino up to a
* @return array of bytes read from ArduinoBot
*/
public String readArduinoBot() throws Exception {
return(robotConnection.readBotToTerminator(ReadTerminator.LF));
}
public void printCompass() {
int compassReading;
startTime = System.currentTimeMillis();
compassReading = getCompass();
if (compassReading < 0) return;
elapsedTime = System.currentTimeMillis() - startTime;
System.out.println("Heading: " + compassReading + " in " + elapsedTime + " ms");
}
public int getCompass() {
// read the uncorrected heading
try {
compassHeading = getUncorrectedCompass();
} catch (Exception e) {
System.out.println("Exception reading uncorrected compass\n" + e.getMessage() + "\n" );
e.printStackTrace();
return -1;
}
// if we haven't initialized the compass offset yet, try to read an offset from a config file
// compassOffsetInitialized ensures we only try this once
try {
if (!compassOffsetInitialized) { // get the compass offset from file if it exists
compassOffset = readConfigInt(new String("compass_offset"));
compassOffsetInitialized = true;
}
} catch (Exception e) {
System.out.println("No compass_offset file could be opened\n" + e.getMessage());
compassOffsetInitialized = true;
}
// offset the uncorrected heading by the compassOffset, which depends on mounting position
compassHeading -= compassOffset;
compassHeading %= 360; // compensate for offset which can make it exceed 360
if (compassHeading < 0) compassHeading += 360;
return compassHeading;
}
private int getUncorrectedCompass() throws Exception {
byte [] compassCmd = {'c', '\r'};
int heading;
//System.out.println("getUncorrectedCompass");
startTime = System.currentTimeMillis();
if (!send(compassCmd)) {
System.out.println("Error sending c command");
}
try {
arduinoString = robotConnection.readBotToTerminator(ReadTerminator.LF);
if (arduinoString == null) {
System.out.println("Error: getUncorrectedCompass failed to read heading - null string");
}
} catch (Exception e) {
elapsedTime = System.currentTimeMillis() - startTime;
System.err.println("Exception reading compass in readBotToTerminator in " + elapsedTime + " ms");
e.printStackTrace();
throw e;
}
elapsedTime = System.currentTimeMillis() - startTime;
//System.out.println("received string: " + arduinoString);
String headingString = arduinoString.split("\\s")[0];
heading = new Integer(headingString);
return heading;
}
public boolean setCompassOffset(int currentMagHeading)
{
int heading;
if ((currentMagHeading < 0) || (currentMagHeading > 360)) {
System.out.println("currentMagHeading must be between 0 and 360");
return false;
}
try {
heading = getUncorrectedCompass();
if (heading < 0) return false;
compassOffset = heading - currentMagHeading;
if (compassOffset < 0) compassOffset += 360;
writeConfigInt(new String("compass_offset"), compassOffset);
} catch (Exception e) {
System.err.println("Error getting compass or writing compass offset");
e.printStackTrace();
return false;
}
compassOffsetInitialized = true;
return true;
}
/*
* Print, Get the current wheel encoders reading
*/
public void printEncoders()
{
double readEncDistance = 0;
startTime = System.currentTimeMillis();
try {
readEncDistance = getEncoders();
} catch (Exception e) {
System.err.println("Exception reading robot encoders: " + e.getMessage());
}
long elapsedTime = System.currentTimeMillis() - startTime;
System.out.println("Wheel encoders reading: " + readEncDistance + " in " + elapsedTime + " ms");
}
public double getEncoders() throws Exception {
byte [] cmd = {'e', '\r'};
//System.out.println("read encoders");
startTime = System.currentTimeMillis();
if (!send(cmd)) {
System.out.println("Error sending e command");
}
try {
arduinoString = robotConnection.readBotToTerminator(ReadTerminator.LF);
elapsedTime = System.currentTimeMillis() - startTime;
if (arduinoString == null) {
System.err.println("Error: getEncoders failed to read encoders, null returned in " + elapsedTime + " ms");
}
} catch (Exception e) {
elapsedTime = System.currentTimeMillis() - startTime;
System.err.println("Exception reading encoders from readBotToTerminator in " + elapsedTime + " ms");
e.printStackTrace();
throw e;
}
//System.out.println("received string: " + arduinoString);
String [] encoderStrings = arduinoString.split("\\s");
if (encoderStrings.length == 1) {
encoder = new Integer(encoderStrings[0]);
} else {
System.out.println("Error reading encoders - expected 1 string, found:" + encoderStrings.length
+ " in: " + arduinoString + " in " + elapsedTime + " ms");
return -1;
}
double encoderDistance = encoder /robotType.countsPerInch;
return encoderDistance;
}
/**
* Read roomba 26-byte sensor record using robotConnection. Tries once to read valid data, allowing 100ms
* timeout on each attempt.
* @return true if read 26 bytes of valid data. Data has been stored in sensor_bytes. False otherwise
*/
public boolean updateSensors()
{
return updateSensors(SENSORS_ALL);
}
public boolean updateSensors(int sensorGroup)
{
int sensorGroupSize;
if (robotConnection == null) {
System.out.println("Error at ArduinoBot.updateSensors(): no connection object for robot");
return false;
}
switch(sensorGroup) {
case SENSORS_ALL: sensorGroupSize = 26; break;
case 100: sensorGroupSize = 80; break;
default:
System.err.println("Invalid sensor group in updateSensors(): " + sensorGroup);
return false;
}
sensors(sensorGroup);
return getSensorData(sensorGroupSize);
}
/**
* Writes an int to the named file
* @throws FileNotFoundException If it can't open the file
* @throws IOException If it can't write to the file
*/
public void writeConfigInt(String filename, int value) throws FileNotFoundException, IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream(new File(filename)));
dos.writeInt(value);
}
/**
* Read a configuration file containing an int.
* @param filename The file to open & read an int from
* @return Int read from file
* @throws Exception related to attempting to open & read int from file
*/
public int readConfigInt(String filename) throws Exception {
int configInt = -1;
try {
DataInputStream dis = new DataInputStream(new FileInputStream(new File(filename)));
configInt = dis.readInt();
} catch (Exception e) {
System.out.println("No config file could be read\n" + e.getMessage());
}
return configInt;
}
/**
* Writes a double to the named file
* @throws FileNotFoundException If it can't open the file
* @throws IOException If it can't write to the file
*/
public void writeConfigDouble(String filename, double value) throws FileNotFoundException, IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream(new File(filename)));
dos.writeDouble(value);
}
/**
* Read a configuration file containing a double.
* @param filename The file to open & read a double from
* @return double read from file
* @throws Exception related to attempting to open & read double from file
*/
public double readConfigDouble(String filename) throws Exception {
double configDouble = -1;
try {
DataInputStream dis = new DataInputStream(new FileInputStream(new File(filename)));
configDouble = dis.readDouble();
} catch (Exception e) {
System.out.println("No config file could be read\n" + e.getMessage());
throw(e);
}
return configDouble;
}
public int getEncoder() {
return encoder;
}
/**
* Bogus methods which should never be called on ArduinoClient
*/
public String [] listPorts()
{
System.out.println("Error at ArduinoBot.listPorts(): should never get here");
return (new String[] {"Error: listports calledon ArdunoBot; not supported"});
}
public boolean connect(String s)
{
System.out.println("Error at ArduinoBot.connect(): should never get here");
return false;
}
public void disconnect()
{
System.out.println("Error at ArduinoBot.disconnect(): should never get here");
return;
}
}