001package jmri.jmrix.loconet.loconetovertcp; 002 003import java.util.StringTokenizer; 004import jmri.jmrix.loconet.LnNetworkPortController; 005import jmri.jmrix.loconet.LnPacketizer; 006import jmri.jmrix.loconet.LocoNetMessage; 007import jmri.jmrix.loconet.LocoNetMessageException; 008import jmri.jmrix.loconet.LocoNetSystemConnectionMemo; 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012/** 013 * Converts Stream-based I/O over the LocoNetOverTcp system network 014 * connection to/from LocoNet messages. The "LocoNetInterface" 015 * side sends/receives LocoNetMessage objects. The connection to a 016 * LnPortnetworkController is via a pair of *Streams, which then carry sequences 017 * of characters for transmission. 018 * <p> 019 * Messages come to this via the main GUI thread, and are forwarded back to 020 * listeners in that same thread. Reception and transmission are handled in 021 * dedicated threads by RcvHandler and XmtHandler objects. Those are internal 022 * classes defined here. The thread priorities are: 023 * <ul> 024 * <li> RcvHandler - at highest available priority 025 * <li> XmtHandler - down one, which is assumed to be above the GUI 026 * <li> (everything else) 027 * </ul> 028 * 029 * Some of the message formats used in this class are Copyright Digitrax, Inc. 030 * and used with permission as part of the JMRI project. That permission does 031 * not extend to uses in other software products. If you wish to use this code, 032 * algorithm or these message formats outside of JMRI, please contact Digitrax 033 * Inc for separate permission. 034 * 035 * @author Bob Jacobsen Copyright (C) 2001 036 * @author Alex Shepherd Copyright (C) 2003, 2006 037 */ 038public class LnOverTcpPacketizer extends LnPacketizer { 039 040 static final String RECEIVE_PREFIX = "RECEIVE"; 041 static final String SEND_PREFIX = "SEND"; 042 043 public LnOverTcpPacketizer(LocoNetSystemConnectionMemo m) { 044 super(m); 045 xmtHandler = new XmtHandler(); 046 rcvHandler = new RcvHandler(this); 047 } 048 049 public LnNetworkPortController networkController = null; 050 051 @Override 052 public boolean isXmtBusy() { 053 if (networkController == null) { 054 return false; 055 } 056 return true; 057 } 058 059 /** 060 * Make connection to an existing LnPortnetworkController object. 061 * 062 * @param p Port networkController for connected. Save this for a later 063 * disconnect call 064 */ 065 public void connectPort(LnNetworkPortController p) { 066 istream = p.getInputStream(); 067 ostream = p.getOutputStream(); 068 if (networkController != null) { 069 log.warn("connectPort: connect called while connected"); 070 } 071 networkController = p; 072 } 073 074 /** 075 * Break connection to existing LnPortnetworkController object. Once broken, 076 * attempts to send via "message" member will fail. 077 * 078 * @param p previously connected port 079 */ 080 public void disconnectPort(LnNetworkPortController p) { 081 istream = null; 082 ostream = null; 083 if (networkController != p) { 084 log.warn("disconnectPort: disconnect called from non-connected LnPortnetworkController"); 085 } 086 networkController = null; 087 } 088 089 /** 090 * Captive class to handle incoming characters. This is a permanent loop, 091 * looking for input messages in character form on the stream connected to 092 * the LnPortnetworkController via <code>connectPort</code>. 093 */ 094 class RcvHandler implements Runnable { 095 096 /** 097 * Remember the LnPacketizer object. 098 */ 099 LnOverTcpPacketizer trafficController; 100 101 public RcvHandler(LnOverTcpPacketizer lt) { 102 trafficController = lt; 103 } 104 105 // readline is deprecated, but there are no problems 106 // with multi-byte characters here. 107 @SuppressWarnings("deprecation") // InputStream#readline 108 @Override 109 public void run() { 110 111 String rxLine; 112 while (true) { // loop permanently, program close will exit 113 try { 114 // start by looking for a complete line 115 rxLine = istream.readLine(); 116 if (rxLine == null) { 117 log.warn("run: input stream returned null, exiting loop"); 118 return; 119 } 120 121 log.debug("Received: {}", rxLine); 122 123 StringTokenizer st = new StringTokenizer(rxLine); 124 if (st.nextToken().equals(RECEIVE_PREFIX)) { 125 LocoNetMessage msg = null; 126 int opCode = Integer.parseInt(st.nextToken(), 16); 127 int byte2 = Integer.parseInt(st.nextToken(), 16); 128 129 // Decide length 130 switch ((opCode & 0x60) >> 5) { 131 default: // not really possible, but this closes selection for SpotBugs 132 case 0: 133 /* 2 byte message */ 134 135 msg = new LocoNetMessage(2); 136 break; 137 138 case 1: 139 /* 4 byte message */ 140 141 msg = new LocoNetMessage(4); 142 break; 143 144 case 2: 145 /* 6 byte message */ 146 147 msg = new LocoNetMessage(6); 148 break; 149 150 case 3: 151 /* N byte message */ 152 153 if (byte2 < 2) { 154 log.error("LocoNet message length invalid: {} opcode: {}", 155 byte2, Integer.toHexString(opCode)); 156 } 157 msg = new LocoNetMessage(byte2); 158 break; 159 } 160 161 // message exists, now fill it 162 msg.setOpCode(opCode); 163 msg.setElement(1, byte2); 164 int len = msg.getNumDataElements(); 165 //log.debug("len: {}", len); 166 167 for (int i = 2; i < len; i++) { 168 // check for message-blocking error 169 int b = Integer.parseInt(st.nextToken(), 16); 170 // log.debug("char {} is: {}", i, Integer.toHexString(b)); 171 if ((b & 0x80) != 0) { 172 log.warn("LocoNet message with opCode: {} ended early. Expected length: {} seen length: {} unexpected byte: {}", Integer.toHexString(opCode), len, i, Integer.toHexString(b)); 173 throw new LocoNetMessageException(); 174 } 175 msg.setElement(i, b); 176 } 177 178 // message is complete, dispatch it !! 179 if (log.isDebugEnabled()) { 180 log.debug("queue message for notification"); 181 } 182 183 final LocoNetMessage thisMsg = msg; 184 final LnPacketizer thisTc = trafficController; 185 // return a notification via the queue to ensure end 186 Runnable r = new Runnable() { 187 LocoNetMessage msgForLater = thisMsg; 188 LnPacketizer myTc = thisTc; 189 190 @Override 191 public void run() { 192 myTc.notify(msgForLater); 193 } 194 }; 195 javax.swing.SwingUtilities.invokeLater(r); 196 } 197 // done with this one 198 } catch (LocoNetMessageException e) { 199 // just let it ride for now 200 log.warn("run: unexpected LocoNetMessageException: ", e); 201 } catch (java.io.EOFException e) { 202 // posted from idle port when enableReceiveTimeout used 203 log.debug("EOFException, is LocoNet serial I/O using timeouts?"); 204 } catch (java.io.IOException e) { 205 // fired when write-end of HexFile reaches end 206 log.debug("IOException, should only happen with HexFile: ", e); 207 log.info("End of file"); 208// disconnectPort(networkController); 209 return; 210 } // normally, we don't catch RuntimeException, but in this 211 // permanently running loop it seems wise. 212 catch (RuntimeException e) { 213 log.warn("run: unexpected Exception: ", e); 214 } 215 } // end of permanent loop 216 } 217 } 218 219 /** 220 * Captive class to handle transmission. 221 */ 222 class XmtHandler implements Runnable { 223 224 @Override 225 public void run() { 226 227 while (true) { // loop permanently 228 // any input? 229 try { 230 // get content; blocks write until present 231 log.debug("check for input"); 232 233 byte msg[] = xmtList.take(); 234 235 // input - now send 236 try { 237 if (ostream != null) { 238 // Commented out as the original LnPortnetworkController always returned true. 239 // if (!networkController.okToSend()) log.warn("LocoNet port not ready to receive"); // TCP, not RS232, so message is a real warning 240 log.debug("start write to stream"); 241 StringBuffer packet = new StringBuffer(msg.length * 3 + SEND_PREFIX.length() + 2); 242 packet.append(SEND_PREFIX); 243 String hexString; 244 for (int Index = 0; Index < msg.length; Index++) { 245 packet.append(' '); 246 hexString = Integer.toHexString(msg[Index] & 0xFF).toUpperCase(); 247 if (hexString.length() == 1) { 248 packet.append('0'); 249 } 250 packet.append(hexString); 251 } 252 if (log.isDebugEnabled()) { // Avoid building unneeded Strings 253 log.debug("Write to LbServer: {}", packet.toString()); 254 } 255 packet.append("\r\n"); 256 ostream.write(packet.toString().getBytes()); 257 ostream.flush(); 258 log.debug("end write to stream"); 259 } else { 260 // no stream connected 261 log.warn("sendLocoNetMessage: no connection established"); 262 } 263 } catch (java.io.IOException e) { 264 log.warn("sendLocoNetMessage: IOException: {}", e.toString()); 265 } 266 } catch (InterruptedException ie) { 267 return; // ending the thread 268 } 269 } 270 } 271 } 272 273 private final static Logger log = LoggerFactory.getLogger(LnOverTcpPacketizer.class); 274 275}