001package jmri.jmrix.loconet.Intellibox; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import jmri.jmrix.loconet.LnPacketizer; 005import jmri.jmrix.loconet.LocoNetMessage; 006import jmri.jmrix.loconet.LocoNetMessageException; 007import jmri.jmrix.loconet.LocoNetSystemConnectionMemo; 008 009/** 010 * Converts Stream-based I/O to/from LocoNet messages. The "LocoNetInterface" 011 * side sends/receives LocoNetMessage objects. The connection to a 012 * LnPortController is via a pair of *Streams, which then carry sequences of 013 * characters for transmission. 014 * <p> 015 * Messages come to this via the main GUI thread, and are forwarded back to 016 * listeners in that same thread. Reception and transmission are handled in 017 * dedicated threads by RcvHandler and XmtHandler objects. Those are internal 018 * classes defined here. The thread priorities are: 019 * <ul> 020 * <li> RcvHandler - at highest available priority 021 * <li> XmtHandler - down one, which is assumed to be above the GUI 022 * <li> (everything else) 023 * </ul> 024 * Some of the message formats used in this class are Copyright Digitrax, Inc. 025 * and used with permission as part of the JMRI project. That permission does 026 * not extend to uses in other software products. If you wish to use this code, 027 * algorithm or these message formats outside of JMRI, please contact Digitrax 028 * Inc for separate permission. 029 * 030 * @author Bob Jacobsen Copyright (C) 2001, 2010 031 */ 032public class IBLnPacketizer extends LnPacketizer { 033 034 @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", 035 justification = "Only used during system initialization") 036 public IBLnPacketizer() { 037 super(new LocoNetSystemConnectionMemo()); 038 echo = true; 039 } 040 041 /** 042 * Captive class to handle incoming characters. This is a permanent loop, 043 * looking for input messages in character form on the stream connected to 044 * the LnPortController via <code>connectPort</code>. 045 */ 046 class RcvHandler implements Runnable { 047 048 /** 049 * Remember the LnPacketizer object 050 */ 051 LnPacketizer trafficController; 052 053 @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", 054 justification = "single threaded during init; will eventually be replaced for multi-connection support") 055 public RcvHandler(LnPacketizer lt) { 056 trafficController = lt; 057 } 058 059 private byte readNextByteFromUSB() { 060 byte inbyte; 061 while (true) { 062 try { 063 inbyte = istream.readByte(); 064 return inbyte; 065 } catch (java.io.IOException e) { 066 continue; 067 } 068 } 069 } 070 071 @Override 072 public void run() { 073 074 int opCode; 075 while (true) { // loop permanently, program close will exit 076 try { 077 // start by looking for command - skip if bit not set 078 while (((opCode = (readNextByteFromUSB() & 0xFF)) & 0x80) == 0) { 079 if (log.isDebugEnabled()) { // Avoid building unneeded Strings 080 log.debug("Skipping: {}", Integer.toHexString(opCode)); 081 } 082 } 083 // here opCode is OK. Create output message 084 if (log.isDebugEnabled()) { // Avoid building unneeded Strings 085 log.debug("Start message with opcode: {}", Integer.toHexString(opCode)); 086 } 087 LocoNetMessage msg = null; 088 while (msg == null) { 089 try { 090 // Capture 2nd byte, always present 091 int byte2 = readNextByteFromUSB() & 0xFF; 092 //log.debug("Byte2: "+Integer.toHexString(byte2)); 093 if ((byte2 & 0x80) != 0) { 094 log.warn("LocoNet message with opCode: {} ended early. Byte2 is also an opcode: {}", Integer.toHexString(opCode), Integer.toHexString(byte2)); 095 opCode = byte2; 096 throw new LocoNetMessageException(); 097 } 098 // Decide length 099 switch ((opCode & 0x60) >> 5) { 100 case 0: 101 /* 2 byte message */ 102 103 msg = new LocoNetMessage(2); 104 break; 105 106 case 1: 107 /* 4 byte message */ 108 109 msg = new LocoNetMessage(4); 110 break; 111 112 case 2: 113 /* 6 byte message */ 114 115 msg = new LocoNetMessage(6); 116 break; 117 118 case 3: 119 /* N byte message */ 120 121 if (byte2 < 2) { 122 log.error("LocoNet message length invalid: {} opcode: {}", byte2, Integer.toHexString(opCode)); 123 } 124 msg = new LocoNetMessage(byte2); 125 break; 126 default: // can't happen with this code, but just in case... 127 throw new LocoNetMessageException("decode failure " + byte2); 128 } 129 // message exists, now fill it 130 msg.setOpCode(opCode); 131 msg.setElement(1, byte2); 132 int len = msg.getNumDataElements(); 133 //log.debug("len: "+len); 134 for (int i = 2; i < len; i++) { 135 // check for message-blocking error 136 int b = readNextByteFromUSB() & 0xFF; 137 //log.debug("char "+i+" is: "+Integer.toHexString(b)); 138 if ((b & 0x80) != 0) { 139 log.warn("LocoNet message with opCode: {} ended early. Expected length: {} seen length: {} unexpected byte: {}", Integer.toHexString(opCode), len, i, Integer.toHexString(b)); 140 opCode = b; 141 throw new LocoNetMessageException(); 142 } 143 msg.setElement(i, b); 144 } 145 } catch (LocoNetMessageException e) { 146 // retry by going around again 147 // opCode is set for the newly-started packet 148 msg = null; 149 continue; 150 } 151 } 152 // check parity 153 if (!msg.checkParity()) { 154 log.warn("Ignore LocoNet packet with bad checksum: {}", msg.toString()); 155 throw new LocoNetMessageException(); 156 } 157 // message is complete, dispatch it !! 158 { 159 if (log.isDebugEnabled()) { 160 log.debug("queue message for notification"); 161 } 162 final LocoNetMessage thisMsg = msg; 163 final LnPacketizer thisTc = trafficController; 164 // return a notification via the queue to ensure end 165 Runnable r = new Runnable() { 166 LocoNetMessage msgForLater = thisMsg; 167 LnPacketizer myTc = thisTc; 168 169 @Override 170 public void run() { 171 myTc.notify(msgForLater); 172 } 173 }; 174 javax.swing.SwingUtilities.invokeLater(r); 175 } 176 177 // done with this one 178 } catch (LocoNetMessageException e) { 179 // just let it ride for now 180 log.warn("run: unexpected LocoNetMessageException", e); 181 } // normally, we don't catch the unnamed Exception, but in this 182 // permanently running loop it seems wise. 183 catch (Exception e) { 184 log.warn("run: unexpected Exception", e); 185 } 186 } // end of permanent loop 187 } 188 } 189 190 /** 191 * Captive class to handle transmission 192 */ 193 class XmtHandler implements Runnable { 194 195 @Override 196 public void run() { 197 198 while (true) { // loop permanently 199 // any input? 200 try { 201 // get content; blocks until present 202 log.debug("check for input"); 203 204 byte msg[] = xmtList.take(); 205 206 // input - now send 207 try { 208 if (ostream != null) { 209 if (!controller.okToSend()) { 210 log.debug("LocoNet port not ready to receive"); 211 } 212 log.debug("start write to stream"); 213 214 // The Intellibox cannot handle messges over 4 bytes without 215 // stopping the sender via CTS/RTS hardware handshake 216 // While this should work already by using the normal hardware 217 // handshake - it doesn't seem to so we need to check/send/flush 218 // each byte to make sure we don't overflow the IB input buffer 219 for (int i = 0; i < msg.length; i++) { 220 while (!controller.okToSend()) { 221 Thread.yield(); 222 } 223 224 ostream.write(msg[i]); 225 ostream.flush(); 226 } 227 228 log.debug("end write to stream"); 229 messageTransmitted(msg); 230 } else { 231 // no stream connected 232 log.warn("sendLocoNetMessage: no connection established"); 233 } 234 } catch (java.io.IOException e) { 235 log.warn("sendLocoNetMessage: IOException: {}", e.toString()); 236 } 237 } catch (InterruptedException ie) { 238 return; // ending the thread 239 } 240 } 241 } 242 } 243 244 /** 245 * Invoked at startup to start the threads needed here. 246 */ 247 @Override 248 public void startThreads() { 249 int priority = Thread.currentThread().getPriority(); 250 log.debug("startThreads current priority = {} max available = " + Thread.MAX_PRIORITY + " default = " + Thread.NORM_PRIORITY + " min available = " + Thread.MIN_PRIORITY, priority); 251 252 // make sure that the xmt priority is no lower than the current priority 253 int xmtpriority = (Thread.MAX_PRIORITY - 1 > priority ? Thread.MAX_PRIORITY - 1 : Thread.MAX_PRIORITY); 254 // start the XmtHandler in a thread of its own 255 if (xmtHandler == null) { 256 xmtHandler = new XmtHandler(); 257 } 258 xmtThread = new Thread(xmtHandler, "LocoNet Intellibox transmit handler"); 259 log.debug("Xmt thread starts at priority {}", xmtpriority); 260 xmtThread.setDaemon(true); 261 xmtThread.setPriority(Thread.MAX_PRIORITY - 1); 262 xmtThread.start(); 263 264 // start the RcvHandler in a thread of its own 265 if (rcvHandler == null) { 266 rcvHandler = new RcvHandler(this); 267 } 268 rcvThread = new Thread(rcvHandler, "LocoNet Intellibox receive handler"); 269 rcvThread.setDaemon(true); 270 rcvThread.setPriority(Thread.MAX_PRIORITY); 271 rcvThread.start(); 272 273 } 274 275 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(IBLnPacketizer.class); 276}