001package jmri.jmrix.bidib; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import java.util.HashMap; 006import java.util.TreeMap; 007import java.util.LinkedHashSet; 008import java.util.List; 009import java.util.Map; 010import java.util.Set; 011import java.util.concurrent.atomic.AtomicBoolean; 012import jmri.CommandStation; 013import jmri.jmrix.PortAdapter; 014import jmri.NmraPacket; 015import jmri.InstanceManager; 016import jmri.ShutDownManager; 017import jmri.ShutDownTask; 018import jmri.implementation.AbstractShutDownTask; 019 020import org.bidib.jbidibc.messages.BidibLibrary; 021import org.bidib.jbidibc.messages.exception.ProtocolException; 022import org.bidib.jbidibc.messages.utils.ByteUtils; 023 024import org.bidib.jbidibc.core.BidibMessageProcessor; 025import org.bidib.jbidibc.core.BidibInterface; 026import org.bidib.jbidibc.messages.BidibPort; 027import org.bidib.jbidibc.messages.ConnectionListener; 028import org.bidib.jbidibc.core.DefaultMessageListener; 029import org.bidib.jbidibc.messages.Feature; 030import org.bidib.jbidibc.messages.LcConfig; 031import org.bidib.jbidibc.messages.LcConfigX; 032import org.bidib.jbidibc.core.MessageListener; 033import org.bidib.jbidibc.messages.base.RawMessageListener; 034import org.bidib.jbidibc.messages.Node; 035import org.bidib.jbidibc.core.NodeListener; 036import org.bidib.jbidibc.messages.ProtocolVersion; 037import org.bidib.jbidibc.messages.StringData; 038import org.bidib.jbidibc.messages.helpers.Context; 039import org.bidib.jbidibc.core.node.listener.TransferListener; 040import org.bidib.jbidibc.messages.exception.PortNotFoundException; 041import org.bidib.jbidibc.core.node.BidibNode; 042import org.bidib.jbidibc.messages.message.BidibCommandMessage; 043import org.bidib.jbidibc.core.node.BidibNodeAccessor; 044import org.bidib.jbidibc.messages.utils.NodeUtils; 045import org.bidib.jbidibc.messages.enums.CommandStationState; 046import org.bidib.jbidibc.messages.enums.LcOutputType; 047import org.bidib.jbidibc.messages.enums.PortModelEnum; 048import org.bidib.jbidibc.messages.message.AccessoryGetMessage; 049import org.bidib.jbidibc.messages.message.BidibRequestFactory; 050import org.bidib.jbidibc.messages.message.CommandStationSetStateMessage; 051import org.bidib.jbidibc.messages.message.FeedbackGetRangeMessage; 052import org.bidib.jbidibc.core.node.CommandStationNode; 053import org.bidib.jbidibc.core.node.BoosterNode; 054import org.bidib.jbidibc.messages.BoosterStateData; 055import org.bidib.jbidibc.messages.enums.BoosterControl; 056import org.bidib.jbidibc.messages.enums.BoosterState; 057import org.bidib.jbidibc.messages.enums.CommandStationProgState; 058import org.bidib.jbidibc.messages.port.BytePortConfigValue; 059import org.bidib.jbidibc.messages.port.PortConfigValue; 060import org.bidib.jbidibc.messages.port.ReconfigPortConfigValue; 061import org.bidib.jbidibc.simulation.comm.SimulationBidib; 062 063import org.slf4j.Logger; 064import org.slf4j.LoggerFactory; 065 066/** 067 * The BiDiB Traffic Controller provides the interface for JMRI to the BiDiB Library (jbidibc) - it 068 * does not handle any protocol functions itself. Therefor it does not extend AbstractMRTrafficController. 069 * Instead, it delegates BiDiB handling to a BiDiB controller instance (serial, simulation, etc.) using BiDiBInterface. 070 * 071 * @author Bob Jacobsen Copyright (C) 2002 072 * @author Eckart Meyer Copyright (C) 2019-2024 073 * 074 */ 075 076@SuppressFBWarnings(value = "JLM_JSR166_UTILCONCURRENT_MONITORENTER") 077// This code uses several AtomicBoolean variables as synch objects. In this use, 078// they're synchronizing access to code blocks, not just synchronizing access 079// to the underlying boolean value. it would be possible to use separate 080// Object variables for this process, but keeping those in synch would actually 081// be more complex and confusing than this approach. 082 083public class BiDiBTrafficController implements CommandStation { 084 085 private final BidibInterface bidib; 086 private final Set<TransferListener> transferListeners = new LinkedHashSet<>(); 087 private final Set<MessageListener> messageListeners = new LinkedHashSet<>(); 088 private final Set<NodeListener> nodeListeners = new LinkedHashSet<>(); 089 private final AtomicBoolean stallLock = new AtomicBoolean(); 090 private java.util.TimerTask watchdogTimer = null; 091 private final AtomicBoolean watchdogStatus = new AtomicBoolean(); 092 093 private final BiDiBNodeInitializer nodeInitializer; 094 protected final TreeMap<Long, Node> nodes = new TreeMap<>(); //our node list - use TreeMap since it retains order if insertion (HashMap has arbitrary order) 095 096 private Node cachedCommandStationNode = null; 097 098 //volatile protected boolean mIsProgMode = false; 099 private final AtomicBoolean mIsProgMode = new AtomicBoolean(); 100 volatile protected CommandStationState mSavedMode; 101 private Node currentGlobalProgrammerNode = null; 102 private final javax.swing.Timer progTimer = new javax.swing.Timer(3000, e -> progTimeout()); 103 104 //private Thread shutdownHook = null; // retain shutdown hook for possible removal. 105 106 private final Map<Long, String> debugStringBuffer = new HashMap<>(); 107 108 /** 109 * Create a new BiDiBTrafficController instance. 110 * Must provide a BidibInterface reference at creation time. 111 * 112 * @param b reference to associated jbidibc object, 113 * preserved for later. 114 */ 115 public BiDiBTrafficController(BidibInterface b) { 116 bidib = b; 117 log.debug("BiDiBTrafficController created"); 118 mSavedMode = CommandStationState.OFF; 119 setWatchdogTimer(false); //preset not enabled 120 121 progTimer.setRepeats(false); 122 mIsProgMode.set(false); 123 124 nodeInitializer = new BiDiBNodeInitializer(this, bidib, nodes); 125 126 // NO LONGER USED - When using JSerialComm, the port is obviously 127 // already closed when the ShutdownHook is executed. 128 // With PureJavacomm, this was not a problem... 129 130 // Copied from AbstractMRTrafficController: 131 // We use a shutdown hook here to make sure the connection is left 132 // in a clean state prior to exiting. This is required on systems 133 // which have a service mode to ensure we don't leave the system 134 // in an unusable state (This code predates the ShutdownTask 135 // mechanisim). Once the shutdown hook executes, the connection 136 // must be considered closed. 137 //shutdownHook = new Thread(new CleanupHook(this)); 138 //Runtime.getRuntime().addShutdownHook(shutdownHook); 139 140 // We now use the ShutdownTask method. This task is executed earlier 141 // (and before the external tasks are executed) 142 // and the JSerialComm port is still usable. 143 // And registering the shutdown task is moved to connnectPort(). 144 145 } 146 147 /** 148 * Opens the BiDiB connection in the jbidibc library, add listeners and initialize BiDiB. 149 * 150 * @param p BiDiB port adapter (serial or simulation) 151 * @return a jbidibc context 152 */ 153 @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST",justification = "Cast safe by design") 154 public Context connnectPort(PortAdapter p) { 155 // init bidib 156 Context context = ((BiDiBPortController)p).getContext(); 157 stallLock.set(false); //not stalled 158 159 messageListeners.add(new DefaultMessageListener() { 160 161 @Override 162 public void error(byte[] address, int messageNum, int errorCode, byte[] reasonData) { 163 log.debug("Node error event: addr: {}, msg num: {}, error code: {}, data: {}", address, messageNum, errorCode, reasonData); 164 if (errorCode == 1) { 165 log.info("error: {}", new String(reasonData)); 166 } 167 } 168 169 @Override 170 @SuppressFBWarnings(value = "SLF4J_SIGN_ONLY_FORMAT",justification = "info message contains context information") 171 public void nodeString(byte[] address, int messageNum, int namespace, int stringId, String value) { 172 // handle debug messages from a node 173 if (namespace == StringData.NAMESPACE_DEBUG) { 174 Node node = getNodeByAddr(address); 175 String uid = ByteUtils.getUniqueIdAsString(node.getUniqueId()); 176 // the debug string buffer key is the node's 40 bit UID plus the string id in the upper 24 bit 177 long key = (node.getUniqueId() & 0x0000ffffffffffL) | (long)stringId << 40; 178 String prefix = "===== BiDiB"; 179 if (value.charAt(value.length() - 1) == '\n') { 180 String txt = ""; 181 // check if we have previous received imcomplete text 182 if (debugStringBuffer.containsKey(key)) { 183 txt = debugStringBuffer.get(key); 184 debugStringBuffer.remove(key); 185 } 186 txt += value.replace("\n",""); 187 switch(stringId) { 188 case StringData.INDEX_DEBUG_STDOUT: 189 log.info("{} {} stdout: {}", prefix, uid, txt); 190 break; 191 case StringData.INDEX_DEBUG_STDERR: 192 log.info("{} {} stderr: {}", prefix, uid, txt); 193 break; 194 case StringData.INDEX_DEBUG_WARN: 195 log.warn("{} {}: {}", prefix, uid, txt); 196 break; 197 case StringData.INDEX_DEBUG_INFO: 198 log.info("{} {}: {}", prefix, uid, txt); 199 break; 200 case StringData.INDEX_DEBUG_DEBUG: 201 log.debug("{} {}: {}", prefix, uid, txt); 202 break; 203 case StringData.INDEX_DEBUG_TRACE: 204 log.trace("{} {}: {}", prefix, uid, txt); 205 break; 206 default: break; 207 } 208 } 209 else { 210 log.trace("incomplete debug string received: [{}]", value); 211 String txt = ""; 212 if (debugStringBuffer.containsKey(key)) { 213 txt = debugStringBuffer.get(key); 214 } 215 debugStringBuffer.put(key, (txt + value)); 216 } 217 } 218 } 219 220 @Override 221 public void nodeLost(byte[] address, int messageNum, Node node) { 222 log.debug("Node lost event: {}", node); 223 nodeInitializer.nodeLost(node); 224 } 225 226 @Override 227 public void nodeNew(byte[] address, int messageNum, Node node) { 228 log.debug("Node new event: {}", node); 229 nodeInitializer.nodeNew(node); 230 } 231 232 @Override 233 public void stall(byte[] address, int messageNum, boolean stall) { 234 synchronized (stallLock) { 235 if (log.isDebugEnabled()) { 236 Node node = getNodeByAddr(address); 237 log.debug("stall - msg num: {}, new state: {}, node: {}, ", messageNum, stall, node); 238 } 239 if (stall != stallLock.get()) { 240 stallLock.set(stall); 241 if (!stall) { 242 log.debug("stall - wake send"); 243 stallLock.notifyAll(); //wake pending send if any 244 } 245 } 246 } 247 } 248 249 // don't know if this is the correct place... 250 @Override 251 public void csState(byte[] address, int messageNum, CommandStationState commandStationState) { 252 Node node = getNodeByAddr(address); 253 log.debug("CS STATE event: {} on node {}, current watchdog status: {}", commandStationState, node, watchdogStatus.get()); 254 synchronized (mIsProgMode) { 255 if (CommandStationState.isPtProgState(commandStationState)) { 256 mIsProgMode.set(true); 257 } 258 else { 259 mIsProgMode.set(false); 260 mSavedMode = commandStationState; 261 } 262 } 263 boolean newState = (commandStationState == CommandStationState.GO); 264 if (node == getFirstCommandStationNode() && newState != watchdogStatus.get()) { 265 log.trace("watchdog: new state: {}, current state: {}", newState, watchdogStatus.get()); 266 setWatchdogTimer(newState); 267 } 268 } 269 @Override 270 public void csProgState( 271 byte[] address, int messageNum, CommandStationProgState commandStationProgState, int remainingTime, int cvNumber, int cvData) { 272 synchronized (progTimer) { 273 if ( (commandStationProgState.getType() & 0x80) != 0) { //bit 7 = 1 means operation has finished 274 progTimer.restart(); 275 log.trace("PROG finished, progTimer (re)started."); 276 } 277 else { 278 progTimer.stop(); 279 log.trace("PROG pending, progTimer stopped."); 280 } 281 } 282 } 283 @Override 284 public void boosterState(byte[] address, int messageNum, BoosterState state, BoosterControl control) { 285 Node node = getNodeByAddr(address); 286 log.info("BOOSTER STATE & CONTROL was signalled: {}, control: {}", state.getType(), control.getType()); 287 if (node != getFirstCommandStationNode() && node == currentGlobalProgrammerNode && control != BoosterControl.LOCAL) { 288 currentGlobalProgrammerNode = null; 289 } 290 } 291 }); 292 293 transferListeners.add(new TransferListener() { 294 295 @Override 296 public void sendStopped() { 297 // no implementation 298 //log.trace("sendStopped"); 299 } 300 301 @Override 302 public void sendStarted() { 303 log.debug("sendStarted"); 304 // TODO check node! 305 synchronized (stallLock) { 306 if (stallLock.get()) { 307 try { 308 log.debug("sendStarted is stalled - waiting..."); 309 stallLock.wait(1000L); 310 log.debug("sendStarted stall condition has been released"); 311 } 312 catch (InterruptedException e) { 313 log.warn("waited too long for releasing stall condition - continue..."); 314 stallLock.set(false); 315 } 316 } 317 } 318 } 319 320 @Override 321 public void receiveStopped() { 322 // no implementation 323 //log.trace("receiveStopped"); 324 } 325 326 @Override 327 public void receiveStarted() { 328 // no implementation 329 //log.trace("receiveStarted"); 330 } 331 332 @Override 333 public void ctsChanged(boolean cts, boolean manualEvent) { //new 334// public void ctsChanged(boolean cts) { //jbidibc 12.5 335 // no implementation 336 log.trace("ctsChanged"); 337 } 338 }); 339 340 ConnectionListener connectionListener = new ConnectionListener() { 341 @Override 342 public void opened(String port) { 343 // no implementation 344 log.trace("opened port {}", port); 345 } 346 347 @Override 348 public void closed(String port) { 349 // no implementation 350 log.trace("closed port {}", port); 351 } 352 353 @Override 354 public void status(String messageKey, Context context) { 355 // no implementation 356 log.trace("status - message key {}", messageKey); 357 } 358 }; 359 360 String portName = ((BiDiBPortController)p).getRealPortName(); 361 log.info("Open BiDiB connection on \"{}\"", portName); 362 363 try { 364 if (!bidib.isOpened()) { 365 bidib.setResponseTimeout(1600); 366 bidib.open(portName, connectionListener, nodeListeners, messageListeners, transferListeners, context); 367 } 368 else { 369 // if we get here, we assume that the adapter has already opened the port just for scanning the device 370 // and that NO listeners have been registered. So just add them now. 371 // If one day we start to really use the listeners we would have to check if this is o.k. 372 ((BiDiBPortController)p).registerAllListeners(connectionListener, nodeListeners, messageListeners, transferListeners); 373 } 374 375 // the connection has been established - register a shutdown task 376 log.info("registering shutdown task"); 377 ShutDownTask shutDownTask = new AbstractShutDownTask("BiDiB Shutdown Task") { 378 @Override 379 public void run() { 380 log.info("Shutdown Task - Terminate BiDiB"); 381 terminate(); 382 log.info("Shutdown task finished"); 383 } 384 }; 385 InstanceManager.getDefault(ShutDownManager.class).register(shutDownTask); 386 387 // get data from root node and from all other nodes 388 log.debug("get relevant node data"); 389 BidibNode rootNode = bidib.getRootNode(); 390 int count = rootNode.getNodeCount(); 391 log.debug("node count: {}", count); 392 byte[] nodeaddr = rootNode.getAddr(); 393 log.debug("node addr length: {}", nodeaddr.length); 394 log.debug("node addr: {}", nodeaddr); 395 for (int i = 0; i < nodeaddr.length; i++) { 396 log.debug(" byte {}: {}", i, nodeaddr[i]); 397 } 398// int featureCount = rootNode.getFeatureCount(); 399// log.debug("feature count: {}", featureCount); 400// log.debug("** Unique ID: {}", String.format("0x%X",rootNode.getUniqueId())); 401 402 for (int index = 1; index <= count; index++) { 403 Node node = rootNode.getNextNode(null); //TODO org.bidib.jbidibc.messages.logger.Logger 404 nodeInitializer.initNode(node); 405 long uid = node.getUniqueId() & 0x0000ffffffffffL; //mask the classid 406 nodes.put(uid, node); 407 } 408 rootNode.sysEnable(); 409 log.info("--- node init finished ---"); 410 411 Node csnode = getFirstCommandStationNode(); 412 if (csnode != null) { 413 sendBiDiBMessage(new CommandStationSetStateMessage(CommandStationState.QUERY), csnode); 414 // TODO: Should we remove all Locos from command station? MSG_SET_DRIVE with loco 0 and bitfields = 0 (see BiDiB spec) 415 // TODO: use MSG_CS_ALLOCATE every second to disable direct control from local controllers like handhelds? 416 } 417 418 return context; 419 420 } 421 catch (PortNotFoundException ex) { 422 log.error("The provided port was not found: {}. Verify that the BiDiB device is connected.", ex.getMessage()); 423 } 424 catch (Exception ex) { 425 log.error("Execute command failed: ", ex); // NOSONAR 426 } 427 return null; 428 } 429 430 Node debugSavedNode; 431 public void TEST(boolean a) {///////////////////DEBUG 432 log.debug("TEST {}", a); 433 String nodename = "XXXX"; 434 Node node = a ? debugSavedNode : getNodeByUserName(nodename); 435 if (node != null) { 436 if (a) { 437 nodeInitializer.nodeNew(node); 438 //nodeInitializer.nodeLost(node); 439 } 440 else { 441 debugSavedNode = node; 442 nodeInitializer.nodeLost(node); 443 } 444 } 445 } 446 447 /** 448 * Get Bidib Interface 449 * 450 * @return Bidib Interface 451 */ 452 public BidibInterface getBidib() { 453 return bidib; 454 } 455 456// convenience methods for node handling 457 458 /** 459 * Get the list of nodes found 460 * 461 * @return list of nodes 462 */ 463 public Map<Long, Node> getNodeList() { 464 return nodes; 465 } 466 467 /** 468 * Get node by unique id from nodelist 469 * 470 * @param uniqueId search for this 471 * @return node 472 */ 473 public Node getNodeByUniqueID(long uniqueId) { 474 return nodes.get(uniqueId); 475 } 476 477 /** 478 * Get node by node address from nodelist 479 * 480 * @param addr input to search 481 * @return node 482 */ 483 public Node getNodeByAddr(byte[] addr) { 484 for(Map.Entry<Long, Node> entry : nodes.entrySet()) { 485 Node node = entry.getValue(); 486 if (NodeUtils.isAddressEqual(node.getAddr(), addr)) { 487 return node; 488 } 489 } 490 return null; 491 } 492 493 /** 494 * Get node by node username from nodelist 495 * 496 * @param userName input to search 497 * @return node 498 */ 499 public Node getNodeByUserName(String userName) { 500 //log.debug("getNodeByUserName: [{}]", userName); 501 for(Map.Entry<Long, Node> entry : nodes.entrySet()) { 502 Node node = entry.getValue(); 503 //log.debug(" node: {}, usename: {}", node, node.getStoredString(StringData.INDEX_USERNAME)); 504 if (node.getStoredString(StringData.INDEX_USERNAME).equals(userName)) { 505 return node; 506 } 507 } 508 return null; 509 } 510 511 /** 512 * Get root node from nodelist 513 * 514 * @return node 515 */ 516 public Node getRootNode() { 517 byte[] addr = {0}; 518 return getNodeByAddr(addr); 519 } 520 521 /** 522 * A node suitable as a global programmer must be 523 * 524 * - a command station, 525 * - must support service mode programming and 526 * - must be a booster. 527 * - for other nodes than the global command station the local DCC generator 528 * must be switched on (MSG_BOOST_STAT returns this as "control") 529 * 530 * @param node to check 531 * 532 * @return true if the node is suitable as a global progreammer 533 */ 534 public boolean isGlobalProgrammerNode(Node node) { 535 536 if (NodeUtils.hasCommandStationFunctions(node.getUniqueId())) { 537// if (node.equals(getRootNode())) { //DEBUG 538// log.trace("---is root node: {}", node); 539// continue;//TEST: pretend that the root does not support service mode. 540// } 541 if (NodeUtils.hasCommandStationProgrammingFunctions(node.getUniqueId()) 542 && NodeUtils.hasBoosterFunctions(node.getUniqueId())) { 543 log.trace("node supports command station, programming and booster functions: {}", node); 544 if (node == getFirstCommandStationNode() || hasLocalDccEnabled(node)) { 545 return true; 546 } 547 } 548 } 549 return false; 550 } 551 552 /** 553 * Get the most probably only global programmer node (aka service mode node, used for prgramming track - PT, not for POM) 554 * If there are more than one suitable node, get the last one (reverse nodes list search). 555 * In this case the command station (probably the root node and first entry in the list) should 556 * probably not be used as a global programmer and the other have been added just for that purpose. 557 * TODO: the user should select the global programmer node if there multiple nodes suitable 558 * as a global programmer. 559 * 560 * 561 * @return programmer node or null if none available 562 */ 563 public Node getFirstGlobalProgrammerNode() { 564 //log.debug("find global programmer node"); 565 for(Map.Entry<Long, Node> entry : nodes.descendingMap().entrySet()) { 566 Node node = entry.getValue(); 567 //log.trace("node entry: {}", node); 568 if (isGlobalProgrammerNode(node)) { 569 synchronized (progTimer) { 570 log.debug("global programmer found: {}", node); 571 progTimer.restart(); 572 return node; 573 } 574 } 575 } 576 return null; 577 } 578 579 /** 580 * Set the global programmer node to use. 581 * 582 * @param node to be used as global programmer node or null to remove the currentGlobalProgrammerNode 583 * 584 * @return true if node is a suitable global programmer node, currentGlobalProgrammerNode is set to that node. false if it is not. 585 */ 586 public boolean setCurrentGlobalProgrammerNode(Node node) { 587 if (node == null || isGlobalProgrammerNode(node)) { 588 currentGlobalProgrammerNode = node; 589 return true; 590 } 591 return false; 592 } 593 594 /** 595 * Get the cached global programmer node. If there is no, try to find a suitable node. 596 * Note that the global programmer node may dynamically change by user settings. 597 * Be sure to update or invalidate currentGlobalProgrammerNode. 598 * 599 * @return the current global programmer node or null if none available. 600 */ 601 public Node getCurrentGlobalProgrammerNode() { 602 //log.trace("get current global programmer node: {}", currentGlobalProgrammerNode); 603 if (currentGlobalProgrammerNode == null) { 604 currentGlobalProgrammerNode = getFirstGlobalProgrammerNode(); 605 } 606 return currentGlobalProgrammerNode; 607 } 608 609 /** 610 * Ask the node if the local DCC generator is enabled. The state is returned as a 611 * BoosterControl value of LOCAL. 612 * Note that this function is expensive since it gets the information directly 613 * from the node and receiving a MSG_BOOST_STATE message. 614 * 615 * As far as I know (2023) there is only one device that supports the dynamically DCC generator switching: the Fichtelbahn ReadyBoost 616 * 617 * @param node to ask 618 * @return true if local DCC generator is enabled, false if the booster is connected to the 619 * global command station. 620 * 621 */ 622 private boolean hasLocalDccEnabled(Node node) { 623 if ((bidib instanceof SimulationBidib)) { // **** this a hack for the simulator since it will never return LOCAL dcc... 624 return true; 625 } 626 boolean hasLocalDCC = false; 627 // check if the node has a local DCC generator 628 BoosterNode bnode = getBidib().getBoosterNode(node); 629 if (bnode != null) { 630 try { 631 BoosterStateData bdata = bnode.queryState(); //send and wait for response 632 log.trace("Booster state data: {}", bdata); 633 if (bdata.getControl() == BoosterControl.LOCAL) { 634 hasLocalDCC = true; //local DCC generator is enabled 635 } 636 } 637 catch (ProtocolException e) {} 638 } 639 log.debug("node has local DCC enabled: {}, {}", hasLocalDCC, node); 640 return hasLocalDCC; 641 } 642 643 644 /** 645 * Get the first and most probably only command station node (also used for Programming on the Main - POM) 646 * A cached value is returned here for performance reasons since the function is called very often. 647 * We don't expect the command station to change. 648 * 649 * @return command station node 650 */ 651 public Node getFirstCommandStationNode() { 652 if (cachedCommandStationNode == null) { 653 for(Map.Entry<Long, Node> entry : nodes.entrySet()) { 654 Node node = entry.getValue(); 655 if (NodeUtils.hasCommandStationFunctions(node.getUniqueId())) { 656 log.trace("node has command station functions: {}", node); 657 cachedCommandStationNode = node; 658 break; 659 } 660 } 661 } 662 return cachedCommandStationNode; 663 } 664 665 /** 666 * Get the first booster node. 667 * There may be more booster nodes, so prefer the command station and then try the others 668 * @return booster node 669 */ 670 public Node getFirstBoosterNode() { 671 Node node = getFirstCommandStationNode(); 672 log.trace("getFirstBoosterNode: CS is {}", node); 673 if (node != null && NodeUtils.hasBoosterFunctions(node.getUniqueId())) { 674 // if the command station has a booster, use this one 675 log.trace("CS node also has booster functions: {}", node); 676 return node; 677 } 678 // if the command station does not have a booster, try to find another node with booster capability 679 for(Map.Entry<Long, Node> entry : nodes.entrySet()) { 680 node = entry.getValue(); 681 if (NodeUtils.hasBoosterFunctions(node.getUniqueId())) { 682 log.trace("node has booster functions: {}", node); 683 return node; 684 } 685 } 686 return null; 687 } 688 689 /** 690 * Get the first output node - a node that can control LC ports. 691 * TODO: the method does not make much sense and its only purpose is to check if we have an output node at all. 692 * Therefor it should be converted to "hasOutputNode" or similar. 693 * @return output node 694 */ 695 public Node getFirstOutputNode() { 696 for(Map.Entry<Long, Node> entry : nodes.entrySet()) { 697 Node node = entry.getValue(); 698 if (NodeUtils.hasAccessoryFunctions(node.getUniqueId()) || NodeUtils.hasSwitchFunctions(node.getUniqueId())) { 699 log.trace("node has output functions (accessories or ports): {}", node); 700 return node; 701 } 702 } 703 return null; 704 } 705 706 /** 707 * Check if we have at least one node capable of Accessory functions 708 * @return true or false 709 */ 710 public boolean hasAccessoryNode() { 711 for(Map.Entry<Long, Node> entry : nodes.entrySet()) { 712 Node node = entry.getValue(); 713 if (NodeUtils.hasAccessoryFunctions(node.getUniqueId())) { 714 log.trace("node has accessory functions: {}", node); 715 return true; 716 } 717 } 718 return false; 719 } 720 721// convenience methods for feature handling 722 723 /** 724 * Find a feature for given node. 725 * 726 * @param node selected node 727 * @param requestedFeatureId as integer 728 * @return a Feature object or null if the node does not have the feature at all 729 */ 730 public Feature findNodeFeature(Node node, final int requestedFeatureId) { 731 return Feature.findFeature(node.getFeatures(), requestedFeatureId); 732 } 733 734 /** 735 * Get the feature value of a node. 736 * 737 * @param node selected node 738 * @param requestedFeatureId feature to get 739 * @return the feature value as integer or 0 if the node does not have the feature 740 */ 741 public int getNodeFeature(Node node, final int requestedFeatureId) { 742 Feature f = Feature.findFeature(node.getFeatures(), requestedFeatureId); 743 if (f == null) { 744 return 0; 745 } 746 else { 747 return f.getValue(); 748 } 749 } 750 751 // this is here only as a workaround until PortModelEnum.getPortModel eventually will support flat_extended itself 752 public PortModelEnum getPortModel(final Node node) { 753 if (PortModelEnum.getPortModel(node) == PortModelEnum.type) { 754 return PortModelEnum.type; 755 } 756 else { 757 if (node.getPortFlatModel() >= 256) { 758 return PortModelEnum.flat_extended; 759 } 760 else { 761 return PortModelEnum.flat; 762 } 763 } 764 } 765 766 // convenience methods to handle Message Listeners 767 768 /** 769 * Add a message Listener to the connection 770 * @param messageListener to be added 771 */ 772 public void addMessageListener(MessageListener messageListener) { 773 if (bidib != null) { 774 log.trace("addMessageListener called!"); 775 BidibMessageProcessor rcv = bidib.getBidibMessageProcessor(); 776 if (rcv != null) { 777 rcv.addMessageListener(messageListener); 778 } 779 } 780 } 781 782 /** 783 * Remove a message Listener from the connection 784 * @param messageListener to be removed 785 */ 786 public void removeMessageListener(MessageListener messageListener) { 787 if (bidib != null) { 788 log.trace("removeMessageListener called!"); 789 BidibMessageProcessor rcv = bidib.getBidibMessageProcessor(); 790 if (rcv != null) { 791 rcv.removeMessageListener(messageListener); 792 } 793 } 794 } 795 796 /** 797 * Add a raw message Listener to the connection 798 * @param rawMessageListener to be added 799 */ 800 public void addRawMessageListener(RawMessageListener rawMessageListener) { 801 if (bidib != null) { 802 bidib.addRawMessageListener(rawMessageListener); 803 } 804 } 805 806 /** 807 * Remove a raw message Listener from the connection 808 * @param rawMessageListener to be removed 809 */ 810 public void removeRawMessageListener(RawMessageListener rawMessageListener) { 811 if (bidib != null) { 812 bidib.removeRawMessageListener(rawMessageListener); 813 } 814 } 815 816 817// Config and ConfigX handling 818// NOTE: All of these methods should be either obsolete to moved to BiDiBOutputMessageHandler 819 820 private int getTypeCount(Node node, LcOutputType type) { 821 int id; 822 switch (type) { 823 case SWITCHPORT: 824 case SWITCHPAIRPORT: 825 id = BidibLibrary.FEATURE_CTRL_SWITCH_COUNT; 826 break; 827 case LIGHTPORT: 828 id = BidibLibrary.FEATURE_CTRL_LIGHT_COUNT; 829 break; 830 case SERVOPORT: 831 id = BidibLibrary.FEATURE_CTRL_SERVO_COUNT; 832 break; 833 case SOUNDPORT: 834 id = BidibLibrary.FEATURE_CTRL_SOUND_COUNT; 835 break; 836 case MOTORPORT: 837 id = BidibLibrary.FEATURE_CTRL_MOTOR_COUNT; 838 break; 839 case ANALOGPORT: 840 id = BidibLibrary.FEATURE_CTRL_ANALOGOUT_COUNT; 841 break; 842 case BACKLIGHTPORT: 843 id = BidibLibrary.FEATURE_CTRL_BACKLIGHT_COUNT; 844 break; 845 case INPUTPORT: 846 id = BidibLibrary.FEATURE_CTRL_INPUT_COUNT; 847 break; 848 default: 849 return 0; 850 } 851 return getNodeFeature(node, id); 852 } 853 854// semi-synchroneous methods using MSG_LC_CONFIGX_GET_ALL / MSG_LC_CONFIGX_GET (or MSG_LC_CONFIG_GET for older nodes) - use for init only 855// semi-synchroneous means, that this method waits until all data has been received, 856// but the data itself is delivered through the MessageListener to each registered components (e.g. BiDiBLight) 857// Therefor this method must only be called after all component managers have initialized all components and thus their message listeners 858 859 /** 860 * Request CONFIGX from all ports on all nodes of this connection. 861 * Returns after all data has been received. 862 * Received data is delivered to registered Message Listeners. 863 */ 864 public void allPortConfigX() { 865 log.debug("{}: get alle LC ConfigX", getUserName()); 866 for(Map.Entry<Long, Node> entry : nodes.entrySet()) { 867 Node node = entry.getValue(); 868 if (NodeUtils.hasSwitchFunctions(node.getUniqueId())) { 869 getAllPortConfigX(node, null); 870 } 871 } 872 } 873 874 /** 875 * Request CONFIGX from all ports on a given node and possibly only for a given type. 876 * 877 * @param node requested node 878 * @param type - if null, request all port types 879 * @return Note: always returns null, since data is not collected synchroneously but delivered to registered Message Listeners. 880 */ 881 public List<LcConfigX> getAllPortConfigX(Node node, LcOutputType type) { 882 // get all ports of a type or really all ports if type = null or flat addressing is enabled 883 List<LcConfigX> portConfigXList = null; 884 int numPorts; 885 try { 886 if (node.getProtocolVersion().isHigherThan(ProtocolVersion.VERSION_0_5)) { //ConfigX is available since V0.6 887 if (node.isPortFlatModelAvailable()) { //flat addressing 888 numPorts = node.getPortFlatModel(); 889 if (numPorts > 0) { 890 bidib.getNode(node).getAllConfigX(getPortModel(node), type, 0, numPorts); 891 } 892 } 893 else { //type based addressing 894 for (LcOutputType t : LcOutputType.values()) { 895 if ( (type == null || type == t) && t.hasPortStatus() && t.getType() <= 15) { 896 numPorts = getTypeCount(node, t); 897 if (numPorts > 0) { 898 bidib.getNode(node).getAllConfigX(getPortModel(node), t, 0, numPorts); 899 } 900 } 901 } 902 } 903 } 904 else { //old Config - type based adressing only 905 for (LcOutputType t : LcOutputType.values()) { 906 if ( (type == null || type == t) && t.hasPortStatus() && t.getType() <= 15) { 907 numPorts = getTypeCount(node, t); 908 if (numPorts > 0) { 909 int[] plist = new int[numPorts]; 910 for (int i = 0; i < numPorts; i++) { 911 plist[i] = i; 912 } 913 bidib.getNode(node).getConfigBulk(PortModelEnum.type, t, plist); 914 } 915 } 916 } 917 } 918 } catch (ProtocolException e) { 919 log.error("getAllConfigX message failed:", e); 920 } 921 return portConfigXList; 922 } 923 924 /** 925 * Request CONFIGX if a given port on a given node and possibly only for a given type. 926 * 927 * @param node requested node 928 * @param portAddr as an integer 929 * @param type - if null, request all port types 930 * @return Note: always returns null, since data is not collected synchroneously but delivered to registered Message Listeners. 931 */ 932 public LcConfigX getPortConfigX(Node node, int portAddr, LcOutputType type) { 933 // synchroneous method using MSG_LC_CONFIGX_GET - use for init only 934 try { 935 if (node.getProtocolVersion().isHigherThan(ProtocolVersion.VERSION_0_5)) { //ConfigX is available since V0.6 936 if (node.isPortFlatModelAvailable()) { 937 bidib.getNode(node).getConfigXBulk(getPortModel(node), type, 2 /*Window Size*/, portAddr); 938 } 939 else { 940 for (LcOutputType t : LcOutputType.values()) { 941 if ( (type == null || type == t) && t.hasPortStatus() && t.getType() <= 15) { 942 bidib.getNode(node).getConfigXBulk(getPortModel(node), t, 2 /*Window Size*/, portAddr); 943 } 944 } 945 } 946 } 947 else { 948 for (LcOutputType t : LcOutputType.values()) { 949 if ( (type == null || type == t) && t.hasPortStatus() && t.getType() <= 15) { 950 bidib.getNode(node).getConfigBulk(PortModelEnum.type, t, portAddr); 951 } 952 } 953 } 954 } catch (ProtocolException e) { 955 log.error("getConfigXBulk message failed", e); 956 } 957 return null; ////////TODO remove return value completely 958 } 959 960 /** 961 * Convert a CONFIG object to a CONFIGX object. 962 * This is a convenience method so the JMRI components need only to handle the CONFIGX format 963 * 964 * @param node context node 965 * @param lcConfig the LcConfig object 966 * @return a new LcConfigX object 967 */ 968 public LcConfigX convertConfig2ConfigX(Node node, LcConfig lcConfig) { 969 Map<Byte, PortConfigValue<?>> portConfigValues = new HashMap<>(); 970 BidibPort bidibPort; 971 PortModelEnum model; 972 if (node.isPortFlatModelAvailable()) { 973 model = PortModelEnum.flat; 974 byte portType = lcConfig.getOutputType(model).getType(); 975 int portMap = 1 << portType; //set the corresponding bit only 976 ReconfigPortConfigValue pcfg = new ReconfigPortConfigValue(portType, portMap); 977 portConfigValues.put(BidibLibrary.BIDIB_PCFG_RECONFIG, pcfg); 978 } 979 else { 980 model = PortModelEnum.type; 981 } 982 bidibPort = BidibPort.prepareBidibPort(model, lcConfig.getOutputType(model), lcConfig.getOutputNumber(model)); 983 // fill portConfigValues from lcConfig 984 switch (lcConfig.getOutputType(model)) { 985 case SWITCHPORT: 986 if (getNodeFeature(node, BidibLibrary.FEATURE_SWITCH_CONFIG_AVAILABLE) > 0) { 987 byte ioCtrl = ByteUtils.getLowByte(lcConfig.getValue1()); //BIDIB_PCFG_IO_CTRL - this is not supported for ConfigX any more 988 byte ticks = ByteUtils.getLowByte(lcConfig.getValue2()); //BIDIB_PCFG_TICKS 989 byte switchControl = (2 << 4) | 2; //tristate 990 switch (ioCtrl) { 991 case 0: //simple output 992 ticks = 0; //disable 993 switchControl = (1 << 4); //1 << 4 | 0 994 break; 995 case 1: //high pulse (same than simple output, but turns off after ticks 996 switchControl = (1 << 4); //1 << 4 | 0 997 break; 998 case 2: //low pulse 999 switchControl = 1; // 0 << 4 | 1 1000 break; 1001 case 3: //tristate 1002 ticks = 0; 1003 break; 1004 default: 1005 // same as tristate TODO: Support 4 (pullup) and 5 (pulldown) - port is an input then (??, spec not clear) 1006 ticks = 0; 1007 break; 1008 } 1009 BytePortConfigValue pcfgTicks = new BytePortConfigValue(ticks); 1010 BytePortConfigValue pcfgSwitchControl = new BytePortConfigValue(switchControl); 1011 portConfigValues.put(BidibLibrary.BIDIB_PCFG_TICKS, pcfgTicks); 1012 portConfigValues.put(BidibLibrary.BIDIB_PCFG_SWITCH_CTRL, pcfgSwitchControl); 1013 } 1014 break; 1015 case LIGHTPORT: 1016 BytePortConfigValue pcfgLevelPortOff = new BytePortConfigValue(ByteUtils.getLowByte(lcConfig.getValue1())); 1017 BytePortConfigValue pcfgLevelPortOn = new BytePortConfigValue(ByteUtils.getLowByte(lcConfig.getValue2())); 1018 BytePortConfigValue pcfgDimmDown = new BytePortConfigValue(ByteUtils.getLowByte(lcConfig.getValue3())); 1019 BytePortConfigValue pcfgDimmUp = new BytePortConfigValue(ByteUtils.getLowByte(lcConfig.getValue4())); 1020 portConfigValues.put(BidibLibrary.BIDIB_PCFG_LEVEL_PORT_OFF, pcfgLevelPortOff); 1021 portConfigValues.put(BidibLibrary.BIDIB_PCFG_LEVEL_PORT_ON, pcfgLevelPortOn); 1022 portConfigValues.put(BidibLibrary.BIDIB_PCFG_DIMM_DOWN, pcfgDimmDown); 1023 portConfigValues.put(BidibLibrary.BIDIB_PCFG_DIMM_UP, pcfgDimmUp); 1024 break; 1025 case SERVOPORT: 1026 BytePortConfigValue pcfgServoAdjL = new BytePortConfigValue(ByteUtils.getLowByte(lcConfig.getValue1())); 1027 BytePortConfigValue pcfgServoAdjH = new BytePortConfigValue(ByteUtils.getLowByte(lcConfig.getValue2())); 1028 BytePortConfigValue pcfgServoSpeed = new BytePortConfigValue(ByteUtils.getLowByte(lcConfig.getValue3())); 1029 portConfigValues.put(BidibLibrary.BIDIB_PCFG_SERVO_ADJ_L, pcfgServoAdjL); 1030 portConfigValues.put(BidibLibrary.BIDIB_PCFG_SERVO_ADJ_H, pcfgServoAdjH); 1031 portConfigValues.put(BidibLibrary.BIDIB_PCFG_SERVO_SPEED, pcfgServoSpeed); 1032 break; 1033 case BACKLIGHTPORT: 1034 BytePortConfigValue pcfgDimmDown2 = new BytePortConfigValue(ByteUtils.getLowByte(lcConfig.getValue1())); 1035 BytePortConfigValue pcfgDimmUp2 = new BytePortConfigValue(ByteUtils.getLowByte(lcConfig.getValue2())); 1036 BytePortConfigValue pcfgOutputMap = new BytePortConfigValue(ByteUtils.getLowByte(lcConfig.getValue3())); 1037 portConfigValues.put(BidibLibrary.BIDIB_PCFG_DIMM_DOWN, pcfgDimmDown2); 1038 portConfigValues.put(BidibLibrary.BIDIB_PCFG_DIMM_UP, pcfgDimmUp2); 1039 portConfigValues.put(BidibLibrary.BIDIB_PCFG_OUTPUT_MAP, pcfgOutputMap); 1040 break; 1041 case INPUTPORT: 1042 // not really specified, but seems logical... 1043 byte ioCtrl = ByteUtils.getLowByte(lcConfig.getValue1()); //BIDIB_PCFG_IO_CTRL - this is not supported for ConfigX any more 1044 byte inputControl = 1; //active HIGH 1045 switch (ioCtrl) { 1046 case 4: //pullup 1047 inputControl = 2; //active LOW + Pullup 1048 break; 1049 case 5: //pulldown 1050 inputControl = 3; //active HIGH + Pulldown 1051 break; 1052 default: 1053 // do nothing, leave inputControl at 1 1054 break; 1055 } 1056 BytePortConfigValue pcfgInputControl = new BytePortConfigValue(inputControl); 1057 portConfigValues.put(BidibLibrary.BIDIB_PCFG_INPUT_CTRL, pcfgInputControl); 1058 break; 1059 default: 1060 break; 1061 } 1062 LcConfigX configX = new LcConfigX(bidibPort, portConfigValues); 1063 return configX; 1064 } 1065 1066 // Asynchronous methods to request the status of all BiDiB ports 1067 // For this reason there is no need to query FEATURE_CTRL_PORT_QUERY_AVAILABLE, since without this feature there would simply be no answer for the port. 1068 1069 /** 1070 * Request LC_STAT from all ports on all nodes of this connection. 1071 * Returns immediately. 1072 * Received data is delivered to registered Message Listeners. 1073 */ 1074 public void allPortLcStat() { 1075 log.debug("{}: get alle LC stat", getUserName()); 1076 for(Map.Entry<Long, Node> entry : nodes.entrySet()) { 1077 Node node = entry.getValue(); 1078 if (NodeUtils.hasSwitchFunctions(node.getUniqueId())) { 1079 portLcStat(node, 0xFFFF); 1080 } 1081 } 1082 } 1083 1084 /** 1085 * Request LC_STAT from all ports on a given node. 1086 * Returns immediately. 1087 * Received data is delivered to registered Message Listeners. 1088 * The differences for the addressing model an the old LC_STAT handling are hidden to the caller. 1089 * 1090 * @param node selected node 1091 * @param typemask a 16 bit type mask where each bit represents a type, Bit0 is SWITCHPORT, Bit1 is LIGHTPORT and so on. Bit 15 is INPUTPORT. 1092 * Return LC_STAT only for ports which are selected on the type mask (the correspondend bit is set). 1093 */ 1094 public void portLcStat(Node node, int typemask) { 1095 if (NodeUtils.hasSwitchFunctions(node.getUniqueId())) { 1096 BidibRequestFactory rf = getBidib().getRootNode().getRequestFactory(); 1097 if (node.getProtocolVersion().isHigherThan(ProtocolVersion.VERSION_0_6)) { 1098 // fast bulk query of all ports (new in bidib protocol version 0.7) 1099// int numPorts; 1100// if (node.isPortFlatModelAvailable()) { 1101// numPorts = node.getPortFlatModel(); 1102// if (numPorts > 0) { 1103// //BidibCommandMessage m = (BidibCommandMessage)rf.createPortQueryAll(typemask, 0, numPorts); 1104// BidibCommandMessage m = (BidibCommandMessage)rf.createPortQueryAll(typemask, 0, 0xFFFF); 1105// sendBiDiBMessage(m, node); 1106// } 1107// } 1108// else { //type based addressing 1109// for (LcOutputType t : LcOutputType.values()) { 1110// int tmask = 1 << t.getType(); 1111// if ( ((tmask & typemask) != 0) && t.hasPortStatus() && t.getType() <= 15) { 1112// numPorts = getTypeCount(node, t); 1113// if (numPorts > 0) { 1114// // its not clear how PORT_QUERY_ALL is defined for type based addressing 1115// // so we try the strictest way 1116// BidibPort fromPort = BidibPort.prepareBidibPort(PortModelEnum.type, t, 0); 1117// BidibPort toPort = BidibPort.prepareBidibPort(PortModelEnum.type, t, numPorts); 1118// int from = ByteUtils.getWORD(fromPort.getValues()); 1119// int to = ByteUtils.getWORD(toPort.getValues()); 1120// BidibCommandMessage m = (BidibCommandMessage)rf.createPortQueryAll(tmask, from, to); 1121// //BidibCommandMessage m = (BidibCommandMessage)rf.createPortQueryAll(typemask, 0, 0xFFFF); 1122// sendBiDiBMessage(m, node); 1123// //break; 1124// } 1125// } 1126// } 1127// } 1128 // just query everything 1129 BidibCommandMessage m = rf.createPortQueryAll(typemask, 0, 0xFFFF); 1130 sendBiDiBMessage(m, node); 1131 } 1132 else { 1133 // old protocol versions (<= 0.6 - request every single port 1134 int numPorts; 1135 if (node.isPortFlatModelAvailable()) { 1136 // since flat addressing is only available since version 0.6, this is only possible with exactly version 0.6 1137 numPorts = node.getPortFlatModel(); 1138 for (int addr = 0; addr < numPorts; addr++) { 1139 BidibCommandMessage m = rf.createLcPortQuery(getPortModel(node), null, addr); 1140 sendBiDiBMessage(m, node); 1141 } 1142 } 1143 else { //type based adressing 1144 for (LcOutputType t : LcOutputType.values()) { 1145 int tmask = 1 << t.getType(); 1146 if ( ((tmask & typemask) != 0) && t.hasPortStatus() && t.getType() <= 7) { //outputs only - for old protocol version 1147 numPorts = getTypeCount(node, t); 1148 for (int addr = 0; addr < numPorts; addr++) { 1149 BidibCommandMessage m = rf.createLcPortQuery(getPortModel(node), t, addr); 1150 sendBiDiBMessage(m, node); 1151 } 1152 } 1153 } 1154 // inputs have a separate message type in old versions: MSG_LC_KEY_QUERY 1155 LcOutputType t = LcOutputType.INPUTPORT; 1156 int tmask = 1 << t.getType(); 1157 if ( ((tmask & typemask) != 0) ) { 1158 numPorts = getTypeCount(node, t); 1159 for (int addr = 0; addr < numPorts; addr++) { 1160 BidibCommandMessage m = rf.createLcKey(addr); 1161 sendBiDiBMessage(m, node); 1162 } 1163 } 1164 } 1165 } 1166 } 1167 } 1168 1169// Asynchronous methods to request the status of all BiDiB feedback channels 1170 1171 public void allAccessoryState() { 1172 log.debug("{}: get alle accessories", getUserName()); 1173 for(Map.Entry<Long, Node> entry : nodes.entrySet()) { 1174 Node node = entry.getValue(); 1175 if (NodeUtils.hasAccessoryFunctions(node.getUniqueId())) { 1176 accessoryState(node); 1177 } 1178 } 1179 } 1180 1181 public void accessoryState(Node node) { 1182 int accSize = getNodeFeature(node, BidibLibrary.FEATURE_ACCESSORY_COUNT); 1183 if (NodeUtils.hasAccessoryFunctions(node.getUniqueId()) && accSize > 0 ) { 1184 log.info("Requesting accessory status on node {}", node); 1185 for (int addr = 0; addr < accSize; addr++) { 1186 sendBiDiBMessage(new AccessoryGetMessage(addr), node); 1187 } 1188 } 1189 } 1190 1191 /** 1192 * Request Feedback Status (called BM status in BiDiB - BM (Belegtmelder) is german for "feedback") from all ports on all nodes of this connection. 1193 * Returns immediately. 1194 * Received data is delivered to registered Message Listeners. 1195 */ 1196 public void allFeedback() { 1197 //log.debug("{}: get alle feedback", getUserName()); 1198 for(Map.Entry<Long, Node> entry : nodes.entrySet()) { 1199 Node node = entry.getValue(); 1200 if (NodeUtils.hasFeedbackFunctions(node.getUniqueId())) { 1201 feedback(node); 1202 } 1203 } 1204 } 1205 1206 /** 1207 * Request Feedback Status (called BM status in BiDiB - BM (Belegtmelder) is german for "feedback") from all ports on a given node. 1208 * Returns immediately. 1209 * Received data is delivered to registered Message Listeners. 1210 * 1211 * @param node selected node 1212 */ 1213 public void feedback(Node node) { 1214 int bmSize = getNodeFeature(node, BidibLibrary.FEATURE_BM_SIZE); 1215 if (NodeUtils.hasFeedbackFunctions(node.getUniqueId()) && bmSize > 0 ) { 1216 log.info("Requesting feedback status on node {}", node); 1217 sendBiDiBMessage(new FeedbackGetRangeMessage(0, bmSize), node); 1218 } 1219 } 1220 1221// End of obsolete Methods 1222 1223// BiDiB Message handling 1224 1225 /** 1226 * Forward a preformatted BiDiBMessage to the actual interface. 1227 * 1228 * @param m Message to send; 1229 * @param node BiDiB node to send the message to 1230 */ 1231 public void sendBiDiBMessage(BidibCommandMessage m, Node node) { 1232 if (node == null) { 1233 log.error("node is undefined! - can't send message."); 1234 return; 1235 } 1236 log.trace("sendBiDiBMessage: {} on node {}", m, node); 1237 // be sure that the node is in correct mode 1238 if (checkProgMode((m.getType() == BidibLibrary.MSG_CS_PROG), node) >= 0) { 1239 try { 1240 log.trace(" bidib node: {}", getBidib().getNode(node)); 1241 BidibNodeAccessor.sendNoWait(getBidib().getNode(node), m); 1242 } catch (ProtocolException e) { 1243 log.error("sending BiDiB message failed", e); 1244 } 1245 } 1246 else { 1247 log.error("switching to or from PROG mode (global programmer) failed!"); 1248 } 1249 } 1250 1251 /** 1252 * Check if the command station is in the requested state (Normal, PT) 1253 * If the command station is not in the requested state, a message is sent to BiDiB to switch to the requested state. 1254 * 1255 * @param needProgMode true if we request the command station to be in programming state, false if normal state is requested 1256 * @param node selected node 1257 * @return 0 if nothing to do, 1 if state has been changed, -1 on error 1258 */ 1259 public synchronized int checkProgMode(boolean needProgMode, Node node) { 1260 log.trace("checkProgMode: needProgMode: {}, node: {}", needProgMode, node); 1261 int hasChanged = 0; 1262 CommandStationState neededMode = needProgMode ? CommandStationState.PROG : mSavedMode; 1263 if (needProgMode != mIsProgMode.get()) { 1264 Node progNode = getCurrentGlobalProgrammerNode(); 1265 if (node == progNode) { 1266 Node csNode = getFirstCommandStationNode(); 1267 1268 log.debug("use global programmer node: {}", progNode); 1269 CommandStationNode progCsNode = getBidib().getCommandStationNode(progNode); //check if the programmer node also a command station - should have been tested before anyway 1270 if (progCsNode == null) { //just in case... 1271 currentGlobalProgrammerNode = null; 1272 hasChanged = -1; 1273 } 1274 else { 1275 log.debug("change command station mode to PROG? {}", needProgMode); 1276 if (needProgMode) { 1277 if (node == csNode) { 1278 // if we have to switch to prog mode, disable watchdog timer - but only, if we switch the command station 1279 setWatchdogTimer(false); 1280 } 1281 } 1282 try { 1283 CommandStationState CurrentMode = progCsNode.setState(neededMode); //send and wait for response 1284 synchronized (mIsProgMode) { 1285 if (!needProgMode) { 1286 mSavedMode = CurrentMode; 1287 } 1288 mIsProgMode.set(needProgMode); 1289 } 1290 hasChanged = 1; 1291 } 1292 catch (ProtocolException e) { 1293 log.error("sending MSG_CS_STATE message failed", e); 1294 currentGlobalProgrammerNode = null; 1295 hasChanged = -1; 1296 } 1297 log.trace("new saved mode: {}, is ProgMode: {}", mSavedMode, mIsProgMode); 1298 } 1299 } 1300 } 1301 if (!mIsProgMode.get()) { 1302 synchronized (progTimer) { 1303 if (progTimer.isRunning()) { 1304 progTimer.stop(); 1305 log.trace("progTimer stopped."); 1306 } 1307 } 1308 setCurrentGlobalProgrammerNode(null); //invalidate programmer node so it must be evaluated again the next time 1309 } 1310 1311 return hasChanged; 1312 } 1313 1314 private void progTimeout() { 1315 log.trace("timeout - stop global programmer PROG mode - reset to {}", mSavedMode); 1316 checkProgMode(false, getCurrentGlobalProgrammerNode()); 1317 } 1318 1319 1320// BiDiB Watchdog 1321 1322 private class WatchdogTimerTask extends java.util.TimerTask { 1323 @Override 1324 public void run () { 1325 // If the timer times out, send MSG_CS_STATE_ON message 1326 synchronized (watchdogStatus) { 1327 if (watchdogStatus.get()) { //if still enabled 1328 sendBiDiBMessage(new CommandStationSetStateMessage(CommandStationState.GO), getFirstCommandStationNode()); 1329 } 1330 } 1331 } 1332 } 1333 1334 public final void setWatchdogTimer(boolean state) { 1335 synchronized (watchdogStatus) { 1336 Node csnode = getFirstCommandStationNode(); 1337 long timeout = 0; 1338 log.trace("setWatchdogTimer {} on node {}", state, csnode); 1339 if (csnode != null) { 1340 timeout = getNodeFeature(csnode, BidibLibrary.FEATURE_GEN_WATCHDOG) * 100L; //value in milliseconds 1341 log.trace("FEATURE_GEN_WATCHDOG in ms: {}", timeout); 1342 if (timeout < 2000) { 1343 timeout = timeout / 2; //half the devices watchdog timeout value for small values 1344 } 1345 else { 1346 timeout = timeout - 1000; //one second less the devices watchdog timeout value for larger values 1347 } 1348 } 1349 if (timeout > 0 && state) { 1350 log.debug("set watchdog TRUE, timeout: {} ms", timeout); 1351 watchdogStatus.set(true); 1352 if (watchdogTimer != null) { 1353 watchdogTimer.cancel(); 1354 } 1355 watchdogTimer = new WatchdogTimerTask(); // Timer used to periodically MSG_CS_STATE_ON 1356 jmri.util.TimerUtil.schedule(watchdogTimer, timeout, timeout); 1357 } 1358 else { 1359 log.debug("set watchdog FALSE, requested state: {}, timeout", state); 1360 watchdogStatus.set(false); 1361 if (watchdogTimer != null) { 1362 watchdogTimer.cancel(); 1363 } 1364 watchdogTimer = null; 1365 } 1366 } 1367 } 1368 1369 1370 /** 1371 * Reference to the system connection memo. 1372 */ 1373 BiDiBSystemConnectionMemo mMemo = null; 1374 1375 /** 1376 * Get access to the system connection memo associated with this traffic 1377 * controller. 1378 * 1379 * @return associated systemConnectionMemo object 1380 */ 1381 public BiDiBSystemConnectionMemo getSystemConnectionMemo() { 1382 return (mMemo); 1383 } 1384 1385 /** 1386 * Set the system connection memo associated with this traffic controller. 1387 * 1388 * @param m associated systemConnectionMemo object 1389 */ 1390 public void setSystemConnectionMemo(BiDiBSystemConnectionMemo m) { 1391 mMemo = m; 1392 } 1393 1394// Command Station interface 1395 1396 /** 1397 * {@inheritDoc} 1398 */ 1399 @Override 1400 public String getSystemPrefix() { 1401 if (mMemo != null) { 1402 return mMemo.getSystemPrefix(); 1403 } 1404 return ""; 1405 } 1406 1407 /** 1408 * {@inheritDoc} 1409 */ 1410 @Override 1411 public String getUserName() { 1412 if (mMemo != null) { 1413 return mMemo.getUserName(); 1414 } 1415 return ""; 1416 } 1417 1418 /** 1419 * {@inheritDoc} 1420 * 1421 * Not supported! We probably don't need the command station interface at all... 1422 * ... besides perhaps consist control or DCC Signal Mast / Head ?? 1423 */ 1424 @Override 1425 public boolean sendPacket(byte[] packet, int repeats) { 1426 log.debug("sendPacket: {}, prefix: {}", packet, mMemo.getSystemPrefix()); 1427 if (packet != null && packet.length >= 4) { 1428 //log.debug("Addr: {}, aspect: {}, repeats: {}", NmraPacket.getAccSignalDecoderPktAddress(packet), packet[2], repeats); 1429 //log.debug("Addr: {}, aspect: {}, repeats: {}", NmraPacket.getAccDecoderPktAddress(packet), packet[2], repeats); 1430 log.debug("Addr: {}, addr type: {}, aspect: {}, repeats: {}", NmraPacket.extractAddressType(packet), NmraPacket.extractAddressNumber(packet), packet[2], repeats); 1431 //throw new UnsupportedOperationException("Not supported yet."); 1432 log.warn("sendPacket is not supported for BiDiB so far"); 1433 } 1434 return false; 1435 } 1436 1437// NOT USED for now 1438// /** 1439// * Get the Lower byte of a locomotive address from the decimal locomotive 1440// * address. 1441// */ 1442// public static int getDCCAddressLow(int address) { 1443// /* For addresses below 128, we just return the address, otherwise, 1444// we need to return the upper byte of the address after we add the 1445// offset 0xC000. The first address used for addresses over 127 is 0xC080*/ 1446// if (address < 128) { 1447// return (address); 1448// } else { 1449// int temp = address + 0xC000; 1450// temp = temp & 0x00FF; 1451// return temp; 1452// } 1453// } 1454// 1455// /** 1456// * Get the Upper byte of a locomotive address from the decimal locomotive 1457// * address. 1458// */ 1459// public static int getDCCAddressHigh(int address) { 1460// /* this isn't actually the high byte, For addresses below 128, we 1461// just return 0, otherwise, we need to return the upper byte of the 1462// address after we add the offset 0xC000 The first address used for 1463// addresses over 127 is 0xC080*/ 1464// if (address < 128) { 1465// return (0x00); 1466// } else { 1467// int temp = address + 0xC000; 1468// temp = temp & 0xFF00; 1469// temp = temp / 256; 1470// return temp; 1471// } 1472// } 1473 1474 1475// Shutdown function 1476 1477 protected void terminate () { 1478 log.debug("Cleanup starts {}", this); 1479 if (bidib == null || !bidib.isOpened()) { 1480 return; // no connection established 1481 } 1482 Node node = getCurrentGlobalProgrammerNode(); 1483 if (node != null) { 1484 checkProgMode(false, node); //possibly switch to normal mode 1485 } 1486 setWatchdogTimer(false); //stop watchdog 1487 // sending SYS_DISABLE disables all spontaneous messages and thus informs all nodes that the host will probably disappear 1488 try { 1489 log.info("sending sysDisable to {}", getRootNode()); 1490 bidib.getRootNode().sysDisable(); //Throws ProtocolException 1491 } 1492 catch (ProtocolException e) { 1493 log.error("unable to disable node", e); 1494 } 1495 1496 log.debug("Cleanup ends"); 1497 } 1498 1499// /** NO LONGER USED 1500// * Internal class to handle traffic controller cleanup. The primary task of 1501// * this thread is to make sure the DCC system has exited service mode when 1502// * the program exits. 1503// */ 1504// static class CleanupHook implements Runnable { 1505// 1506// BiDiBTrafficController tc; 1507// 1508// CleanupHook(BiDiBTrafficController tc) { 1509// this.tc = tc; 1510// } 1511// 1512// @Override 1513// public void run() { 1514// tc.terminate(); 1515// } 1516// } 1517// 1518 1519 private final static Logger log = LoggerFactory.getLogger(BiDiBTrafficController.class); 1520 1521}