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}