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