001package jmri.jmrix.roco.z21; 002 003import java.io.DataInputStream; 004import java.io.DataOutputStream; 005import java.io.IOException; 006import java.io.PipedInputStream; 007import java.io.PipedOutputStream; 008 009import jmri.jmrix.loconet.LocoNetListener; 010import jmri.jmrix.loconet.LocoNetMessage; 011import jmri.jmrix.loconet.LocoNetMessageException; 012import jmri.jmrix.loconet.LocoNetSystemConnectionMemo; 013import jmri.jmrix.loconet.streamport.LnStreamPortController; 014import jmri.util.ImmediatePipedOutputStream; 015 016import org.slf4j.Logger; 017import org.slf4j.LoggerFactory; 018 019/** 020 * Interface between z21 messages and an LocoNet stream. 021 * <p> 022 * Parts of this code are derived from the 023 * jmri.jmrix.lenz.xnetsimulator.XNetSimulatorAdapter class. 024 * 025 * @author Paul Bender Copyright (C) 2014 026 */ 027public class Z21LocoNetTunnel implements Z21Listener, LocoNetListener , Runnable { 028 029 LnStreamPortController lsc = null; 030 private DataOutputStream pout = null; // for output to other classes 031 private DataInputStream pin = null; // for input from other classes 032 // internal ends of the pipes 033 private DataOutputStream outpipe = null; // feed pin 034 private DataInputStream inpipe = null; // feed pout 035 private final Z21SystemConnectionMemo _memo; 036 private Thread sourceThread; 037 private volatile boolean stopThread = false; 038 039 /** 040 * Build a new LocoNet tunnel. 041 * @param memo system connection. 042 */ 043 public Z21LocoNetTunnel(Z21SystemConnectionMemo memo) { 044 // save the SystemConnectionMemo. 045 _memo = memo; 046 init(); 047 } 048 049 private void init() { 050 051 // configure input and output pipes to use for 052 // the communication with the LocoNet implementation. 053 try { 054 PipedOutputStream tempPipeI = new ImmediatePipedOutputStream(); 055 pout = new DataOutputStream(tempPipeI); 056 inpipe = new DataInputStream(new PipedInputStream(tempPipeI)); 057 PipedOutputStream tempPipeO = new ImmediatePipedOutputStream(); 058 outpipe = new DataOutputStream(tempPipeO); 059 pin = new DataInputStream(new PipedInputStream(tempPipeO)); 060 } catch (java.io.IOException e) { 061 log.error("init (pipe): Exception: {}", e.toString()); 062 return; 063 } 064 065 // start a thread to read from the input pipe. 066 sourceThread = new Thread(this); 067 sourceThread.setName("z21.Z21LocoNetTunnel sourceThread"); 068 sourceThread.setDaemon(true); 069 sourceThread.start(); 070 071 // Then use those pipes as the input and output pipes for 072 // a new LnStreamPortController object. 073 LocoNetSystemConnectionMemo lnMemo = new LocoNetSystemConnectionMemo(); 074 setStreamPortController(new Z21LnStreamPortController(lnMemo,pin, pout, "None")); 075 076 // register as a Z21Listener, so we can receive replies 077 _memo.getTrafficController().addz21Listener(this); 078 079 // start the LocoNet configuration. 080 lsc.configure(); 081 } 082 083 @Override 084 public void run() { // start a new thread 085 // this thread has one task. It repeatedly reads from the input pipe 086 // and writes modified data to the output pipe. This is the heart 087 // of the command station simulation. 088 log.debug("LocoNet Tunnel Thread Started"); 089 while (!stopThread) { 090 LocoNetMessage m = readMessage(); 091 if(m != null) { 092 // don't forward a null message. 093 message(m); 094 } 095 } 096 } 097 098 /** 099 * Read one incoming message from the buffer and set 100 * outputBufferEmpty to true. 101 */ 102 private LocoNetMessage readMessage() { 103 LocoNetMessage msg = null; 104 try { 105 msg = loadChars(); 106 } catch (java.io.IOException|LocoNetMessageException e) { 107 // should do something meaningful here. 108 } 109 return (msg); 110 } 111 112 /** 113 * Get characters from the input source, and file a message. 114 * <p> 115 * Returns only when the message is complete. 116 * <p> 117 * Only used in the Receive thread. 118 * 119 * @return filled message 120 * @throws IOException when presented by the input source. 121 */ 122 private LocoNetMessage loadChars() throws java.io.IOException,LocoNetMessageException { 123 int opCode; 124 // start by looking for command - skip if bit not set 125 while (((opCode = (readByteProtected(inpipe) & 0xFF)) & 0x80) == 0) { // the real work is in the loop check 126 log.trace("Skipping: {}", Integer.toHexString(opCode)); // NOI18N 127 } 128 // here opCode is OK. Create output message 129 log.trace(" (RcvHandler) Start message with opcode: {}", Integer.toHexString(opCode)); // NOI18N 130 LocoNetMessage msg = null; 131 while (msg == null) { 132 try { 133 // Capture 2nd byte, always present 134 int byte2 = readByteProtected(inpipe) & 0xFF; 135 log.trace("Byte2: {}", Integer.toHexString(byte2)); // NOI18N 136 int len = 2; 137 switch ((opCode & 0x60) >> 5) { 138 case 0: 139 /* 2 byte message */ 140 len = 2; 141 break; 142 case 1: 143 /* 4 byte message */ 144 len = 4; 145 break; 146 case 2: 147 /* 6 byte message */ 148 149 len = 6; 150 break; 151 case 3: 152 /* N byte message */ 153 if (byte2 < 2) { 154 log.error("LocoNet message length invalid: {} opcode: {}", byte2, Integer.toHexString(opCode)); // NOI18N 155 } 156 len = byte2; 157 break; 158 default: 159 log.warn("Unhandled code: {}", (opCode & 0x60) >> 5); 160 break; 161 } 162 msg = new LocoNetMessage(len); 163 // message exists, now fill it 164 msg.setOpCode(opCode); 165 msg.setElement(1, byte2); 166 log.trace("len: {}", len); // NOI18N 167 for (int i = 2; i < len; i++) { 168 // check for message-blocking error 169 int b = readByteProtected(inpipe) & 0xFF; 170 log.trace("char {} is: {}", i, Integer.toHexString(b)); // NOI18N 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)); // NOI18N 173 opCode = b; 174 throw new LocoNetMessageException(); 175 } 176 msg.setElement(i, b); 177 } 178 } catch (LocoNetMessageException e) { 179 // retry by destroying the existing message 180 // opCode is set for the newly-started packet 181 msg = null; 182 } 183 } 184 // check parity 185 if (!msg.checkParity()) { 186 log.warn("Ignore LocoNet packet with bad checksum: {}", msg); 187 throw new LocoNetMessageException(); 188 } 189 // message is complete, dispatch it !! 190 return msg; 191 } 192 193 /** 194 * Read a single byte, protecting against various timeouts, etc. 195 * <p> 196 * When a port is set to have a receive timeout (via the 197 * enableReceiveTimeout() method), some will return zero bytes or an 198 * EOFException at the end of the timeout. In that case, the read should be 199 * repeated to get the next real character. 200 */ 201 private byte readByteProtected(DataInputStream istream) throws java.io.IOException { 202 byte[] rcvBuffer = new byte[1]; 203 while (true) { // loop will repeat until character found 204 int nchars; 205 nchars = istream.read(rcvBuffer, 0, 1); 206 if (nchars > 0) { 207 return rcvBuffer[0]; 208 } 209 } 210 } 211 212 // Z21Listener interface methods. 213 214 /** 215 * Member function that will be invoked by a z21Interface implementation to 216 * forward a z21 message from the layout. 217 * 218 * @param msg The received z21 message. Note that this same object may be 219 * presented to multiple users. It should not be modified here. 220 */ 221 @Override 222 public void reply(Z21Reply msg) { 223 // This funcction forwards the payload of an LocoNet message 224 // tunneled in a z21 message and forwards it to the XpressNet 225 // implementation's input stream. 226 if (msg.isLocoNetTunnelMessage()) { 227 LocoNetMessage reply = msg.getLocoNetMessage(); 228 log.debug("Z21 Reply {} forwarded to XpressNet implementation as {}", 229 msg, reply); 230 for (int i = 0; i < reply.getNumDataElements(); i++) { 231 try { 232 outpipe.writeByte(reply.getElement(i)); 233 } catch (java.io.IOException ioe) { 234 log.error("Error writing XpressNet Reply to XpressNet input stream."); 235 } 236 } 237 } 238 } 239 240 /** 241 * Member function that will be invoked by a z21Interface implementation to 242 * forward a z21 message sent to the layout. Normally, this function will do 243 * nothing. 244 * 245 * @param msg The received z21 message. Note that this same object may be 246 * presented to multiple users. It should not be modified here. 247 */ 248 @Override 249 public void message(Z21Message msg) { 250 // this function does nothing. 251 } 252 253 // LocoNetListener Interface methods. 254 255 /** 256 * Member function that will be invoked by a LocoNet Interface implementation to 257 * forward a LocoNet message sent to the layout. Normally, this function will 258 * do nothing. 259 * 260 * @param msg The received LocoNet message. Note that this same object may be 261 * presented to multiple users. It should not be modified here. 262 */ 263 @Override 264 public void message(LocoNetMessage msg) { 265 // when an LocoNet message shows up here, package it in a Z21Message 266 Z21Message message = new Z21Message(msg); 267 log.debug("LocoNet Message {} forwarded to z21 Interface as {}", 268 msg, message); 269 // and send the z21 message to the interface 270 _memo.getTrafficController().sendz21Message(message, this); 271 } 272 273 /** 274 * Package protected method to retrieve the stream port controller 275 * associated with this tunnel. 276 * @return PortController for this connection 277 */ 278 jmri.jmrix.loconet.streamport.LnStreamPortController getStreamPortController() { 279 return lsc; 280 } 281 282 /** 283 * Package protected method to set the stream port controller 284 * associated with this tunnel. 285 * @param x PortController for this connection 286 */ 287 void setStreamPortController(LnStreamPortController x){ 288 lsc = x; 289 290 // configure the XpressNet connections properties. 291 lsc.getSystemConnectionMemo().setSystemPrefix("L"); 292 lsc.getSystemConnectionMemo().setUserName(_memo.getUserName() + "LocoNet"); 293 294 } 295 296 @SuppressWarnings("deprecation") // Thread.stop 297 public void dispose(){ 298 if (sourceThread != null) { 299 stopThread = true; 300 sourceThread.interrupt(); 301 try { 302 sourceThread.join(); 303 } catch (InterruptedException e) { 304 // Do nothing 305 } 306 } 307 if(lsc != null){ 308 lsc.dispose(); 309 } 310 if( _memo != null ) { 311 Z21TrafficController tc = _memo.getTrafficController(); 312 if ( tc != null ) { 313 tc.removez21Listener(this); 314 } 315 } 316 try { 317 inpipe.close(); 318 } catch (IOException ex) { 319 // Ignore IO error 320 } 321 } 322 323 private final static Logger log = LoggerFactory.getLogger(Z21LocoNetTunnel.class); 324 325}