package com.hackingroomba.roombacomm; import java.io.*; import java.net.*; import java.awt.*; import java.awt.image.*; import java.util.*; import java.net.URL; class RoboRealmAPI { // default read and write socket timeout public final static int DEFAULT_TIMEOUT = 60000; // the port number to listen on ... needs to match that used in RR interface public final static int SERVER_PORTNUM = 6060; // indicates that the application is connected to RoboRealm Server boolean connected = false; // holds the previously read data size int lastDataTop = 0; // holds the previously read data buffer int lastDataSize = 0; // contains the read/write socket timeouts int timeout = DEFAULT_TIMEOUT; // general buffer for data manipulation and socket reading byte buffer[] = new byte[4096]; // our instance of our primitive XML parser XML xml = new XML(); // socket based reader and writer objects BufferedInputStream bufferedReader; BufferedOutputStream bufferedWriter; // out main socket handle Socket handle; public int width=0, height=0; /******************************************************************************/ /* Text string manipulation routines */ /******************************************************************************/ public RoboRealmAPI() { } /* Generalized string replace routine used in escaping strings to the appropriate XML string. Java only has the character replace routine as apposed to string replace. */ private String replace(String txt, String src, String dest) { if (txt==null) return new String(""); int i,j; int len=src.length(); StringBuffer sb=new StringBuffer(txt.length()); j=0; while ((i=txt.indexOf(src,j))>=0) { sb.append(txt.substring(j,i)); sb.append(dest); i+=len; j=i; } sb.append(txt.substring(j)); return sb.toString(); } /* Escapes strings to be included in XML message. This can be accomplished by a sequence of replace statements. & -> & " -> "e; < -> < > -> > */ private String escape(String txt) { txt = replace(txt, "&", "&"); txt = replace(txt, "\"", ""e;"); txt = replace(txt, "<", "<"); txt = replace(txt, ">", ">"); return txt; } /******************************************************************************/ /* Socket Routines */ /******************************************************************************/ /* Initiates a socket connection to the RoboRealm server */ public boolean connect(String hostname) { connected=false; try { handle = new Socket(hostname, SERVER_PORTNUM); handle.setSoTimeout(timeout); bufferedReader = new BufferedInputStream(handle.getInputStream()); bufferedWriter = new BufferedOutputStream(handle.getOutputStream()); } catch (IOException e2) { //Unable to open connection to RoboRealm port 6060 return false; } connected=true; return true; } /* close the socket handle */ public void disconnect() { try { if (connected) handle.close(); } catch (IOException e) { } } // cause the roborealm application to close public boolean close() { if (!connected) return false; if (send("")) { // read in variable length String buffer; if ((buffer = readMessage())!=null) { return buffer.equals("ok"); } } return false; } // sends a String over the socket port to RoboRealm private boolean send(String txt) { try { bufferedWriter.write(txt.getBytes(), 0, txt.length()); bufferedWriter.flush(); } catch (IOException e) { return false; } return true; } /* Buffered socket image read. Since we don't know how much data was read from a previous socket operation we have to add in any previously read information that may still be in our buffer. We detect the end of XML messages by the tag but this may require reading in part of the image data that follows a message. Thus when reading the image data we have to move previously read data to the front of the buffer and continuing reading in the complete image size from that point. */ public int readImageData(byte pixels[], int len) { int num; // check if we have any information left from the previous read num = lastDataSize-lastDataTop; if (num>len) { System.arraycopy(pixels, lastDataTop, buffer, 0, len); lastDataTop+=num; return num; } System.arraycopy(pixels, lastDataTop, buffer, 0, num); len-=num; lastDataSize=lastDataTop=0; // then keep reading until we're read in the entire image length do { int res; try { res = bufferedReader.read(pixels, num, len); } catch (IOException e) { return 0; } if (res<0) { lastDataSize=lastDataTop=0; return -1; } num+=res; len-=res; } while (len>0); return num; } /* If an image is too large for the provided buffer the rest of the data needs to be skipped so we can continue to interact with the XML API. This routine will remove that additional data from the socket*/ public int skipData(int len) { int num; // check if we have any information left from the previous read num = lastDataSize-lastDataTop; if (num>len) { lastDataTop+=num; return num; } len-=num; lastDataSize=lastDataTop=0; try { bufferedReader.skip(len); } catch (IOException e) { return 0; } return num+len; } /* Read's in an XML message from the RoboRealm Server. The message is always delimited by a tag. We need to keep reading in information until this tag is seen. Sometimes this will accidentally read more than needed into the buffer such as when the message is followed by image data. We need to keep this information for the next readImage call.*/ private String readMessage() { int num=0; byte delimiter[] = "".getBytes(); int top=0; int i; // read in blocks of data looking for the delimiter while (true) { int res; try { res = bufferedReader.read(buffer, num, 4096-num); } catch (IOException e) { System.out.println(e.getMessage()); return null; } if (res<0) { lastDataSize=lastDataTop=0; return null; } lastDataSize=num+res; for (i=num;i=delimiter.length) { num=i+1; buffer[num]=0; lastDataTop=num; return new String(buffer, 0, num); } } else top=0; } num+=res; } } /******************************************************************************/ /* API Routines */ /******************************************************************************/ /* Returns the current image dimension */ public Dimension getDimension() { if (!connected) return null; if (send("")) { // read in variable length String buffer; if ((buffer = readMessage())!=null) { if (xml.parse(buffer)) { return new Dimension(xml.getInt("response.width"), xml.getInt("response.height")); } } } return null; } /* Returns the current processed image as a Java image. */ public int[] getImage(String name) { if (!connected) return null; if (name==null) name=""; // create the message request if (send(""+escape(name)+"")) { String buffer; // read in response which contains image information if ((buffer=readMessage())!=null) { // parse image width and height xml.parse(buffer); int len = xml.getInt("response.length"); width = xml.getInt("response.width"); height = xml.getInt("response.height"); // ensure that we have enough room in pixels byte pixels[] = new byte[len]; // actual image data follows the message if (readImageData(pixels, len)==len) { //DataBuffer db = new DataBufferByte(pixels, width*height*3, 0); //WritableRaster raster = Raster.createWritableRaster(BufferedImage.TYPE_3BYTE_BGR, db, null); //return new BufferedImage(ColorModel.getRGBdefault(), raster, false, null); int pixelInts[] = new int[width*height]; int l = width*height*3; int i,j; for (j=i=0;i"+escape(name)+"")) { String buffer; // read in response which contains image information if ((buffer=readMessage())!=null) { // parse image width and height xml.parse(buffer); int len = xml.getInt("response.length"); int width = xml.getInt("response.width"); int height = xml.getInt("response.height"); // ensure that we have enough room in pixels if (len>max) { skipData(len); return null; } // actual image data follows the message if (readImageData(pixels, len)==len) return new Dimension(width, height); } } return null; } /* Sets the current source image. pixels - input - contains RGB 8 bit byte. width - input - contains grabbed image width height - input - contains image height */ public boolean setImage(byte pixels[], int width, int height) { return setImage(null, pixels, width, height); } /* Sets the current source image. name - input - the name of the image to set. Can be source or marker name pixels - input - contains RGB 8 bit byte. width - input - contains grabbed image width height - input - contains image height */ public boolean setImage(String name, byte pixels[], int width, int height) { if (!connected) return false; if (name==null) name=""; // setup the message request if (send(""+escape(name)+""+width+"" +height+"RGB1")) { // send the RGB triplet pixels after message try { bufferedWriter.write(pixels, 0, width*height*3); } catch (IOException e) { return false; } // read message response String buffer; if ((buffer = readMessage())!=null) { if (buffer.equals("ok")) return true; } } return false; } /* Returns the value of the specified variable. name - input - the name of the variable to query result - output - contains the current value of the variable max - input - the maximum size of what the result can hold */ public String getVariable(String name) { if (!connected) return null; if ((name==null)||(name.length()==0)) return null; if (send(""+escape(name)+"")) { // read in variable length String buffer; if ((buffer=readMessage())!=null) { if (xml.parse(buffer)) { return xml.getFirst(); } } } return null; } /* Returns the value of the specified variables. name - input - the names of the variable to query result - output - contains the current values of the variables max - input - the maximum size of what the result can hold */ public Vector getVariables(String names) { if (!connected) return null; if ((names==null)||(names.length()==0)) return null; if (send(""+escape(names)+"")) { String buffer; if ((buffer=readMessage())!=null) { return xml.parseVector(buffer); } } return null; } /* Sets the value of the specified variable. name - input - the name of the variable to set value - input - contains the current value of the variable to be set */ public boolean setVariable(String name, String value) { if (!connected) return false; if ((name==null)||(name.length()==0)) return false; if (send(""+escape(name)+""+escape(value)+"")) { // read in confirmation String buffer; if ((buffer=readMessage())!=null) { if (buffer.equals("ok")) return true; } } return false; } /* Sets the value of the specified variables. names - input - the name of the variable to set values - input - contains the current value of the variable to be set */ public boolean setVariables(String names[], String values[], int num) { if (!connected) return false; if ((names==null)||(values==null)||(names[0].length()==0)) return false; int j=0; int i; StringBuffer sb = new StringBuffer(); // create request message sb.append(""); for (i=0;(i"); sb.append(escape(names[i])); sb.append(""); sb.append(escape(values[i])); sb.append(""); } sb.append(""); // send that message to RR Server if (send(sb.toString())) { // read in confirmation String buffer; if ((buffer=readMessage())!=null) { if (buffer.equals("ok")) return true; } } return false; } /* Deletes the specified variable name - input - the name of the variable to delete */ public boolean deleteVariable(String name) { if (!connected) return false; if ((name==null)||(name.length()==0)) return false; if (send(""+escape(name)+"")) { // read in variable length String buffer; if ((buffer=readMessage())!=null) { if (buffer.equals("ok")) return true; } } return false; } /* Executes the provided image processing pipeline source - the XML .robo file string */ public boolean execute(String source) { if (!connected) return false; if ((source==null)||(source.length()==0)) return false; //send the string if (send(""+escape(source)+"")) { // read in result String buffer; if ((buffer=readMessage())!=null) { if (buffer.equals("ok")) return true; } } return false; } /* Executes the provided .robo file. Note that the file needs to be on the machine running RoboRealm. This is similar to pressing the 'open program' button in the main RoboRealm dialog. filename - the XML .robo file to run */ public boolean loadProgram(String filename) { if (!connected) return false; if ((filename==null)||(filename.length()==0)) return false; if (send(""+escape(filename)+"")) { String buffer; if ((buffer=readMessage())!=null) { if (buffer.equals("ok")) return true; } } return false; } /* Loads an image into RoboRealm. Note that the image needs to exist on the machine running RoboRealm. The image format must be one that RoboRealm using the freeimage.dll component supports. This includes gif, pgm, ppm, jpg, png, bmp, and tiff. This is similar to pressing the 'load image' button in the main RoboRealm dialog. name - name of the image. Can be "source" or a marker name, filename - the filename of the image to load */ public boolean loadImage(String name, String filename) { if (!connected) return false; if ((filename==null)||(filename.length()==0)) return false; if ((name==null)||(name.length()==0)) name="source"; if (send(""+escape(filename)+""+escape(name)+"")) { String buffer; if ((buffer=readMessage())!=null) { if (buffer.equals("ok")) return true; } } return false; } /* Saves the specified image in RoboRealm to disk. Note that the filename is relative to the machine that is running RoboRealm. The image format must be one that RoboRealm using the freeimage.dll component supports. This includes gif, pgm, ppm, jpg, png, bmp, and tiff. This is similar to pressing the 'save image' button in the main RoboRealm dialog. name - name of the image. Can be "source","processed", or a marker name, filename - the filename of the image to save */ public boolean saveImage(String source, String filename) { if (!connected) return false; if ((filename==null)||(filename.length()==0)) return false; if ((source==null)||(source.length()==0)) source="processed"; // create the save image message if (send(""+escape(filename)+""+escape(source)+"")) { String buffer; if ((buffer=readMessage())!=null) { if (buffer.equals("ok")) return true; } } return false; } /* Sets the current camera driver. This can be used to change the current viewing camera to another camera installed on the same machine. Note that this is a small delay when switching between cameras. The specified name needs only to partially match the camera driver name seen in the dropdown picklist in the RoboRealm options dialog. For example, specifying "Logitech" will select any installed Logitech camera including "Logitech QuickCam PTZ". */ public boolean setCamera(String name) { if (!connected) return false; if ((name==null)||(name.length()==0)) return false; // create the save image message if (send(""+escape(name)+"")) { String buffer; if ((buffer=readMessage())!=null) { if (buffer.equals("ok")) return true; } } return false; } /* This routine provides a way to stop processing incoming video. Some image processing tasks can be very CPU intensive and you may only want to enable processing when required but otherwise not process any incoming images to release the CPU for other tasks. The run mode can also be used to processing individual frames or only run the image processing pipeline for a short period. This is similar to pressing the "run" button in the main RoboRealm dialog. mode - can be toggle, on, off, once, or a number of frames to process */ public boolean run(String mode) { if (!connected) return false; if ((mode==null)||(mode.length()==0)) return false; // create the save image message if (send(""+escape(mode)+"")) { String buffer; if ((buffer=readMessage())!=null) { if (buffer.equals("ok")) return true; } } return false; } /* There is often a need to pause your own Robot Controller program to wait for RoboRealm to complete its task. The eaisest way to accomplish this is to wait on a specific variable that is set to a specific value by RoboRealm. Using the waitVariable routine you can pause processing and then continue when a variable changes within RoboRealm. name - name of the variable to wait for value - the value of that variable which will cancel the wait timeout - the maximum time to wait for the variable value to be set */ public boolean waitVariable(String name, String value, int timeout) { if (timeout==0) timeout=100000000; if (!connected) return false; if ((name==null)||(name.length()==0)) return false; if (send(""+escape(name)+""+escape(value)+""+timeout+"")) { try { handle.setSoTimeout(timeout); } catch (SocketException e) { return false; } String buffer; if ((buffer=readMessage())!=null) { try { handle.setSoTimeout(DEFAULT_TIMEOUT); } catch (SocketException e) { return false; } if (buffer.equals("ok")) return true; } try { handle.setSoTimeout(DEFAULT_TIMEOUT); } catch (SocketException e) { return false; } } return false; } /* If you are rapdily grabbing images you will need to wait inbetween each get_image for a new image to be grabbed from the video camera. The wait_image request ensures that a new image is available to grab. Without this routine you may be grabbing the same image more than once. */ public boolean waitImage(int timeout) { if (!connected) return false; if (send(""+timeout+"")) { String buffer; if ((buffer=readMessage())!=null) { if (buffer.equals("ok")) return true; } } return false; } /* If you are running RoboRealm on the same machine as your API program you can use this routine to start RoboRealm if it is not already running. filename - the path to RoboRealm on your machine */ //////////////////////////////////// Basic Image Load/Save routines //////////////////////// // Utility routine to save a basic PPM public boolean savePPM(String filename, byte buffer[], int width, int height) { try { FileOutputStream fos = new FileOutputStream(new File(filename)); String header = "P6\n"+width+" "+height+"\n255\n"; fos.write(header.getBytes(), 0, header.length()); fos.write(buffer, 0, width*height*3); fos.close(); } catch (Exception e) { return false; }; return true; } private String readLine(FileInputStream fis) { StringBuffer sb = new StringBuffer(); while (true) { try { int c = fis.read(); if (c=='\n') { if (sb.charAt(0)!='#') return sb.toString(); sb.setLength(0); } sb.append((char)c); } catch (Exception e) { return null; } } } // Utility routine to load a basic PPM. Note that this routine does NOT handle // comments and is only included as a quick example. public Dimension loadPPM(String filename, byte buffer[], int max) { int width=0, height=0; try { FileInputStream fis = new FileInputStream(new File(filename)); // read in P6 header skipping comments String header = readLine(fis); if (!header.equals("P6")) return null; // read in width height header skipping comments String size = readLine(fis); int ind = size.indexOf(' '); if (ind<0) return null; width = Integer.parseInt(size.substring(0, ind)); height = Integer.parseInt(size.substring(ind+1)); if ((width*height*3)>max) return null; fis.read(buffer, 0, width*height*3); fis.close(); } catch (Exception e) { return null; }; return new Dimension(width, height); } }