001package jmri.jmrix.dccpp;
002
003import java.util.HashMap;
004import java.util.concurrent.LinkedBlockingQueue;
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 class for implementations of DCCppInterface.
014 * <p>
015 * This provides just the basic interface, plus the "" static method for
016 * locating the local implementation.
017 *
018 * @author Bob Jacobsen Copyright (C) 2002
019 * @author Paul Bender Copyright (C) 2004-2010
020 * @author Mark Underwood Copyright (C) 2015
021 *
022 * Based on XNetTrafficController by Bob Jacobsen and Paul Bender
023 */
024public abstract class DCCppTrafficController extends AbstractMRTrafficController implements DCCppInterface {
025
026    @Override
027    protected void transmitLoop() {
028        log.debug("Don't start sending for 1.5 seconds to avoid Arduino restart");
029        try {
030            Thread.sleep(1500);
031        } catch (InterruptedException ignore) {
032            Thread.currentThread().interrupt();
033        }
034        super.transmitLoop();
035    }
036
037    /**
038     * Create a new DCCppTrafficController instance.
039     * Must provide a DCCppCommandStation reference at creation time.
040     *
041     * @param pCommandStation reference to associated command station object,
042     *                        preserved for later.
043     */
044    DCCppTrafficController(DCCppCommandStation pCommandStation) {
045        mCommandStation = pCommandStation;
046        setAllowUnexpectedReply(true);
047        mListenerMasks = new HashMap<>();
048        highPriorityQueue = new LinkedBlockingQueue<>();
049        highPriorityListeners = new LinkedBlockingQueue<>();
050        log.debug("DCCppTrafficController created");
051    }
052
053    protected HashMap<DCCppListener, Integer> mListenerMasks;
054
055    // Abstract methods for the DCCppInterface
056
057    /**
058     * Forward a preformatted DCCppMessage to the actual interface.
059     *
060     * @param m Message to send; will be updated with CRC
061     */
062    @Override
063    abstract public void sendDCCppMessage(DCCppMessage m, DCCppListener reply);
064
065    @Override
066    protected int lengthOfByteStream(AbstractMRMessage m) {
067        int len = m.getNumDataElements();
068        return len + 2;
069    }
070
071    /**
072     * Forward a preformatted DCCppMessage to a specific listener interface.
073     *
074     * @param m Message to send;
075     */
076    @Override
077    public void forwardMessage(AbstractMRListener reply, AbstractMRMessage m) {
078        if (reply instanceof DCCppListener && m instanceof DCCppMessage) {
079            ((DCCppListener) reply).message((DCCppMessage) m);
080        }
081    }
082
083    /**
084     * Forward a preformatted DCCppMessage to the registered DCCppListeners.
085     * NOTE: this drops the packet if the checksum is bad.
086     *
087     * @param client Client to send message to
088     * @param m      Message to send
089     */
090    // TODO: This should be fleshed out to allow listeners to register for only
091    // certain types of DCCppReply-s.  The analogous code from the Lenz interface
092    // has been left here and commented out for future reference.
093    @Override
094    public void forwardReply(AbstractMRListener client, AbstractMRReply m) {
095        // check parity
096        if (!(client instanceof DCCppListener)) { // split check to prevent class cast exception later
097            return;
098        }
099        if (!(m instanceof DCCppReply)){
100            return;
101        }
102        try {
103            // NOTE: For now, just forward ALL messages without filtering
104            ((DCCppListener) client).message((DCCppReply) m);
105            // NOTE: For now, all listeners should register for DCCppInterface.ALL
106            /*
107              int mask = (mListenerMasks.get(client)).intValue();
108              if (mask == DCCppInterface.ALL) {
109              ((DCCppListener) client).message((DCCppReply) m);
110              } else if ((mask & DCCppInterface.COMMINFO)
111              == DCCppInterface.COMMINFO
112              && (((DCCppReply) m).getElement(0)
113              == DCCppConstants.LI_MESSAGE_RESPONSE_HEADER)) {
114              ((DCCppListener) client).message((DCCppReply) m);
115              } else if ((mask & DCCppInterface.CS_INFO)
116              == DCCppInterface.CS_INFO
117              && (((DCCppReply) m).getElement(0)
118              == DCCppConstants.CS_INFO
119              || ((DCCppReply) m).getElement(0)
120              == DCCppConstants.CS_SERVICE_MODE_RESPONSE
121              || ((DCCppReply) m).getElement(0)
122              == DCCppConstants.CS_REQUEST_RESPONSE
123              || ((DCCppReply) m).getElement(0)
124              == DCCppConstants.BC_EMERGENCY_STOP)) {
125              ((DCCppListener) client).message((DCCppReply) m);
126              } else if ((mask & DCCppInterface.FEEDBACK)
127              == DCCppInterface.FEEDBACK
128              && (((DCCppReply) m).isFeedbackMessage()
129              || ((DCCppReply) m).isFeedbackBroadcastMessage())) {
130              ((DCCppListener) client).message((DCCppReply) m);
131              } else if ((mask & DCCppInterface.THROTTLE)
132              == DCCppInterface.THROTTLE
133              && ((DCCppReply) m).isThrottleMessage()) {
134              ((DCCppListener) client).message((DCCppReply) m);
135              } else if ((mask & DCCppInterface.CONSIST)
136              == DCCppInterface.CONSIST
137              && ((DCCppReply) m).isConsistMessage()) {
138              ((DCCppListener) client).message((DCCppReply) m);
139              } else if ((mask & DCCppInterface.INTERFACE)
140              == DCCppInterface.INTERFACE
141              && (((DCCppReply) m).getElement(0)
142              == DCCppConstants.LI_VERSION_RESPONSE
143              || ((DCCppReply) m).getElement(0)
144              == DCCppConstants.LI101_REQUEST)) {
145              ((DCCppListener) client).message((DCCppReply) m);
146              }
147             */
148        } catch (NullPointerException e) {
149            // catch null pointer exceptions, caused by a client
150            // that sent a message without being a registered listener
151            ((DCCppListener) client).message((DCCppReply) m);
152        }
153    }
154
155    // We use the pollMessage routines for high priority messages.
156    // This means responses to time critical messages (turnout off
157    // messages).
158    LinkedBlockingQueue<DCCppMessage> highPriorityQueue;
159    LinkedBlockingQueue<DCCppListener> highPriorityListeners;
160
161    public void sendHighPriorityDCCppMessage(DCCppMessage m, DCCppListener reply) {
162        try {
163            highPriorityQueue.put(m);
164            highPriorityListeners.put(reply);
165        } catch (java.lang.InterruptedException ie) {
166            log.error("Interrupted while adding High Priority Message to Queue");
167        }
168    }
169
170    @Override
171    protected AbstractMRMessage pollMessage() {
172        try {
173            if (highPriorityQueue.peek() == null) {
174                return null;
175            } else {
176                return highPriorityQueue.take();
177            }
178        } catch (java.lang.InterruptedException ie) {
179            log.error("Interrupted while removing High Priority Message from Queue");
180        }
181        return null;
182    }
183
184    @Override
185    protected AbstractMRListener pollReplyHandler() {
186        try {
187            if (highPriorityListeners.peek() == null) {
188                return null;
189            } else {
190                return highPriorityListeners.take();
191            }
192        } catch (java.lang.InterruptedException ie) {
193            log.error("Interrupted while removing High Priority Message Listener from Queue");
194        }
195        return null;
196    }
197
198    @Override
199    public synchronized void addDCCppListener(int mask, DCCppListener l) {
200        addListener(l);
201        // This adds all the mask information.  A better way to do
202        // this would be to allow updating individual bits
203        mListenerMasks.put(l, mask);
204    }
205
206    @Override
207    public synchronized void removeDCCppListener(int mask, DCCppListener l) {
208        removeListener(l);
209        // This removes all the mask information.  A better way to do
210        // this would be to allow updating of individual bits
211        mListenerMasks.remove(l);
212    }
213
214    /**
215     * Has to be available, even though it doesn't do anything
216     * on DCC++.
217     */
218    @Override
219    protected AbstractMRMessage enterProgMode() {
220        return null;
221    }
222
223    /**
224     *
225     * @return the value of getExitProgModeMsg();
226     */
227    @Override
228    protected AbstractMRMessage enterNormalMode() {
229        //return DCCppMessage.getExitProgModeMsg();
230        return null;
231    }
232
233    /**
234     * Check to see if the programmer associated with this
235     * interface is idle or not.
236     */
237    @Override
238    protected boolean programmerIdle() {
239        if (mMemo == null) {
240            return true;
241        }
242        DCCppProgrammer progrmr = (jmri.jmrix.dccpp.DCCppProgrammer) mMemo.getProgrammerManager().getGlobalProgrammer();
243        if ( progrmr!=null ) {
244            return !(progrmr.programmerBusy());
245        }
246        log.warn("Unable to fetch DCCppProgrammer");
247        return true;
248    }
249
250    @Override
251    // endOfMessage() not really used in DCC++ .. it's handled in the Packetizer.
252    protected boolean endOfMessage(AbstractMRReply msg) {
253        return msg.getElement(msg.getNumDataElements() - 1) == '>';
254    }
255
256    @Override
257    protected AbstractMRReply newReply() {
258        return new DCCppReply();
259    }
260
261    //    /**
262    //     * Get characters from the input source, and file a message.
263    //     * <p>
264    //     * Returns only when the message is complete.
265    //     * <p>
266    //     * Only used in the Receive thread.
267    //     *
268    //     * @param msg     message to fill
269    //     * @param istream character source.
270    //     * @throws java.io.IOException when presented by the input source.
271    //     */
272    //        protected void loadChars(AbstractMRReply msg, java.io.DataInputStream istream) throws java.io.IOException {
273    // // Spin waiting for start-of-frame '<' character (and toss it)
274    // String s = new String();
275    // byte char1;
276    // boolean found_start = false;
277    //
278    //        log.debug("Calling DCCppTrafficController.loadChars()");
279    //
280    // while (!found_start) {
281    //     char1 = readByteProtected(istream);
282    //     log.debug("Char1: {}", char1);
283    //     if ((char1 & 0xFF) == '<') {
284    //  found_start = true;
285    //  log.debug("Found starting < ");
286    //  break; // A bit redundant with setting the loop condition true (false)
287    //     } else {
288    //  //char1 = readByteProtected(istream);
289    //     }
290    // }
291    //
292    // // Now, suck in the rest of the message...
293    //        for (int i = 0; i < DCCppConstants.MAX_MESSAGE_SIZE; i++) {
294    //            char1 = readByteProtected(istream);
295    //     if (char1 == '>') {
296    //  log.debug("msg found > ");
297    //  // Don't store the >
298    //  break;
299    //     } else {
300    //  log.debug("msg read byte {}", char1);
301    //  char c = (char) (char1 & 0x00FF);
302    //  s += Character.toString(c);
303    //     }
304    // }
305    // // TODO: Still need to strip leading and trailing whitespace.
306    // log.debug("Complete message = {}", s);
307    //        ((DCCppReply)msg).parseReply(s);
308    //    }
309
310    @Override
311    protected void handleTimeout(AbstractMRMessage msg, AbstractMRListener l) {
312        super.handleTimeout(msg, l);
313        if (l != null) {
314            ((DCCppListener) l).notifyTimeout((DCCppMessage) msg);
315        }
316    }
317
318    /**
319     * Reference to the command station in communication here.
320     */
321    DCCppCommandStation mCommandStation;
322
323    /**
324     * Get access to communicating command station object.
325     *
326     * @return associated Command Station object
327     */
328    public DCCppCommandStation getCommandStation() {
329        return mCommandStation;
330    }
331
332    /**
333     * Reference to the system connection memo.
334     */
335    DCCppSystemConnectionMemo mMemo = null;
336
337    /**
338     * Get access to the system connection memo associated with this traffic
339     * controller.
340     *
341     * @return associated systemConnectionMemo object
342     */
343    public DCCppSystemConnectionMemo getSystemConnectionMemo() {
344        return (mMemo);
345    }
346
347    /**
348     * Set the system connection memo associated with this traffic controller.
349     *
350     * @param m associated systemConnectionMemo object
351     */
352    public void setSystemConnectionMemo(DCCppSystemConnectionMemo m) {
353        mMemo = m;
354    }
355
356    private DCCppTurnoutReplyCache _TurnoutReplyCache = null;
357
358    /**
359     *
360     * @return an DCCppTurnoutReplyCache object associated with this traffic
361     * controller
362     */
363    public DCCppTurnoutReplyCache getTurnoutReplyCache() {
364        if (_TurnoutReplyCache == null) {
365            _TurnoutReplyCache = new DCCppTurnoutReplyCache(this);
366        }
367        return _TurnoutReplyCache;
368    }
369
370    private final static Logger log = LoggerFactory.getLogger(DCCppTrafficController.class);
371
372}