001package jmri.jmrix.bidib.serialdriver; 002 003import java.io.DataInputStream; 004import java.io.DataOutputStream; 005import java.io.File; 006import java.io.IOException; 007import java.util.Arrays; 008import java.util.List; 009import java.util.ArrayList; 010import java.util.HashMap; 011import java.util.Map; 012import java.util.Collections; 013import java.util.Set; 014import jmri.util.SystemType; 015import jmri.jmrix.bidib.BiDiBSerialPortController; 016import jmri.jmrix.bidib.BiDiBTrafficController; 017import org.bidib.jbidibc.core.BidibFactory; 018import org.bidib.jbidibc.core.BidibInterface; 019import org.bidib.jbidibc.core.MessageListener; 020import org.bidib.jbidibc.core.node.listener.TransferListener; 021import org.bidib.jbidibc.core.NodeListener; 022import org.bidib.jbidibc.messages.exception.PortNotFoundException; 023import org.bidib.jbidibc.messages.exception.PortNotOpenedException; 024import org.bidib.jbidibc.messages.helpers.DefaultContext; 025import org.bidib.jbidibc.core.node.BidibNode; 026import org.bidib.jbidibc.messages.ConnectionListener; 027import org.bidib.jbidibc.messages.helpers.Context; 028import org.bidib.jbidibc.messages.utils.ByteUtils; 029import org.bidib.jbidibc.purejavacomm.PureJavaCommSerialBidib; 030import org.bidib.jbidibc.purejavacomm.PortIdentifierUtils; 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033 034/** 035 * Implements SerialPortAdapter for the BiDiB system. 036 * <p> 037 * This connects an BiDiB device via a serial com port. 038 * 039 * @author Bob Jacobsen Copyright (C) 2001, 2002 040 * @author Eckart Meyer Copyright (C) 2019-2023 041 */ 042public class SerialDriverAdapter extends BiDiBSerialPortController { 043 044 private static final boolean usePurjavacomm = true; 045 private static final boolean useScm = !usePurjavacomm; 046 private static final Map<String, Long> connectionRootNodeList = new HashMap<>(); //our static connection list 047 048 protected String portNameFilter = ""; 049 protected Long rootNodeUid; 050 protected boolean useAutoScan = false; 051 052// @SuppressWarnings("OverridableMethodCallInConstructor") 053 public SerialDriverAdapter() { 054 //super(new BiDiBSystemConnectionMemo()); 055 setManufacturer(jmri.jmrix.bidib.BiDiBConnectionTypeList.BIDIB); 056 configureBaudRate(validSpeeds[0]); 057 058 if (SystemType.isLinux()) { 059 //portNameFilter = "/dev/ttyUSB*"; 060 portNameFilter = "ttyUSB*"; 061 //portNameFilter = "/dev/tty*"; 062 } 063 //test 064 List<String> portList = getPortIdentifiers(); 065 log.info("portList: {}", portList); 066 } 067 068 /** 069 * Get the filter string for port names to scan when autoScan is on 070 * @return port name filter as a string (wildcard is allowed at the end) 071 */ 072 public String getPortNameFilter() { 073 return portNameFilter; 074 } 075 076 /** 077 * Set the port name filter 078 * @param filter filter string 079 */ 080 public void setPortNameFilter(String filter) { 081 portNameFilter = filter; 082 } 083 084 /** 085 * Get the root node unique ID 086 * @return UID as Long 087 */ 088 public Long getRootNodeUid() { 089 return rootNodeUid; 090 } 091 092 /** 093 * Set the root node unique ID 094 * @param uid Unique ID as Long 095 */ 096 public void setRootNodeUid(Long uid) { 097 rootNodeUid = uid; 098 } 099 100 /** 101 * Get the AutoScan status 102 * @return true of autoScan is on, false if not 103 */ 104 public boolean getUseAutoScan() { 105 return useAutoScan; 106 } 107 108 /** 109 * Set the AutoScan status 110 * @param flag true of ON is requested 111 */ 112 public void setUseAutoScan(boolean flag) { 113 useAutoScan = flag; 114 } 115 116 /** 117 * {@inheritDoc} 118 * 119 * Get the port name in the format which is used by jbidibc 120 * @return real port name 121 */ 122 @Override 123 public String getRealPortName() { 124 return getRealPortName(getCurrentPortName()); 125 } 126 127 /** 128 * Get the canonical port name from the underlying operating system. 129 * For a symbolic link, the real path is returned. 130 * 131 * @param portName human-readable name 132 * @return canonical path 133 */ 134 static public String getCanonicalPortName(String portName) { 135 File file = new File(portName); 136 if (file.exists()) { 137 try { 138 portName = file.getCanonicalPath(); 139 log.debug("Canonical port name: {}", portName); 140 } 141 catch (IOException ex) { 142 } 143 } 144 return portName; 145 } 146 147 /** 148 * Static function to get the port name in the format which is used by jbidibc 149 * @param portName displayed port name 150 * @return real port name 151 */ 152 static public String getRealPortName(String portName) { 153 if (SystemType.isLinux()) { 154 portName = "/dev/" + portName; 155 } 156 // TODO: MaxOSX. Windows just uses the displayed port name (COMx:) 157 return getCanonicalPortName(portName); 158 } 159 160 /** 161 * This methods is called from serial connection config and creates the BiDiB object from jbidibc and opens it. 162 * The connectPort method of the traffic controller is called for generic initialisation. 163 * 164 * @param portName port name from XML 165 * @param appName not used 166 * @return error string to be displayed by JMRI. null of no error 167 */ 168 @Override 169 public String openPort(String portName, String appName) { 170 log.debug("openPort called for {}, driver: {}, expected UID: {}", portName, getRealPortName(), ByteUtils.formatHexUniqueId(rootNodeUid)); 171 172 MSG_RAW_LOGGER.debug("RAW> create BiDiB Instance for port {}", getCurrentPortName()); 173 //BidibInterface bidib = createSerialBidib(); 174 if (useAutoScan) { 175 String err = findPortbyUniqueID(rootNodeUid);//returns known port in "portName" or scan all ports for the requested unique ID of the root node 176 if (err != null) { 177 if (bidib != null) { 178 bidib.close(); 179 bidib = null; 180 } 181 return err; 182 } 183 } 184 log.debug("port table: {}", connectionRootNodeList); 185 //bidib.close(); //we can leave it open (creating a new one is time consuming!) - tc.connect then will skip the internal open then 186 if (bidib == null) { 187 context = getContext(); 188 bidib = createSerialBidib(context); 189 } 190 BiDiBTrafficController tc = new BiDiBTrafficController(bidib); 191 context = tc.connnectPort(this); //must be done before configuring managers since they may need features from the device 192 log.debug("memo: {}", this.getSystemConnectionMemo()); 193 this.getSystemConnectionMemo().setBiDiBTrafficController(tc); 194 if (context != null) { 195 opened = true; 196 Long uid = tc.getRootNode().getUniqueId() & 0x0000ffffffffffL; //mask the classid 197 if (context.get("serial.baudrate") != null) { 198 // Bidib serial controller has Auto-Baud with two baudrates: 115200 and 19200. Bidib specified only those two. 199 // If the controller has already an open connection, the baudrate is not set in context but it should have been configured before when it was opened 200 log.debug("opened with baud rate {}", context.get("serial.baudrate")); 201 configureBaudRateFromNumber(context.get("serial.baudrate").toString()); 202 } 203 if (rootNodeUid != null && !uid.equals(rootNodeUid)) { 204 opened = false; 205 connectionRootNodeList.remove(getRealPortName()); 206 tc.getBidib().close(); //wrong UID close to make it available for other checks 207 return "Device found on port " + getRealPortName() + "(" + getCurrentPortName() + ") has Unique ID " + ByteUtils.formatHexUniqueId(uid) + ", but should be " + ByteUtils.formatHexUniqueId(rootNodeUid); 208 } 209 connectionRootNodeList.put(getRealPortName(), tc.getRootNode().getUniqueId() & 0x0000ffffffffffL); 210 setRootNodeUid(uid); 211 } 212 else { 213 opened = false; 214 connectionRootNodeList.put(getRealPortName(), null); //this port does not have a BiDiB device connected - remember this 215 return "No device found on port " + getCurrentPortName() + "(" + getCurrentPortName() + ")"; 216 } 217 218 return null; // indicates OK return 219// return "CANT DO!!"; //DEBUG 220 221 } 222 223 /** 224 * Set up all of the other objects to operate with an BiDiB command station 225 * connected to this port. 226 */ 227 @Override 228 public void configure() { 229 log.debug("configure"); 230 this.getSystemConnectionMemo().configureManagers(); 231 } 232 233 /** 234 * Create a BiDiB object. jbidibc has support for various serial implementations. 235 * We tested SCM and PUREJAVACOMM. Both worked without problems. Since 236 * JMRI generally uses PUREJAVACOMM, we also use it here 237 * 238 * @return a BiDiB object from jbidibc 239 */ 240 static private BidibInterface createSerialBidib(Context context) { 241 if (useScm) { 242// return BidibFactory.createBidib(ScmSerialBidib.class.getName()); 243 } 244 if (usePurjavacomm) { 245 return BidibFactory.createBidib(PureJavaCommSerialBidib.class.getName(), context); 246 } 247 return null; 248 } 249 250 /** 251 * {@inheritDoc} 252 */ 253 @Override 254 public void registerAllListeners(ConnectionListener connectionListener, Set<NodeListener> nodeListeners, 255 Set<MessageListener> messageListeners, Set<TransferListener> transferListeners) { 256 257 if (useScm) { //NOT SUPPORTED ANY MORE 258// PureJavaCommSerialBidib b = (ScmSerialBidib)bidib; 259// b.setConnectionListener(connectionListener); 260// b.registerListeners(nodeListeners, messageListeners, transferListeners); 261 } 262 if (usePurjavacomm) { 263 PureJavaCommSerialBidib b = (PureJavaCommSerialBidib)bidib; 264 b.setConnectionListener(connectionListener); 265 b.registerListeners(nodeListeners, messageListeners, transferListeners); 266 } 267 } 268 269 /** 270 * Get a list of available port names 271 * @return list of portnames 272 */ 273 /*static - no longer, since we need portNameFilter here */ 274 public List<String> getPortIdentifiers() { 275 List<String> ret = null; 276 List<String> list = null; 277// if (useScm) { 278// list = new ArrayList<>(); 279// for (String s : ScmPortIdentifierUtils.getPortIdentifiers()) { 280// list.add(s.replace("/dev/", "")); 281// } 282// } 283 if (usePurjavacomm) { 284 list = PortIdentifierUtils.getPortIdentifiers(); 285 } 286 if (list != null) { 287 ret = new ArrayList<>(); 288 String portPrefix = portNameFilter.replaceAll("\\*", ""); 289 log.trace("port name filter: {}", portPrefix); 290 for (String s : list) { 291 if (s.startsWith(portPrefix)) { 292 ret.add(s); 293 } 294 } 295 } 296 return ret; 297 } 298 299 /** 300 * Internal method to find a port, possibly with already created BiDiB object 301 * @param requid requested unique ID of the root node 302 * @return port name as String 303 */ 304 //private String findPortbyUniqueID(Long requid, BidibInterface bidib) { 305 public String findPortbyUniqueID(Long requid) { 306 // find the port for the given UID 307 // first check our static if the port was already seen. 308 String port = getKownPortName(requid); 309 if (port == null) { 310 // then try the given port if it has the requested UID 311 if (!getCurrentPortName().isEmpty()) { 312 if (!connectionRootNodeList.containsKey(getRealPortName())) { 313 //if (bidib == null) { 314 // bidib = createSerialBidib(); 315 //} 316 //Long uid = checkPort(bidib, getCurrentPortName()); 317 Long uid = checkPort(getCurrentPortName()); 318 if (uid != null && uid.equals(requid)) { 319 port = getCurrentPortName(); 320 } 321 } 322 } 323 } 324 if (port == null) { 325 // if still not found, we have to scan all known ports 326 //if (bidib == null) { 327 // bidib = createSerialBidib(); 328 //} 329 //port = scanPorts(bidib, requid, portNameFilter); 330 port = scanPorts(requid, portNameFilter); 331 } 332 if (port != null) { 333 setPort(port); 334 } 335 else { 336 if (bidib != null) { 337 bidib.close(); 338 bidib = null; 339 } 340 if (requid != null) { 341 return "No Device found for BiDiB Unique ID " + ByteUtils.formatHexUniqueId(requid); 342 } 343 else if (!getCurrentPortName().isEmpty()) { 344 return "No Device found on port " + getCurrentPortName(); 345 } 346 else { 347 return "port name or Unique ID not specified!"; 348 } 349 } 350 return null; 351 } 352 353 /** 354 * Scan all ports (filtered by portNameFilter) for a unique ID of the root node. 355 * 356 * @param requid requested unique ID of the root node 357 * @param portNameFilter a port name filter (e.g. /dev/ttyUSB* for Linux) 358 * @return found port name (e.g. /dev/ttyUSB0) or null of not found 359 */ 360 //static private String scanPorts(BidibInterface bidib, Long requid, String portNameFilter) { 361 private String scanPorts(Long requid, String portNameFilter) { 362 //String portPrefix = portNameFilter.replaceAll("\\*", ""); 363 log.trace("scanPorts for UID {}, filter: {}", ByteUtils.formatHexUniqueId(requid), portNameFilter); 364 List<String> portNameList = getPortIdentifiers(); 365 for (String portName : portNameList) { 366 //log.trace("check port {}", portName); 367 //if (portName.startsWith(portPrefix)) { 368 log.trace("check port {}", portName); 369 if (!connectionRootNodeList.containsKey(getRealPortName(portName))) { 370 log.debug("BIDIB: try port {}", portName); 371 //Long uid = checkPort(bidib, portName); 372 Long uid = checkPort(portName); 373 if (uid.equals(requid)) { 374 return portName; 375 } 376 } 377 //} 378 } 379 return null; 380 } 381 382 /** 383 * Check if the given port is a BiDiB connection and returns the unique ID of the root node. 384 * Return the UID from cache if we already know the UID. 385 * 386 * @param portName port name to check 387 * @return unique ID of the root node 388 */ 389// public Long checkPort(String portName) { 390// if (connectionRootNodeList.containsKey(portName)) { 391// return connectionRootNodeList.get(portName); 392// } 393// else { 394// BidibInterface bidib = createSerialBidib(); 395// Long uid = checkPort(bidib, portName); 396// bidib.close(); 397// return uid; 398// } 399// } 400 401 /** 402 * Internal method to check if the given port is a BiDiB connection and returns the unique ID of the root node. 403 * Return the UID from cache if we already know the UID. 404 * 405// * @param bidib a BiDiB object from jbidibc 406 * @param portName port name to check 407 * @return unique ID of the root node 408 */ 409 //private Long checkPort(BidibInterface bidib, String portName) { 410 public Long checkPort(String portName) { 411 Long uid = null; 412 try { 413 context = new DefaultContext(); 414// if (bidib.isOpened()) { 415// bidib.close(); 416// } 417// bidib.close(); 418 log.trace("checkPort: port name: {}, real port name: {}", portName, getRealPortName(portName)); 419 if (bidib != null) { 420 bidib.close(); 421 bidib = null; 422 } 423 bidib = createSerialBidib(context); 424 String realPortName = getRealPortName(portName); 425 bidib.open(realPortName, null, Collections.<NodeListener> emptySet(), Collections.<MessageListener> emptySet(), Collections.<TransferListener> emptySet(), context); 426 BidibNode rootNode = bidib.getRootNode(); 427 428 uid = rootNode.getUniqueId() & 0x0000ffffffffffL; //mask the classid 429 log.info("root node UID: {}", ByteUtils.formatHexUniqueId(uid)); 430 connectionRootNodeList.put(realPortName, uid); 431 432 } 433 catch (PortNotOpenedException ex) { 434 log.warn("port not opened: {}", portName); 435 } 436 catch (PortNotFoundException ex) { 437 log.warn("port not found 1: {}", portName); 438 } 439 catch (Exception ex) { 440 log.warn("port not found 2: {}", portName); 441 } 442 if (uid != null) { 443 bidib.close(); 444 bidib = null; 445 } 446 return uid; 447 } 448 449 /** 450 * Check if the port name of a given UID already exists in the cache. 451 * 452 * @param reqUid requested UID 453 * @return port name or null if not found in cache 454 */ 455 public String getKownPortName(Long reqUid) { 456 for(Map.Entry<String, Long> entry : connectionRootNodeList.entrySet()) { 457 Long uid = entry.getValue(); 458 if (uid.equals(reqUid)) { 459 File f = new File(entry.getKey()); 460 String portName = f.getName(); 461 //return entry.getKey(); 462 return portName; 463 } 464 } 465 return null; 466 } 467 468 469 470 // base class methods for the BiDiBSerialPortController interface 471 // not used but must be implemented 472 473 @Override 474 public DataInputStream getInputStream() { 475// if (!opened) { 476// log.error("getInputStream called before load(), stream not available"); 477// return null; 478// } 479// return new DataInputStream(serialStream); 480 return null; 481 } 482 483 @Override 484 public DataOutputStream getOutputStream() { 485// if (!opened) { 486// log.error("getOutputStream called before load(), stream not available"); 487// } 488// try { 489// return new DataOutputStream(activeSerialPort.getOutputStream()); 490// } catch (java.io.IOException e) { 491// log.error("getOutputStream exception: " + e); 492// } 493 return null; 494 } 495 496 /** 497 * {@inheritDoc} 498 */ 499 @Override 500 public boolean status() { 501 return opened; 502 } 503 504 /** 505 * {@inheritDoc} 506 */ 507 @Override 508 public String[] validBaudRates() { 509 return Arrays.copyOf(validSpeeds, validSpeeds.length); 510 } 511 512 /** 513 * {@inheritDoc} 514 */ 515 @Override 516 public int[] validBaudNumbers() { 517 return Arrays.copyOf(validSpeedValues, validSpeedValues.length); 518 } 519 520 // see Bidib SCM serial controller - only those two baud rates are specified 521 protected String[] validSpeeds = new String[]{Bundle.getMessage("Baud115200"), 522 Bundle.getMessage("Baud19200")}; 523 protected int[] validSpeedValues = new int[]{115200, 19200}; 524 protected String selectedSpeed = validSpeeds[0]; 525 526 527 private final static Logger log = LoggerFactory.getLogger(SerialDriverAdapter.class); 528 private static final Logger MSG_RAW_LOGGER = LoggerFactory.getLogger("RAW"); 529 530}