001package jmri.jmrix.jserialcomm; 002 003import jmri.jmrix.*; 004 005import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 006 007import java.io.*; 008import java.util.Set; 009import java.util.Vector; 010import java.util.regex.Pattern; 011import java.util.stream.Collectors; 012import java.util.stream.Stream; 013 014import jmri.util.SystemType; 015 016/** 017 * Implementation of serial port using jSerialComm. 018 * 019 * @author Daniel Bergqvist (C) 2024 020 */ 021public class JSerialPort implements SerialPort { 022 023// public static final int LISTENING_EVENT_DATA_AVAILABLE = com.fazecast.jSerialComm.SerialPort.LISTENING_EVENT_DATA_AVAILABLE; 024// public static final int ONE_STOP_BIT = com.fazecast.jSerialComm.SerialPort.ONE_STOP_BIT; 025// public static final int NO_PARITY = com.fazecast.jSerialComm.SerialPort.NO_PARITY; 026 private final com.fazecast.jSerialComm.SerialPort serialPort; 027 028 /*.* 029 * Enumerate the possible parity choices 030 *./ 031 public enum Parity { 032 NONE(com.fazecast.jSerialComm.SerialPort.NO_PARITY), 033 EVEN(com.fazecast.jSerialComm.SerialPort.EVEN_PARITY), 034 ODD(com.fazecast.jSerialComm.SerialPort.ODD_PARITY); 035 036 private final int value; 037 038 Parity(int value) { 039 this.value = value; 040 } 041 042 public int getValue() { 043 return value; 044 } 045 046 public static Parity getParity(int parity) { 047 for (Parity p : Parity.values()) { 048 if (p.value == parity) { 049 return p; 050 } 051 } 052 throw new IllegalArgumentException("Unknown parity"); 053 } 054 } 055*/ 056 private JSerialPort(com.fazecast.jSerialComm.SerialPort serialPort) { 057 this.serialPort = serialPort; 058 } 059 060 @Override 061 public void addDataListener(SerialPortDataListener listener) { 062 this.serialPort.addDataListener(new com.fazecast.jSerialComm.SerialPortDataListener() { 063 @Override 064 public int getListeningEvents() { 065 return listener.getListeningEvents(); 066 } 067 068 @Override 069 public void serialEvent(com.fazecast.jSerialComm.SerialPortEvent event) { 070 listener.serialEvent(new JSerialPortEvent(event)); 071 } 072 }); 073 } 074 075 @Override 076 public InputStream getInputStream() { 077 return this.serialPort.getInputStream(); 078 } 079 080 @Override 081 public OutputStream getOutputStream() { 082 return this.serialPort.getOutputStream(); 083 } 084 085 @Override 086 public void setRTS() { 087 this.serialPort.setRTS(); 088 } 089 090 @Override 091 public void clearRTS() { 092 this.serialPort.clearRTS(); 093 } 094 095 @Override 096 public void setBaudRate(int baudrate) { 097 this.serialPort.setBaudRate(baudrate); 098 } 099 100 @Override 101 public int getBaudRate() { 102 return this.serialPort.getBaudRate(); 103 } 104 105 @Override 106 public void setNumDataBits(int bits) { 107 this.serialPort.setNumDataBits(bits); 108 } 109 110 @Override 111 public final int getNumDataBits() { 112 return serialPort.getNumDataBits(); 113 } 114 115 @Override 116 public void setNumStopBits(int bits) { 117 this.serialPort.setNumStopBits(bits); 118 } 119 120 @Override 121 public final int getNumStopBits() { 122 return serialPort.getNumStopBits(); 123 } 124 125 @Override 126 public void setParity(Parity parity) { 127 serialPort.setParity(parity.getValue()); // constants are defined with values for the specific port class 128 } 129 130 @Override 131 public Parity getParity() { 132 return Parity.getParity(serialPort.getParity()); // constants are defined with values for the specific port class 133 } 134 135 @Override 136 public void setDTR() { 137 this.serialPort.setDTR(); 138 } 139 140 @Override 141 public void clearDTR() { 142 this.serialPort.clearDTR(); 143 } 144 145 @Override 146 public boolean getDTR() { 147 return this.serialPort.getDTR(); 148 } 149 150 @Override 151 public boolean getRTS() { 152 return this.serialPort.getRTS(); 153 } 154 155 @Override 156 public boolean getDSR() { 157 return this.serialPort.getDSR(); 158 } 159 160 @Override 161 public boolean getCTS() { 162 return this.serialPort.getCTS(); 163 } 164 165 @Override 166 public boolean getDCD() { 167 return this.serialPort.getDCD(); 168 } 169 170 @Override 171 public boolean getRI() { 172 return this.serialPort.getRI(); 173 } 174 175 /** 176 * Configure the flow control settings. Keep this in synch with the 177 * FlowControl enum. 178 * 179 * @param flow set which kind of flow control to use 180 */ 181 @Override 182 public final void setFlowControl(AbstractSerialPortController.FlowControl flow) { 183 boolean result = true; 184 if (null == flow) { 185 log.error("Invalid null FlowControl enum member"); 186 } else { 187 switch (flow) { 188 case RTSCTS: 189 result = serialPort.setFlowControl(com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_RTS_ENABLED | com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_CTS_ENABLED); 190 break; 191 case XONXOFF: 192 result = serialPort.setFlowControl(com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_XONXOFF_IN_ENABLED | com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_XONXOFF_OUT_ENABLED); 193 break; 194 case NONE: 195 result = serialPort.setFlowControl(com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_DISABLED); 196 break; 197 default: 198 log.error("Invalid FlowControl enum member: {}", flow); 199 break; 200 } 201 } 202 if (!result) { 203 log.error("Port did not accept flow control setting {}", flow); 204 } 205 } 206 207 @Override 208 public void setBreak() { 209 this.serialPort.setBreak(); 210 } 211 212 @Override 213 public void clearBreak() { 214 this.serialPort.clearBreak(); 215 } 216 217 @Override 218 public final int getFlowControlSettings() { 219 return serialPort.getFlowControlSettings(); 220 } 221 222 @Override 223 public final boolean setComPortTimeouts(int newTimeoutMode, int newReadTimeout, int newWriteTimeout) { 224 return serialPort.setComPortTimeouts(newTimeoutMode, newReadTimeout, newWriteTimeout); 225 } 226 227 @Override 228 public void closePort() { 229 this.serialPort.closePort(); 230 } 231 232 @Override 233 public String getDescriptivePortName() { 234 return this.serialPort.getDescriptivePortName(); 235 } 236 237 @Override 238 public String toString() { 239 return this.serialPort.toString(); 240 } 241 242 /** 243 * Open the port. 244 * 245 * @param systemPrefix the system prefix 246 * @param inputPortName local system name for the desired port 247 * @param log Logger to use for errors, passed so that errors are logged from low-level class' 248 * @param stop_bits The number of stop bits, either 1 or 2 249 * @param parity one of the defined parity contants 250 * @return the serial port object for later use 251 */ 252 public static JSerialPort activatePort(String systemPrefix, String inputPortName, org.slf4j.Logger log, int stop_bits, Parity parity) { 253 com.fazecast.jSerialComm.SerialPort serialPort; 254 255 // check environment for overriding portName 256 String portName; 257 final String envVar = "JMRI_SERIALPORT"; 258 String fromEnv = System.getenv(envVar); 259 log.debug("Environment {} {} was {}", envVar, fromEnv, inputPortName); 260 String fromProp = System.getProperty(envVar); 261 log.debug("Property {} {} was {}", envVar, fromProp, inputPortName); 262 if (fromEnv != null ) { 263 jmri.util.LoggingUtil.infoOnce(log,"{} set, using environment \"{}\" as Port Name", envVar, fromEnv); 264 portName = fromEnv; 265 } else if (fromProp != null ) { 266 jmri.util.LoggingUtil.infoOnce(log,"{} set, using property \"{}\" as Port Name", envVar, fromProp); 267 portName = fromProp; 268 } else { 269 portName = inputPortName; 270 } 271 272 // convert the 1 or 2 stop_bits argument to the proper jSerialComm code value 273 int stop_bits_code; 274 switch (stop_bits) { 275 case 1: 276 stop_bits_code = com.fazecast.jSerialComm.SerialPort.ONE_STOP_BIT; 277 break; 278 case 2: 279 stop_bits_code = com.fazecast.jSerialComm.SerialPort.TWO_STOP_BITS; 280 break; 281 default: 282 throw new IllegalArgumentException("Incorrect stop_bits argument: " + stop_bits); 283 } 284 try { 285 serialPort = com.fazecast.jSerialComm.SerialPort.getCommPort(portName); 286 serialPort.openPort(); 287 serialPort.setComPortTimeouts(com.fazecast.jSerialComm.SerialPort.TIMEOUT_READ_BLOCKING, 0, 0); 288 serialPort.setNumDataBits(8); 289 serialPort.setNumStopBits(stop_bits_code); 290 serialPort.setParity(parity.getValue()); 291 AbstractPortController.purgeStream(serialPort.getInputStream()); 292 } catch (java.io.IOException | com.fazecast.jSerialComm.SerialPortInvalidPortException ex) { 293 // IOException includes 294 // com.fazecast.jSerialComm.SerialPortIOException 295 AbstractSerialPortController.handlePortNotFound(systemPrefix, portName, log, ex); 296 return null; 297 } 298 return new JSerialPort(serialPort); 299 } 300 301 private static String getSymlinkTarget(File symlink) { 302 try { 303 // Path.toRealPath() follows a symlink 304 return symlink.toPath().toRealPath().toFile().getName(); 305 } catch (IOException e) { 306 return null; 307 } 308 } 309 310 /** 311 * Provide the actual serial port names. 312 * As a public static method, this can be accessed outside the jmri.jmrix 313 * package to get the list of names for e.g. context reports. 314 * 315 * @return the port names in the form they can later be used to open the port 316 */ 317 // @SuppressWarnings("UseOfObsoleteCollectionType") // historical interface 318 @SuppressFBWarnings(value = "DMI_HARDCODED_ABSOLUTE_FILENAME") 319 public static Vector<String> getActualPortNames() { 320 // first, check that the comm package can be opened and ports seen 321 java.util.Vector<java.lang.String> portNameVector = new Vector<String>(); 322 com.fazecast.jSerialComm.SerialPort[] portIDs = com.fazecast.jSerialComm.SerialPort.getCommPorts(); 323 // find the names of suitable ports 324 for (com.fazecast.jSerialComm.SerialPort portID : portIDs) { 325 portNameVector.addElement(portID.getSystemPortName()); 326 } 327 // On Linux and Mac, try to find symlinks and to use the system property purejavacomm.portnamepattern 328 if (SystemType.isLinux() || SystemType.isMacOSX()) { 329 File[] files = new File("/dev").listFiles(); 330 if (files != null ) { 331 // Find symlinks linked to real ports 332 Set<String> symlinkPorts = Stream.of(files).filter(file -> !file.isDirectory() 333 && portNameVector.contains(getSymlinkTarget(file)) 334 && !portNameVector.contains(file.getName())).map(File::getName).collect(Collectors.toSet()); 335 portNameVector.addAll(symlinkPorts); 336 log.info("Adding symlink port {}", symlinkPorts); 337 338 // Let the user add additional serial ports 339 String portnamePattern = System.getProperty("purejavacomm.portnamepattern"); 340 if (portnamePattern != null) { 341 Pattern pattern = Pattern.compile(portnamePattern); 342 Set<String> ports = Stream.of(files).filter(file -> !file.isDirectory() 343 && pattern.matcher(file.getName()).matches() 344 && !portNameVector.contains(file.getName())).map(File::getName).collect(Collectors.toSet()); 345 portNameVector.addAll(ports); 346 log.info("Adding user-specified ports {} matching pattern {}", ports, portnamePattern); 347 } 348 } 349 } 350 return portNameVector; 351 } 352 353 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(JSerialPort.class); 354}