001package jmri.jmrix.loconet.locobuffer; 002 003import java.io.DataInputStream; 004import java.io.DataOutputStream; 005import java.io.IOException; 006import java.io.InputStream; 007import java.util.Arrays; 008import java.util.Enumeration; 009import java.util.Vector; 010import jmri.jmrix.loconet.LnCommandStationType; 011import jmri.jmrix.loconet.LnPacketizer; 012import jmri.jmrix.loconet.LnPacketizerStrict; 013import jmri.jmrix.loconet.LnPortController; 014import jmri.jmrix.loconet.LocoNetSystemConnectionMemo; 015import org.slf4j.Logger; 016import org.slf4j.LoggerFactory; 017import purejavacomm.CommPortIdentifier; 018import purejavacomm.NoSuchPortException; 019import purejavacomm.PortInUseException; 020import purejavacomm.SerialPort; 021import purejavacomm.UnsupportedCommOperationException; 022 023/** 024 * Provide access to LocoNet via a LocoBuffer attached to a serial com port. 025 * <p> 026 * Normally controlled by the LocoBufferFrame class. 027 * 028 * @author Bob Jacobsen Copyright (C) 2001, 2008, 2010 029 */ 030public class LocoBufferAdapter extends LnPortController { 031 032 public LocoBufferAdapter() { 033 this(new LocoNetSystemConnectionMemo()); 034 } 035 036 public LocoBufferAdapter(LocoNetSystemConnectionMemo adapterMemo) { 037 super(adapterMemo); 038 option1Name = "FlowControl"; // NOI18N 039 option2Name = "CommandStation"; // NOI18N 040 option3Name = "TurnoutHandle"; // NOI18N 041 option4Name = "PacketizerType"; // NOI18N 042 options.put(option1Name, new Option(Bundle.getMessage("XconnectionUsesLabel", Bundle.getMessage("TypeSerial")), validOption1)); // NOI18N 043 options.put(option2Name, new Option(Bundle.getMessage("CommandStationTypeLabel"), getCommandStationListWithStandaloneLN(), false)); // NOI18N 044 options.put(option3Name, new Option(Bundle.getMessage("TurnoutHandling"), 045 new String[]{Bundle.getMessage("HandleNormal"), Bundle.getMessage("HandleSpread"), Bundle.getMessage("HandleOneOnly"), Bundle.getMessage("HandleBoth")})); // I18N 046 options.put(option4Name, new Option(Bundle.getMessage("PacketizerTypeLabel"), packetizerOptions())); // NOI18N 047 options.put("TranspondingPresent", new Option(Bundle.getMessage("TranspondingPresent"), 048 new String[]{Bundle.getMessage("ButtonNo"), Bundle.getMessage("ButtonYes")} )); // NOI18N 049 options.put("InterrogateOnStart", new Option(Bundle.getMessage("InterrogateOnStart"), 050 new String[]{Bundle.getMessage("ButtonYes"), Bundle.getMessage("ButtonNo")} )); // NOI18N 051 options.put("LoconetProtocolAutoDetect", new Option(Bundle.getMessage("LoconetProtocolAutoDetectLabel"), 052 new String[]{Bundle.getMessage("ButtonNo"),Bundle.getMessage("LoconetProtocolAutoDetect")} )); // NOI18N 053 } 054 055 /** 056 * Create a list of possible command stations and append "Standalone LocoNet" 057 * 058 * Note: This is not suitable for use by any class which extends this class if 059 * the hardware interface is part of a command station. 060 * 061 * @return String[] containing the array of command stations, plus "Standalone 062 * LocoNet" 063 */ 064 public String[] getCommandStationListWithStandaloneLN() { 065 String[] result = new String[commandStationNames.length + 1]; 066 for (int i = 0 ; i < result.length-1; ++i) { 067 result[i] = commandStationNames[i]; 068 } 069 result[commandStationNames.length] = LnCommandStationType.COMMAND_STATION_STANDALONE.getName(); 070 return result; 071 } 072 073 Vector<String> portNameVector = null; 074 SerialPort activeSerialPort = null; 075 076 @Override 077 public Vector<String> getPortNames() { 078 // first, check that the comm package can be opened and ports seen 079 portNameVector = new Vector<>(); 080 Enumeration<CommPortIdentifier> portIDs = CommPortIdentifier.getPortIdentifiers(); 081 // find the names of suitable ports 082 while (portIDs.hasMoreElements()) { 083 CommPortIdentifier id = portIDs.nextElement(); 084 // filter out line printers 085 if (id.getPortType() != CommPortIdentifier.PORT_PARALLEL) // accumulate the names in a vector 086 { 087 portNameVector.addElement(id.getName()); 088 } 089 } 090 return portNameVector; 091 } 092 093 @Override 094 public String openPort(String portName, String appName) { 095 // open the primary and secondary ports in LocoNet mode, check ability to set moderators 096 try { 097 // get and open the primary port 098 CommPortIdentifier portID = CommPortIdentifier.getPortIdentifier(portName); 099 try { 100 activeSerialPort = (SerialPort) portID.open(appName, 2000); // name of program, msec to wait 101 } catch (PortInUseException p) { 102 return handlePortBusy(p, portName, log); 103 } 104 // try to set it for LocoNet via LocoBuffer 105 try { 106 setSerialPort(activeSerialPort); 107 } catch (UnsupportedCommOperationException e) { 108 log.error("Cannot set serial parameters on port {}: {}", portName, e.getMessage()); 109 return "Cannot set serial parameters on port " + portName + ": " + e.getMessage(); // NOI18N 110 } 111 112 // set timeout 113 try { 114 activeSerialPort.enableReceiveTimeout(10); 115 log.debug("Serial timeout was observed as: {} enabled: {} threshold: {} enabled: {}", // NOI18N 116 activeSerialPort.getReceiveTimeout(), 117 activeSerialPort.isReceiveTimeoutEnabled(), 118 activeSerialPort.getReceiveThreshold(), 119 activeSerialPort.isReceiveThresholdEnabled() 120 ); 121 } catch (Exception et) { 122 log.info("failed to set serial timeout: ", et); // NOI18N 123 } 124 125 // get and save stream 126 serialStream = activeSerialPort.getInputStream(); 127 128 // purge contents, if any 129 purgeStream(serialStream); 130 131 // report status? 132 if (log.isInfoEnabled()) { 133 // report now 134 log.info("{} port opened at {} baud with DTR: {} RTS: {} DSR: {} CTS: {} CD: {}", portName, activeSerialPort.getBaudRate(), activeSerialPort.isDTR(), activeSerialPort.isRTS(), activeSerialPort.isDSR(), activeSerialPort.isCTS(), activeSerialPort.isCD()); 135 } 136 if (log.isDebugEnabled()) { 137 // report additional status 138 log.debug(" port flow control shows {}", activeSerialPort.getFlowControlMode() == SerialPort.FLOWCONTROL_RTSCTS_OUT ? "hardware flow control" : "no flow control"); // NOI18N 139 140 // log events 141 setPortEventLogging(activeSerialPort); 142 } 143 144 opened = true; 145 146 } catch (NoSuchPortException p) { 147 return handlePortNotFound(p, portName, log); 148 } catch (IOException ex) { 149 log.error("Unexpected exception while opening port {} trace follows:", portName, ex); // NOI18N 150 return "Unexpected error while opening port " + portName + ": " + ex; 151 } 152 153 return null; // normal operation 154 } 155 156 /** 157 * Can the port accept additional characters? The state of CTS determines 158 * this, as there seems to be no way to check the number of queued bytes and 159 * buffer length. This might go false for short intervals, but it might also 160 * stick off if something goes wrong. 161 * 162 * @return an indication of whether the interface is accepting transmit messages. 163 */ 164 @Override 165 public boolean okToSend() { 166 return activeSerialPort.isCTS(); 167 } 168 169 /** 170 * Set up all of the other objects to operate with a LocoBuffer connected to 171 * this port. 172 */ 173 @Override 174 public void configure() { 175 176 setCommandStationType(getOptionState(option2Name)); 177 setTurnoutHandling(getOptionState(option3Name)); 178 setTranspondingAvailable(getOptionState("TranspondingPresent")); 179 setInterrogateOnStart(getOptionState("InterrogateOnStart")); 180 setLoconetProtocolAutoDetect(getOptionState("LoconetProtocolAutoDetect")); 181 // connect to a packetizing traffic controller 182 LnPacketizer packets = getPacketizer(getOptionState(option4Name)); 183 packets.connectPort(this); 184 185 // create memo 186 this.getSystemConnectionMemo().setLnTrafficController(packets); 187 // do the common manager config 188 189 this.getSystemConnectionMemo().configureCommandStation(commandStationType, 190 mTurnoutNoRetry, mTurnoutExtraSpace, mTranspondingAvailable, mInterrogateAtStart, mLoconetProtocolAutoDetect); 191 this.getSystemConnectionMemo().configureManagers(); 192 193 // start operation 194 packets.startThreads(); 195 } 196 197 // base class methods for the LnPortController interface 198 @Override 199 public DataInputStream getInputStream() { 200 if (!opened) { 201 log.error("getInputStream called before load(), stream not available"); // NOI18N 202 return null; 203 } 204 return new DataInputStream(serialStream); 205 } 206 207 @Override 208 public DataOutputStream getOutputStream() { 209 if (!opened) { 210 log.error("getOutputStream called before load(), stream not available"); // NOI18N 211 } 212 try { 213 return new DataOutputStream(activeSerialPort.getOutputStream()); 214 } catch (java.io.IOException e) { 215 log.error("getOutputStream exception: {}", e.getMessage()); 216 } 217 return null; 218 } 219 220 @Override 221 public boolean status() { 222 return opened; 223 } 224 225 /** 226 * Local method to do specific configuration, overridden in class 227 * @param activeSerialPort is the serial port to be configured 228 * @throws UnsupportedCommOperationException Usually if the hardware isn't present or capable 229 */ 230 protected void setSerialPort(SerialPort activeSerialPort) throws UnsupportedCommOperationException { 231 // find the baud rate value, configure comm options 232 int baud = currentBaudNumber(mBaudRate); 233 activeSerialPort.setSerialPortParams(baud, SerialPort.DATABITS_8, 234 SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); 235 236 // find and configure flow control from option 237 int flow = SerialPort.FLOWCONTROL_RTSCTS_OUT; // default, but also defaults in selectedOption1 238 if (getOptionState(option1Name).equals(validOption1[1])) { 239 flow = SerialPort.FLOWCONTROL_NONE; 240 } 241 configureLeadsAndFlowControl(activeSerialPort, flow); 242 243 log.info("LocoBuffer (serial) adapter{}{} RTSCTS_OUT=" + SerialPort.FLOWCONTROL_RTSCTS_OUT + " RTSCTS_IN=" + SerialPort.FLOWCONTROL_RTSCTS_IN, activeSerialPort.getFlowControlMode() == SerialPort.FLOWCONTROL_RTSCTS_OUT ? " set hardware flow control, mode=" : " set no flow control, mode=", activeSerialPort.getFlowControlMode()); 244 } 245 246 /** 247 * {@inheritDoc} 248 */ 249 @Override 250 public String[] validBaudRates() { 251 return Arrays.copyOf(validSpeeds, validSpeeds.length); 252 } 253 254 /** 255 * {@inheritDoc} 256 */ 257 @Override 258 public int[] validBaudNumbers() { 259 return Arrays.copyOf(validSpeedValues, validSpeedValues.length); 260 } 261 262 protected String[] validSpeeds = new String[]{Bundle.getMessage("Baud19200LB"), Bundle.getMessage("Baud57600LB")}; 263 protected int[] validSpeedValues = new int[]{19200, 57600}; 264 265 @Override 266 public int defaultBaudIndex() { 267 return 0; 268 } 269 270 // meanings are assigned to these above, so make sure the order is consistent 271 protected String[] validOption1 = new String[]{Bundle.getMessage("FlowOptionHwRecomm"), Bundle.getMessage("FlowOptionNo")}; 272 273 // private control members 274 private boolean opened = false; 275 InputStream serialStream = null; 276 277 /** 278 * Define the readable data and internal code 279 */ 280 private static String[][] packetizers = { {Bundle.getMessage("PacketizerTypelnPacketizer"),"lnPacketizer" }, 281 {Bundle.getMessage("PacketizerTypelnPacketizerStrict"),"lnPacketizerStrict"} }; 282 283 /** 284 * 285 * @return String array of readable choices 286 */ 287 private String[] packetizerOptions() { 288 String[] retval = new String[packetizers.length]; 289 for (int i=0;i < packetizers.length; i++) { 290 retval[i] = packetizers[i][0]; 291 } 292 return retval; 293 } 294 /** 295 * for a given readable choice return internal value 296 * or the default 297 * 298 * @param s string containing ?a packetizer name? 299 * @return internal value 300 */ 301 protected String getPacketizerOption(String s) { 302 for (int i=0;i < packetizers.length; i++) { 303 if (packetizers[i][0].equals(s)) { 304 return packetizers[i][1]; 305 } 306 } 307 return "lnPacketizer"; 308 } 309 /** 310 * 311 * @param s the packetizer to use in its readable form. 312 * @return a LnPacketizer 313 */ 314 protected LnPacketizer getPacketizer(String s) { 315 LnPacketizer packets; 316 String packetSelection = getPacketizerOption(s); 317 switch (packetSelection) { 318 case "lnPacketizer": 319 packets = new LnPacketizer(this.getSystemConnectionMemo()); 320 break; 321 case "lnPacketizerStrict": 322 packets = new LnPacketizerStrict(this.getSystemConnectionMemo()); 323 break; 324 default: 325 packets = new LnPacketizer(this.getSystemConnectionMemo()); 326 log.warn("Using Normal do not understand option [{}]", packetSelection); 327 } 328 return packets; 329 } 330 331 private final static Logger log = LoggerFactory.getLogger(LocoBufferAdapter.class); 332 333}