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