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 log.debug("Skipping: {}", Integer.toHexString(opCode)); 080 } 081 // here opCode is OK. Create output message 082 log.debug("Start message with opcode: {}", Integer.toHexString(opCode)); 083 LocoNetMessage msg = null; 084 while (msg == null) { 085 try { 086 // Capture 2nd byte, always present 087 int byte2 = readNextByteFromUSB() & 0xFF; 088 //log.debug("Byte2: "+Integer.toHexString(byte2)); 089 if ((byte2 & 0x80) != 0) { 090 log.warn("LocoNet message with opCode: {} ended early. Byte2 is also an opcode: {}", Integer.toHexString(opCode), Integer.toHexString(byte2)); 091 opCode = byte2; 092 throw new LocoNetMessageException(); 093 } 094 // Decide length 095 switch ((opCode & 0x60) >> 5) { 096 case 0: 097 /* 2 byte message */ 098 099 msg = new LocoNetMessage(2); 100 break; 101 102 case 1: 103 /* 4 byte message */ 104 105 msg = new LocoNetMessage(4); 106 break; 107 108 case 2: 109 /* 6 byte message */ 110 111 msg = new LocoNetMessage(6); 112 break; 113 114 case 3: 115 /* N byte message */ 116 117 if (byte2 < 2) { 118 log.error("LocoNet message length invalid: {} opcode: {}", byte2, Integer.toHexString(opCode)); 119 } 120 msg = new LocoNetMessage(byte2); 121 break; 122 default: // can't happen with this code, but just in case... 123 throw new LocoNetMessageException("decode failure " + byte2); 124 } 125 // message exists, now fill it 126 msg.setOpCode(opCode); 127 msg.setElement(1, byte2); 128 int len = msg.getNumDataElements(); 129 //log.debug("len: "+len); 130 for (int i = 2; i < len; i++) { 131 // check for message-blocking error 132 int b = readNextByteFromUSB() & 0xFF; 133 //log.debug("char "+i+" is: "+Integer.toHexString(b)); 134 if ((b & 0x80) != 0) { 135 log.warn("LocoNet message with opCode: {} ended early. Expected length: {} seen length: {} unexpected byte: {}", Integer.toHexString(opCode), len, i, Integer.toHexString(b)); 136 opCode = b; 137 throw new LocoNetMessageException(); 138 } 139 msg.setElement(i, b); 140 } 141 } catch (LocoNetMessageException e) { 142 // retry by going around again 143 // opCode is set for the newly-started packet 144 msg = null; 145 continue; 146 } 147 } 148 // check parity 149 if (!msg.checkParity()) { 150 log.warn("Ignore LocoNet packet with bad checksum: {}", msg.toString()); 151 throw new LocoNetMessageException(); 152 } 153 // message is complete, dispatch it !! 154 log.trace("message complete: {}", msg); 155 156 // check if this message was supposed to be ignored 157 // sentList will be empty if preference "LoconetUpdateSlotOnMessageCreation" is not activated 158 if(trafficController.getSentList().contains(msg)) { 159 trafficController.getSentList().remove(msg); 160 log.trace("found packet {} in sentList, ignoring. {} packets in sentList remaining.", msg, trafficController.getSentList().size()); 161 } 162 else { 163 log.trace("queue message for notification: {}", msg); 164 165 final LocoNetMessage thisMsg = msg; 166 final LnPacketizer thisTc = trafficController; 167 // return a notification via the queue to ensure end 168 Runnable r = new Runnable() { 169 LocoNetMessage msgForLater = thisMsg; 170 LnPacketizer myTc = thisTc; 171 172 @Override 173 public void run() { 174 myTc.notify(msgForLater); 175 } 176 }; 177 javax.swing.SwingUtilities.invokeLater(r); 178 } 179 180 // done with this one 181 } catch (LocoNetMessageException e) { 182 // just let it ride for now 183 log.warn("run: unexpected LocoNetMessageException", e); 184 } // normally, we don't catch the unnamed Exception, but in this 185 // permanently running loop it seems wise. 186 catch (Exception e) { 187 log.warn("run: unexpected Exception", e); 188 } 189 } // end of permanent loop 190 } 191 } 192 193 /** 194 * Captive class to handle transmission 195 */ 196 class XmtHandler implements Runnable { 197 198 @Override 199 public void run() { 200 201 while (true) { // loop permanently 202 // any input? 203 try { 204 // get content; blocks until present 205 log.debug("check for input"); 206 207 byte msg[] = xmtList.take(); 208 209 // input - now send 210 try { 211 if (ostream != null) { 212 if (!controller.okToSend()) { 213 log.debug("LocoNet port not ready to receive"); 214 } 215 log.debug("start write to stream"); 216 217 // The Intellibox cannot handle messges over 4 bytes without 218 // stopping the sender via CTS/RTS hardware handshake 219 // While this should work already by using the normal hardware 220 // handshake - it doesn't seem to so we need to check/send/flush 221 // each byte to make sure we don't overflow the IB input buffer 222 for (int i = 0; i < msg.length; i++) { 223 while (!controller.okToSend()) { 224 Thread.yield(); 225 } 226 227 ostream.write(msg[i]); 228 ostream.flush(); 229 } 230 231 log.debug("end write to stream"); 232 messageTransmitted(msg); 233 } else { 234 // no stream connected 235 log.warn("sendLocoNetMessage: no connection established"); 236 } 237 } catch (java.io.IOException e) { 238 log.warn("sendLocoNetMessage: IOException: {}", e.toString()); 239 } 240 } catch (InterruptedException ie) { 241 return; // ending the thread 242 } 243 } 244 } 245 } 246 247 /** 248 * Invoked at startup to start the threads needed here. 249 */ 250 @Override 251 public void startThreads() { 252 int priority = Thread.currentThread().getPriority(); 253 log.debug("startThreads current priority = {} max available = " + Thread.MAX_PRIORITY + " default = " + Thread.NORM_PRIORITY + " min available = " + Thread.MIN_PRIORITY, priority); 254 255 // make sure that the xmt priority is no lower than the current priority 256 int xmtpriority = (Thread.MAX_PRIORITY - 1 > priority ? Thread.MAX_PRIORITY - 1 : Thread.MAX_PRIORITY); 257 // start the XmtHandler in a thread of its own 258 if (xmtHandler == null) { 259 xmtHandler = new XmtHandler(); 260 } 261 xmtThread = new Thread(xmtHandler, "LocoNet Intellibox transmit handler"); 262 log.debug("Xmt thread starts at priority {}", xmtpriority); 263 xmtThread.setDaemon(true); 264 xmtThread.setPriority(Thread.MAX_PRIORITY - 1); 265 xmtThread.start(); 266 267 // start the RcvHandler in a thread of its own 268 if (rcvHandler == null) { 269 rcvHandler = new RcvHandler(this); 270 } 271 rcvThread = new Thread(rcvHandler, "LocoNet Intellibox receive handler"); 272 rcvThread.setDaemon(true); 273 rcvThread.setPriority(Thread.MAX_PRIORITY); 274 rcvThread.start(); 275 276 } 277 278 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(IBLnPacketizer.class); 279}