/* * RoombaComm Interface * * Copyright (c) 2006 Tod E. Kurt, tod@todbot.com, ThingM * * 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 gnu.io.SerialPort; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.util.Map; /** * The abstract base for all Roomba communications. * *

Overview

* This class contains the communications layer-independent parts of * how to communicate with a Roomba. It does assume a very serial port-like * interaction. * * Standard lifecyle of this object (and its subclasses)
 *   RoombaComm roomba = new RoombaCommSubClass();  // (e.g. RoombaCommSerial)
 *   roomba.listports();               // if implemented
 *   roomba.connect("someportid");
 *   roomba.startup();
 *   roomba.updateSensors();
 *   while( ... ) {
 *      roomba.sensors();
 *      roomba.playNote( 53, 12 );
 *      roomba.goForward( 400 );
 *      roomba.spinRight( 45 );
 *      if( roomba.bump() ) roomba.goBackward( 100 );
 *   }    
 *   roomba.disconnect();
 * 
* *

API levels

* Describe different API levels * *

Sensor Functions

* Describe sensor functions * *

Sublass behavior

* Describe subclassing strategries * * * @author Tod E. Kurt * SVN id value is $Id: RoombaComm.java 182 2010-11-02 03:49:10Z bouchier $ */ public abstract class RoombaComm { /** version of the library */ static public final String VERSION = "0.96.3"; /** * contains a list of all the ports * keys are port names (e.g. "/dev/usbserial1") * values are Boolean in-use indicator */ protected static Map ports = null; /** turns on/off various debugging messages */ public boolean debug = false; public boolean isDebug() { return debug; } public void setDebug(boolean debug) { this.debug = debug; } /** distance between wheels on the roomba, in millimeters */ public static final int wheelbase = 258; /** mm/deg is circumference distance divided by 360 degrees */ public static final float millimetersPerDegree = (float)(wheelbase * Math.PI / 360.0); /** mm/rad is a circumference distance divied by two pi */ public static final float millimetersPerRadian = (float)(wheelbase/2); /** default speed for movement operations if speed isn't specified */ public static final int defaultSpeed = 200; /** default update time in ms for auto sensors update */ public static final int defaultSensorsUpdateTime = 200; /** current mode, if known */ int mode; /** current speed for movement operations that don't take a speed */ public int speed = defaultSpeed; /** computed boolean for when Roomba is errored out of safe mode */ boolean safetyFault = false; /** if sensor variables have been updated successfully */ protected boolean sensorsValid = false; /** Set to true to make sensors auto-update (at expense of serial b/w) */ boolean sensorsAutoUpdate = false; /** Time in milliseconds between sensor updates */ int sensorsUpdateTime = 200; /** last time (System.currentTimeMillis) that the sensors were updated */ protected long sensorsLastUpdateTime; /** how many bytes we expect to read from the sensor command */ protected int readRequestLength; /** internal storage for all roomba sensor data */ protected byte[] sensor_bytes = new byte[1024]; /** connected to a serial port or not, not necessarily to roomba */ boolean connected = false; public boolean isConnected() { String str = this.getSensorsAsString(); if (str != null && str.length() >=1){ if (debug){ logmsg("isConnected found sensorString as ("+str+")"); } this.setConnected(true); return true; }else{ if (debug){ if (str != null){ logmsg("isConnected found sensorString ("+str+")"); }else{ logmsg("isConnected found sensorString (null)"); } } } return connected; } public void setConnected(boolean connected) { this.connected = connected; } /** set of flgs for the current state of the LEDs */ /** note this is a superset of all protocol's supported */ private boolean redOn = false; private boolean greenOn = false; private boolean toggleSpot = false; private boolean toggleClean = false; private boolean toggleMax = false; private boolean toggleDirt = false; private boolean toggleDock = false; private boolean toggleCheckRobot = false; private int power_color = 0; private int power_int = 0; /** default RoombaComm protocol to identify classes of API calls to be made */ private String protocol = "SCI"; /** default baud rate for the default protocol */ protected int rate = 57600; protected String portname = null; /** connection object to use when appropriate */ RobotConnection robotConnection; /** * Some "virtual" serial ports like Bluetooth serial on Windows * return weird errors deep inside RXTX if an opened port is used * before the virtual COM port is ready. One way to check that it * is ready is to look for the DSR line going high. * However, most simple, real serial ports do not do hardware handshaking * so never set DSR high. * Thus, if using Bluetooth serial on Windows, do: * roombacomm.waitForDSR = true; * before using it and see if it works. */ public boolean waitForDSR = false; /** The RXTX port object, normally you don't need access to this */ public SerialPort serialPort = null; public RoombaComm() { connected = false; mode = MODE_UNKNOWN; } public RoombaComm(boolean autoUpdate) { this(); if( autoUpdate ) startAutoUpdate(); } public RoombaComm(boolean autoUpdate, int updateTime) { this(autoUpdate); sensorsUpdateTime = updateTime; } public RoombaComm(RobotConnection rc) { robotConnection = rc; } public void startAutoUpdate() { new Thread( new Runnable() { public void run() { try { while( sensorsUpdateTime > 0 ) { if( connected() ) sensors(); Thread.sleep( sensorsUpdateTime ); } } catch(InterruptedException ex) {} } }).start(); } /** * List available ports * @return a list available portids, if applicable * or empty set if no ports, * or return null if list is not enumerable */ public abstract String[] listPorts(); /** * Connect to a port * (for serial, portid is serial port name, for net, portid is url?) * @return true on successful connect, false otherwise */ public abstract boolean connect(String portid); /** * Disconnect from a port, clean up any memory in use */ public abstract void disconnect(); /** * Send given byte array to Roomba. * @param bytes byte array of ROI commands to send * @return true on successful send */ public abstract boolean send(byte[] bytes); /** * Send a single byte to the Roomba * (defined as int because of stupid java signed bytes) * @param b byte of an ROI command to send * @return true on successful send */ public abstract boolean send(int b); /** * Query Roomba for sensor status and sync its state with this object's * Subclasses should query Roomba and fill up 'sensor_bytes' with the full * sensor data set * If a RooombaComm object is constructed with 'autoUpdate' true, * calling this method is not required because a separate thread is created * to do sensor updating. * * @return true on successful sensor update, false otherwise */ //public abstract boolean updateSensors(); /** * Wake's Roomba up, if possible, thus optional * To wake up the Roomba requires twiddling its DD line, often * hooked up to the RS-232 DTR line, which may not be available in some * implementations */ public void wakeup() { logmsg("subclass has not implemented"); // byte cmd[] = { (byte)POWER, (byte)v, (byte)power_color, (byte)power_intensity }; // send(cmd); // MSComm1.Output = "+++" & Chr(13) // MSComm1.Output = "ATSW22,6,1,1" & Chr(13) // MSComm1.Output = "ATSW23,6,0,1" & Chr(13) // MSComm1.Output = "ATSW23,6,1,1" & Chr(13) // MSComm1.Output = "ATMD" & Chr(13) String str="+++\nATSW22,6,1,1\n,ATSW23,6,0,1\nATSW23,6,1,1\nATMD\n"; send(str.getBytes()); // byte bytes[] = str.getBytes(); // for (int i = 0; i < bytes.length; i++) { // // } } /** * Put Roomba in safe mode. * As opposed to full mode. Safe mode is the preferred working state * when playing with the Roomba as it provides some measure of * autonomous self-preservation if it encounters a cliff or is picked up * If that happens it goes into passive mode and must be 'reset()'. * @see #reset() */ public void startup() { logmsg("startup"); speed = defaultSpeed; start(); } /** * Reset Roomba after a fault. This takes it out of whatever mode it was * in and puts it into safe mode. * This command also syncs the object's sensor state with the Roomba's * by calling updateSensors() * @see #startup() * @see #updateSensors() */ public void reset() { logmsg("reset"); stop(); startup(); control(); updateSensors(); } /** Send START command */ public void start() { logmsg("start"); mode = MODE_PASSIVE; send( START ); } /** Send CONTROL command */ public void control() { logmsg("control"); mode = MODE_SAFE; send( CONTROL ); // set blue dirt LED on so we know roomba is powered on & under control // (and we don't forget to turn it off, and run it's batteries flat) // FIXME: first time after a poweron, the lights flash then turn off setLEDs(false, false, false, false, false, true, 128, 255); } /** Send SAFE command */ public void safe() { logmsg("safe"); mode = MODE_SAFE; send( SAFE ); } /** Send FULL command */ public void full() { logmsg("full"); mode = MODE_FULL; send( FULL ); } /** * Power off the Roomba. Once powered off, the only way to wake it * is via wakeup() (if implemented) or via a physically pressing * the Power button * @see #wakeup() */ public void powerOff() { logmsg("powerOff"); mode = MODE_UNKNOWN; send( POWER ); } /** Send the SPOT command */ public void spot() { logmsg("spot"); mode = MODE_PASSIVE; send( SPOT ); } /** Send the CLEAN command */ public void clean() { logmsg("clean"); mode = MODE_PASSIVE; send( CLEAN ); } /** Send the max command */ public void max() { logmsg("max"); mode = MODE_PASSIVE; send( MAX ); } /** Send the max command */ public void dock() { logmsg("dock"); mode = MODE_PASSIVE; // send( CLEAN ); send( DOCK ); } /** * Send the SENSORS command * with one of the SENSORS_ arguments * Typically, one does "sensors(SENSORS_ALL)" to get all sensor data * @param packetcode one of SENSORS_ALL, SENSORS_PHYSICAL, * SENSORS_INTERNAL, or SENSORS_POWER, or for roomba 5xx, it * is the sensor packet number (from the spec) */ public void sensors(int packetcode ) { sensorsValid = false; logmsg("sensors:"+packetcode); switch (packetcode) { case 0: readRequestLength = 26; break; case 1: readRequestLength = 10; break; case 2: readRequestLength = 6; break; case 3: readRequestLength = 10; break; case 4: readRequestLength = 14; break; case 5: readRequestLength = 12; break; case 6: readRequestLength = 52; break; case 100: readRequestLength = 80; break; case 101: readRequestLength = 28; break; case 106: readRequestLength = 12; break; case 107: readRequestLength = 9; break; case 19: case 20: case 22: case 23: case 25: case 26: case 27: case 28: case 29: case 30: case 39: case 40: case 41: case 42: case 43: case 44: case 46: case 47: case 48: case 49: case 50: case 51: case 54: case 55: case 56: case 57: readRequestLength = 2; break; default: readRequestLength = 1; break; } byte cmd[] = { (byte)SENSORS, (byte)packetcode}; send(cmd); } /** * get all sensor data */ public void sensors() { readRequestLength = 26; sensors( SENSORS_ALL ); } /** * 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); } /** * Query a list of sensors. This is a roomba 5xx only command. * @param sensorList A byte array containing the sensor groups requested to be read * @param returnLen The number of bytes of data expected to be returned from roomba */ public void queryList(byte[] sensorList, int returnLen) { int i = 0; int j; sensorsValid = false; readRequestLength = returnLen; byte cmd[] = new byte[2+sensorList.length]; cmd[i++] = (byte) QUERYLIST; cmd[i++] = (byte)sensorList.length; for (j=0; j 1) || (readData[1] < 0)) && (sensorGroupSize == 26)) { sensorsValid = false; logmsg("updateSensors: received invalid data while attempting to read Roomba sensors!"); } else { sensorsValid = true; System.arraycopy(readData, 0, sensor_bytes, 0, sensorGroupSize); logmsg("updateSensors: sensorsValid!"); //elapsedTime = System.currentTimeMillis() - startTime; return true; } } System.out.println("Error: timeout on sensor read"); return false; } // // basic functions // /** * Alias to pause * @see #pause(int) */ public void delay( int millis ) { pause( millis ); } /** * Just a simple pause function. * Makes the thread block with Thread.sleep() * @param millis number of milliseconds to wait */ public void pause( int millis ) { try { Thread.sleep(millis); } catch(Exception e) { } } // // higher-level functions // /** * Stop Rooomba's motion. * Sends drive(0,0) */ public void stop() { logmsg("stop"); drive( 0, 0 ); } /** Set speed for movement commands */ public void setSpeed( int s ) { speed = Math.abs(s); } /** Get speed for movement commands */ public int getSpeed() { return speed; } /** * Go straight at the current speed for a specified distance. * Positive distance moves forward, negative distance moves backward. * This method blocks until the action is finished. * @param distance distance in millimeters, positive or negative */ public void goStraight( int distance ) { float pausetime = Math.abs(distance / speed); // mm/(mm/sec) = sec if (distance > 0) goStraightAt( speed ); else goStraightAt( -speed); pause( (int)(pausetime*1000) ); stop(); } /** * @param distance distance in millimeters, positive */ public void goForward( int distance ) { if( distance < 0 ) return; goStraight( distance ); } /** * @param distance distance in millimeters, positive */ public void goBackward( int distance ) { if( distance < 0 ) return; goStraight( -distance ); } /** * */ public void turnLeft() { turn(129); } public void turnRight() { turn(-129); } public void turn( int radius ) { drive( speed, radius ); } /** * Spin right or spin left a particular number of degrees * @param angle angle in degrees, * positive to spin left, negative to spin right */ public void spin( int angle ) { if( angle > 0 ) spinLeft( angle ); else if( angle < 0 ) spinRight( -angle ); } /** * Spin right the current speed for a specified angle * @param angle angle in degrees, positive */ public void spinRight( int angle ) { if( angle < 0 ) return; float pausetime = Math.abs( millimetersPerDegree * angle / speed ); spinRightAt( Math.abs(speed) ); pause( (int)(pausetime*1000) ); stop(); } /** * Spin left a specified angle at a specified speed * @param angle angle in degrees, positive */ public void spinLeft( int angle ) { if( angle<0 ) return; //float pausetime = float pausetime = Math.abs( millimetersPerDegree * angle / speed ); spinLeftAt( Math.abs(speed) ); pause( (int)(pausetime*1000) ); stop(); } /** * Spin in place anti-clockwise, at the current speed */ public void spinLeft() { spinLeftAt( speed ); } /** * Spin in place clockwise, at the current speed */ public void spinRight() { spinRightAt( speed ); } /** * Spin in place anti-clockwise, at the current speed. * @param aspeed speed to spin at */ public void spinLeftAt(int aspeed) { drive( aspeed, 1 ); } /** * Spin in place clockwise, at the current speed. * @param aspeed speed to spin at, positive */ public void spinRightAt(int aspeed) { drive( aspeed, -1 ); } // // mid-level movement, no blocking, parameterized by speed, not distance // /** * Go straight at a specified speed. * Positive is forward, negative is backward * @param velocity velocity of motion in mm/sec */ public void goStraightAt( int velocity ) { //System.out.println("goStraightAt: velocity:"+velocity); if( velocity > 500 ) velocity = 500; if( velocity < -500 ) velocity = -500; drive( velocity, 0x8000 ); } /** * Go forward the current (positive) speed */ public void goForward() { goStraightAt( Math.abs(speed) ); } /** * Go backward at the current (negative) speed */ public void goBackward() { goStraightAt( - Math.abs(speed) ); } /** * Go forward at a specified speed */ public void goForwardAt( int aspeed ) { if( aspeed < 0 ) return; goStraightAt( aspeed ); } /** * Go backward at a specified speed */ public void goBackwardAt( int aspeed ) { if( aspeed < 0 ) return; goStraightAt( -aspeed ); } // // low-level movement and action // /** * Move the Roomba via the low-level velocity + radius method. * See the 'Drive' section of the Roomba ROI spec for more details. * Low-level command. * @param velocity speed in millimeters/second, * positive forward, negative backward * @param radius radius of turn in millimeters */ public void drive( int velocity, int radius ) { byte cmd[] = { (byte)DRIVE,(byte)(velocity>>>8),(byte)(velocity&0xff), (byte)(radius >>> 8), (byte)(radius & 0xff) }; logmsg("drive: "+hex(cmd[0])+","+hex(cmd[1])+","+hex(cmd[2])+","+ hex(cmd[3])+","+hex(cmd[4])); send( cmd ); } /** * Play a musical note * Does it via the hacky method of defining a one-note song & playing it * Uses up song slot 15. * If another note is played before one is finished, the new note cuts off * the old one. * @param note a note number from 31 (G0) to 127 (G8) * @param duration duration of note in 1/64ths of a second */ public void playNote( int note, int duration ) { logmsg("playnote: "+note+":"+duration); byte cmd[] = { (byte)SONG, 3, 1, (byte)note, (byte)duration, // define song (byte)PLAY, 3 }; // play it back send( cmd ); } public void playSong( int songnum ) { byte cmd[] = { (byte)PLAY, (byte)songnum }; send(cmd); } /** * Make a song * @param songnum number of song to define * @param song array of songnotes, * even entries are notenums, odd are duration of 1/6ths */ public void createSong( int songnum, int song[] ) { int len = song.length; int songlen = len/2; logmsg("createSong: songnum:"+songnum+", songlen:"+songlen); byte cmd[] = new byte[len+3]; cmd[0] = (byte) SONG; cmd[1] = (byte) songnum; cmd[2] = (byte) songlen; for( int i=0; i < len; i++ ) { cmd[3+i] = (byte)song[i]; } send(cmd); } /** * Make a song * @param songnum number of song to define * @param song array of Notes */ public void createSong( int songnum, Note song[] ) { int songlen = song.length; logmsg("createSong: songnum:"+songnum+", songlen:"+songlen); byte cmd[] = new byte[songlen+3]; cmd[0] = (byte) SONG; cmd[1] = (byte) songnum; cmd[2] = (byte) songlen; int j=3; for( int i=0; i < songlen; i++ ) { cmd[j++] = (byte)song[i].notenum; cmd[j++] = (byte)song[i].toSec64ths(); } send(cmd); } /** * Turns on/off the non-drive motors (main brush, vacuum, sidebrush). * Sort of low-level. * @param mainbrush mainbrush motor on/off state * @param vacuum vacuum motor on/off state * @param sidebrush sidebrush motor on/off state */ public void setMotors(boolean mainbrush,boolean vacuum,boolean sidebrush) { byte cmd[] = { (byte)MOTORS, (byte)((mainbrush?0x04:0) | (vacuum?0x02:0) | (sidebrush?0x01:0))}; send( cmd ); } /** * Turns on/off the various LEDs. * Low-level command. * FIXME: this is too complex */ public void setLEDs( boolean status_green, boolean status_red, boolean spot,boolean clean,boolean max,boolean dirt, int power_color, int power_intensity ) { int v = (status_green?0x20:0) | (status_red?0x10:0) | (spot?0x08:0) | (clean?0x04:0) | (max?0x02:0) | (dirt?0x01:0); logmsg("setLEDS: "+binary(v)); byte cmd[] = { (byte)LEDS, (byte)v, (byte)power_color, (byte)power_intensity }; send(cmd); } //500 series public void setLEDsOI( boolean checkRobot, boolean spot,boolean dock,boolean dirt, int power_color, int power_intensity ) { updateDisplay("setLEDsOI ("+checkRobot+")("+spot+")("+dock+")("+dirt+")("+power_color+")("+power_intensity+")", this.debug); int v = (checkRobot?0x08:0) | (dock?0x04:0) | (spot?0x02:0) | (dirt?0x01:0); logmsg("setLEDS: "+binary(v)); byte cmd[] = { (byte)LEDS, (byte)v, (byte)power_color, (byte)power_intensity }; // TODO: find a way to do an updateDisplay with a byte array send(cmd); } /** * Turn all vacuum motors on or off according to state * @param state true to turn on vacuum function, false to turn it off */ public void vacuum(boolean state) { logmsg("vacuum: "+state); setMotors(state,state,state); } // // sensor functions // /** * Compute possible safety fault. * Called on every successful updateSensors(). * In normal use, call updateSensors() then check safetyFault(). * @return true if indicates we had an event that took the Roomba out of * safe mode * @see #updateSensors() */ public boolean computeSafetyFault() { safetyFault = (sensor_bytes[BUMPSWHEELDROPS] & WHEELDROP_MASK) != 0 || sensor_bytes[CLIFFLEFT]==1 || sensor_bytes[CLIFFFRONTLEFT]==1 || sensor_bytes[CLIFFRIGHT]==1 || sensor_bytes[CLIFFFRONTRIGHT]==1; if( safetyFault && (mode == MODE_SAFE) ) mode = MODE_PASSIVE; return safetyFault; } /** * Returns current connected state. * It's up to subclasses to ensure this variable is correct. * @return current connected state */ public boolean connected() { return connected; } /** current ROI mode RoombaComm thinks the Roomba is in */ public int mode() { return mode; } /** mode as String */ public String modeAsString() { String s=null; switch(mode) { case MODE_UNKNOWN: s = "unknown"; break; case MODE_PASSIVE: s = "passive"; break; case MODE_SAFE: s = "safe"; break; case MODE_FULL: s = "full"; break; } return s; } /** */ public boolean sensorsAutoUpdate() { return sensorsAutoUpdate; } /** */ public void setSensorsAutoUpdate(boolean b) { sensorsAutoUpdate=b; } /** */ public int sensorsUpdateTime() { return sensorsUpdateTime; } /** */ public void setSensorsUpdateTime(int i) { sensorsUpdateTime=i; } /** * */ public boolean safetyFault() { return safetyFault; } /** * */ public boolean sensorsValid() { // FIXME: // if( sensorsValid ) { // may be valid but stale // long difftime = System.currentTimeMillis() - sensorsLastUpdateTime; // if( difftime > 2*sensorsUpdateTime ) { // give it some space // return false; // } // else return true; // } return sensorsValid; } public String getSensorsAsString() { return sensorsAsString(); } public String convertByteArrayToString(byte[] byteArray) { String value = new String(byteArray); return value; } /** * @return all sensor data as a string */ //* this likely needs to know about protocal to know how to read the sensors */ public String sensorsAsString() { String sd=""; if( debug ) { sd = "\n"; for( int i=0; i<26; i++ ) sd += " "+hex(sensor_bytes[i]); } return "*****\n" + "bump:" + (bumpLeft()?"l":"_") + (bumpRight()?"r":"_") + " wheel:" + (wheelDropLeft() ?"l":"_") + (wheelDropCenter()?"c":"_") + (wheelDropRight() ?"r":"_") + " wall:" + (wall() ?"Y":"n") + " cliff:" + (cliffLeft() ?"l":"_") + (cliffFrontLeft() ?"L":"_") + (cliffFrontRight() ?"R":"_") + (cliffRight() ?"r":"_") + " dirtL:"+dirtLeft()+ " dirtR:"+dirtRight()+ "\n" + "vwal:" + virtual_wall() + " motr:" + motor_overcurrents() + " dirt:" + dirt_left() + "," + dirt_right() + " remo:" + hex(remote_opcode()) + " butt:" + hex(buttons()) + " dist:" + distance() + " angl:" + angle() + "\n" + "chst:" + charging_state() + " volt:" + voltage() + " curr:" + current() + " temp:" + temperatureF() + "F" + " chrg:" + charge() + " capa:" + capacity() + sd ; } public String chargeDataAsString() { String sd=""; if( debug ) { sd = "\n"; for( int i=0; i<26; i++ ) sd += " "+hex(sensor_bytes[i]); } return "Charging State: " + charging_state() + " Temperature: " + temperatureF() + "F\n" + "Voltage: " + voltage() + " Current: " + current() + "\n" + "Capacity: " + capacity() + " Charge: " + charge() + sd; } /** Did we bump into anything */ public boolean bump() { return (sensor_bytes[BUMPSWHEELDROPS] & BUMP_MASK) !=0; } /** Left bump sensor */ public boolean bumpLeft() { return (sensor_bytes[BUMPSWHEELDROPS] & BUMPLEFT_MASK) !=0; } /** Right bump sensor */ public boolean bumpRight() { return (sensor_bytes[BUMPSWHEELDROPS] & BUMPRIGHT_MASK) !=0; } /** Left wheeldrop sensor */ public boolean wheelDropLeft() { return (sensor_bytes[BUMPSWHEELDROPS] & WHEELDROPLEFT_MASK) !=0; } /** Right wheeldrop sensor */ public boolean wheelDropRight() { return (sensor_bytes[BUMPSWHEELDROPS] & WHEELDROPRIGHT_MASK) !=0; } /** Center wheeldrop sensor */ public boolean wheelDropCenter() { return (sensor_bytes[BUMPSWHEELDROPS] & WHEELDROPCENT_MASK) !=0; } /** Can we see a wall? */ public boolean wall() { return sensor_bytes[WALL] != 0; } /** * @return true if dirt present */ public boolean dirt() { int dl = sensor_bytes[DIRTLEFT] & 0xff; int dr = sensor_bytes[DIRTRIGHT] & 0xff; //if(debug) println("Roomba:dirt: dl,dr="+dl+","+dr); return (dl > 100) || (dr > 100); } /** * amount of dirt seen by left dirt sensor */ public int dirtLeft() { return dirt_left(); // yeah yeah } /** * amount of dirt seen by right dirt sensor */ public int dirtRight() { return dirt_right(); } /** left cliff sensor */ public boolean cliffLeft() { return (sensor_bytes[CLIFFLEFT] != 0); } /** front left cliff sensor */ public boolean cliffFrontLeft() { return (sensor_bytes[CLIFFFRONTLEFT] != 0); } /** front right cliff sensor */ public boolean cliffFrontRight() { return (sensor_bytes[CLIFFFRONTRIGHT] != 0); } /** right cliff sensor */ public boolean cliffRight() { return sensor_bytes[CLIFFRIGHT] != 0; } /** overcurrent on left drive wheel */ public boolean motorOvercurrentDriveLeft() { return (sensor_bytes[MOTOROVERCURRENTS] & MOVERDRIVELEFT_MASK) != 0; } /** overcurrent on right drive wheel */ public boolean motorOvercurrentDriveRight() { return (sensor_bytes[MOTOROVERCURRENTS] & MOVERDRIVERIGHT_MASK) != 0; } /** overcurrent on main brush */ public boolean motorOvercurrentMainBrush() { return (sensor_bytes[MOTOROVERCURRENTS] & MOVERMAINBRUSH_MASK) != 0; } /** overcurrent on vacuum */ public boolean motorOvercurrentVacuum() { return (sensor_bytes[MOTOROVERCURRENTS] & MOVERVACUUM_MASK) != 0; } /** overcurrent on side brush */ public boolean motorOvercurrentSideBrush() { return (sensor_bytes[MOTOROVERCURRENTS] & MOVERSIDEBRUSH_MASK) !=0; } /** 'Power' button pressed state */ public boolean powerButton() { return (sensor_bytes[BUTTONS] & POWERBUTTON_MASK) != 0; } /** 'Spot' button pressed state */ public boolean spotButton() { return (sensor_bytes[BUTTONS] & SPOTBUTTON_MASK) != 0; } /** 'Clean' button pressed state */ public boolean cleanButton() { return (sensor_bytes[BUTTONS] & CLEANBUTTON_MASK) != 0; } /** 'Max' button pressed state */ public boolean maxButton() { return (sensor_bytes[BUTTONS] & MAXBUTTON_MASK) != 0; } // // lower-level sensor access // /** lower-level func, returns raw byte */ public int bumps_wheeldrops() { return sensor_bytes[BUMPSWHEELDROPS]; } /** lower-level func, returns raw byte */ public int cliff_left() { return sensor_bytes[CLIFFLEFT]; } /** lower-level func, returns raw byte */ public int cliff_frontleft() { return sensor_bytes[CLIFFFRONTLEFT]; } /** lower-level func, returns raw byte */ public int cliff_frontright() { return sensor_bytes[CLIFFFRONTRIGHT]; } /** lower-level func, returns raw byte */ public int cliff_right() { return sensor_bytes[CLIFFRIGHT]; } /** lower-level func, returns raw byte */ public int virtual_wall() { return sensor_bytes[VIRTUALWALL]; } /** lower-level func, returns raw byte */ public int motor_overcurrents() { return sensor_bytes[MOTOROVERCURRENTS]; } /** */ public int dirt_left() { return sensor_bytes[DIRTLEFT] & 0xff; } /** */ public int dirt_right() { return sensor_bytes[DIRTRIGHT] & 0xff; } /** lower-level func, returns raw byte */ public int remote_opcode() { return sensor_bytes[REMOTEOPCODE]; } /** lower-level func, returns raw byte */ public int buttons() { return sensor_bytes[BUTTONS]; } /** * Distance traveled since last requested * units: mm * range: -32768 - 32767 */ public short distance() { return toShort(sensor_bytes[DISTANCE_HI], sensor_bytes[DISTANCE_LO]); } /** * Angle traveled since last requested * units: mm, diff in distance traveled by two drive wheels * range: -32768 - 32767 */ public short angle() { return toShort(sensor_bytes[ANGLE_HI], sensor_bytes[ANGLE_LO]); } /** * angle since last read, but in degrees */ // FIXME I think this should be (360 * angle())/(258 * PI) public float angleInDegrees() { return (float) angle() / millimetersPerDegree; } /** * angle since last read, but in radians */ // FIXME I think this should be (2 * angle())/258 public float angleInRadians() { return (float) angle() / millimetersPerRadian; } /** * Charging state * units: enumeration * range: */ public int charging_state() { return sensor_bytes[CHARGINGSTATE] & 0xff; } /** * Voltage of battery * units: mV * range: 0 - 65535 */ public int voltage() { return toUnsignedShort(sensor_bytes[VOLTAGE_HI], sensor_bytes[VOLTAGE_LO]); } /** * Current flowing in or out of battery * units: mA * range: -332768 - 32767 */ public short current() { return toShort(sensor_bytes[CURRENT_HI], sensor_bytes[CURRENT_LO]); } /** * temperature of battery * units: degrees Celcius * range: -128 - 127 */ public byte temperature() { return sensor_bytes[TEMPERATURE]; } public byte temperatureF() { int c = sensor_bytes[TEMPERATURE]; return (byte) ((9.0/5.0)*c + 32); } /** * Current charge of battery * units: mAh * range: 0-65535 */ public int charge() { return toUnsignedShort(sensor_bytes[CHARGE_HI], sensor_bytes[CHARGE_LO]); } /** * Estimated charge capacity of battery * units: mAh * range: 0-65535 */ public int capacity() { return toUnsignedShort(sensor_bytes[CAPACITY_HI], sensor_bytes[CAPACITY_LO]); } // possible modes public static final int MODE_UNKNOWN = 0; public static final int MODE_PASSIVE = 1; public static final int MODE_SAFE = 2; public static final int MODE_FULL = 3; // Roomba ROI opcodes // these should all be bytes, but Java bytes are signed, sucka public static final int START = 128; // 0 public static final int BAUD = 129; // 1 public static final int CONTROL = 130; // 0 public static final int SAFE = 131; // 0 public static final int FULL = 132; // 0 public static final int POWER = 133; // 0 public static final int SPOT = 134; // 0 public static final int CLEAN = 135; // 0 public static final int MAX = 136; // 0 public static final int DRIVE = 137; // 4 public static final int MOTORS = 138; // 1 public static final int LEDS = 139; // 3 public static final int SONG = 140; // 2N+2 public static final int PLAY = 141; // 1 public static final int SENSORS = 142; // 1 public static final int DOCK = 143; // 0 public static final int PWMMOTORS = 144; // 3 public static final int DRIVEWHEELS = 145; // 4 public static final int DRIVEPWM = 146; // 4 public static final int STREAM = 148; // N+1 public static final int QUERYLIST = 149; // N+1 public static final int STOPSTARTSTREAM = 150; // 1 public static final int SCHEDULINGLEDS = 162; // 2 public static final int DIGITLEDSRAW = 163; // 4 public static final int DIGITLEDSASCII = 164; // 4 public static final int BUTTONSCMD = 165; // 1 public static final int SCHEDULE = 167; // n public static final int SETDAYTIME = 168; // 3 // offsets into sensor_bytes data public static final int BUMPSWHEELDROPS = 0; public static final int WALL = 1; public static final int CLIFFLEFT = 2; public static final int CLIFFFRONTLEFT = 3; public static final int CLIFFFRONTRIGHT = 4; public static final int CLIFFRIGHT = 5; public static final int VIRTUALWALL = 6; public static final int MOTOROVERCURRENTS = 7; public static final int DIRTLEFT = 8; public static final int DIRTRIGHT = 9; public static final int REMOTEOPCODE = 10; public static final int BUTTONS = 11; public static final int DISTANCE_HI = 12; public static final int DISTANCE_LO = 13; public static final int ANGLE_HI = 14; public static final int ANGLE_LO = 15; public static final int CHARGINGSTATE = 16; public static final int VOLTAGE_HI = 17; public static final int VOLTAGE_LO = 18; public static final int CURRENT_HI = 19; public static final int CURRENT_LO = 20; public static final int TEMPERATURE = 21; public static final int CHARGE_HI = 22; public static final int CHARGE_LO = 23; public static final int CAPACITY_HI = 24; public static final int CAPACITY_LO = 25; // bitmasks for various thingems public static final int WHEELDROP_MASK = 0x1C; public static final int BUMP_MASK = 0x03; public static final int BUMPRIGHT_MASK = 0x01; public static final int BUMPLEFT_MASK = 0x02; public static final int WHEELDROPRIGHT_MASK = 0x04; public static final int WHEELDROPLEFT_MASK = 0x08; public static final int WHEELDROPCENT_MASK = 0x10; public static final int MOVERDRIVELEFT_MASK = 0x10; public static final int MOVERDRIVERIGHT_MASK= 0x08; public static final int MOVERMAINBRUSH_MASK = 0x04; public static final int MOVERVACUUM_MASK = 0x02; public static final int MOVERSIDEBRUSH_MASK = 0x01; public static final int POWERBUTTON_MASK = 0x08; public static final int SPOTBUTTON_MASK = 0x04; public static final int CLEANBUTTON_MASK = 0x02; public static final int MAXBUTTON_MASK = 0x01; // which sensor packet, argument for sensors(int) public static final int SENSORS_ALL = 0; public static final int SENSORS_PHYSICAL = 1; public static final int SENSORS_INTERNAL = 2; public static final int SENSORS_POWER = 3; public static final int REMOTE_NONE = 0xff; public static final int REMOTE_POWER = 0x8a; public static final int REMOTE_PAUSE = 0x89; public static final int REMOTE_CLEAN = 0x88; public static final int REMOTE_MAX = 0x85; public static final int REMOTE_SPOT = 0x84; public static final int REMOTE_SPINLEFT = 0x83; public static final int REMOTE_FORWARD = 0x82; public static final int REMOTE_SPINRIGHT = 0x81; /* no button = -1 power = -118 8a pause = -119 89 clean = -120 88 max = -123 85 spot = -124 84 spinleft = -125 81 (8d keyup?) forward = -126 82 (8c?) spinright = -127 83 */ // // utility methods // /** * */ static public final short toShort(byte hi, byte lo) { return (short)((hi << 8) | (lo & 0xff)); } /** * */ static public final int toUnsignedShort(byte hi, byte lo) { return (int)(hi & 0xff) << 8 | lo & 0xff; } public void println(String s) { System.out.println(s); } public String hex(byte b) { return Integer.toHexString(b&0xff); } public String hex(int i) { return Integer.toHexString(i); } public String binary(int i) { return Integer.toBinaryString(i); } /** * just a little debug */ public void logmsg(String msg) { if(debug) { System.err.println("RoombaComm ("+System.currentTimeMillis()+"):"+msg); System.err.flush(); } } /** * General error reporting, all corraled here just in case * I think of something slightly more intelligent to do. */ public void errorMessage(String where, Throwable e) { e.printStackTrace(); throw new RuntimeException("Error inside Serial." + where + "()"); } public String getProtocol() { return protocol; } public void setProtocol(String protocol) { if (protocol.equals("SCI")) { rate = 57600; } else if (protocol.equals("OI")) { rate = 115200; } this.protocol = protocol; logmsg("Protocol: " + protocol +" , rate: "+rate); writeConfigFile(portname, protocol, waitForDSR?'Y':'N'); } /** * Write a config file with current settings */ protected void writeConfigFile(String port, String protocol, char waitForDSR) { try { FileWriter f = new FileWriter(".roomba_config", false); BufferedWriter w = new BufferedWriter(f); // create file if (port != null){ w.write(port); }else{ w.newLine(); } w.newLine(); if (protocol != null){ w.write(protocol); }else{ w.newLine(); } w.newLine(); w.write(waitForDSR); w.newLine(); w.close(); f.close(); } catch (IOException e) { logmsg("unable to write .roomba_config " + e); } } protected void readConfigFile() { try { FileReader f = new FileReader(".roomba_config"); BufferedReader r = new BufferedReader(f); portname = r.readLine(); setProtocol(r.readLine()); if (getProtocol().equals("SCI")) { rate = 57600; }else if (getProtocol().equals("OI")) { rate = 115200; } waitForDSR = r.readLine().equals("Y")?true:false; logmsg("read config port: " + serialPort + " protocol: " + getProtocol() + " waitDSR: " + waitForDSR); } catch (IOException e) { logmsg("unable to read .roomba_config " + e); } } public void setLEDs(RoombaComm roombacomm) { if( !roombacomm.connected() ){ updateDisplay("not-connected", this.debug); return; } updateDisplay("setLEDs protocal is ("+this.protocol+")", this.debug); if (this.protocol.equalsIgnoreCase("SCI")){ roombacomm.setLEDs(this.greenOn, this.redOn, this.toggleSpot, this.toggleClean, this.toggleMax, this.toggleDirt, this.power_color, this.power_int); } if (this.protocol.equalsIgnoreCase("OI")){ roombacomm.setLEDsOI(this.toggleCheckRobot, this.toggleSpot, this.toggleDock, this.toggleDirt, this.power_color, this.power_int); updateDisplay("Checkrobot("+this.toggleCheckRobot +"),Spot("+ this.toggleSpot +"),Dock("+ this.toggleDock +"),Dirt("+ this.toggleDirt +"),Pcolor("+ this.power_color +"),Pint("+ this.power_int+")",true); } } public void setChgGreenLED(RoombaComm roombacomm, boolean green) { this.greenOn=green; updateDisplay("setChgGreenLED", true); this.setLEDs(roombacomm); } public void setChgRedLED(RoombaComm roombacomm, boolean red) { this.redOn=red; updateDisplay("setChgRedLED", true); this.setLEDs(roombacomm); } public void setChgSpotLED(RoombaComm roombacomm, boolean spot) { this.toggleSpot=spot; updateDisplay("setChgSpotLED value("+spot+")", true); this.setLEDs(roombacomm); } public void setChgCleanLED(RoombaComm roombacomm, boolean clean) { updateDisplay("setChgCleanLED",true); this.toggleClean=clean; this.setLEDs(roombacomm); } public void setChgMaxLED(RoombaComm roombacomm, boolean max) { updateDisplay("setChgMaxLED",true); this.toggleMax=max; this.setLEDs(roombacomm); } public void setChgDirtLED(RoombaComm roombacomm, boolean dirt) { updateDisplay("setChgDirtLED",true); this.toggleDirt=dirt; this.setLEDs(roombacomm); } public void setChgPowerColorLED(RoombaComm roombacomm, int power_color) { updateDisplay("setChgPowerColorLED",true); this.power_color=power_color; this.setLEDs(roombacomm); } public void setChgPowerIntensityLED(RoombaComm roombacomm, int power_intensity) { updateDisplay("setChgPowerIntensityLED",true); this.power_int=power_intensity; this.setLEDs(roombacomm); } public void setChgCheckRobotLED(RoombaComm roombacomm, boolean toggleCheckRobot) { this.toggleCheckRobot = toggleCheckRobot; this.setLEDs(roombacomm); } public void setChgDockLED(RoombaComm roombacomm, boolean toggleDock) { this.toggleDock = toggleDock; this.setLEDs(roombacomm); } protected void updateDisplay(String s, boolean onlyDebug) { if (onlyDebug && debug){ updateDisplay(s); System.out.println(s); } } protected void updateDisplay(String s) { // displayText.append( s ); // displayText.setCaretPosition(displayText.getDocument().getLength()); } public boolean isRedOn() { return redOn; } public boolean isGreenOn() { return greenOn; } public boolean isToggleSpot() { return toggleSpot; } public boolean isToggleClean() { return toggleClean; } public boolean isToggleMax() { return toggleMax; } public boolean isToggleDirt() { return toggleDirt; } public boolean isToggleCheckRobot() { return toggleCheckRobot; } public boolean isToggleDock() { return toggleDock; } /** * Returns the number of bytes that have been read from serial * and are waiting to be dealt with by the user. * (from processing.serial.Serial) * private int available() { return (bufferLast - bufferIndex); } /** * Return a byte array of anything that's in the serial buffer. * Not particularly memory/speed efficient, because it creates * a byte array on each read, but it's easier to use than * readBytes(byte b[]) (see below). * (from processing.serial.Serial) * private byte[] readBytes() { if (bufferIndex == bufferLast) return null; synchronized (buffer) { int length = bufferLast - bufferIndex; byte outgoing[] = new byte[length]; System.arraycopy(buffer, bufferIndex, outgoing, 0, length); bufferIndex = 0; // rewind bufferLast = 0; return outgoing; } } /** * Grab whatever is in the serial buffer, and stuff it into a * byte buffer passed in by the user. This is more memory/time * efficient than readBytes() returning a byte[] array. * * Returns an int for how many bytes were read. If more bytes * are available than can fit into the byte array, only those * that will fit are read. * (from processing.serial.Serial) * public int readBytes(byte outgoing[]) { if (bufferIndex == bufferLast) return 0; synchronized (buffer) { int length = bufferLast - bufferIndex; if (length > outgoing.length) length = outgoing.length; System.arraycopy(buffer, bufferIndex, outgoing, 0, length); bufferIndex += length; if (bufferIndex == bufferLast) { bufferIndex = 0; // rewind bufferLast = 0; } return length; } } */ public void powerOn() { logmsg("powerOn"); mode = MODE_PASSIVE; // MSComm1.Output = "+++" & Chr(13) // MSComm1.Output = "ATSW22,6,1,1" & Chr(13) // MSComm1.Output = "ATSW23,6,0,1" & Chr(13) // MSComm1.Output = "ATSW23,6,1,1" & Chr(13) // MSComm1.Output = "ATMD" & Chr(13 send( ("+++"+(char)13).getBytes()); send( ("ATSW22,6,1,1"+(char)13).getBytes()); send( ("ATSW23,6,0,1"+(char)13).getBytes()); send( ("ATSW23,6,1,1"+(char)13).getBytes()); send( ("ATMD"+(char)13).getBytes()); //: TYPE : ATSW22,6,0,1 ; First change it to high //REPLY: OK //TYPE : ATSW22,6,0,0 ; Change it to low //REPLY: OK //TYPE : ATSW22,6,0,1 //REPLY: OK ; Change it to high // send( "+++\n".getBytes()); // send( "ATSW22,6,0,1\n".getBytes()); // send( "ATSW22,6,0,0\n".getBytes()); // send( "ATSW22,6,0,1\n".getBytes()); // send( "ATMD\n".getBytes()); } public byte[] getSensor_bytes() { return sensor_bytes; } }