001package jmri.jmrix.dccpp.network;
002    
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import java.util.Arrays;
005import javax.swing.SwingUtilities;
006import jmri.jmrix.AbstractMRListener;
007import jmri.jmrix.AbstractMRMessage;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011/**
012 * An extension of the DCCppPacketizer to handle the device specific
013 * requirements of the DCC++ Ethernet.
014 * <p>
015 * In particular, DCCppEthernetPacketizer counts the number of commands
016 * received.
017 *
018 * Based on LIUSBEthernetXnetPacketizer
019 * 
020 * @author Paul Bender, Copyright (C) 2011
021 * @author      Mark Underwood, Copyright (C) 2015
022 */
023public class DCCppEthernetPacketizer extends jmri.jmrix.dccpp.serial.SerialDCCppPacketizer {
024
025    public DCCppEthernetPacketizer(jmri.jmrix.dccpp.DCCppCommandStation pCommandStation) {
026        super(pCommandStation);
027        log.debug("Loading DCC++ Ethernet Extension to DCCppPacketizer");
028    }
029
030    /**
031     * Actually transmits the next message to the port
032     */
033    // NOTE: This is (for now) an EXACT copy of the content of AbstractMRTrafficController.forwardToPort()
034    // except for adding the call to controller.recover() at the bottom in the "catch"
035    @Override
036    @SuppressFBWarnings(value = {"TLW_TWO_LOCK_WAIT"},justification = "Two locks needed for synchronization here, this is OK")
037    synchronized protected void forwardToPort(AbstractMRMessage m, AbstractMRListener reply) {
038        log.debug("forwardToPort message: [{}]", m);
039        // remember who sent this
040        mLastSender = reply;
041        
042        // forward the message to the registered recipients,
043        // which includes the communications monitor, except the sender.
044        // Schedule notification via the Swing event queue to ensure order
045        Runnable r = new XmtNotifier(m, mLastSender, this);
046        SwingUtilities.invokeLater(r);
047        
048        // stream to port in single write, as that's needed by serial
049        byte[] msg = new byte[lengthOfByteStream(m)];
050        // add header
051        int offset = addHeaderToOutput(msg, m);
052        
053        // add data content
054        int len = m.getNumDataElements();
055        for (int i = 0; i < len; i++) {
056            msg[i + offset] = (byte) m.getElement(i);
057        }
058        // add trailer
059        addTrailerToOutput(msg, len + offset, m);
060        // and stream the bytes
061        try {
062            if (ostream != null) {
063                if (log.isDebugEnabled()) {
064                    StringBuilder f = new StringBuilder();
065                    for (byte b : msg) {
066                        f.append(Integer.toHexString(0xFF & b));
067                        f.append(" ");
068                    }
069                    log.debug("formatted message: {}", f);
070                }
071                while (m.getRetries() >= 0) {
072                    if (portReadyToSend(controller)) {
073                        ostream.write(msg);
074                        ostream.flush();
075                        log.debug("written, msg timeout: {} mSec", m.getTimeout());
076                        break;
077                    } else if (m.getRetries() >= 0) {
078                        log.debug("Retry message: '{}' attempts remaining: {}", m, m.getRetries());
079                        m.setRetries(m.getRetries() - 1);
080                        try {
081                            int timeOut = m.getTimeout();
082                            synchronized (xmtRunnable) {
083                                if (timeOut > 0) {
084                                    xmtRunnable.wait(timeOut);
085                                }
086                            }
087                        } catch (InterruptedException e) {
088                            Thread.currentThread().interrupt(); // retain if needed later
089                            log.error("retry wait interrupted");
090                        }
091                    } else {
092                        log.warn("sendMessage: port not ready for data sending: {}", Arrays.toString(msg));
093                    }
094                }
095            } else {  // ostream is null
096                // no stream connected
097                connectionWarn();
098            }
099        } catch (java.io.IOException e) {
100            // TODO Currently there's no port recovery if an exception occurs
101            // must restart JMRI to clear xmtException.
102            //xmtException = true;
103            portWarn(e);
104            // Attempt to recover the connection...
105            controller.recover();
106        }
107    }
108
109    private final static Logger log = LoggerFactory.getLogger(DCCppEthernetPacketizer.class);
110
111}