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                    // If the timer times out, send a request for status
093                    DCCppEthernetAdapter.this.getSystemConnectionMemo().getDCCppTrafficController()
094                        .sendDCCppMessage(jmri.jmrix.dccpp.DCCppMessage.makeCSStatusMsg(), null);
095                }
096            };
097        jmri.util.TimerUtil.schedule(keepAliveTimer, keepAliveTimeoutValue, keepAliveTimeoutValue);
098    }
099    
100    private boolean mDNSConfigure = false;
101    
102    /**
103     * Set whether or not this adapter should be
104     * configured automatically via MDNS.
105     *
106     * @param autoconfig boolean value.
107     */
108    @Override
109    public void setMdnsConfigure(boolean autoconfig) {
110        log.debug("Setting DCC++ Ethernet adapter autoconfiguration to: {}", autoconfig);
111        mDNSConfigure = autoconfig;
112    }
113    
114    /**
115     * Get whether or not this adapter is configured
116     * to use autoconfiguration via MDNS.
117     *
118     * @return true if configured using MDNS.
119     */
120    @Override
121    public boolean getMdnsConfigure() {
122        return mDNSConfigure;
123    }
124    
125    /**
126     * Set the server's host name and port
127     * using mdns autoconfiguration.
128     */
129    @Override
130    public void autoConfigure() {
131        log.info("Configuring DCC++ interface via JmDNS");
132        if (getHostName().equals(DEFAULT_IP_ADDRESS)) {
133            setHostName(""); // reset the hostname to none.
134        }
135        String serviceType = Bundle.getMessage("defaultMDNSServiceType");
136        log.debug("Listening for service: {}", serviceType);
137        
138        if (mdnsClient == null) {
139            mdnsClient = new ZeroConfClient();
140            mdnsClient.startServiceListener(serviceType);
141        }
142        // leave the wait code below commented out for now.  It
143        // does not appear to be needed for proper ZeroConf discovery.
144        //try {
145        //  synchronized(mdnsClient){
146        //  // we may need to add a timeout here.
147        //  mdnsClient.wait(keepAliveTimeoutValue);
148        //  if(log.isDebugEnabled()) mdnsClient.listService(serviceType);
149        //  }
150        //} catch(java.lang.InterruptedException ie){
151        //  log.error("MDNS auto Configuration failed.");
152        //  return;
153        //}
154        try {
155            // if there is a hostname set, use the host name (which can
156            // be changed) to find the service.
157            String qualifiedHostName = m_HostName
158                + "." + Bundle.getMessage("defaultMDNSDomainName");
159            setHostAddress(mdnsClient.getServiceOnHost(serviceType,
160                                                       qualifiedHostName).getHostAddresses()[0]);
161        } catch (java.lang.NullPointerException npe) {
162            // if there is no hostname set, use the service name (which can't
163            // be changed) to find the service.
164            String qualifiedServiceName = Bundle.getMessage("defaultMDNSServiceName")
165                + "." + serviceType;
166            setHostAddress(mdnsClient.getServicebyAdName(serviceType,
167                                                         qualifiedServiceName).getHostAddresses()[0]);
168        }
169    }
170    
171    ZeroConfClient mdnsClient = null;
172    
173    /**
174     * Get the ZeroConf/mDNS advertisement name.
175     * this value is fixed on the LIUSB-Ethernet, so return the default
176     * value.
177     */
178    @Override
179    public String getAdvertisementName() {
180        return Bundle.getMessage("defaultMDNSServiceName");
181    }
182    
183    /**
184     * Get the ZeroConf/mDNS service type.
185     * this value is fixed on the LIUSB-Ethernet, so return the default
186     * value.
187     */
188    @Override
189    public String getServiceType() {
190        return Bundle.getMessage("defaultMDNSServiceType");
191    }
192
193    private final static Logger log = LoggerFactory.getLogger(DCCppEthernetAdapter.class);
194
195}