001/**
002 * SerialDCCppPacketizer.java
003 */
004package jmri.jmrix.dccpp.serial;
005
006import java.util.concurrent.DelayQueue;
007
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011import jmri.jmrix.dccpp.DCCppCommandStation;
012import jmri.jmrix.dccpp.DCCppListener;
013import jmri.jmrix.dccpp.DCCppMessage;
014import jmri.jmrix.dccpp.DCCppPacketizer;
015import jmri.util.ThreadingUtil;
016
017/**
018 * This is an extension of the DCCppPacketizer to handle the device specific
019 * requirements of the DCC++.
020 * <p>
021 * In particular, SerialDCCppPacketizer adds functions to add and remove the
022 * {@literal "<" and ">"} bytes that appear around any message read in.
023 *
024 * Note that the bracket-adding could be pushed up to DCCppPacketizer, as it is
025 * a protocol thing, not an interface implementation thing. We'll come back to
026 * that later.
027 *
028 * What is however interface specific is the background refresh of functions.
029 * DCC++ sends the DCC commands exactly once. A background thread will
030 * repeat the last seen function commands to compensate for any momentary
031 * power loss or to recover from power off / power on events. It only makes
032 * sense to do this on the actual serial interface as it will be transparent for
033 * the
034 * network clients.
035 *
036 * @author Paul Bender Copyright (C) 2005
037 * @author Mark Underwood Copyright (C) 2015
038 * @author Costin Grigoras Copyright (C) 2018
039 *
040 *         Based on LIUSBXNetPacketizer by Paul Bender
041 */
042public class SerialDCCppPacketizer extends DCCppPacketizer {
043
044    final DelayQueue<DCCppMessage> resendFunctions = new DelayQueue<>();
045
046    boolean activeBackgroundRefresh = true;
047    private DCCppCommandStation cs;
048    
049    public SerialDCCppPacketizer(final DCCppCommandStation pCommandStation) {
050        super(pCommandStation);
051        log.debug("Loading Serial Extention to DCCppPacketizer");
052        cs = pCommandStation; //remember this for later
053    }
054
055    /**
056     * Determine how many bytes the entire message will take, including
057     * space for header and trailer
058     *
059     * @param m The message to be sent
060     * @return Number of bytes
061     */
062    @Override
063    protected int lengthOfByteStream(final jmri.jmrix.AbstractMRMessage m) {
064        return m.getNumDataElements() + 2;
065    }
066
067    /**
068     * <code>true</code> when the self-rescheduling function refresh action was
069     * initially queued, to avoid duplicate actions
070     */
071    private boolean backgroundRefreshStarted = false;
072
073    final class RefreshAction implements ThreadingUtil.ThreadAction {
074        @Override
075        public void run() {
076            try {
077                if (activeBackgroundRefresh) {
078                    final DCCppMessage message = resendFunctions.poll();
079
080                    if (message != null) {
081                        message.setRetries(0);
082                        sendDCCppMessage(message, null);
083                    }
084                }
085            } finally {
086                ThreadingUtil.runOnLayoutDelayed(this, 250);
087            }
088        }
089    }
090
091    private void enqueueFunction(final DCCppMessage m) {
092        /**
093         * Set again the same group function value 250ms later (or more,
094         * depending on the queue depth)
095         */
096        m.delayFor(250);
097        resendFunctions.offer(m);
098
099        synchronized (this) {
100            if (!backgroundRefreshStarted) {
101                ThreadingUtil.runOnLayoutDelayed(new RefreshAction(), 250);
102                backgroundRefreshStarted = true;
103            }
104        }
105    }
106
107    @Override
108    public void sendDCCppMessage(final DCCppMessage m, final DCCppListener reply) {
109        final boolean isFunction = m.isFunctionMessage();
110
111        /**
112         * Remove a previous value for the same function (DCC address + function
113         * group) based on
114         * {@link jmri.jmrix.dccpp.DCCppMessage#equals(DCCppMessage)}
115         */
116        if (isFunction)
117            resendFunctions.remove(m);
118
119        super.sendDCCppMessage(m, reply);
120
121        if (isFunction) { //repeat the message if the command station needs JMRI to send the refresh
122            if (cs.isFunctionRefreshRequired()) {  
123                enqueueFunction(m);
124            }
125        }
126    }
127
128    /**
129     * Clear the background refresh queue. The state is still kept in JMRI.
130     */
131    public void clearRefreshQueue() {
132        resendFunctions.clear();
133    }
134
135    /**
136     * Check how many entries are in the background refresh queue
137     *
138     * @return number of queued function groups
139     */
140    public int getQueueLength() {
141        return resendFunctions.size();
142    }
143
144    /**
145     * Enable or disable the background refresh thread
146     *
147     * @param activeState <code>true</code> to keep refreshing the functions,
148     *            <code>false</code> to disable this functionality.
149     * @return the previous active state of the background refresh thread
150     */
151    public boolean setActiveRefresh(final boolean activeState) {
152        final boolean oldActiveState = activeBackgroundRefresh;
153
154        activeBackgroundRefresh = activeState;
155
156        return oldActiveState;
157    }
158
159    /**
160     * Check if the background function refresh thread is active or not
161     *
162     * @return the background refresh status, <code>true</code> for active,
163     *         <code>false</code> if disabled.
164     */
165    public boolean isActiveRefresh() {
166        return activeBackgroundRefresh;
167    }
168
169    private static final Logger log = LoggerFactory.getLogger(SerialDCCppPacketizer.class);
170}