package org.myrobotlab.roomba; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.myrobotlab.framework.QueueStats; import org.myrobotlab.service.Serial; import org.myrobotlab.service.interfaces.SerialDataListener; /** * The serial-port based implementation of RoombaComm. Handles both physical * RS-232 ports, USB adapter ports like Keyspan USA-19HS, and Bluetooth serial * port profiles. * * <p> * Some code taken from processing.serial.Serial. Thanks guys! * </p> * * The interaction model for setting the port and protocol and WaitForDSR * parameters is as follows. * <p> * On creation, the class initializes the parameters, then tries to read * .roomba_config. If it can read the config file and parse out the parameters, * it sets the parameters to the values in the config file. Apps can read the * current settings for display using methods on the class. Apps can override * the settings by accepting user input and setting the parameters using methods * on the class, or the connect() method. Parameters that are changed by the app * are re-written in the config file, for use as defaults next run. Command-line * apps can make these parameters optional, by using the defaults if the user * doesn't specify them. */ public class RoombaCommPort extends RoombaComm implements SerialDataListener { private int rate = 57600; static final int databits = 8; static final int parity = 0; static final int stopbits = 1; private String protocol = "SCI"; Serial serial; /** * contains a list of all the ports keys are port names (e.g. * "/dev/usbserial1") values are Boolean in-use indicator */ static Map<String, Boolean> ports = null; /** * The time to wait in milliseconds after sending sensors command before * attempting to read */ public static int updateSensorsPause = 400; /** the serial input stream, normally you don't need access to this */ // public InputStream input; /** the serial output stream, normally you don't need access to this */ // public OutputStream output; private String portname = null; // "/dev/cu.KeySerial1" for instance /** * RXTX bombs when flushing output sometimes, so by default do not flush the * output stream. If the output is too buffered to be useful, do: * roombacomm.comm.flushOutput = true; before using it and see if it works. */ public boolean flushOutput = false; /** * 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; // Warning: public attribute - setting // won't trigger config file write byte buffer[] = new byte[32768]; int bufferLast; // int bufferSize = 26; // how big before reset or event firing // boolean bufferUntil; // int bufferUntilByte; /** * Let you check to see if a port is in use by another Rooomba before trying * to use it. */ public static boolean isPortInUse(String pname) { Boolean inuse = (Boolean) ports.get(pname); if (inuse != null) { return inuse.booleanValue(); } return false; } // constructor public RoombaCommPort() { super(); serial = new Serial("serial"); serial.startService(); makePorts(); } public RoombaCommPort(boolean autoupdate) { super(autoupdate); makePorts(); } public RoombaCommPort(boolean autoupdate, int updateTime) { super(autoupdate, updateTime); makePorts(); } public void computeSensors() { sensorsValid = true; sensorsLastUpdateTime = System.currentTimeMillis(); computeSafetyFault(); } public boolean connect(String portid) { logmsg("connecting to port '" + portid + "'"); portname = portid; if (isPortInUse(portid)) { logmsg("port is in use"); return false; } try { openPort(); } catch(Exception e){ log.error("cannot connect", e); return false; } if (connected) { // log in the global ports hash if the port is in use now or not ports.put(portname, new Boolean(connected)); sensorsValid = false; } else { disconnect(); } return true; } /* * (non-Javadoc) * * @see org.myrobotlab.roomba.Z#disconnect() */ @Override public void disconnect() { connected = false; // log in the global ports hash if the port is in use now or not ports.put(portname, new Boolean(connected)); /* * try { // do io streams need to be closed first? if (input != null) * input.close(); if (output != null) output.close(); } catch (Exception e) * { e.printStackTrace(); } input = null; output = null; */ serial.disconnect(); } @Override public String getName() { // TODO Auto-generated method stub return null; } public String getPortname() { return portname; } public String getProtocol() { return protocol; } public boolean isWaitForDSR() { return waitForDSR; } /* * (non-Javadoc) * * @see org.myrobotlab.roomba.Z#listPorts() */ @Override public String[] listPorts() { List<String> portNames = serial.getPortNames(); return portNames.toArray(new String[portNames.size()]); } void makePorts() { if (ports == null) ports = Collections.synchronizedMap(new TreeMap<String, Boolean>()); } /* * pause(updateSensorsPause); // take a breather to let data come back * sensorsValid = false; // assume the worst, we're gothy int n = available(); * //logmsg("updateSensors:n="+n); if( n >= 26) { // there are enough bytes to * read n = readBytes(sensor_bytes); if( n==26 ) { // did we get enough? * sensorsValid = true; // then everything's good, otherwise bad * computeSafetyFault(); } } else { logmsg("updateSensors:only "+n+ * " bytes available, not updating sensors"); } * * //logmsg("buffer contains: "+ buffer ); return sensorsValid; */ /* * (non-Javadoc) * * @see * org.myrobotlab.roomba.Z#serialEvent(org.myrobotlab.serial.SerialDeviceEvent * ) */ @Override public Integer onByte(Integer newByte) { buffer[bufferLast++] = (byte) newByte.intValue(); if (bufferLast == 26) { bufferLast = 0; System.arraycopy(buffer, 0, sensor_bytes, 0, 26); computeSensors(); } return newByte; } /** * internal method, used by connect() FIXME: make it faile more gracefully, * recognize bad port * @throws IOException */ private void openPort() throws IOException { serial.open(portname, rate, databits, stopbits, parity); } /* * (non-Javadoc) * * @see org.myrobotlab.roomba.Z#send(byte[]) */ // FIXME - IS THIS RIGHT ?!?!? - why would you send real Java bytes - you'd // have to // "load" them incorrectly send them // @Override public boolean send(byte[] bytes) { try { // BLECH - conversion to support silly send(byte[] bytes) int[] ints = new int[bytes.length]; for (int i = 0; i < ints.length; ++i) { ints[i] = bytes[i]; } serial.write(ints); // if( flushOutput ) port.flush(); // hmm, not sure if a good idea } catch (Exception e) { // null pointer or serial port dead e.printStackTrace(); } return true; } /* * (non-Javadoc) * * @see org.myrobotlab.roomba.Z#send(int) */ @Override public boolean send(int b) { // will also cover char or byte try { serial.write(b & 0xff); // for good measure do the & // if( flushOutput ) output.flush(); // hmm, not sure if a good idea } catch (Exception e) { // null pointer or serial port dead // errorMessage("send", e); e.printStackTrace(); } return true; } public void setPortname(String p) { portname = p; logmsg("Port: " + portname); // writeConfigFile(portname, protocol, waitForDSR?'Y':'N'); fixme - use // Service.save() } public void setProtocol(String protocol) { if (protocol.equals("SCI")) { rate = 57600; } else if (protocol.equals("OI")) { rate = 115200; } this.protocol = protocol; logmsg("Protocol: " + protocol); // writeConfigFile(portname, protocol, waitForDSR?'Y':'N'); FIXME - // remove use Service.save() ! } public void setWaitForDSR(boolean waitForDSR) { this.waitForDSR = waitForDSR; } // ------------------------------------------------------------- // below only used internally to this class // ------------------------------------------------------------- /* * (non-Javadoc) * * @see org.myrobotlab.roomba.Z#updateSensors() */ @Override public boolean updateSensors() { sensorsValid = false; sensors(); for (int i = 0; i < 20; i++) { if (sensorsValid) { logmsg("updateSensors: sensorsValid!"); break; } logmsg("updateSensors: pausing..."); pause(50); } return sensorsValid; } public boolean updateSensors(int packetcode) { sensorsValid = false; sensors(packetcode); for (int i = 0; i < 20; i++) { if (sensorsValid) { logmsg("updateSensors: sensorsValid!"); break; } logmsg("updateSensors: pausing..."); pause(50); } return sensorsValid; } /* * (non-Javadoc) * * @see org.myrobotlab.roomba.Z#wakeup() */ @Override public void wakeup() { serial.setDTR(false); pause(500); serial.setDTR(true); } @Override public String onConnect(String portName) { log.info(String.format("%s connected to %s", getName(), portName)); return portName; } @Override public String onDisconnect(String portName) { log.info(String.format("%s disconnected from %s", getName(), portName)); return portName; } @Override public QueueStats publishStats(QueueStats stats) { // TODO Auto-generated method stub return null; } @Override public void updateStats(QueueStats stats) { // TODO Auto-generated method stub } }