/* * Copyright (C) 2008 Manish Pandya, [manish at meetamanish dot com] * * 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 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 * */ package org.hooliguns.ninja.telnet.phiPiMod; import gnu.io.CommPort; import gnu.io.CommPortIdentifier; import gnu.io.NoSuchPortException; import gnu.io.PortInUseException; import gnu.io.SerialPort; import gnu.io.UnsupportedCommOperationException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.ParseException; import java.util.ArrayList; import org.hooliguns.ninja.telnet.Command; import org.hooliguns.ninja.telnet.CommandExecutionUnit; import org.hooliguns.ninja.telnet.CommandQueue; import org.hooliguns.ninja.telnet.ValueOutOfRangeException; /** * The command execution unit the implements interfacing with a Ninja that has * been modded with Phipi mods and is running his firmware. This implementation * uses direct gnu.io packagereadily be converted to use javax.comm API. * * @author Manish Pandya (July 1 2008) * */ public class PhiPiModCommandExecutionUnit extends CommandExecutionUnit { /** * The serial port output stream */ protected OutputStream out = null; /** * The serial port input stream */ protected InputStream in = null; /** * The Baud rate for serial port */ protected static int BAUD_RATE = 4800; /** * Creates an instance with command queue. * * @param commandQueue */ public PhiPiModCommandExecutionUnit(CommandQueue commandQueue) { super(commandQueue); BAUD_RATE = 4800; } /** * The initialization method that sets up the serial port and opens * communication channel with Ninja hardware. This method uses direct gnu.io * package but can easily be converted to use javax.comm api. * * @param portName */ public void init(String portName) { CommPortIdentifier portIdentifier; try { portIdentifier = CommPortIdentifier.getPortIdentifier(portName); if (portIdentifier.isCurrentlyOwned()) { System.out.println("Error: Port is currently in use"); } else { CommPort commPort = portIdentifier.open(this.getClass().getName(), 2000); if (commPort instanceof SerialPort) { SerialPort serialPort = (SerialPort) commPort; /* * 4800 baud rate, 8 bit, 1 stop bit, no parity and no hardware flow * control as per PhiPi's firmware. */ serialPort.setSerialPortParams(BAUD_RATE, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE); in = serialPort.getInputStream(); out = serialPort.getOutputStream(); } else { System.out .println("Error: Only serial ports are handled by this example."); } } } catch (NoSuchPortException e) { e.printStackTrace(); System.exit(1); } catch (PortInUseException e) { e.printStackTrace(); System.exit(1); } catch (UnsupportedCommOperationException e) { e.printStackTrace(); System.exit(1); } catch (IOException e) { e.printStackTrace(); System.exit(1); } } /** * A helper method to find the position of the last digit occurrence in a * substring of a string with integer represented in ascii. * * @param cmd * the original command string as passed to the parser, used just for * the exception * @param cmdchars * efficient array of chars of the same command string * @param startPos * where to begin looking for an integer * @return the last digit index; so that the output can directly be used in a * subSting call * @throws ParseException * if no integer is found at startPos */ private int getIntEndPos(String cmd, char[] cmdchars, int startPos) throws ParseException { int endPos = startPos; boolean hasAtleastOnedigit = false; if (endPos < cmdchars.length && '-' == cmdchars[endPos]) { endPos++; } while (endPos < cmdchars.length && Character.isDigit(cmdchars[endPos])) { hasAtleastOnedigit = true; endPos++; } if (!hasAtleastOnedigit) { throw new ParseException("Command '" + cmd.charAt(startPos - 1) + "' requires an integer following it", startPos); } return endPos; } /* * (non-Javadoc) * * @see org.hooliguns.ninja.telnet.CommandParser#parse(java.lang.String) */ public Command[] parse(String cmd) throws ParseException { ArrayList cmds = new ArrayList(); char[] chars = cmd.toUpperCase().toCharArray(); int currentPos = 0; try { while (currentPos < chars.length) { switch (chars[currentPos]) { case 'M': cmds.add(new Move()); currentPos++; break; case 'S': cmds.add(new Slew()); currentPos++; break; case 'I': cmds.add(new Increment()); currentPos++; break; case 'C': cmds.add(new Center()); currentPos++; break; case '(': cmds.add(new MacroBegin()); currentPos++; break; case ')': cmds.add(new MacroEnd()); currentPos++; break; case 'R': cmds.add(new Run()); currentPos++; break; case '\b': cmds.add(new BackSpace()); currentPos++; break; case '\u0012': cmds.add(new CntlR()); currentPos++; break; case 'X': { currentPos++; int endPos = getIntEndPos(cmd, chars, currentPos); cmds.add(new SetX(Integer.parseInt(cmd .substring(currentPos, endPos)))); currentPos = endPos; } break; case 'Y': { currentPos++; int endPos = getIntEndPos(cmd, chars, currentPos); cmds.add(new SetY(Integer.parseInt(cmd .substring(currentPos, endPos)))); currentPos = endPos; } break; case 'V': { currentPos++; int endPos = getIntEndPos(cmd, chars, currentPos); cmds.add(new Velocity(Integer.parseInt(cmd.substring(currentPos, endPos)))); currentPos = endPos; } break; case 'P': { currentPos++; int endPos = getIntEndPos(cmd, chars, currentPos); cmds.add(new Pause(Integer.parseInt(cmd.substring(currentPos, endPos)))); currentPos = endPos; } break; default: throw new ParseException("Unknown charector/command '" + cmd.charAt(currentPos) + "'", currentPos); } } } catch (ValueOutOfRangeException e) { throw new ParseException(e.getMessage(), currentPos); } /* * A very helpful diagnostic method is to use this: return new Command[] * {new CommandPassThrough(cmd)}; */ return cmds.toArray(new Command[] {}); } /* * (non-Javadoc) * * @see java.lang.Runnable#run() */ public void run() { runCommandQueue(); } /** * A method that executes commands in the FIFO queue as long as the unit * accepts it. PhiPi firmware has 16 byte internal buffer, everything after * that overflows. * * In future we could implement some sort of queue length and impose * consumption delay on the queue to virtually extend buffering. */ public void runCommandQueue() { Command c = null; try { while ((c = commandQueue.take()) != null) { c.execute(this); } } catch (InterruptedException e) { e.printStackTrace(); } } /* * (non-Javadoc) * * @see org.hooliguns.ninja.telnet.AbortExecutionListener#abortExecution() */ public void abortExecution() { commandQueue.clear(); commandQueue.add(new BackSpace()); } @Override public byte[] writeToHardware(byte[] bytes) { try { if (out != null) { out.write(bytes); out.flush(); } } catch (IOException e) { e.printStackTrace(); } return EMPTY_BYTE_ARRAY; } }