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 portName 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 portName, org.slf4j.Logger log, int stop_bits, Parity parity) { 253 com.fazecast.jSerialComm.SerialPort serialPort; 254 // convert the 1 or 2 stop_bits argument to the proper jSerialComm code value 255 int stop_bits_code; 256 switch (stop_bits) { 257 case 1: 258 stop_bits_code = com.fazecast.jSerialComm.SerialPort.ONE_STOP_BIT; 259 break; 260 case 2: 261 stop_bits_code = com.fazecast.jSerialComm.SerialPort.TWO_STOP_BITS; 262 break; 263 default: 264 throw new IllegalArgumentException("Incorrect stop_bits argument: " + stop_bits); 265 } 266 try { 267 serialPort = com.fazecast.jSerialComm.SerialPort.getCommPort(portName); 268 serialPort.openPort(); 269 serialPort.setComPortTimeouts(com.fazecast.jSerialComm.SerialPort.TIMEOUT_READ_BLOCKING, 0, 0); 270 serialPort.setNumDataBits(8); 271 serialPort.setNumStopBits(stop_bits_code); 272 serialPort.setParity(parity.getValue()); 273 AbstractPortController.purgeStream(serialPort.getInputStream()); 274 } catch (java.io.IOException | com.fazecast.jSerialComm.SerialPortInvalidPortException ex) { 275 // IOException includes 276 // com.fazecast.jSerialComm.SerialPortIOException 277 AbstractSerialPortController.handlePortNotFound(systemPrefix, portName, log, ex); 278 return null; 279 } 280 return new JSerialPort(serialPort); 281 } 282 283 private static String getSymlinkTarget(File symlink) { 284 try { 285 // Path.toRealPath() follows a symlink 286 return symlink.toPath().toRealPath().toFile().getName(); 287 } catch (IOException e) { 288 return null; 289 } 290 } 291 292 /** 293 * Provide the actual serial port names. 294 * As a public static method, this can be accessed outside the jmri.jmrix 295 * package to get the list of names for e.g. context reports. 296 * 297 * @return the port names in the form they can later be used to open the port 298 */ 299 // @SuppressWarnings("UseOfObsoleteCollectionType") // historical interface 300 @SuppressFBWarnings(value = "DMI_HARDCODED_ABSOLUTE_FILENAME") 301 public static Vector<String> getActualPortNames() { 302 // first, check that the comm package can be opened and ports seen 303 java.util.Vector<java.lang.String> portNameVector = new Vector<String>(); 304 com.fazecast.jSerialComm.SerialPort[] portIDs = com.fazecast.jSerialComm.SerialPort.getCommPorts(); 305 // find the names of suitable ports 306 for (com.fazecast.jSerialComm.SerialPort portID : portIDs) { 307 portNameVector.addElement(portID.getSystemPortName()); 308 } 309 // On Linux and Mac, try to find symlinks and to use the system property purejavacomm.portnamepattern 310 if (SystemType.isLinux() || SystemType.isMacOSX()) { 311 File[] files = new File("/dev").listFiles(); 312 if (files != null ) { 313 // Find symlinks linked to real ports 314 Set<String> symlinkPorts = Stream.of(files).filter(file -> !file.isDirectory() 315 && portNameVector.contains(getSymlinkTarget(file)) 316 && !portNameVector.contains(file.getName())).map(File::getName).collect(Collectors.toSet()); 317 portNameVector.addAll(symlinkPorts); 318 log.info("Adding symlink port {}", symlinkPorts); 319 320 // Let the user add additional serial ports 321 String portnamePattern = System.getProperty("purejavacomm.portnamepattern"); 322 if (portnamePattern != null) { 323 Pattern pattern = Pattern.compile(portnamePattern); 324 Set<String> ports = Stream.of(files).filter(file -> !file.isDirectory() 325 && pattern.matcher(file.getName()).matches() 326 && !portNameVector.contains(file.getName())).map(File::getName).collect(Collectors.toSet()); 327 portNameVector.addAll(ports); 328 log.info("Adding user-specified ports {} matching pattern {}", ports, portnamePattern); 329 } 330 } 331 } 332 return portNameVector; 333 } 334 335 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(JSerialPort.class); 336}