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}