001package jmri.jmrix.dccpp.network;
002
003import jmri.jmrix.dccpp.DCCppCommandStation;
004import jmri.jmrix.dccpp.DCCppInitializationManager;
005import jmri.jmrix.dccpp.DCCppNetworkPortController;
006import jmri.jmrix.dccpp.DCCppTrafficController;
007import jmri.util.zeroconf.ZeroConfClient;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011/**
012 * Provide access to DCC-EX Base Station via Ethernet. NOTES: By default,
013 * the LIUSBEthernet has an IP address of 192.168.0.200 and listens to port
014 * 5550. The LIUSBEtherenet disconnects both ports if there is 60 seconds of
015 * inactivity on the port.
016 *
017 * @author Paul Bender (C) 2011-2013
018 * @author Mark Underwood (C) 2015
019 * Based on LIUSBEthernetAdapter
020 */
021public class DCCppEthernetAdapter extends DCCppNetworkPortController {
022
023    static final int COMMUNICATION_TCP_PORT = 2560;
024    static final String DEFAULT_IP_ADDRESS = "192.168.0.200";
025
026    private java.util.TimerTask keepAliveTimer; // Timer used to periodically
027    // send a message to both
028    // ports to keep the ports 
029    // open
030    private static final long keepAliveTimeoutValue = 30000; // Interval 
031    // to send a message
032    // Must be < 60s.
033    
034    public DCCppEthernetAdapter() {
035        super();
036        log.debug("Constructor Called");
037        setHostName(DEFAULT_IP_ADDRESS);
038        setPort(COMMUNICATION_TCP_PORT);
039        this.manufacturerName = jmri.jmrix.dccpp.DCCppConnectionTypeList.DCCPP;
040        // Recover automatically from a dropped connection by default; retry indefinitely.
041        allowConnectionRecovery = true;
042        reconnectMaxAttempts = -1;
043    }
044
045    @Override
046    public void recover() {
047        if (allowConnectionRecovery && opened) {
048            log.info("Connection to {}:{} lost. Attempting to recover...", getHostName(), getPort());
049        }
050        super.recover();
051    }
052    
053    @Override
054    public void connect() throws java.io.IOException {
055        super.connect();
056        log.debug("openPort called");
057        // Set a read timeout so the receive loop detects a dead connection
058        // rather than blocking indefinitely. Value is 3x the keepAlive interval.
059        setConnectionTimeout((int) (keepAliveTimeoutValue * 3));
060        keepAliveTimer();
061    }
062    
063    /**
064     * Can the port accept additional characters?
065     *
066     * @return true if the port is opened
067     */
068    @Override
069    public boolean okToSend() {
070        return status();
071    }
072    
073    @Override
074    public boolean status() {
075        return (opened);
076    }
077    
078    /**
079     * Set up all of the other objects to operate with a LIUSB Ethernet
080     * interface.
081     */
082    @Override
083    public void configure() {
084        log.debug("configure called");
085        // connect to a packetizing traffic controller
086        DCCppTrafficController packets = (new DCCppEthernetPacketizer(new DCCppCommandStation()));
087        packets.connectPort(this);
088        
089        // start operation
090        // packets.startThreads();
091        this.getSystemConnectionMemo().setDCCppTrafficController(packets);
092        
093        new DCCppInitializationManager(this.getSystemConnectionMemo());
094    }
095    
096    @Override
097    protected void closeConnection() {
098        if (keepAliveTimer != null) {
099            keepAliveTimer.cancel();
100            keepAliveTimer = null;
101        }
102        super.closeConnection();
103    }
104
105    /**
106     * Set up the keepAliveTimer, and start it.
107     */
108    private void keepAliveTimer() {
109        if (keepAliveTimer != null) {
110            return; //one already exists, exit
111        }
112        keepAliveTimer = new java.util.TimerTask(){
113                @Override
114                public void run() {
115                    // When the timer times out, send a heartbeat (status request on DCC-EX, max num slots request on DCC-EX
116                    DCCppTrafficController tc = DCCppEthernetAdapter.this.getSystemConnectionMemo().getDCCppTrafficController();
117                    DCCppCommandStation cs = tc.getCommandStation();
118                    if (cs.isMaxNumSlotsMsgSupported()) {
119                        tc.sendDCCppMessage(jmri.jmrix.dccpp.DCCppMessage.makeCSMaxNumSlotsMsg(), null);                        
120                    } else {
121                        tc.sendDCCppMessage(jmri.jmrix.dccpp.DCCppMessage.makeCSStatusMsg(), null);
122                    }
123                }
124            };
125        jmri.util.TimerUtil.schedule(keepAliveTimer, keepAliveTimeoutValue, keepAliveTimeoutValue);
126    }
127    
128    private boolean mDNSConfigure = false;
129    
130    /**
131     * Set whether or not this adapter should be
132     * configured automatically via MDNS.
133     *
134     * @param autoconfig boolean value.
135     */
136    @Override
137    public void setMdnsConfigure(boolean autoconfig) {
138        log.debug("Setting DCC-EX Ethernet adapter autoconfiguration to: {}", autoconfig);
139        mDNSConfigure = autoconfig;
140    }
141    
142    /**
143     * Get whether or not this adapter is configured
144     * to use autoconfiguration via MDNS.
145     *
146     * @return true if configured using MDNS.
147     */
148    @Override
149    public boolean getMdnsConfigure() {
150        return mDNSConfigure;
151    }
152    
153    /**
154     * Set the server's host name and port
155     * using mdns autoconfiguration.
156     */
157    @Override
158    public void autoConfigure() {
159        log.info("Configuring DCC-EX interface via JmDNS");
160        if (getHostName().equals(DEFAULT_IP_ADDRESS)) {
161            setHostName(""); // reset the hostname to none.
162        }
163        String serviceType = Bundle.getMessage("defaultMDNSServiceType");
164        log.debug("Listening for service: {}", serviceType);
165        
166        if (mdnsClient == null) {
167            mdnsClient = new ZeroConfClient();
168            mdnsClient.startServiceListener(serviceType);
169        }
170        // leave the wait code below commented out for now.  It
171        // does not appear to be needed for proper ZeroConf discovery.
172        //try {
173        //  synchronized(mdnsClient){
174        //  // we may need to add a timeout here.
175        //  mdnsClient.wait(keepAliveTimeoutValue);
176        //  if(log.isDebugEnabled()) mdnsClient.listService(serviceType);
177        //  }
178        //} catch(java.lang.InterruptedException ie){
179        //  log.error("MDNS auto Configuration failed.");
180        //  return;
181        //}
182        try {
183            // if there is a hostname set, use the host name (which can
184            // be changed) to find the service.
185            String qualifiedHostName = m_HostName
186                + "." + Bundle.getMessage("defaultMDNSDomainName");
187            setHostAddress(mdnsClient.getServiceOnHost(serviceType,
188                                                       qualifiedHostName).getHostAddresses()[0]);
189        } catch (java.lang.NullPointerException npe) {
190            // if there is no hostname set, use the service name (which can't
191            // be changed) to find the service.
192            String qualifiedServiceName = Bundle.getMessage("defaultMDNSServiceName")
193                + "." + serviceType;
194            setHostAddress(mdnsClient.getServicebyAdName(serviceType,
195                                                         qualifiedServiceName).getHostAddresses()[0]);
196        }
197    }
198    
199    ZeroConfClient mdnsClient = null;
200    
201    /**
202     * Get the ZeroConf/mDNS advertisement name.
203     * this value is fixed on the LIUSB-Ethernet, so return the default
204     * value.
205     */
206    @Override
207    public String getAdvertisementName() {
208        return Bundle.getMessage("defaultMDNSServiceName");
209    }
210    
211    /**
212     * Get the ZeroConf/mDNS service type.
213     * this value is fixed on the LIUSB-Ethernet, so return the default
214     * value.
215     */
216    @Override
217    public String getServiceType() {
218        return Bundle.getMessage("defaultMDNSServiceType");
219    }
220
221    private static final Logger log = LoggerFactory.getLogger(DCCppEthernetAdapter.class);
222
223}