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