001package jmri.jmrix.cmri.serial;
002
003import java.io.DataInputStream;
004import java.util.ArrayList;
005import jmri.jmrix.AbstractMRListener;
006import jmri.jmrix.AbstractMRMessage;
007import jmri.jmrix.AbstractMRNodeTrafficController;
008import jmri.jmrix.AbstractMRReply;
009import jmri.jmrix.cmri.serial.cmrinetmetrics.CMRInetMetricsData;
010import jmri.jmrix.cmri.serial.cmrinetmetrics.CMRInetMetricsCollector;
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
013
014/**
015 * Converts Stream-based I/O to/from C/MRI serial messages.
016 * <p>
017 * The "SerialInterface" side sends/receives message objects.
018 * <p>
019 * The connection to a SerialPortController is via a pair of *Streams, which
020 * then carry sequences of characters for transmission. Note that this
021 * processing is handled in an independent thread.
022 * <p>
023 * This handles the state transitions, based on the necessary state in each
024 * message.
025 * <p>
026 * Handles initialization, polling, output, and input for multiple Serial Nodes.
027 *
028 * @author Bob Jacobsen Copyright (C) 2003
029 * @author Bob Jacobsen, Dave Duchamp, multiNode extensions, 2004
030 * @author Chuck Catania Copyright (C) 2014,2016 CMRInet extensions
031 */
032public class SerialTrafficController extends AbstractMRNodeTrafficController implements SerialInterface {
033
034    /**
035     * Create a new C/MRI SerialTrafficController instance.
036     */
037    CMRInetMetricsCollector metricsCollector;
038
039    public SerialTrafficController() {
040        super();
041
042        // set node range
043        super.init(0, 127);
044
045        // entirely poll driven, so reduce interval
046        mWaitBeforePoll = 5;  // default = 25
047
048        metricsCollector = new CMRInetMetricsCollector();
049        addSerialListener(metricsCollector);
050    }
051
052    // The methods to implement the SerialInterface
053    @Override
054    public synchronized void addSerialListener(SerialListener l) {
055        this.addListener(l);
056    }
057
058    @Override
059    public synchronized void removeSerialListener(SerialListener l) {
060        this.removeListener(l);
061    }
062
063    /**
064     * Initialize a CMRI node.
065     *
066     * @param node the node to initialize
067     */
068    public void initializeSerialNode(SerialNode node) {
069        synchronized (this) {
070            // find the node in the registered node list
071            for (int i = 0; i < getNumNodes(); i++) {
072                if (getNode(i) == node) {
073                    // found node - set up for initialization
074                    setMustInit(i, true);
075                    return;
076                }
077            }
078        }
079    }
080
081    @Override
082    protected AbstractMRMessage enterProgMode() {
083        log.warn("enterProgMode doesn't make sense for C/MRI serial");
084        return null;
085    }
086
087    /**
088     * Expose metrics data
089     * @return metrics data
090     */
091    public CMRInetMetricsData getMetricsData()
092    {
093      return metricsCollector.getMetricData();
094    }
095
096    @Override
097    protected AbstractMRMessage enterNormalMode() {
098        // can happen during error recovery, null is OK
099        return null;
100    }
101
102    /**
103     * Forward a message to all registered listeners.
104     *
105     * @param client the listener to receive the message; may throw an uncaught
106     *               exception if not a
107     *               {@link jmri.jmrix.cmri.serial.SerialListener}
108     * @param m      the message to forward; may throw an uncaught exception if
109     *               not a {@link jmri.jmrix.cmri.serial.SerialMessage}
110     */
111    @Override
112    protected void forwardMessage(AbstractMRListener client, AbstractMRMessage m) {
113        ((SerialListener) client).message((SerialMessage) m);
114//        log.info("forward Message");
115
116    }
117
118    /**
119     * Forward a reply to all registered listeners.
120     *
121     * @param client the listener to receive the reply; may throw an uncaught
122     *               exception if not a
123     *               {@link jmri.jmrix.cmri.serial.SerialListener}
124     * @param m      the reply to forward; may throw an uncaught exception if
125     *               not a {@link jmri.jmrix.cmri.serial.SerialMessage}
126     */
127    @Override
128    protected void forwardReply(AbstractMRListener client, AbstractMRReply m) {
129        ((SerialListener) client).reply((SerialReply) m);
130//        log.info("reply Message");
131    }
132
133    SerialSensorManager mSensorManager = null;
134
135    public void setSensorManager(SerialSensorManager m) {
136        mSensorManager = m;
137    }
138
139    public boolean pollNetwork = true;  // true if network polling enabled
140
141    // For later enhancements to network manager
142    //------------------------------------------
143    private int initTimeout = 500;
144    private int xmitTimeout = 2;
145    // cpNode poll list
146    public ArrayList<Integer> cmriNetPollList = new ArrayList<>();
147
148    public void setPollNetwork(boolean OnOff) {
149        pollNetwork = OnOff;
150    }
151
152    public boolean getPollNetwork() {
153        return pollNetwork;
154    }
155
156    public void setInitTimeout(int init_Timeout) {
157        initTimeout = init_Timeout;
158    }
159
160    public int getInitTimeout() {
161        return initTimeout;
162    }
163
164    public void setXmitTimeout(int init_XmitTimeout) {
165        xmitTimeout = init_XmitTimeout;
166    }
167
168    public int getXmitTimeout() {
169        return xmitTimeout;
170    }
171
172    /**
173     * Handles initialization, output and polling for C/MRI Serial Nodes from
174     * within the running thread.
175     * <p>
176     * {@inheritDoc}
177     */
178    @Override
179    protected synchronized AbstractMRMessage pollMessage() {
180        log.trace("pollMessage starts");
181        // ensure validity of call
182        if (getNumNodes() <= 0) {
183            log.trace("pollMessage ends with zero nodes");
184            return null;
185        }
186
187        // If total network polling not enabled, exit
188        if (!getPollNetwork()) {
189            log.trace("pollMessage ends with getPollNetwork disabled");
190            return null;
191        }
192
193        int previousPollPointer = curSerialNodeIndex;
194        updatePollPointer(); // service next node next
195
196        // ensure that each node is initialized
197        SerialNode n = (SerialNode) getNode(curSerialNodeIndex);
198
199        if (getMustInit(curSerialNodeIndex)) {
200            setMustInit(curSerialNodeIndex, false);
201            AbstractMRMessage m = getNode(curSerialNodeIndex).createInitPacket();
202            log.debug("send init message: {}", m);
203            m.setTimeout(500);  // wait for init to finish (milliseconds)
204            // m.setTimeout( getInitTimeout() );  //c2
205            n.setPollStatus(SerialNode.POLLSTATUS_INIT); //c2
206
207            log.trace("pollMessage provides Init message");
208            return m;
209        }
210        // send Output packet if needed
211        if (getNode(curSerialNodeIndex).mustSend()) {
212            log.debug("request write command to send");
213            getNode(curSerialNodeIndex).resetMustSend();
214            AbstractMRMessage m = getNode(curSerialNodeIndex).createOutPacket();
215            m.setTimeout(2);  // no need to wait for output to answer
216            // m.setTimeout( getXmitTimeout() );  // no need to wait for output to answer
217
218            // reset poll pointer update, so next increment will poll from here
219            curSerialNodeIndex = previousPollPointer;
220            log.trace("pollMessage provides Transmit message");
221            return m;
222        }
223
224        // poll for Sensor input
225        //-------------------------------------
226        // Poll node if polling enabled for this node  //c2
227        // update polling status for the node
228        //-------------------------------------
229
230        if (!n.getPollingEnabled()) {
231            n.setPollStatus(SerialNode.POLLSTATUS_IDLE);
232            log.trace("pollMessage ends with getPollingEnabled disabled");
233            return null;
234        } else if (getNode(curSerialNodeIndex).getSensorsActive()) {
235            if (n.getPollStatus() != SerialNode.POLLSTATUS_POLLING) {
236                n.setPollStatus(SerialNode.POLLSTATUS_POLLING);
237            }
238
239            // Some sensors are active for this node, issue poll
240            SerialMessage m = SerialMessage.getPoll(
241                    getNode(curSerialNodeIndex).getNodeAddress());
242            log.trace("pollMessage ends with poll message");
243            return m;
244        } else {
245            // no Sensors (inputs) are active for this node
246            log.trace("pollMessage ends with no sensors active");
247            return null;
248        }
249    }
250
251    /**
252     * Update the curSerialNodeIndex so next node polled next time
253     */
254    private void updatePollPointer() {
255        curSerialNodeIndex++;
256        if (curSerialNodeIndex >= getNumNodes()) {
257            curSerialNodeIndex = 0;
258        }
259    }
260
261    @Override
262    /**
263     * Log an error message for a message received in an unexpected state.
264     *
265     * The severity depends on whether this is a network connection or not.
266     *
267     * @param State message state.
268     * @param msgString message string.
269     */
270    protected void unexpectedReplyStateError(int State, String msgString) {
271        String[] packages = this.getClass().getName().split("\\.");
272        String name = (packages.length>=2 ? packages[packages.length-2]+"." :"")
273                     +(packages.length>=1 ? packages[packages.length-1] :"");
274        // determine the connection type
275        if (controller instanceof jmri.jmrix.cmri.serial.networkdriver.NetworkDriverAdapter) {
276            // these are probably normal
277            log.debug("reply complete in unexpected state: {} was {} in class {}", State, msgString, name);
278        } else {
279            // other types of connections, make visible
280            log.warn("reply complete in unexpected state: {} was {} in class {}", State, msgString, name);
281        }
282
283
284    }
285
286    @Override
287    protected synchronized void handleTimeout(AbstractMRMessage m, AbstractMRListener l) {
288        // don't use super behavior, as timeout to init, transmit message is normal
289        SerialNode n = (SerialNode) getNode(curSerialNodeIndex);
290
291        // inform node, and if it resets then reinitialize
292        if (getNode(curSerialNodeIndex).handleTimeout(m, l)) {
293            if (n.getPollingEnabled()) //c2
294            {
295                n.setPollStatus(SerialNode.POLLSTATUS_TIMEOUT);
296             metricsCollector.getMetricData().incMetricErrValue(CMRInetMetricsData.CMRInetMetricTimeout);
297            }
298            setMustInit(curSerialNodeIndex, true);
299        }
300
301    }
302
303    @Override
304    protected synchronized void resetTimeout(AbstractMRMessage m) {
305        // don't use super behavior, as timeout to init, transmit message is normal
306
307        // and inform node
308        getNode(curSerialNodeIndex).resetTimeout(m);
309
310    }
311
312    @Override
313    protected AbstractMRListener pollReplyHandler() {
314        return mSensorManager;
315    }
316
317    /**
318     * Forward a pre-formatted message to the actual interface.
319     *
320     * @param m     the message to forward
321     * @param reply the listener for the response to m
322     */
323    @Override
324    public void sendSerialMessage(SerialMessage m, SerialListener reply) {
325        sendMessage(m, reply);
326    }
327
328    @Override
329    protected AbstractMRReply newReply() {
330        return new SerialReply();
331    }
332
333    @Override
334    protected boolean endOfMessage(AbstractMRReply msg) {
335        // our version of loadChars doesn't invoke this, so it shouldn't be called
336        log.error("Not using endOfMessage, should not be called");
337        return false;
338    }
339
340    @Override
341    protected void loadChars(AbstractMRReply msg, DataInputStream istream) throws java.io.IOException {
342        int i;
343        for (i = 0; i < msg.maxSize(); i++) {
344            byte char1 = readByteProtected(istream);
345            if (char1 == 0x03) {
346                break;           // check before DLE handling
347            }
348            if (char1 == 0x10) {
349                char1 = readByteProtected(istream);
350            }
351            msg.setElement(i, char1 & 0xFF);
352        }
353    }
354
355    @Override
356    protected void waitForStartOfReply(DataInputStream istream) throws java.io.IOException {
357        // loop looking for the start character
358        while (readByteProtected(istream) != 0x02) {
359        }
360    }
361
362    /**
363     * Add header to the outgoing byte stream.
364     *
365     * @param msg the output byte stream
366     * @param m   the message in msg
367     * @return next location in the stream to fill
368     */
369    @Override
370    protected int addHeaderToOutput(byte[] msg, AbstractMRMessage m) {
371        msg[0] = (byte) 0xFF;
372        msg[1] = (byte) 0xFF;
373        msg[2] = (byte) 0x02;  // STX
374        return 3;
375    }
376
377    /**
378     * Add trailer to the outgoing byte stream.
379     *
380     * @param msg    the output byte stream
381     * @param offset the first byte not yet used
382     * @param m      the message in msg
383     */
384    @Override
385    protected void addTrailerToOutput(byte[] msg, int offset, AbstractMRMessage m) {
386        msg[offset] = 0x03;  // etx
387    }
388
389    /**
390     * Determine how much many bytes the entire message will take, including
391     * space for header and trailer
392     *
393     * @param m The message to be sent
394     * @return Number of bytes
395     */
396    @Override
397    protected int lengthOfByteStream(AbstractMRMessage m) {
398        int len = m.getNumDataElements();
399        int cr = 4;
400        return len + cr;
401    }
402
403    private final static Logger log = LoggerFactory.getLogger(SerialTrafficController.class);
404
405}