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++ 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    }
041    
042    @Override
043    public void connect() throws java.io.IOException {
044        super.connect();
045        log.debug("openPort called");
046        keepAliveTimer();
047    }
048    
049    /**
050     * Can the port accept additional characters?
051     *
052     * @return true if the port is opened
053     */
054    @Override
055    public boolean okToSend() {
056        return status();
057    }
058    
059    @Override
060    public boolean status() {
061        return (opened);
062    }
063    
064    /**
065     * Set up all of the other objects to operate with a LIUSB Ethernet
066     * interface.
067     */
068    @Override
069    public void configure() {
070        log.debug("configure called");
071        // connect to a packetizing traffic controller
072        DCCppTrafficController packets = (new DCCppEthernetPacketizer(new DCCppCommandStation()));
073        packets.connectPort(this);
074        
075        // start operation
076        // packets.startThreads();
077        this.getSystemConnectionMemo().setDCCppTrafficController(packets);
078        
079        new DCCppInitializationManager(this.getSystemConnectionMemo());
080    }
081    
082    /**
083     * Set up the keepAliveTimer, and start it.
084     */
085    private void keepAliveTimer() {
086        if (keepAliveTimer != null) {
087            return; //one already exists, exit
088        }
089        keepAliveTimer = new java.util.TimerTask(){
090                @Override
091                public void run() {
092                    // When the timer times out, send a heartbeat (status request on DCC++, max num slots request on DCC-EX
093                    DCCppTrafficController tc = DCCppEthernetAdapter.this.getSystemConnectionMemo().getDCCppTrafficController();
094                    DCCppCommandStation cs = tc.getCommandStation();
095                    if (cs.isMaxNumSlotsMsgSupported()) {
096                        tc.sendDCCppMessage(jmri.jmrix.dccpp.DCCppMessage.makeCSMaxNumSlotsMsg(), null);                        
097                    } else {
098                        tc.sendDCCppMessage(jmri.jmrix.dccpp.DCCppMessage.makeCSStatusMsg(), null);
099                    }
100                }
101            };
102        jmri.util.TimerUtil.schedule(keepAliveTimer, keepAliveTimeoutValue, keepAliveTimeoutValue);
103    }
104    
105    private boolean mDNSConfigure = false;
106    
107    /**
108     * Set whether or not this adapter should be
109     * configured automatically via MDNS.
110     *
111     * @param autoconfig boolean value.
112     */
113    @Override
114    public void setMdnsConfigure(boolean autoconfig) {
115        log.debug("Setting DCC++ Ethernet adapter autoconfiguration to: {}", autoconfig);
116        mDNSConfigure = autoconfig;
117    }
118    
119    /**
120     * Get whether or not this adapter is configured
121     * to use autoconfiguration via MDNS.
122     *
123     * @return true if configured using MDNS.
124     */
125    @Override
126    public boolean getMdnsConfigure() {
127        return mDNSConfigure;
128    }
129    
130    /**
131     * Set the server's host name and port
132     * using mdns autoconfiguration.
133     */
134    @Override
135    public void autoConfigure() {
136        log.info("Configuring DCC++ interface via JmDNS");
137        if (getHostName().equals(DEFAULT_IP_ADDRESS)) {
138            setHostName(""); // reset the hostname to none.
139        }
140        String serviceType = Bundle.getMessage("defaultMDNSServiceType");
141        log.debug("Listening for service: {}", serviceType);
142        
143        if (mdnsClient == null) {
144            mdnsClient = new ZeroConfClient();
145            mdnsClient.startServiceListener(serviceType);
146        }
147        // leave the wait code below commented out for now.  It
148        // does not appear to be needed for proper ZeroConf discovery.
149        //try {
150        //  synchronized(mdnsClient){
151        //  // we may need to add a timeout here.
152        //  mdnsClient.wait(keepAliveTimeoutValue);
153        //  if(log.isDebugEnabled()) mdnsClient.listService(serviceType);
154        //  }
155        //} catch(java.lang.InterruptedException ie){
156        //  log.error("MDNS auto Configuration failed.");
157        //  return;
158        //}
159        try {
160            // if there is a hostname set, use the host name (which can
161            // be changed) to find the service.
162            String qualifiedHostName = m_HostName
163                + "." + Bundle.getMessage("defaultMDNSDomainName");
164            setHostAddress(mdnsClient.getServiceOnHost(serviceType,
165                                                       qualifiedHostName).getHostAddresses()[0]);
166        } catch (java.lang.NullPointerException npe) {
167            // if there is no hostname set, use the service name (which can't
168            // be changed) to find the service.
169            String qualifiedServiceName = Bundle.getMessage("defaultMDNSServiceName")
170                + "." + serviceType;
171            setHostAddress(mdnsClient.getServicebyAdName(serviceType,
172                                                         qualifiedServiceName).getHostAddresses()[0]);
173        }
174    }
175    
176    ZeroConfClient mdnsClient = null;
177    
178    /**
179     * Get the ZeroConf/mDNS advertisement name.
180     * this value is fixed on the LIUSB-Ethernet, so return the default
181     * value.
182     */
183    @Override
184    public String getAdvertisementName() {
185        return Bundle.getMessage("defaultMDNSServiceName");
186    }
187    
188    /**
189     * Get the ZeroConf/mDNS service type.
190     * this value is fixed on the LIUSB-Ethernet, so return the default
191     * value.
192     */
193    @Override
194    public String getServiceType() {
195        return Bundle.getMessage("defaultMDNSServiceType");
196    }
197
198    private final static Logger log = LoggerFactory.getLogger(DCCppEthernetAdapter.class);
199
200}