Update of /cvsroot/jmri/jmri/jmrix/lenz In directory usw-pr-cvs1:/tmp/cvs-serv30764/jmri/jmrix/lenz Added Files: COPYING LenzCommandStation.java README XNetException.java XNetInterface.java XNetListener.java XNetMessage.java XNetMessageException.java XNetPacketizer.java XNetPortController.java XNetPowerManager.java XNetProgrammer.java XNetTrafficController.java XNetTrafficRouter.java XNetTurnout.java XNetTurnoutManager.java Log Message: Initial add of Lenz XpressNet support. THIS IS NOT YET COMPLETELY FUNCTIONAL. It does compile, build, run, etc, but there are various details still to be sorted. Note that this has a new structure, different from the other protocol packages. Its the first protocol implementation to (start to) use the new common NetMessage class, and push more functionality higher up in the class tree. --- NEW FILE: COPYING --- The Artistic License Preamble The intent of this document is to state the conditions under which a Package may be copied, such that the Copyright Holder maintains some semblance of artistic control over the development of the package, while giving the users of the package the right to use and distribute the Package in a more-or-less customary fashion, plus the right to make reasonable modifications. Definitions: * "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification. * "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder. * "Copyright Holder" is whoever is named in the copyright or copyrights for the package. * "You" is you, if you're thinking about copying or distributing this Package. * "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication charges, time of people involved, and so on. (You will not be required to justify it to the Copyright Holder, but only to the computing community at large as a market that must bear the fee.) * "Freely Available" means that no fee is charged for the item itself, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it. 1. You may make and give away verbatim copies of the source form of the Standard Version of this Package without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain or from the Copyright Holder. A Package modified in such a way shall still be considered the Standard Version. 3. You may otherwise modify your copy of this Package in any way, provided that you insert a prominent notice in each changed file stating how and when you changed that file, and provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or placing the modifications on a major archive site such as ftp.uu.net, or by allowing the Copyright Holder to include your modifications in the Standard Version of the Package. b) use the modified Package only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided, and provide a separate manual page for each non-standard executable that clearly documents how it differs from the Standard Version. d) make other distribution arrangements with the Copyright Holder. 4. You may distribute the programs of this Package in object code or executable form, provided that you do at least ONE of the following: a) distribute a Standard Version of the executables and library files, together with instructions (in the manual page or equivalent) on where to get the Standard Version. b) accompany the distribution with the machine-readable source of the Package with your modifications. c) accompany any non-standard executables with their corresponding Standard Version executables, giving the non-standard executables non-standard names, and clearly documenting the differences in manual pages (or equivalent), together with instructions on where to get the Standard Version. d) make other distribution arrangements with the Copyright Holder. 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge any fee you choose for support of this Package. You may not charge a fee for this Package itself. However, you may distribute this Package in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution provided that you do not advertise this Package as a product of your own. 6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this Package. 7. C or perl subroutines supplied by you and linked into this Package shall not be considered part of this Package. 8. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. --- NEW FILE: LenzCommandStation.java --- /* * LenzCommandStation.java */ package jmri.jmrix.lenz; /** * Defines standard operations for Dcc command stations. * * @author Bob Jacobsen Copyright (C) 2001 * @version $Revision: 1.1 $ */ public class LenzCommandStation implements jmri.jmrix.DccCommandStation { /** * Lenz does use a service mode */ public boolean getHasServiceMode() {return true;} /** * If this command station has a service mode, is the command * station currently in that mode? */ public boolean getInServiceMode() { return mInServiceMode; } /** * Provides the version string returned during the initial check. * This function is not yet implemented... **/ public String getVersionString() { return "<unknown>"; } /** * Remember whether or not in service mode **/ boolean mInServiceMode = false; /** * Generate a message to change turnout state */ public XNetMessage getTurnoutCommandMsg(int pNumber, boolean pClose, boolean pThrow, boolean pOn) throws jmri.JmriException { XNetMessage l = new XNetMessage(4); l.setOpCode(0x5); // compute address byte fields int hiadr = (pNumber-1)/4; int loadr = ((pNumber-1)-hiadr*4)*2; // load On/Off with on if (!pOn) loadr |= 0x40; if (pThrow) loadr |= 0x01; // we don't know how to command both states right now! if (pClose & pThrow) new jmri.JmriException("XPressNet turnout logic can't handle both THROWN and CLOSED yet"); // store and send l.setElement(1,hiadr); l.setElement(2,loadr); return l; } /** * If this is a turnout-type message, return address. Otherwise * return -1. * Note we only identify the command now; the reponse to a * request for status is not yet seen here. */ public int getTurnoutMsgAddr(XNetMessage pMsg) { if (isTurnoutCommand(pMsg)) { int a1 = pMsg.getElement(1); int a2 = pMsg.getElement(2); return (((a2 & 0xff) * 4) + (a1 & 0x6)/2 + 1); } else return -1; } /** * Is this a command to change turnout state? */ public boolean isTurnoutCommand(XNetMessage pMsg) { return pMsg.getOpCode()==0x05; } // start of programming messages public XNetMessage getEnterProgModeMsg() { XNetMessage m = new XNetMessage(1); return m; } public XNetMessage getExitProgModeMsg() { XNetMessage m = new XNetMessage(1); return m; } public XNetMessage getReadPagedCVMsg(int cv) { XNetMessage m = new XNetMessage(4); return m; } public XNetMessage getWritePagedCVMsg(int cv, int val) { XNetMessage m = new XNetMessage(8); return m; } public XNetMessage getReadRegisterMsg(int reg) { if (reg>8) log.error("register number too large: "+reg); XNetMessage m = new XNetMessage(8); return m; } public XNetMessage getWriteRegisterMsg(int reg, int val) { if (reg>8) log.error("register number too large: "+reg); XNetMessage m = new XNetMessage(6); return m; } static org.apache.log4j.Category log = org.apache.log4j.Category.getInstance(LenzCommandStation.class.getName()); } /* @(#)LenzCommandStation.java */ --- NEW FILE: README --- The jmrix.lenz package contains JMRI implementations for the Lenz system, particularly serial access to a LI100 The JMRI web page is located at http://jmri.sourceforge.net/ --- NEW FILE: XNetException.java --- /** * XNetException.java * * Description: <describe the XNetException class here> * @author Bob Jacobsen Copyright (C) 2001 * @version $Revision: 1.1 $ */ package jmri.jmrix.lenz; import jmri.JmriException; public class XNetException extends JmriException { public XNetException( String m ){ super( m ) ; } } /* @(#)XNetException.java */ --- NEW FILE: XNetInterface.java --- // XNetInterface package jmri.jmrix.lenz; /** * XNetInterface defines the general connection to a XNet layout. * <P> * Use this interface to send messages to a XNet layout. * Classes implementing the XNetListener interface can register * here to receive incoming XNet messages as events. *<P> * The jmri.jrmix.lenz.LnTrafficManager provides the first implementation * of this interface. *<P> * How do you locate an implemenation of this interface? That's an interesting * question. This is inherently XNet specific, so it would be inappropriate * to put it in the jmri.InterfaceManager. And Java interfaces can't have static * members, so we can't provide an implementation() member. For now, we use * a static implementation member in the LnTrafficManager implementation to locate * _any_ implementation; this clearly needs to be improved. *<P> * XNetListener implementations registering for traffic updates * cannot assume that messages will * be returned in any particular thread. See the XNetListener doc * for more background. * * @author Bob Jacobsen Copyright (C) 2001 * @see jmri.jmrix.lenz.LocoNetListener * @see jmri.jmrix.lenz.LnTrafficController * */ public interface XNetInterface { /* * Request a message be sent to the attached XNet. Return is immediate, * with the message being queued for eventual sending. If you're interested * in a reply, you need to register a XNetListener object to watch the * message stream. */ public void sendXNetMessage(XNetMessage msg); /** * Request notification of things happening on the XNet. *<P> * The same listener * can register multiple times with different masks. (Multiple registrations with * a single mask value are equivalent to a single registration) * Mask values are defined as class constants. Note that these are bit masks, * and should be OR'd, not added, if multiple values are desired. *<P> * The event notification contains the received message as source, not this object, * so that we can notify of an incoming message to multiple places and then move on. * * @param mask The OR of the key values of messages to be reported (to reduce traffic, * provide for listeners interested in different things) * * @param listener Object to be notified of new messages as they arrive. * */ void addXNetListener(int mask, XNetListener l); /* * Stop notification of things happening on the XNet. Note that mask and XNetListener * must match a previous request exactly. */ void removeXNetListener(int mask, XNetListener listener); /* * Check whether an implementation is operational. True indicates OK. */ public boolean status(); /** * Mask value to request notification of all incoming messages */ public static final int ALL = ~0; /** * Mask value to request notification of messages effecting slot status, including the programming slot */ public static final int SLOTINFO = 1; /** * Mask value to request notification of messages associated with programming */ public static final int PROGRAMMING = 2; /** * Mask value to request notification of messages indicating changes in turnout status */ public static final int TURNOUTS = 4; /** * Mask value to request notification of messages indicating changes in sensor status */ public static final int SENSORS = 8; /** * Mask value to request notification of messages associated with layout power */ public static final int POWER = 16; } /* @(#)XNetInterface.java */ --- NEW FILE: XNetListener.java --- // XNetListener.java package jmri.jmrix.lenz; /** * XNetListener provides the call-back interface for notification when a * new XNet message arrives from the layout. *<P> * Note that the XNetListener implementation cannot assume that messages will * be returned in any particular thread. We may eventually revisit this, as returning * messages in the Swing GUI thread would result in some simplification of client code. * We've not done that yet because we're not sure that deadlocks can be avoided in that * case. * * @author Bob Jacobsen Copyright (C) 2002 * @version $Revision: 1.1 $ */ public interface XNetListener extends java.util.EventListener{ /** * Member function that will be invoked by a XNetInterface implementation * to forward a XNet message from the layout. * * @param msg The received XNet message. Note that this same object * may be presented to multiple users. It should not be * modified here. */ public void message(XNetMessage msg); } /* @(#)XNetListener.java */ --- NEW FILE: XNetMessage.java --- // XNetMessage.java package jmri.jmrix.lenz; import java.io.Serializable; /** * Represents a single command or response on the XpressNet. *<P> * Content is represented with ints to avoid the problems with * sign-extension that bytes have, and because a Java char is * actually a variable number of bytes in Unicode. * * @author Bob Jacobsen Copyright (C) 2002 * @version $Revision: 1.1 $ * */ public class XNetMessage extends jmri.jmrix.NetMessage implements Serializable { /** Create a new object, representing a specific-length message. * @parameter len Total bytes in message, including opcode and error-detection byte. */ public XNetMessage(int len) { super(len); if (len>15||len<0) log.error("Invalid length in ctor: "+len); } // note that the opcode is part of the message, so we treat it // directly public void setOpCode(int i) { if (i>0xF || i<0) log.error("Opcode invalid: "+i); System.out.println(" test 1 "+i); setElement(0,((i*16)&0xF0)|((getNumDataElements()-2)&0xF)); System.out.println(" test 2 "+(((i*16)&0xF0)|((getNumDataElements()-2)&0xF))); System.out.println(" test 3 "+getElement(0)); } public int getOpCode() {return (getElement(0)/16)&0xF;} /** Get a String representation of the op code in hex */ public String getOpCodeHex() { return "0x"+Integer.toHexString(getOpCode()); } /** * check whether the message has a valid parity */ public boolean checkParity() { int len = getNumDataElements(); int chksum = 0x00; /* the seed */ int loop; for(loop = 0; loop < len-1; loop++) { // calculate contents for data part chksum ^= getElement(loop); } return ((chksum&0xFF) == getElement(len-1)); } public void setParity() { int len = getNumDataElements(); int chksum = 0x00; /* the seed */ int loop; for(loop = 0; loop < len-1; loop++) { // calculate contents for data part chksum ^= getElement(loop); } setElement(len-1, chksum&0xFF); } // decode messages of a particular form // create messages of a particular form // initialize logging static org.apache.log4j.Category log = org.apache.log4j.Category.getInstance(XNetMessage.class.getName()); } /* @(#)XNetMessage.java */ --- NEW FILE: XNetMessageException.java --- /** * XNetMessageException.java * * Description: <describe the JmriException class here> * @author Bob Jacobsen Copyright (C) 2002 * @version $Revision: 1.1 $ */ package jmri.jmrix.lenz; import jmri.JmriException; public class XNetMessageException extends JmriException { public XNetMessageException(String s) { super(s); } public XNetMessageException() {} } /* @(#)XNetMessageException.java */ --- NEW FILE: XNetPacketizer.java --- /** * XNetPacketizer.java */ package jmri.jmrix.lenz; import java.io.InputStream; import java.io.DataInputStream; import java.io.OutputStream; import com.sun.java.util.collections.LinkedList; import com.sun.java.util.collections.NoSuchElementException; import java.util.Vector; /** * Converts Stream-based I/O to/from XNet messages. The "XNetInterface" * side sends/receives XNetMessage objects. The connection to * a XNetPortController is via a pair of *Streams, which then carry sequences * of characters for transmission. *<P> * Messages come to this via the main GUI thread, and are forwarded back to * listeners in that same thread. Reception and transmission are handled in * dedicated threads by RcvHandler and XmtHandler objects. Those are internal * classes defined here. The thread priorities are: *<P><UL> *<LI> RcvHandler - at highest available priority *<LI> XmtHandler - down one, which is assumed to be above the GUI *<LI> (everything else) *</UL> * * @author Bob Jacobsen Copyright (C) 2001 * @version $Revision: 1.1 $ * */ public class XNetPacketizer extends XNetTrafficController { public XNetPacketizer(LenzCommandStation pCommandStation) { super(pCommandStation); self=this; } // The methods to implement the XNetNetInterface protected Vector listeners = new Vector(); public boolean status() { return (ostream != null & istream != null); } public synchronized void addXNetListener(int mask, XNetListener l) { // add only if not already registered if (l == null) throw new java.lang.NullPointerException(); if (!listeners.contains(l)) { listeners.addElement(l); } } public synchronized void removeXNetListener(int mask, XNetListener l) { if (listeners.contains(l)) { listeners.removeElement(l); } } /** * Synchronized list used as a transmit queue */ LinkedList xmtList = new LinkedList(); /** * XmtHandler (a local class) object to implement the transmit thread */ XmtHandler xmtHandler = new XmtHandler(); /** * RcvHandler (a local class) object to implement the receive thread */ RcvHandler rcvHandler = new RcvHandler(this); /** * Forward a preformatted XNetMessage to the actual interface. * * Checksum is computed and overwritten here, then the message * is converted to a byte array and queue for transmission * @param m Message to send; will be updated with CRC */ public void sendXNetMessage(XNetMessage m) { // set the error correcting code byte int len = m.getNumDataElements(); int chksum = 0xff; /* the seed */ int loop; for(loop = 0; loop < len-1; loop++) { // calculate contents for data part chksum ^= m.getElement(loop); } m.setElement(len-1, chksum); // checksum is last element of message // stream to port in single write, as that's needed by serial byte msg[] = new byte[len]; for (int i=0; i< len; i++) msg[i] = (byte) m.getElement(i); if (log.isDebugEnabled()) log.debug("queue LocoNet packet: "+m.toString()); // in an atomic operation, queue the request and wake the xmit thread synchronized(xmtHandler) { xmtList.addLast(msg); xmtHandler.notify(); } } // methods to connect/disconnect to a source of data in a XNetPortController private XNetPortController controller = null; /** * Make connection to existing XNetPortController object. * @param p Port controller for connected. Save this for a later * disconnect call */ public void connectPort(XNetPortController p) { istream = p.getInputStream(); ostream = p.getOutputStream(); if (controller != null) log.warn("connectPort: connect called while connected"); controller = p; } /** * Break connection to existing LnPortController object. Once broken, * attempts to send via "message" member will fail. * @param p previously connected port */ public void disconnectPort(XNetPortController p) { istream = null; ostream = null; if (controller != p) log.warn("disconnectPort: disconnect called from non-connected XNetPortController"); controller = null; } // data members to hold the streams DataInputStream istream = null; OutputStream ostream = null; /** * Forward a XNetMessage to all registered listeners. * @param m Message to forward. Listeners should not modify it! */ protected void notify(XNetMessage m) { // make a copy of the listener vector to synchronized not needed for transmit Vector v; synchronized(this) { v = (Vector) listeners.clone(); } if (log.isDebugEnabled()) log.debug("notify of incoming XNet packet: "+m.toString()); // forward to all listeners int cnt = v.size(); for (int i=0; i < cnt; i++) { XNetListener client = (XNetListener) listeners.elementAt(i); client.message(m); } } /** * Handle incoming characters. This is a permanent loop, * looking for input messages in character form on the * stream connected to the XNetPortController via <code>connectPort</code>. * Terminates with the input stream breaking out of the try block. */ public void run() { int opCode; while (true) { // loop permanently, program close will exit try { // start by looking for command - skip if bit not set while ( ((opCode = (istream.readByte()&0xFF)) & 0x80) == 0 ) { //log.debug("Skipping: "+Integer.toHexString(opCode)); } // here opCode is OK. Create output message // log.debug("Start message with opcode: "+Integer.toHexString(opCode)); XNetMessage msg = null; while (msg == null) { try { // Capture 2nd byte, always present int byte2 = istream.readByte()&0xFF; //log.debug("Byte2: "+Integer.toHexString(byte2)); // Decide length switch((opCode & 0x60) >> 5) { case 0: /* 2 byte message */ msg = new XNetMessage(2); break; case 1: /* 4 byte message */ msg = new XNetMessage(4); break; case 2: /* 6 byte message */ msg = new XNetMessage(6); break; case 3: /* N byte message */ if (byte2<2) log.error("LocoNet message length invalid: "+byte2 +" opcode: "+Integer.toHexString(opCode)); msg = new XNetMessage(byte2); break; } // message exists, now fill it msg.setOpCode(opCode); msg.setElement(1, byte2); int len = msg.getNumDataElements(); //log.debug("len: "+len); for (int i = 2; i < len; i++) { // check for message-blocking error int b = istream.readByte()&0xFF; //log.debug("char "+i+" is: "+Integer.toHexString(b)); if ( (b&0x80) != 0) { log.warn("LocoNet message with opCode: " +Integer.toHexString(opCode) +" ended early. Expected length: "+len +" seen length: "+i +" unexpected byte: " +Integer.toHexString(b)); opCode = b; throw new XNetMessageException(); } msg.setElement(i, b); } } catch (XNetMessageException e) { // retry by destroying the existing message // opCode is set for the newly-started packet msg = null; } } // check parity if (!msg.checkParity()) { log.warn("Ignore packet with bad checksum: "+msg.toString()); throw new XNetMessageException(); } // message is complete, dispatch it !! { final XNetMessage thisMsg = msg; final XNetPacketizer thisTC = this; // return a notification via the queue to ensure end Runnable r = new Runnable() { XNetMessage msgForLater = thisMsg; XNetPacketizer myTC = thisTC; public void run() { myTC.notify(msgForLater); } }; javax.swing.SwingUtilities.invokeLater(r); } // done with this one } catch (XNetMessageException e) { // just let it ride for now } catch (java.io.EOFException e) { // posted from idle port when enableReceiveTimeout used log.debug("EOFException, is serial I/O using timeouts?"); } catch (java.io.IOException e) { // fired when write-end of HexFile reaches end log.debug("IOException, should only happen with HexFIle: "+e); log.info("End of file"); disconnectPort(controller); return; } catch (Exception e) { log.warn("run: unexpected exception: "+e); } } // end of permanent loop } /** * Captive class to handle incoming characters. This is a permanent loop, * looking for input messages in character form on the * stream connected to the LnPortController via <code>connectPort</code>. */ class RcvHandler implements Runnable { /** * Remember the Packetizer object */ XNetPacketizer trafficController; public RcvHandler(XNetPacketizer lt) { trafficController = lt; } public void run() { boolean debug = log.isDebugEnabled(); int opCode; while (true) { // loop permanently, program close will exit try { // start by looking for command - skip if bit not set while ( ((opCode = (istream.readByte()&0xFF)) & 0x80) == 0 ) { if (debug) log.debug("Skipping: "+Integer.toHexString(opCode)); } // here opCode is OK. Create output message if (debug) log.debug("Start message with opcode: "+Integer.toHexString(opCode)); XNetMessage msg = null; while (msg == null) { try { // Capture 2nd byte, always present int byte2 = istream.readByte()&0xFF; //log.debug("Byte2: "+Integer.toHexString(byte2)); // Decide length switch((opCode & 0x60) >> 5) { case 0: /* 2 byte message */ msg = new XNetMessage(2); break; case 1: /* 4 byte message */ msg = new XNetMessage(4); break; case 2: /* 6 byte message */ msg = new XNetMessage(6); break; case 3: /* N byte message */ if (byte2<2) log.error("LocoNet message length invalid: "+byte2 +" opcode: "+Integer.toHexString(opCode)); msg = new XNetMessage(byte2); break; } // message exists, now fill it msg.setOpCode(opCode); msg.setElement(1, byte2); int len = msg.getNumDataElements(); //log.debug("len: "+len); for (int i = 2; i < len; i++) { // check for message-blocking error int b = istream.readByte()&0xFF; //log.debug("char "+i+" is: "+Integer.toHexString(b)); if ( (b&0x80) != 0) { log.warn("LocoNet message with opCode: " +Integer.toHexString(opCode) +" ended early. Expected length: "+len +" seen length: "+i +" unexpected byte: " +Integer.toHexString(b)); opCode = b; throw new XNetMessageException(); } msg.setElement(i, b); } } catch (XNetMessageException e) { // retry by destroying the existing message // opCode is set for the newly-started packet msg = null; } } // check parity if (!msg.checkParity()) { log.warn("Ignore packet with bad checksum: "+msg.toString()); throw new XNetMessageException(); } // message is complete, dispatch it !! { if (log.isDebugEnabled()) log.debug("queue message for notification"); final XNetMessage thisMsg = msg; final XNetPacketizer thisTC = trafficController; // return a notification via the queue to ensure end Runnable r = new Runnable() { XNetMessage msgForLater = thisMsg; XNetPacketizer myTC = thisTC; public void run() { myTC.notify(msgForLater); } }; javax.swing.SwingUtilities.invokeLater(r); } // done with this one } catch (XNetMessageException e) { // just let it ride for now log.warn("run: unexpected MessageException: "+e); } catch (java.io.EOFException e) { // posted from idle port when enableReceiveTimeout used if (debug) log.debug("EOFException, is serial I/O using timeouts?"); } catch (java.io.IOException e) { // fired when write-end of HexFile reaches end if (debug) log.debug("IOException, should only happen with HexFIle: "+e); log.info("End of file"); disconnectPort(controller); return; } // normally, we don't catch the unnamed Exception, but in this // permanently running loop it seems wise. catch (Exception e) { log.warn("run: unexpected Exception: "+e); } } // end of permanent loop } } /** * Captive class to handle transmission */ class XmtHandler implements Runnable { public void run() { boolean debug = log.isDebugEnabled(); while (true) { // loop permanently // any input? try { // get content; failure is a NoSuchElementException if (debug) log.debug("check for input"); byte msg[] = null; synchronized (this) { msg = (byte[])xmtList.removeFirst(); } // input - now send try { if (ostream != null) { if (!controller.okToSend()) log.warn("LocoNet port not ready to receive"); if (debug) log.debug("start write to stream"); ostream.write(msg); if (debug) log.debug("end write to stream"); } else { // no stream connected log.warn("send message: no connection established"); } } catch (java.io.IOException e) { log.warn("send message: IOException: "+e.toString()); } } catch (NoSuchElementException e) { // message queue was empty, wait for input if (debug) log.debug("start wait"); try { synchronized(this) { wait(); } } catch (java.lang.InterruptedException ei) {} if (debug) log.debug("end wait"); } } } } /** * Invoked at startup to start the threads needed here. */ public void startThreads() { int priority = Thread.currentThread().getPriority(); log.debug("startThreads current priority = "+priority+ " max available = "+Thread.MAX_PRIORITY+ " default = "+Thread.NORM_PRIORITY+ " min available = "+Thread.MIN_PRIORITY); // make sure that the xmt priority is no lower than the current priority int xmtpriority = (Thread.MAX_PRIORITY-1>priority ? Thread.MAX_PRIORITY-1 : Thread.MAX_PRIORITY); // start the XmtHandler in a thread of its own Thread xmtThread = new Thread(xmtHandler, "XNet transmit handler"); log.debug("Xmt thread starts at priority "+xmtpriority); xmtThread.setPriority(Thread.MAX_PRIORITY-1); xmtThread.start(); // start the RcvHandler in a thread of its own Thread rcvThread = new Thread(rcvHandler, "XNet receive handler"); rcvThread.setPriority(Thread.MAX_PRIORITY); rcvThread.start(); } static org.apache.log4j.Category log = org.apache.log4j.Category.getInstance(XNetPacketizer.class.getName()); } /* @(#)XNetPacketizer.java */ --- NEW FILE: XNetPortController.java --- /** * XNetPortController.java * * Description: Abstract base for classes representing a XNet communications port * @author Bob Jacobsen Copyright (C) 2001 * @version $Revision: 1.1 $ */ package jmri.jmrix.lenz; import java.io.DataInputStream; import java.io.DataOutputStream; public abstract class XNetPortController { // base class. Implementations will provide InputStream and OutputStream // objects to XNetTrafficController classes, who in turn will deal in messages. // returns the InputStream from the port public abstract DataInputStream getInputStream(); // returns the outputStream to the port public abstract DataOutputStream getOutputStream(); /** * Check that this object is ready to operate. This is a question * of configuration, not transient hardware status. */ public abstract boolean status(); /** * Can the port accept additional characters? This might * go false for short intervals, but it might also stick * off if something goes wrong. */ public abstract boolean okToSend(); } /* @(#)XNetPortController.java */ --- NEW FILE: XNetPowerManager.java --- /** * XNetPowerManager.java * * Description: PowerManager implementation for controlling layout power * @author Bob Jacobsen Copyright (C) 2001 * @version $Revision: 1.1 $ */ package jmri.jmrix.lenz; import jmri.JmriException; import jmri.PowerManager; import java.beans.PropertyChangeListener; public class XNetPowerManager implements PowerManager, XNetListener { public XNetPowerManager() { // connect to the TrafficManager tc = XNetTrafficController.instance(); tc.addXNetListener(~0, this); } int power = UNKNOWN; public void setPower(int v) throws JmriException { power = UNKNOWN; checkTC(); if (v==ON) { // send GPON XNetMessage l = new XNetMessage(2); l.setOpCode(jmri.jmrix.loconet.LnConstants.OPC_GPON); tc.sendXNetMessage(l); } else if (v==OFF) { // send GPOFF XNetMessage l = new XNetMessage(2); l.setOpCode(jmri.jmrix.loconet.LnConstants.OPC_GPOFF); tc.sendXNetMessage(l); } firePropertyChange("Power", null, null); } public int getPower() { return power;} // to free resources when no longer used public void dispose() throws JmriException { tc.removeXNetListener(~0, this); tc = null; } private void checkTC() throws JmriException { if (tc == null) throw new JmriException("attempt to use PowerManager after dispose"); } // to hear of changes java.beans.PropertyChangeSupport pcs = new java.beans.PropertyChangeSupport(this); public synchronized void addPropertyChangeListener(java.beans.PropertyChangeListener l) { pcs.addPropertyChangeListener(l); } protected void firePropertyChange(String p, Object old, Object n) { pcs.firePropertyChange(p,old,n);} public synchronized void removePropertyChangeListener(java.beans.PropertyChangeListener l) { pcs.removePropertyChangeListener(l); } XNetTrafficController tc = null; // to listen for status changes from net public void message(XNetMessage m) { if (m.getOpCode() == jmri.jmrix.loconet.LnConstants.OPC_GPON) { power = ON; firePropertyChange("Power", null, null); } else if (m.getOpCode() == jmri.jmrix.loconet.LnConstants.OPC_GPOFF) { power = OFF; firePropertyChange("Power", null, null); } } } /* @(#)XNetPowerManager.java */ --- NEW FILE: XNetProgrammer.java --- /** * XNetProgrammer.java */ // Convert the jmri.Programmer interface into commands for the Lenz XpressNet package jmri.jmrix.lenz; import jmri.Programmer; import jmri.jmrix.AbstractProgrammer; import java.util.Vector; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeEvent; /** * Programmer support for Lenz XpressNet. * Currently only register and paged mode is implemented. * <P> * The read operation state sequence is: * <UL> * <LI>Send Register Mode / Paged mode read request * <LI>Wait for specific reply * <LI>Wait for Broadcast Service Mode Entry message * <LI>Send Request for Service Mode Results request * <LI>Wait for results reply, interpret * <LI>Send Resume Operations request * <LI>Wait for specific reply * <LI>Wait for Normal Operations Resumed broadcast * </UL> * @author Bob Jacobsen Copyright (c) 2002 * @version $Revision: 1.1 $ */ public class XNetProgrammer extends AbstractProgrammer implements XNetListener { public XNetProgrammer() { // error if more than one constructed? if (self != null) log.error("Creating too many XNetProgrammer objects"); // register this as the default, register as the Programmer self = this; jmri.InstanceManager.setProgrammer(this); } /* * method to find the existing NceProgrammer object, if need be creating one */ static public final XNetProgrammer instance() { if (self == null) self = new XNetProgrammer(); return self; } static XNetProgrammer self = null; // needs to be accessible from tests // handle mode protected int _mode = Programmer.PAGEMODE; /** * Switch to a new programming mode. Note that NCE can only * do register and page mode. If you attempt to switch to * any others, the new mode will set & notify, then * set back to the original. This lets the listeners * know that a change happened, and then was undone. * @param mode The new mode, use values from the jmri.Programmer interface */ public void setMode(int mode) { int oldMode = _mode; // preserve this in case we need to go back if (mode != _mode) { notifyPropertyChange("Mode", _mode, mode); _mode = mode; } if (_mode != Programmer.PAGEMODE && _mode != Programmer.REGISTERMODE) { // attempt to switch to unsupported mode, switch back to previous _mode = oldMode; notifyPropertyChange("Mode", mode, _mode); } } public int getMode() { return _mode; } // notify property listeners - see AbstractProgrammer for more protected void notifyPropertyChange(String name, int oldval, int newval) { // make a copy of the listener vector to synchronized not needed for transmit Vector v; synchronized(this) { v = (Vector) propListeners.clone(); } // forward to all listeners int cnt = v.size(); for (int i=0; i < cnt; i++) { PropertyChangeListener client = (PropertyChangeListener) v.elementAt(i); client.propertyChange(new PropertyChangeEvent(this, name, new Integer(oldval), new Integer(newval))); } } // members for handling the programmer interface int progState = 0; static final int NOTPROGRAMMING = 0;// is notProgramming static final int MODESENT = 1; // waiting reply to command to go into programming mode static final int COMMANDSENT = 2; // read/write command sent, waiting reply static final int RETURNSENT = 4; // waiting reply to go back to ops mode boolean _progRead = false; int _val; // remember the value being read/written for confirmative reply int _cv; // remember the cv being read/written // programming interface public void writeCV(int CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException { if (log.isDebugEnabled()) log.debug("writeCV "+CV+" listens "+p); useProgrammer(p); _progRead = false; // set commandPending state progState = MODESENT; _val = val; _cv = CV; // start the error timer startShortTimer(); // format and send message to go to program mode controller().sendXNetMessage(XNetTrafficController.instance() .getCommandStation().getEnterProgModeMsg()); } public void confirmCV(int CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException { readCV(CV, p); } public void readCV(int CV, jmri.ProgListener p) throws jmri.ProgrammerException { if (log.isDebugEnabled()) log.debug("readCV "+CV+" listens "+p); useProgrammer(p); _progRead = true; // set commandPending state // set commandPending state progState = MODESENT; _cv = CV; // start the error timer startShortTimer(); // format and send message to go to program mode controller().sendXNetMessage(XNetTrafficController.instance() .getCommandStation().getEnterProgModeMsg()); } private jmri.ProgListener _usingProgrammer = null; // internal method to remember who's using the programmer protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException { // test for only one! if (_usingProgrammer != null && _usingProgrammer != p) { if (log.isInfoEnabled()) log.info("programmer already in use by "+_usingProgrammer); throw new jmri.ProgrammerException("programmer in use"); } else { _usingProgrammer = p; return; } } // internal method to create the XNetMessage for programmer task start protected XNetMessage progTaskStart(int mode, int val, int cvnum) throws jmri.ProgrammerException { // val = -1 for read command; mode is direct, etc if (val < 0) { // read if (_mode == Programmer.PAGEMODE) return XNetTrafficController.instance() .getCommandStation().getReadPagedCVMsg(cvnum); else return XNetTrafficController.instance() .getCommandStation().getReadRegisterMsg(registerFromCV(cvnum)); } else { // write if (_mode == Programmer.PAGEMODE) return XNetTrafficController.instance() .getCommandStation().getWritePagedCVMsg(cvnum, val); else return XNetTrafficController.instance() .getCommandStation().getWriteRegisterMsg(registerFromCV(cvnum), val); } } public void message(XNetMessage m) { log.error("message received unexpectedly: "+m.toString()); } synchronized public void reply(XNetMessage m) { // was of reply type if (progState == NOTPROGRAMMING) { // we get the complete set of replies now, so ignore these if (log.isDebugEnabled()) log.debug("reply in NOTPROGRAMMING state"); return; } else if (progState == MODESENT) { if (log.isDebugEnabled()) log.debug("reply in MODESENT state"); // see if reply is the acknowledge of program mode; if not, wait // ... // here ready to send the read/write command progState = COMMANDSENT; // see why waiting try { startLongTimer(); if (_progRead) { // read was in progress - send read command controller().sendXNetMessage(progTaskStart(getMode(), -1, _cv)); } else { // write was in progress - send write command controller().sendXNetMessage(progTaskStart(getMode(), _val, _cv)); } } catch (Exception e) { // program op failed, go straight to end log.error("program operation failed, exception "+e); progState = RETURNSENT; controller().sendXNetMessage(XNetTrafficController.instance() .getCommandStation().getExitProgModeMsg()); } } else if (progState == COMMANDSENT) { if (log.isDebugEnabled()) log.debug("reply in COMMANDSENT state"); // operation done, capture result, then have to leave programming mode progState = RETURNSENT; // check for errors //if (m.match("NO FEEDBACK DETECTED") >= 0) { //if (log.isDebugEnabled()) log.debug("handle NO FEEDBACK DETECTED"); //// perhaps no loco present? Fail back to end of programming //progState = NOTPROGRAMMING; //controller().sendXNetMessage(XNetTrafficController.getInstance() // .getCommandStation().getExitProgMode(), this); //notifyProgListenerEnd(_val, jmri.ProgListener.NoLocoDetected); //} /*else*/ { // see why waiting if (_progRead) { // read was in progress - get return value _val = m.getElement(3); } startShortTimer(); controller().sendXNetMessage(XNetTrafficController.instance() .getCommandStation().getExitProgModeMsg()); } } else if (progState == RETURNSENT) { if (log.isDebugEnabled()) log.debug("reply in RETURNSENT state"); // all done, notify listeners of completion progState = NOTPROGRAMMING; stopTimer(); // if this was a read, we cached the value earlier. If its a // write, we're to return the original write value notifyProgListenerEnd(_val, jmri.ProgListener.OK); } else { if (log.isDebugEnabled()) log.debug("reply in un-decoded state"); } } /** * Internal routine to handle a timeout */ synchronized protected void timeout() { if (progState != NOTPROGRAMMING) { // we're programming, time to stop if (log.isDebugEnabled()) log.debug("timeout!"); // perhaps no loco present? Fail back to end of programming progState = NOTPROGRAMMING; controller().sendXNetMessage(XNetTrafficController.instance() .getCommandStation().getExitProgModeMsg()); notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout); } } // internal method to notify of the final result protected void notifyProgListenerEnd(int value, int status) { if (log.isDebugEnabled()) log.debug("notifyProgListenerEnd value "+value+" status "+status); // the programmingOpReply handler might send an immediate reply, so // clear the current listener _first_ jmri.ProgListener temp = _usingProgrammer; _usingProgrammer = null; temp.programmingOpReply(value, status); } XNetTrafficController _controller = null; protected XNetTrafficController controller() { // connect the first time if (_controller == null) { _controller = XNetTrafficController.instance(); } return _controller; } static org.apache.log4j.Category log = org.apache.log4j.Category.getInstance(XNetProgrammer.class.getName()); } /* @(#)XNetProgrammer.java */ --- NEW FILE: XNetTrafficController.java --- // XNetTrafficController.java package jmri.jmrix.lenz; import java.util.Vector; /** * Abstract base class for implementations of XNetInterface. *<P> * This provides just the basic interface, plus the "" static * method for locating the local implementation. * * @author Bob Jacobsen Copyright (C) 2002 * @version $Revision: 1.1 $ * */ public abstract class XNetTrafficController implements XNetInterface { /** * static function returning the TrafficController instance to use. * @return The registered TrafficController instance for general use, * if need be creating one. */ static public XNetTrafficController instance() { return self; } static protected XNetTrafficController self = null; /** * Must provide a LenzCommandStation reference at creation time * @return */ XNetTrafficController(LenzCommandStation pCommandStation) { mCommandStation = pCommandStation; } // Abstract methods for the XNetInterface abstract public boolean status(); /** * Forward a preformatted XNetMessage to the actual interface. * @param m Message to send; will be updated with CRC */ abstract public void sendXNetMessage(XNetMessage m); // The methods to implement adding and removing listeners protected Vector listeners = new Vector(); public synchronized void addXNetListener(int mask, XNetListener l) { // add only if not already registered if (l == null) throw new java.lang.NullPointerException(); if (!listeners.contains(l)) { listeners.addElement(l); } } public synchronized void removeXNetListener(int mask, XNetListener l) { if (listeners.contains(l)) { listeners.removeElement(l); } } /** * Forward a message to all registered listeners. * @param m Message to forward. Listeners should not modify it! */ protected void notify(XNetMessage m) { // make a copy of the listener vector to synchronized not needed for transmit Vector v; synchronized(this) { v = (Vector) listeners.clone(); } if (log.isDebugEnabled()) log.debug("notify of incoming LocoNet packet: "+m.toString()); // forward to all listeners int cnt = v.size(); for (int i=0; i < cnt; i++) { XNetListener client = (XNetListener) listeners.elementAt(i); client.message(m); } } /** Reference to the command station in communication here */ LenzCommandStation mCommandStation; /** get access to communicating command station object */ public LenzCommandStation getCommandStation() { return mCommandStation; } static org.apache.log4j.Category log = org.apache.log4j.Category.getInstance(XNetTrafficController.class.getName()); } /* @(#)XNetTrafficController.java */ --- NEW FILE: XNetTrafficRouter.java --- // XNetTrafficRouter.java package jmri.jmrix.lenz; import com.sun.java.util.collections.LinkedList; import com.sun.java.util.collections.NoSuchElementException; import java.util.Vector; /** * Implements a XNetInterface by doing a scatter-gather to * another, simpler implementation. * <P> * This is intended for remote operation, where only one copy of * each message should go to/from another node. By putting a * LnTrafficRouter implementation at the remote node, * all of the routing of messages to multiple consumers can be done * without traffic over the connection. * * @author Bob Jacobsen Copyright (C) 2002 * @version $Revision: 1.1 $ * */ public class XNetTrafficRouter extends XNetTrafficController implements XNetListener { public XNetTrafficRouter(LenzCommandStation pCommandStation) { super(pCommandStation); // set the instance to point here self=this; } // The methods to implement the XNetInterface for clients. // These use the parent implementations of listeners, addXNetListener, // removeXNetListener, notify boolean connected = false; public boolean status() { return connected; } /** * Forward a preformatted XNetMessage to the actual interface. * * @param m Message to send; will be updated with CRC */ public void sendXNetMessage(XNetMessage m) { destination.sendXNetMessage(m); } /** * Receive a XNet message from upstream and forward it to * all the local clients. */ public void message(XNetMessage m) { notify(m); } // methods to connect/disconnect to a source of data in another // XNetInterface private XNetInterface destination = null; /** * Make connection to existing XNetInterface object * for upstream communication. * @param i Interface to be connected */ public void connect(XNetInterface i) { destination = i; connected = true; i.addXNetListener(XNetInterface.ALL, this); } /** * Break connection to upstream LocoNetInterface object. Once broken, * attempts to send via "message" member will fail. * @param i previously connected interface */ public void disconnectPort(XNetInterface i) { if (destination != i) log.warn("disconnectPort: disconnect called from non-connected PortController"); destination = null; connected = false; } /** * Forward a XNetMessage to all registered listeners. * @param m Message to forward. Listeners should not modify it! */ protected void notify(XNetMessage m) { // make a copy of the listener vector to synchronized not needed for transmit Vector v; synchronized(this) { v = (Vector) listeners.clone(); } if (log.isDebugEnabled()) log.debug("notify of incoming packet: "+m.toString()); // forward to all listeners int cnt = v.size(); for (int i=0; i < cnt; i++) { XNetListener client = (XNetListener) listeners.elementAt(i); client.message(m); } } static org.apache.log4j.Category log = org.apache.log4j.Category.getInstance(XNetTrafficRouter.class.getName()); } /* @(#)XNetTrafficRouter.java */ --- NEW FILE: XNetTurnout.java --- /** * XNetTurnout.java * * Description: extend jmri.AbstractTurnout for XNet layouts * @author Bob Jacobsen Copyright (C) 2001 * @version $Revision: 1.1 $ */ package jmri.jmrix.lenz; import jmri.AbstractTurnout; public class XNetTurnout extends AbstractTurnout implements XNetListener { public XNetTurnout(int pNumber) { // a human-readable turnout number must be specified! mNumber = pNumber; // At construction, register for messages XNetTrafficController.instance().addXNetListener(~0, this); } public int getNumber() { return mNumber; } public String getSystemName() { return "XT"+getNumber(); } // Handle a request to change state by sending a LocoNet command protected void forwardCommandChangeToLayout(int s) throws jmri.JmriException { // find the command station LenzCommandStation cs = XNetTrafficController.instance().getCommandStation(); // get the right packet XNetMessage msg = cs.getTurnoutCommandMsg(mNumber, (s & CLOSED)!=0, (s & THROWN)!=0, true ); XNetTrafficController.instance().sendXNetMessage(msg); } // implementing classes will typically have a function/listener to get // updates from the layout, which will then call // public void firePropertyChange(String propertyName, // Object oldValue, // Object newValue) // _once_ if anything has changed state (or set the commanded state directly) public void message(XNetMessage l) { // check validity & addressing if (XNetTrafficController.instance() .getCommandStation() .getTurnoutMsgAddr(l) != mNumber) return; // is for this object, parse message type switch (l.getOpCode()) { case jmri.jmrix.loconet.LnConstants.OPC_SW_REQ: { /* page 9 of Loconet PE */ int sw2 = l.getElement(2); if (log.isDebugEnabled()) log.debug("SW_REQ received with valid address"); if ((sw2 & jmri.jmrix.loconet.LnConstants.OPC_SW_REQ_DIR)!=0) newCommandedState(CLOSED); else newCommandedState(THROWN); break; } case jmri.jmrix.loconet.LnConstants.OPC_SW_REP: { /* page 9 of Loconet PE */ int sw2 = l.getElement(2); if (log.isDebugEnabled()) log.debug("SW_REP received with valid address"); // see if its a turnout state report if ((sw2 & jmri.jmrix.loconet.LnConstants.OPC_SW_REP_INPUTS)==0) { // sort out states switch (sw2 & (jmri.jmrix.loconet.LnConstants.OPC_SW_REP_CLOSED|jmri.jmrix.loconet.LnConstants.OPC_SW_REP_THROWN)) { case jmri.jmrix.loconet.LnConstants.OPC_SW_REP_CLOSED: setKnownState(CLOSED); break; case jmri.jmrix.loconet.LnConstants.OPC_SW_REP_THROWN: setKnownState(THROWN); break; case jmri.jmrix.loconet.LnConstants.OPC_SW_REP_CLOSED|jmri.jmrix.loconet.LnConstants.OPC_SW_REP_THROWN: setKnownState(CLOSED+THROWN); break; default: setKnownState(0); break; } } } default: return; } // reach here only in error } public void dispose() { XNetTrafficController.instance().removeXNetListener(~0, this); } // data members int mNumber; // loconet turnout number static org.apache.log4j.Category log = org.apache.log4j.Category.getInstance(XNetTurnout.class.getName()); } /* @(#)XNetTurnout.java */ --- NEW FILE: XNetTurnoutManager.java --- /** * XNetTurnoutManager.java * * Description: Implement turnout manager * @author Bob Jacobsen Copyright (C) 2001 * @version $Revision: 1.1 $ */ // System names are "XTnnn", where nnn is the turnout number without padding. package jmri.jmrix.lenz; import jmri.JmriException; import jmri.Turnout; public class XNetTurnoutManager extends jmri.AbstractTurnoutManager implements XNetListener { // ABC implementations // to free resources when no longer used public void dispose() throws JmriException { } // XNet-specific methods public void putBySystemName(XNetTurnout t) { String system = "XT"+t.getNumber(); _tsys.put(system, t); } public Turnout newTurnout(String systemName, String userName) { // if system name is null, supply one from the number in userName if (systemName == null) systemName = "XT"+userName; // return existing if there is one Turnout t; if ( (userName!=null) && ((t = getByUserName(userName)) != null)) return t; if ( (t = getBySystemName(systemName)) != null) return t; // get number from name if (!systemName.startsWith("XT")) { log.error("Invalid system name for XPressNet turnout: "+systemName); return null; } int addr = Integer.valueOf(systemName.substring(2)).intValue(); t = new XNetTurnout(addr); t.setUserName(userName); _tsys.put(systemName, t); if (userName!=null) _tuser.put(userName, t); t.addPropertyChangeListener(this); return t; } // ctor has to register for XNet events public XNetTurnoutManager() { XNetTrafficController.instance().addXNetListener(~0, this); } // listen for turnouts, creating them as needed public void message(XNetMessage l) { // parse message type int addr = XNetTrafficController.instance() .getCommandStation().getTurnoutMsgAddr(l); if (addr<=0) return; // indicates no message // reach here for switch command; make sure we know about this one String s = "XT"+addr; if (null == getBySystemName(s)) { // need to store a new one XNetTurnout t = new XNetTurnout(addr); putBySystemName(t); } } static org.apache.log4j.Category log = org.apache.log4j.Category.getInstance(XNetTurnoutManager.class.getName()); } /* @(#)XNetTurnoutManager.java */ |