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