001package jmri.jmrix.tmcc; 002 003import java.io.DataInputStream; 004import jmri.jmrix.AbstractMRListener; 005import jmri.jmrix.AbstractMRMessage; 006import jmri.jmrix.AbstractMRReply; 007import jmri.jmrix.AbstractMRTrafficController; 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011/** 012 * Convert Stream-based I/O to/from TMCC serial messages. 013 * <p> 014 * The "SerialInterface" side sends/receives message objects. 015 * <p> 016 * The connection to a SerialPortController is via a pair of *Streams, which 017 * then carry sequences of characters for transmission. Note that this 018 * processing is handled in an independent thread. 019 * <p> 020 * This handles the state transitions, based on the necessary state in each 021 * message. 022 * <p> 023 * Handles initialization, polling, output, and input for multiple Serial Nodes. 024 * 025 * @author Bob Jacobsen Copyright (C) 2003, 2006 026 * @author Bob Jacobsen, Dave Duchamp, multiNode extensions, 2004 027 */ 028public class SerialTrafficController extends AbstractMRTrafficController implements SerialInterface { 029 030 /** 031 * Create a new TMCC SerialTrafficController instance. 032 * 033 * @param adaptermemo the associated SystemConnectionMemo 034 */ 035 public SerialTrafficController(TmccSystemConnectionMemo adaptermemo) { 036 super(); 037 mMemo = adaptermemo; 038 // entirely poll driven, so reduce interval 039 mWaitBeforePoll = 25; // default = 25 040 log.debug("creating a new TMCCTrafficController object"); 041 } 042 043 // The methods to implement the SerialInterface 044 045 @Override 046 public synchronized void addSerialListener(SerialListener l) { 047 this.addListener(l); 048 } 049 050 @Override 051 public synchronized void removeSerialListener(SerialListener l) { 052 this.removeListener(l); 053 } 054 055 @Override 056 protected AbstractMRMessage enterProgMode() { 057 log.warn("enterProgMode doesn't make sense for TMCC serial"); 058 return null; 059 } 060 061 @Override 062 protected AbstractMRMessage enterNormalMode() { 063 return null; 064 } 065 066 /** 067 * Reference to the system connection memo. 068 */ 069 TmccSystemConnectionMemo mMemo = null; 070 071 /** 072 * Get access to the system connection memo associated with this traffic 073 * controller. 074 * 075 * @return associated systemConnectionMemo object 076 */ 077 public TmccSystemConnectionMemo getSystemConnectionMemo() { 078 return mMemo; 079 } 080 081 /** 082 * Set the system connection memo associated with this traffic controller. 083 * 084 * @param m associated systemConnectionMemo object 085 */ 086 public void setSystemConnectionMemo(TmccSystemConnectionMemo m) { 087 mMemo = m; 088 } 089 090 /** 091 * Forward a SerialMessage to all registered SerialInterface listeners. 092 */ 093 @Override 094 protected void forwardMessage(AbstractMRListener client, AbstractMRMessage m) { 095 ((SerialListener) client).message((SerialMessage) m); 096 } 097 098 /** 099 * Forward a SerialReply to all registered SerialInterface listeners. 100 */ 101 @Override 102 protected void forwardReply(AbstractMRListener client, AbstractMRReply m) { 103 ((SerialListener) client).reply((SerialReply) m); 104 } 105 106 /** 107 * Handle initialization, output and polling for TMCC from within the 108 * running thread. 109 */ 110 @Override 111 protected synchronized AbstractMRMessage pollMessage() { 112 // no polling in this protocol 113 return null; 114 } 115 116 @Override 117 protected AbstractMRListener pollReplyHandler() { 118 return null; 119 } 120 121 /** 122 * Forward a preformatted message to the actual interface. 123 */ 124 @Override 125 public void sendSerialMessage(SerialMessage m, SerialListener reply) { 126 sendMessage(m, reply); 127 } 128 129 @Override 130 protected AbstractMRReply newReply() { 131 return new SerialReply(); 132 } 133 134 @Override 135 protected boolean endOfMessage(AbstractMRReply msg) { 136 // our version of loadChars doesn't invoke this, so it shouldn't be called 137 log.error("Not using endOfMessage, should not be called"); 138 return false; 139 } 140 141 @Override 142 protected void loadChars(AbstractMRReply msg, DataInputStream istream) throws java.io.IOException { 143 byte char1; 144 char1 = readByteProtected(istream); 145 msg.setElement(0, char1 & 0xFE); 146 if ( ((char1 & 0xFF) != 0xFE) && ((char1 & 0xFF) != 0xF8) && ((char1 & 0xFF) != 0xF9) ) { 147 log.warn("return short message as 1st byte is {}", char1 & 0xFF); 148 return; 149 } 150 151 char1 = readByteProtected(istream); 152 msg.setElement(1, char1 & 0xFF); 153 154 char1 = readByteProtected(istream); 155 msg.setElement(2, char1 & 0xFF); 156 } 157 158 @Override 159 protected void waitForStartOfReply(DataInputStream istream) throws java.io.IOException { 160 } 161 162 /** 163 * No header needed 164 * 165 * @param msg The output byte stream 166 * @return next location in the stream to fill 167 */ 168 @Override 169 protected int addHeaderToOutput(byte[] msg, AbstractMRMessage m) { 170 return 0; 171 } 172 173 /** 174 * Add trailer to the outgoing byte stream. 175 * 176 * @param msg The output byte stream 177 * @param offset the first byte not yet used 178 */ 179 @Override 180 protected void addTrailerToOutput(byte[] msg, int offset, AbstractMRMessage m) { 181 } 182 183 /** 184 * Determine how much many bytes the entire message will take, including 185 * space for header and trailer 186 * 187 * @param m The message to be sent 188 * @return Number of bytes for msg (3) plus preceeding NOP (3) 189 */ 190 @Override 191 protected int lengthOfByteStream(AbstractMRMessage m) { 192 return 6; 193 } 194 195 /** 196 * Actually transmits the next message to the port 197 */ 198 @Override 199 protected void forwardToPort(AbstractMRMessage m, AbstractMRListener reply) { 200 log.debug("forwardToPort message: [{}]", m); 201 // remember who sent this 202 mLastSender = reply; 203 204 // forward the message to the registered recipients, 205 // which includes the communications monitor, except the sender. 206 // Schedule notification via the Swing event queue to ensure order 207 Runnable r = new XmtNotifier(m, mLastSender, this); 208 javax.swing.SwingUtilities.invokeLater(r); 209 210 // stream to port in single write, as that's needed by serial 211 byte msg[] = new byte[lengthOfByteStream(m)]; 212 // add header 213 int offset = addHeaderToOutput(msg, m); 214 215 // add data content 216 int len = m.getNumDataElements(); 217 for (int i = 0; i < len; i++) { 218 msg[i + offset] = (byte) m.getElement(i); 219 } 220 221 // add trailer 222 addTrailerToOutput(msg, len + offset, m); 223 224 // and stream the bytes 225 try { 226 if (ostream != null) { 227 if (log.isDebugEnabled()) { 228 StringBuilder f = new StringBuilder(""); 229 for (int i = 0; i < msg.length; i++) { 230 f.append(Integer.toHexString(0xFF & msg[i])).append(" "); 231 } 232 log.debug("write message: {}", f); 233 } 234 while (m.getRetries() >= 0) { 235 if (portReadyToSend(controller)) { 236 for (int i = 0; i < len; i++) { 237 ostream.write(msg[i]); 238 try { 239 synchronized (xmtRunnable) { 240 xmtRunnable.wait(10); 241 } 242 } catch (InterruptedException e) { 243 Thread.currentThread().interrupt(); // retain if needed later 244 log.warn("char send wait interrupted"); 245 } 246 } 247 break; 248 } else if (m.getRetries() >= 0) { 249 log.debug("Retry message: {} attempts remaining: {}", m, m.getRetries()); 250 m.setRetries(m.getRetries() - 1); 251 try { 252 synchronized (xmtRunnable) { 253 xmtRunnable.wait(m.getTimeout()); 254 } 255 } catch (InterruptedException e) { 256 log.error("retry wait interrupted"); 257 } 258 } else { 259 log.warn("sendMessage: port not ready for data sending: {}", java.util.Arrays.toString(msg)); 260 } 261 } 262 } else { 263 // no stream connected 264 log.warn("sendMessage: no connection established"); 265 } 266 } catch (java.io.IOException | RuntimeException e) { 267 log.warn("sendMessage: Exception:", e); 268 } 269 } 270 271 private final static Logger log = LoggerFactory.getLogger(SerialTrafficController.class); 272 273}