001package jmri.jmrix.can; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import java.util.Arrays; 005import jmri.jmrix.AbstractMRListener; 006import jmri.jmrix.AbstractMRMessage; 007import jmri.jmrix.AbstractMRReply; 008import jmri.jmrix.AbstractMRTrafficController; 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012/** 013 * Abstract base for TrafficControllers in a CANbus based Message/Reply 014 * protocol. 015 * <p> 016 * AbstractMRTrafficController is extended to allow for the translation between 017 * CAN messages and the message format of the CAN adapter that connects to the 018 * layout. 019 * 020 * @author Andrew Crosland Copyright (C) 2008 021 */ 022abstract public class AbstractCanTrafficController 023 extends AbstractMRTrafficController 024 implements CanInterface { 025 026 public AbstractCanTrafficController() { 027 super(); 028 allowUnexpectedReply = true; 029 } 030 031 // The methods to implement the CAN Interface 032 /** 033 * {@inheritDoc} 034 */ 035 @Override 036 public synchronized void addCanListener(CanListener l) { 037 this.addListener(l); 038 } 039 040 /** 041 * Add a CanListener to start of the notification list. 042 * This is intended only for Consoles to receive the CanFrame 1st, 043 * not after another Listener has sent a response to that Frame. 044 * @param l The CanListener to add. 045 */ 046 public synchronized void addCanConsoleListener(CanListener l) { 047 this.addConsoleListener(l); 048 } 049 050 /** 051 * {@inheritDoc} 052 */ 053 @Override 054 public synchronized void removeCanListener(CanListener l) { 055 this.removeListener(l); 056 } 057 058 /** 059 * Actually transmits the next message to the port 060 * 061 * Overridden to include translation to the correct CAN hardware message 062 * format 063 * {@inheritDoc} 064 */ 065 @Override 066 protected void forwardToPort(AbstractMRMessage m, AbstractMRListener reply) { 067// if (log.isDebugEnabled()) log.debug("forwardToPort message: ["+m+"]"); 068 log.debug("forwardToPort message: [{}]", m);//warn 069 070 // remember who sent this 071 mLastSender = reply; 072 073 // forward the message to the registered recipients, 074 // which includes the communications monitor, except the sender. 075 // Schedule notification via the Swing event queue to ensure order 076 Runnable r = new XmtNotifier(m, mLastSender, this); 077 javax.swing.SwingUtilities.invokeLater(r); 078 079 // Create the correct concrete class for sending to the hardware and encode the message to be sent 080 AbstractMRMessage hm; 081 if (((CanMessage) m).isTranslated()) { 082 hm = m; 083 } else { 084 hm = encodeForHardware((CanMessage) m); 085 } 086 log.debug("Encoded for hardware: [{}]", hm); // no eager toString(), called for every frame 087 088 // stream to port in single write, as that's needed by serial 089 byte msg[] = new byte[lengthOfByteStream(hm)]; 090 091 // add header 092 int offset = addHeaderToOutput(msg, hm); // always returns 0 093 094 // add data content 095 int len = hm.getNumDataElements(); 096 for (int i = 0; i < len; i++) { 097 msg[i + offset] = (byte) hm.getElement(i); 098 } 099 100 // add trailer 101 addTrailerToOutput(msg, len + offset, hm); 102 103 // and stream the bytes 104 try { 105 if (ostream != null) { 106 if (log.isDebugEnabled()) { 107 //String f = "formatted message: "; 108 StringBuilder buf = new StringBuilder(); 109 //for (int i = 0; i<msg.length; i++) f=f+Integer.toHexString(0xFF&msg[i])+" "; 110 for (int i = 0; i < msg.length; i++) { 111 buf.append(Integer.toHexString(0xFF & msg[i])); 112 buf.append(" "); 113 } 114 log.debug("formatted message: {}",buf.toString()); 115 } 116 while (hm.getRetries() >= 0) { 117 if (portReadyToSend(controller)) { 118 ostream.write(msg); 119 log.debug("message written"); 120 break; 121 } else if (hm.getRetries() >= 0) { 122 if (log.isDebugEnabled()) { 123 log.debug("Retry message: {} attempts remaining: {}", hm.toString(), hm.getRetries()); 124 } 125 hm.setRetries(hm.getRetries() - 1); 126 try { 127 synchronized (xmtRunnable) { 128 xmtRunnable.wait(hm.getTimeout()); 129 } 130 } catch (InterruptedException e) { 131 Thread.currentThread().interrupt(); // retain if needed later 132 log.error("retry wait interrupted"); 133 } 134 } else { 135 log.warn("sendMessage: port not ready for data sending: {}", Arrays.toString(msg)); 136 } 137 } 138 } else { 139 // no stream connected 140 connectionWarn(); 141 } 142 } catch (java.io.IOException | RuntimeException e) { 143 portWarn(e); 144 } 145 } 146 147 /** 148 * {@inheritDoc} 149 * Always null 150 */ 151 @Override 152 protected AbstractMRMessage pollMessage() { 153 return null; 154 } 155 156 /** 157 * {@inheritDoc} 158 * Always null 159 */ 160 @Override 161 protected AbstractMRListener pollReplyHandler() { 162 return null; 163 } 164 165 /** 166 * {@inheritDoc} 167 * Always null 168 */ 169 @Override 170 protected AbstractMRMessage enterProgMode() { 171 return null; 172 } 173 174 /** 175 * {@inheritDoc} 176 * Always null 177 */ 178 @Override 179 protected AbstractMRMessage enterNormalMode() { 180 return null; 181 } 182 183 /** 184 * Get the correct concrete class for the hardware connection message 185 * @return new blank message 186 */ 187 abstract protected AbstractMRMessage newMessage(); 188 189 abstract public CanReply decodeFromHardware(AbstractMRReply m); 190 191 abstract public AbstractMRMessage encodeForHardware(CanMessage m); 192 193 /** 194 * Handle each reply when complete. 195 * <p> 196 * Overridden to include translation form the CAN hardware format 197 * 198 */ 199 @SuppressFBWarnings(value = "DLS_DEAD_LOCAL_STORE") 200 // Ignore false positive that msg is never used 201 @Override 202 public void handleOneIncomingReply() throws java.io.IOException { 203 // we sit in this until the message is complete, relying on 204 // threading to let other stuff happen 205 206 // Create messages off the right concrete classes 207 // for the CanReply 208 CanReply msg; 209 210 // and for the incoming reply from the hardware 211 AbstractMRReply hmsg = newReply(); 212 213 // wait for start if needed 214 waitForStartOfReply(istream); 215 216 // message exists, now fill it 217 loadChars(hmsg, istream); 218 if (threadStopRequest) { 219 return; // connection closing, do not dispatch a partial or replayed frame 220 } 221 222 // Decode the message from the hardware into a CanReply 223 msg = decodeFromHardware(hmsg); 224 if (msg == null) { 225 return; // some replies don't get forwarded 226 } 227 // message is complete, dispatch it !! 228 replyInDispatch = true; 229 230 if (log.isDebugEnabled()) { 231 log.debug("dispatch reply of length {} contains {} state {}", msg.getNumDataElements(), msg.toString(), mCurrentState); 232 } 233 234 // Actually distribute the reply, to the registered listeners only. 235 // A CAN bus has no request / reply pairing, so the frame is not 236 // addressed to whichever listener happened to send last. Passing 237 // mLastSender here also delivered every frame to that listener after 238 // it had been removed, eg. a closed tool window that sent the last 239 // frame kept receiving all traffic. 240 distributeOneReply(msg, null); 241 242 if (!msg.isUnsolicited()) { 243 log.debug("switch on state {}", mCurrentState); 244 // effect on transmit: 245 switch (mCurrentState) { 246 247 case WAITMSGREPLYSTATE: { 248 // update state, and notify to continue 249 synchronized (xmtRunnable) { 250 mCurrentState = NOTIFIEDSTATE; 251 replyInDispatch = false; 252 xmtRunnable.notify(); 253 } 254 break; 255 } 256 257 case WAITREPLYINPROGMODESTATE: { 258 // entering programming mode 259 mCurrentMode = PROGRAMINGMODE; 260 replyInDispatch = false; 261 262 // check to see if we need to delay to allow decoders to become 263 // responsive 264 int warmUpDelay = enterProgModeDelayTime(); 265 if (warmUpDelay != 0) { 266 try { 267 synchronized (xmtRunnable) { 268 xmtRunnable.wait(warmUpDelay); 269 } 270 } catch (InterruptedException e) { 271 Thread.currentThread().interrupt(); // retain if needed later 272 } 273 274 } 275 276 // update state, and notify to continue 277 synchronized (xmtRunnable) { 278 mCurrentState = OKSENDMSGSTATE; 279 xmtRunnable.notify(); 280 } 281 break; 282 } 283 284 case WAITREPLYINNORMMODESTATE: { 285 // entering normal mode 286 mCurrentMode = NORMALMODE; 287 replyInDispatch = false; 288 // update state, and notify to continue 289 synchronized (xmtRunnable) { 290 mCurrentState = OKSENDMSGSTATE; 291 xmtRunnable.notify(); 292 } 293 break; 294 } 295 296 default: { 297 replyInDispatch = false; 298 if (allowUnexpectedReply == true) { 299 log.debug("Allowed unexpected reply received in state: {} was {}", mCurrentState, msg); 300 } else { 301 unexpectedReplyStateError(mCurrentState,msg.toString()); 302 } 303 } 304 } 305 // Unsolicited message 306 } else { 307 replyInDispatch = false; 308 } 309 } 310 311 public void distributeOneReply(CanReply msg, AbstractMRListener mLastSender) { 312 // forward the message to the registered recipients, 313 // which includes the communications monitor 314 if (msg == null) log.error("found unexpected null message", new Exception("traceback")); 315 Runnable r = newRcvNotifier(msg, mLastSender, this); 316 distributeReply(r); 317 } 318 319 private static final Logger log = LoggerFactory.getLogger(AbstractCanTrafficController.class); 320 321}