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.toString()); 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 219 // Decode the message from the hardware into a CanReply 220 msg = decodeFromHardware(hmsg); 221 if (msg == null) { 222 return; // some replies don't get forwarded 223 } 224 // message is complete, dispatch it !! 225 replyInDispatch = true; 226 227 if (log.isDebugEnabled()) { 228 log.debug("dispatch reply of length {} contains {} state {}", msg.getNumDataElements(), msg.toString(), mCurrentState); 229 } 230 231 // actually distribute the reply 232 distributeOneReply(msg, mLastSender); 233 234 if (!msg.isUnsolicited()) { 235 log.debug("switch on state {}", mCurrentState); 236 // effect on transmit: 237 switch (mCurrentState) { 238 239 case WAITMSGREPLYSTATE: { 240 // update state, and notify to continue 241 synchronized (xmtRunnable) { 242 mCurrentState = NOTIFIEDSTATE; 243 replyInDispatch = false; 244 xmtRunnable.notify(); 245 } 246 break; 247 } 248 249 case WAITREPLYINPROGMODESTATE: { 250 // entering programming mode 251 mCurrentMode = PROGRAMINGMODE; 252 replyInDispatch = false; 253 254 // check to see if we need to delay to allow decoders to become 255 // responsive 256 int warmUpDelay = enterProgModeDelayTime(); 257 if (warmUpDelay != 0) { 258 try { 259 synchronized (xmtRunnable) { 260 xmtRunnable.wait(warmUpDelay); 261 } 262 } catch (InterruptedException e) { 263 Thread.currentThread().interrupt(); // retain if needed later 264 } 265 266 } 267 268 // update state, and notify to continue 269 synchronized (xmtRunnable) { 270 mCurrentState = OKSENDMSGSTATE; 271 xmtRunnable.notify(); 272 } 273 break; 274 } 275 276 case WAITREPLYINNORMMODESTATE: { 277 // entering normal mode 278 mCurrentMode = NORMALMODE; 279 replyInDispatch = false; 280 // update state, and notify to continue 281 synchronized (xmtRunnable) { 282 mCurrentState = OKSENDMSGSTATE; 283 xmtRunnable.notify(); 284 } 285 break; 286 } 287 288 default: { 289 replyInDispatch = false; 290 if (allowUnexpectedReply == true) { 291 log.debug("Allowed unexpected reply received in state: {} was {}", mCurrentState, msg); 292 } else { 293 unexpectedReplyStateError(mCurrentState,msg.toString()); 294 } 295 } 296 } 297 // Unsolicited message 298 } else { 299 replyInDispatch = false; 300 } 301 } 302 303 public void distributeOneReply(CanReply msg, AbstractMRListener mLastSender) { 304 // forward the message to the registered recipients, 305 // which includes the communications monitor 306 if (msg == null) log.error("found unexpected null message", new Exception("traceback")); 307 Runnable r = newRcvNotifier(msg, mLastSender, this); 308 distributeReply(r); 309 } 310 311 private final static Logger log = LoggerFactory.getLogger(AbstractCanTrafficController.class); 312 313}