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     * {@inheritDoc}
042     */
043    @Override
044    public synchronized void removeCanListener(CanListener l) {
045        this.removeListener(l);
046    }
047
048    /**
049     * Actually transmits the next message to the port
050     *
051     * Overridden to include translation to the correct CAN hardware message
052     * format
053     * {@inheritDoc}
054     */
055    @Override
056    protected void forwardToPort(AbstractMRMessage m, AbstractMRListener reply) {
057//        if (log.isDebugEnabled()) log.debug("forwardToPort message: ["+m+"]");
058        log.debug("forwardToPort message: [{}]", m);//warn
059
060        // remember who sent this
061        mLastSender = reply;
062
063        // forward the message to the registered recipients,
064        // which includes the communications monitor, except the sender.
065        // Schedule notification via the Swing event queue to ensure order
066        Runnable r = new XmtNotifier(m, mLastSender, this);
067        javax.swing.SwingUtilities.invokeLater(r);
068
069        // Create the correct concrete class for sending to the hardware and encode the message to be sent
070        AbstractMRMessage hm;
071        if (((CanMessage) m).isTranslated()) {
072            hm = m;
073        } else {
074            hm = encodeForHardware((CanMessage) m);
075        }
076        log.debug("Encoded for hardware: [{}]", hm.toString());
077
078        // stream to port in single write, as that's needed by serial
079        byte msg[] = new byte[lengthOfByteStream(hm)];
080
081        // add header
082        int offset = addHeaderToOutput(msg, hm);
083
084        // add data content
085        int len = hm.getNumDataElements();
086        for (int i = 0; i < len; i++) {
087            msg[i + offset] = (byte) hm.getElement(i);
088        }
089
090        // add trailer
091        addTrailerToOutput(msg, len + offset, hm);
092
093        // and stream the bytes
094        try {
095            if (ostream != null) {
096                if (log.isDebugEnabled()) {
097                    //String f = "formatted message: ";
098                    StringBuilder buf = new StringBuilder("formatted message: ");
099                    //for (int i = 0; i<msg.length; i++) f=f+Integer.toHexString(0xFF&msg[i])+" ";
100                    for (int i = 0; i < msg.length; i++) {
101                        buf.append(Integer.toHexString(0xFF & msg[i]));
102                        buf.append(" ");
103                    }
104                    log.debug(buf.toString());
105                }
106                while (hm.getRetries() >= 0) {
107                    if (portReadyToSend(controller)) {
108                        ostream.write(msg);
109                        log.debug("message written");
110                        break;
111                    } else if (hm.getRetries() >= 0) {
112                        if (log.isDebugEnabled()) {
113                            log.debug("Retry message: {} attempts remaining: {}", hm.toString(), hm.getRetries());
114                        }
115                        hm.setRetries(hm.getRetries() - 1);
116                        try {
117                            synchronized (xmtRunnable) {
118                                xmtRunnable.wait(hm.getTimeout());
119                            }
120                        } catch (InterruptedException e) {
121                            Thread.currentThread().interrupt(); // retain if needed later
122                            log.error("retry wait interrupted");
123                        }
124                    } else {
125                        log.warn("sendMessage: port not ready for data sending: {}", Arrays.toString(msg));
126                    }
127                }
128            } else {
129                // no stream connected
130                connectionWarn();
131            }
132        } catch (java.io.IOException | RuntimeException e) {
133            portWarn(e);
134        }
135    }
136
137    /**
138     * {@inheritDoc}
139     * Always null
140     */
141    @Override
142    protected AbstractMRMessage pollMessage() {
143        return null;
144    }
145
146    /**
147     * {@inheritDoc}
148     * Always null
149     */
150    @Override
151    protected AbstractMRListener pollReplyHandler() {
152        return null;
153    }
154
155    /**
156     * {@inheritDoc}
157     * Always null
158     */
159    @Override
160    protected AbstractMRMessage enterProgMode() {
161        return null;
162    }
163
164    /**
165     * {@inheritDoc}
166     * Always null
167     */
168    @Override
169    protected AbstractMRMessage enterNormalMode() {
170        return null;
171    }
172
173    /**
174     * Get the correct concrete class for the hardware connection message
175     * @return new blank message
176     */
177    abstract protected AbstractMRMessage newMessage();
178
179    abstract public CanReply decodeFromHardware(AbstractMRReply m);
180
181    abstract public AbstractMRMessage encodeForHardware(CanMessage m);
182
183    /**
184     * Handle each reply when complete.
185     * <p>
186     * Overridden to include translation form the CAN hardware format
187     *
188     */
189    @SuppressFBWarnings(value = "DLS_DEAD_LOCAL_STORE")
190    // Ignore false positive that msg is never used
191    @Override
192    public void handleOneIncomingReply() throws java.io.IOException {
193        // we sit in this until the message is complete, relying on
194        // threading to let other stuff happen
195
196        // Create messages off the right concrete classes
197        // for the CanReply
198        CanReply msg;
199
200        // and for the incoming reply from the hardware
201        AbstractMRReply hmsg = newReply();
202
203        // wait for start if needed
204        waitForStartOfReply(istream);
205
206        // message exists, now fill it
207        loadChars(hmsg, istream);
208
209        // Decode the message from the hardware into a CanReply
210        msg = decodeFromHardware(hmsg);
211        if (msg == null) {
212            return;  // some replies don't get forwarded
213        }
214        // message is complete, dispatch it !!
215        replyInDispatch = true;
216
217        if (log.isDebugEnabled()) {
218            log.debug("dispatch reply of length {} contains {} state {}", msg.getNumDataElements(), msg.toString(), mCurrentState);
219        }
220
221        // actually distribute the reply
222        distributeOneReply(msg, mLastSender);
223
224        if (!msg.isUnsolicited()) {
225            if (log.isDebugEnabled()) {
226                log.debug("switch on state {}", mCurrentState);
227            }
228            // effect on transmit:
229            switch (mCurrentState) {
230
231                case WAITMSGREPLYSTATE: {
232                    // update state, and notify to continue
233                    synchronized (xmtRunnable) {
234                        mCurrentState = NOTIFIEDSTATE;
235                        replyInDispatch = false;
236                        xmtRunnable.notify();
237                    }
238                    break;
239                }
240
241                case WAITREPLYINPROGMODESTATE: {
242                    // entering programming mode
243                    mCurrentMode = PROGRAMINGMODE;
244                    replyInDispatch = false;
245
246                    // check to see if we need to delay to allow decoders to become
247                    // responsive
248                    int warmUpDelay = enterProgModeDelayTime();
249                    if (warmUpDelay != 0) {
250                        try {
251                            synchronized (xmtRunnable) {
252                                xmtRunnable.wait(warmUpDelay);
253                            }
254                        } catch (InterruptedException e) {
255                            Thread.currentThread().interrupt(); // retain if needed later
256                        }
257
258                    }
259
260                    // update state, and notify to continue
261                    synchronized (xmtRunnable) {
262                        mCurrentState = OKSENDMSGSTATE;
263                        xmtRunnable.notify();
264                    }
265                    break;
266                }
267
268                case WAITREPLYINNORMMODESTATE: {
269                    // entering normal mode
270                    mCurrentMode = NORMALMODE;
271                    replyInDispatch = false;
272                    // update state, and notify to continue
273                    synchronized (xmtRunnable) {
274                        mCurrentState = OKSENDMSGSTATE;
275                        xmtRunnable.notify();
276                    }
277                    break;
278                }
279
280                default: {
281                    replyInDispatch = false;
282                    if (allowUnexpectedReply == true) {
283                        if (log.isDebugEnabled()) {
284                            log.debug("Allowed unexpected reply received in state: {} was {}", mCurrentState, msg.toString());
285                        }
286                    } else {
287                        unexpectedReplyStateError(mCurrentState,msg.toString());
288                    }
289                }
290            }
291            // Unsolicited message
292        } else {
293            replyInDispatch = false;
294        }
295    }
296
297    public void distributeOneReply(CanReply msg, AbstractMRListener mLastSender) {
298        // forward the message to the registered recipients,
299        // which includes the communications monitor
300        Runnable r = newRcvNotifier(msg, mLastSender, this);
301        distributeReply(r);
302    }
303
304    private final static Logger log = LoggerFactory.getLogger(AbstractCanTrafficController.class);
305
306}