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