001package jmri.jmrix.nce; 002 003import org.slf4j.Logger; 004import org.slf4j.LoggerFactory; 005 006import jmri.CommandStation; 007import jmri.JmriException; 008import jmri.NmraPacket; 009import jmri.jmrix.AbstractMRListener; 010import jmri.jmrix.AbstractMRMessage; 011import jmri.jmrix.AbstractMRReply; 012import jmri.jmrix.AbstractMRTrafficController; 013 014/** 015 * Converts Stream-based I/O to/from NCE messages. The "NceInterface" side 016 * sends/receives message objects. 017 * <p> 018 * The connection to a NcePortController is via a pair of *Streams, which then 019 * carry sequences of characters for transmission. Note that this processing is 020 * handled in an independent thread. 021 * <p> 022 * This handles the state transitions, based on the necessary state in each 023 * message. 024 * 025 * @author Bob Jacobsen Copyright (C) 2001 026 * @author Ken Cameron Copyright (C) 2013, 2023 027 */ 028public class NceTrafficController extends AbstractMRTrafficController implements NceInterface, CommandStation { 029 030 /** 031 * Create a new NCE SerialTrafficController instance. Simple implementation. 032 */ 033 public NceTrafficController() { 034 super(); 035 } 036 037 // The methods to implement the NceInterface 038 @Override 039 public synchronized void addNceListener(NceListener l) { 040 this.addListener(l); 041 } 042 043 @Override 044 public synchronized void removeNceListener(NceListener l) { 045 this.removeListener(l); 046 } 047 048 @Override 049 protected int enterProgModeDelayTime() { 050 // we should to wait at least a second after enabling the programming track 051 return 1000; 052 } 053 054 /** 055 * CommandStation implementation 056 */ 057 @Override 058 public boolean sendPacket(byte[] packet, int count) { 059 NceMessage m; 060 061 boolean isUsb = ((getUsbSystem() == NceTrafficController.USB_SYSTEM_POWERCAB 062 || getUsbSystem() == NceTrafficController.USB_SYSTEM_SB3 063 || getUsbSystem() == NceTrafficController.USB_SYSTEM_SB5 064 || getUsbSystem() == NceTrafficController.USB_SYSTEM_TWIN)); 065 066 if (NmraPacket.isAccSignalDecoderPkt(packet) 067 && (NmraPacket.getAccSignalDecoderPktAddress(packet) > 0) 068 && (NmraPacket.getAccSignalDecoderPktAddress(packet) <= 2044)) { 069 // intercept only those NMRA signal cmds we can handle with NCE binary commands 070 int addr = NmraPacket.getAccSignalDecoderPktAddress(packet); 071 int aspect = packet[2]; 072 log.debug("isAccSignalDecoderPkt(packet) sigAddr ={}, aspect ={}", addr, aspect); 073 m = NceMessage.createAccySignalMacroMessage(this, 5, addr, aspect); 074 } else if (isUsb && NmraPacket.isAccDecoderPktOpsMode(packet)) { 075 // intercept NMRA accessory decoder ops programming cmds to USB systems 076 int accyAddr = NmraPacket.getAccDecoderPktOpsModeAddress(packet); 077 int cvAddr = (((0x03 & packet[2]) << 8) | (0xFF & packet[3])) + 1; 078 int cvData = (0xFF & packet[4]); 079 log.debug("isAccDecoderPktOpsMode(packet) accyAddr ={}, cvAddr = {}, cvData ={}", accyAddr, cvAddr, cvData); 080 m = NceMessage.createAccDecoderPktOpsMode(this, accyAddr, cvAddr, cvData); 081 } else if (isUsb && NmraPacket.isAccDecoderPktOpsModeLegacy(packet)) { 082 // intercept NMRA accessory decoder ops programming cmds to USB systems 083 int accyAddr = NmraPacket.getAccDecoderPktOpsModeLegacyAddress(packet); 084 int cvData = (0xFF & packet[3]); 085 int cvAddr = (((0x03 & packet[1]) << 8) | (0xFF & packet[2])) + 1; 086 log.debug("isAccDecoderPktOpsModeLegacy(packet) accyAddr ={}, cvAddr = {}, cvData ={}", accyAddr, cvAddr, cvData); 087 m = NceMessage.createAccDecoderPktOpsMode(this, accyAddr, cvAddr, cvData); 088 } else { 089 m = NceMessage.sendPacketMessage(this, packet); 090 if (m == null) { 091 return false; 092 } 093 } 094 this.sendNceMessage(m, null); 095 return true; 096 } 097 098 /** 099 * Forward a NceMessage to all registered NceInterface listeners. 100 */ 101 @Override 102 protected void forwardMessage(AbstractMRListener client, AbstractMRMessage m) { 103 ((NceListener) client).message((NceMessage) m); 104 } 105 106 /** 107 * Forward a NceReply to all registered NceInterface listeners. 108 */ 109 @Override 110 protected void forwardReply(AbstractMRListener client, AbstractMRReply r) { 111 ((NceListener) client).reply((NceReply) r); 112 } 113 114 NceSensorManager mSensorManager = null; 115 116 public void setSensorManager(NceSensorManager m) { 117 mSensorManager = m; 118 } 119 120 public NceSensorManager getSensorManager() { 121 return mSensorManager; 122 } 123 124 /** 125 * Create all commands in the ASCII format. 126 */ 127 static public final int OPTION_FORCE_ASCII = -1; 128 /** 129 * Create commands compatible with the 1999 EPROM. 130 * <p> 131 * This is binary for everything except service-mode CV programming 132 * operations. 133 */ 134 static public final int OPTION_1999 = 0; 135 /** 136 * Create commands compatible with the 2004 EPROM. 137 * <p> 138 * This is binary for everything except service-mode CV programming 139 * operations. 140 */ 141 static public final int OPTION_2004 = 10; 142 /** 143 * Create commands compatible with the 2006 EPROM. 144 * <p> 145 * This is binary for everything, including service-mode CV programming 146 * operations. 147 */ 148 static public final int OPTION_2006 = 20; 149 /** 150 * Create commands compatible with the 1.28 EPROM. 151 * <p> 152 * For PowerCab/SB3 original pre-Nov 2012 153 */ 154 static public final int OPTION_1_28 = 30; 155 /** 156 * Create commands compatible with the 1.65 EPROM. 157 * <p> 158 * For PowerCab/SB5/Twin update post-Nov 2012 159 */ 160 static public final int OPTION_1_65 = 40; 161 /** 162 * Create commands compatible with the PH5. 163 * <p> 164 * For PH5 165 */ 166 static public final int OPTION_PH5 = 80; 167 /** 168 * Create all commands in the binary format. 169 */ 170 static public final int OPTION_FORCE_BINARY = 10000; 171 172 private int commandOptions = OPTION_2006; 173 public boolean commandOptionSet = false; 174 private boolean nceEpromMarch2007 = false; // flag to allow JMRI to be bug for bug compatible 175 private boolean pwrProVer060203orLater = false; 176 private final int[] pwrProVers = new int[3]; 177 private boolean simulatorRunning = false; // true if simulator is running 178 179 /** 180 * Return the Power Pro firmware version as user-friendly hex text. 181 * 182 * @return period-separated firmware version 183 */ 184 public String getPwrProVersHexText() { 185 StringBuilder sb = new StringBuilder(); 186 sb.append(Integer.toHexString(pwrProVers[0] & 0xFF)).append("."); 187 sb.append(Integer.toHexString(pwrProVers[1] & 0xFF)).append("."); 188 sb.append(Integer.toHexString(pwrProVers[2] & 0xFF)); 189 return sb.toString(); 190 } 191 192 /** 193 * Store the Power Pro firmware version. 194 * 195 * @param VV major version 196 * @param MM intermediate version 197 * @param mm minor version 198 */ 199 public void setPwrProVers(byte VV, byte MM, byte mm) { 200 this.pwrProVers[0] = VV & 0xFF; 201 this.pwrProVers[1] = MM & 0xFF; 202 this.pwrProVers[2] = mm & 0xFF; 203 } 204 205 /** 206 * Ask whether Power Pro firmware version is 6.2.3 or later. 207 * 208 * @return {@code true} if it does, otherwise {@code false} 209 */ 210 public boolean isPwrProVer060203orLater() { 211 return pwrProVer060203orLater; 212 } 213 214 /** 215 * Specify whether Power Pro firmware version is 6.2.3 or later. 216 * 217 * @param isTrue {@code true} if it does, otherwise {@code false} 218 */ 219 public void setPwrProVer060203orLater(boolean isTrue) { 220 pwrProVer060203orLater = isTrue; 221 } 222 223 public boolean isNceEpromMarch2007() { 224 return nceEpromMarch2007; 225 } 226 227 public void setNceEpromMarch2007(boolean b) { 228 nceEpromMarch2007 = b; 229 } 230 231 public boolean isSimulatorRunning() { 232 return simulatorRunning; 233 } 234 235 public void setSimulatorRunning(boolean b) { 236 simulatorRunning = b; 237 } 238 239 /** 240 * Control which command format should be used for various commands: ASCII 241 * or binary. 242 * <p> 243 * The valid argument values are the class "OPTION" constants, which are 244 * interpreted in the various methods to get a particular message. 245 * <ul> 246 * <li>{@link #OPTION_FORCE_ASCII} 247 * <li>{@link #OPTION_1999} 248 * <li>{@link #OPTION_2004} 249 * <li>{@link #OPTION_2006} 250 * <li>{@link #OPTION_1_28} 251 * <li>{@link #OPTION_1_65} 252 * <li>{@link #OPTION_FORCE_BINARY} 253 * </ul> 254 * 255 * @param val command station options 256 * 257 */ 258 public void setCommandOptions(int val) { 259 commandOptions = val; 260 if (commandOptionSet) { 261 log.warn("setCommandOptions called more than once"); 262 } 263 commandOptionSet = true; 264 } 265 266 /** 267 * Determine which command format should be used for various commands: ASCII 268 * or binary. 269 * <p> 270 * The valid return values are the class "OPTION" constants, which are 271 * interpreted in the various methods to get a particular message. 272 * <ul> 273 * <li>{@link #OPTION_FORCE_ASCII} 274 * <li>{@link #OPTION_1999} 275 * <li>{@link #OPTION_2004} 276 * <li>{@link #OPTION_2006} 277 * <li>{@link #OPTION_1_28} 278 * <li>{@link #OPTION_1_65} 279 * <li>{@link #OPTION_FORCE_BINARY} 280 * </ul> 281 * 282 * @return command station options value 283 * 284 */ 285 public int getCommandOptions() { 286 return commandOptions; 287 } 288 289 /** 290 * Default when a NCE USB isn't selected in user system preferences. Also 291 * the case when Serial or Simulator is selected. 292 */ 293 public static final int USB_SYSTEM_NONE = 0; 294 295 /** 296 * Create commands compatible with a NCE USB connected to a PowerCab. 297 */ 298 public static final int USB_SYSTEM_POWERCAB = 1; 299 300 /** 301 * Create commands compatible with a NCE USB connected to a Smart Booster. 302 */ 303 public static final int USB_SYSTEM_SB3 = 2; 304 305 /** 306 * Create commands compatible with a NCE USB connected to a PowerPro. 307 */ 308 public static final int USB_SYSTEM_POWERPRO = 3; 309 310 /** 311 * Create commands compatible with a NCE USB with {@literal >=7.*} connected 312 * to a Twin. 313 */ 314 public static final int USB_SYSTEM_TWIN = 4; 315 316 /** 317 * Create commands compatible with a NCE USB with SB5. 318 */ 319 public static final int USB_SYSTEM_SB5 = 5; 320 321 private int usbSystem = USB_SYSTEM_NONE; 322 private boolean usbSystemSet = false; 323 324 /** 325 * Set the type of system the NCE USB is connected to 326 * <ul> 327 * <li>{@link #USB_SYSTEM_NONE} 328 * <li>{@link #USB_SYSTEM_POWERCAB} 329 * <li>{@link #USB_SYSTEM_SB3} 330 * <li>{@link #USB_SYSTEM_POWERPRO} 331 * <li>{@link #USB_SYSTEM_TWIN} 332 * <li>{@link #USB_SYSTEM_SB5} 333 * </ul> 334 * 335 * @param val usb command station options 336 * 337 */ 338 public void setUsbSystem(int val) { 339 usbSystem = val; 340 if (usbSystemSet) { 341 log.warn("setUsbSystem called more than once"); 342 } 343 usbSystemSet = true; 344 } 345 346 /** 347 * Get the type of system the NCE USB is connected to 348 * <ul> 349 * <li>{@link #USB_SYSTEM_NONE} 350 * <li>{@link #USB_SYSTEM_POWERCAB} 351 * <li>{@link #USB_SYSTEM_SB3} 352 * <li>{@link #USB_SYSTEM_POWERPRO} 353 * <li>{@link #USB_SYSTEM_TWIN} 354 * <li>{@link #USB_SYSTEM_SB5} 355 * </ul> 356 * 357 * @return usb command station options 358 * 359 */ 360 public int getUsbSystem() { 361 return usbSystem; 362 } 363 364 /** 365 * Initializer for supported command groups 366 */ 367 static public final long CMDS_NONE = 0; 368 369 /** 370 * Limit max accy decoder to addr 250 371 */ 372 static public final long CMDS_ACCYADDR250 = 0x0001; 373 374 /** 375 * Supports programming track and related commands 376 */ 377 static public final long CMDS_PROGTRACK = 0x0002; 378 379 /** 380 * Supports read AIU status commands {@code 0x9B} 381 */ 382 static public final long CMDS_AUI_READ = 0x004; 383 384 /** 385 * Supports USB read/write memory commands {@code 0xB3 -> 0xB5} 386 */ 387 static public final long CMDS_MEM = 0x0008; 388 389 /** 390 * Support Ops Mode Pgm commands {@code 0xAE -> 0xAF} 391 */ 392 static public final long CMDS_OPS_PGM = 0x0010; 393 394 /** 395 * Support Clock commands {@code 0x82 -> 0x87} 396 */ 397 static public final long CMDS_CLOCK = 0x0020; 398 399 /** 400 * Support USB Interface commands {@code 0xB1} 401 */ 402 static public final long CMDS_USB = 0x0040; 403 404 /** 405 * Disable for USB commands 406 */ 407 static public final long CMDS_NOT_USB = 0x0080; 408 409 /** 410 * All Connections Support commands 411 */ 412 static public final long CMDS_ALL_SYS = 0x0100; 413 414 private long cmdGroups = CMDS_NONE; 415 private boolean cmdGroupsSet = false; 416 417 /** 418 * Set the types of commands valid connected system 419 * <ul> 420 * <li>{@link #CMDS_NONE} 421 * <li>{@link #CMDS_ACCYADDR250} 422 * <li>{@link #CMDS_PROGTRACK} 423 * <li>{@link #CMDS_AUI_READ} 424 * <li>{@link #CMDS_MEM} 425 * <li>{@link #CMDS_OPS_PGM} 426 * <li>{@link #CMDS_CLOCK} 427 * <li>{@link #CMDS_USB} 428 * <li>{@link #CMDS_NOT_USB} 429 * <li>{@link #CMDS_ALL_SYS} 430 * </ul> 431 * 432 * @param val command group supported options 433 * 434 */ 435 public void setCmdGroups(long val) { 436 cmdGroups = val; 437 if (cmdGroupsSet) { 438 log.warn("setCmdGroups called more than once"); 439 } 440 cmdGroupsSet = true; 441 } 442 443 /** 444 * Get the types of commands valid for the NCE USB and connected system 445 * <ul> 446 * <li>{@link #CMDS_NONE} 447 * <li>{@link #CMDS_ACCYADDR250} 448 * <li>{@link #CMDS_PROGTRACK} 449 * <li>{@link #CMDS_AUI_READ} 450 * <li>{@link #CMDS_MEM} 451 * <li>{@link #CMDS_OPS_PGM} 452 * <li>{@link #CMDS_CLOCK} 453 * <li>{@link #CMDS_USB} 454 * <li>{@link #CMDS_NOT_USB} 455 * <li>{@link #CMDS_ALL_SYS} 456 * </ul> 457 * 458 * @return command group supported options 459 * 460 */ 461 public long getCmdGroups() { 462 return cmdGroups; 463 } 464 465 private boolean nceProgMode = false; // Do not use exit program mode unless active 466 467 /** 468 * Gets the state of the command station 469 * 470 * @return true if in programming mode 471 */ 472 public boolean getNceProgMode() { 473 return nceProgMode; 474 } 475 476 /** 477 * Sets the state of the command station 478 * 479 * @param b when true, set programming mode 480 */ 481 public void setNceProgMode(boolean b) { 482 nceProgMode = b; 483 } 484 485 /** 486 * Check NCE EPROM and start NCE CS accessory memory poll 487 */ 488 @Override 489 protected AbstractMRMessage pollMessage() { 490 491 // Check to see if command options are valid 492 if (commandOptionSet == false) { 493 if (log.isDebugEnabled()) { 494 log.debug("Command options are not valid yet!!"); 495 } 496 return null; 497 } 498 499 // Keep checking the state of the communication link by polling 500 // the command station using the EPROM checker 501 NceMessage m = pollEprom.nceEpromPoll(); 502 if (m != null) { 503 expectReplyEprom = true; 504 return m; 505 } else { 506 expectReplyEprom = false; 507 } 508 509 // Have we checked to see if AIU broadcasts are enabled? 510 if (pollAiuStatus == null) { 511 // No, do it this time 512 pollAiuStatus = new NceAIUChecker(this); 513 return pollAiuStatus.nceAiuPoll(); 514 } 515 516 // Start NCE memory poll for accessory states 517 if (pollHandler == null) { 518 pollHandler = new NceTurnoutMonitor(this); 519 } 520 521 // minimize impact to NCE CS 522 mWaitBeforePoll = NceTurnoutMonitor.POLL_TIME; // default = 25 523 524 return pollHandler.pollMessage(); 525 526 } 527 528 NceConnectionStatus pollEprom = new NceConnectionStatus(this); 529 NceAIUChecker pollAiuStatus = null; 530 NceTurnoutMonitor pollHandler = null; 531 532 boolean expectReplyEprom = false; 533 534 @Override 535 protected AbstractMRListener pollReplyHandler() { 536 // First time through, handle reply by checking EPROM revision 537 // Second time through, handle AIU broadcast check 538 if (expectReplyEprom) { 539 return pollEprom; 540 } else if (pollHandler == null) { 541 return pollAiuStatus; 542 } else { 543 return pollHandler; 544 } 545 } 546 547 /** 548 * Forward a preformatted message to the actual interface. 549 */ 550 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value = "SLF4J_FORMAT_SHOULD_BE_CONST", 551 justification = "passing exception text") 552 @Override 553 public void sendNceMessage(NceMessage m, NceListener reply) { 554 try { 555 NceMessageCheck.checkMessage(getAdapterMemo(), m); 556 } catch (JmriException e) { 557 log.error(e.getMessage(), e); 558 return; // don't send bogus message to interface 559 } 560 sendMessage(m, reply); 561 } 562 563 @Override 564 protected void forwardToPort(AbstractMRMessage m, AbstractMRListener reply) { 565 replyBinary = m.isBinary(); 566 replyLen = ((NceMessage) m).getReplyLen(); 567 super.forwardToPort(m, reply); 568 } 569 570 protected int replyLen; 571 protected boolean replyBinary; 572 protected boolean unsolicitedSensorMessageSeen = false; 573 574 @Override 575 protected AbstractMRMessage enterProgMode() { 576 return NceMessage.getProgMode(this); 577 } 578 579 @Override 580 protected AbstractMRMessage enterNormalMode() { 581 return NceMessage.getExitProgMode(this); 582 } 583 584 /** 585 * 586 * @param adaptermemo the SystemConnectionMemo to associate with this 587 * TrafficController 588 */ 589 public void setAdapterMemo(NceSystemConnectionMemo adaptermemo) { 590 memo = adaptermemo; 591 } 592 593 public NceSystemConnectionMemo getAdapterMemo() { 594 return memo; 595 } 596 597 private NceSystemConnectionMemo memo = null; 598 599 @Override 600 protected AbstractMRReply newReply() { 601 NceReply reply = new NceReply(this); 602 reply.setBinary(replyBinary); 603 return reply; 604 } 605 606 // pre 2006 EPROMs can't stop AIU broadcasts so we have to accept them 607 @Override 608 protected boolean canReceive() { 609 if (getCommandOptions() < OPTION_2006) { 610 return true; 611 } else if (replyLen > 0) { 612 return true; 613 } else { 614 if (log.isDebugEnabled()) { 615 log.error("unsolicited character received"); 616 } 617 return false; 618 } 619 } 620 621 @Override 622 protected boolean endOfMessage(AbstractMRReply msg) { 623 msg.setBinary(replyBinary); 624 // first try boolean 625 if (replyBinary) { 626 // Attempt to detect and correctly forward AIU broadcast from pre 627 // 2006 EPROMS. We'll check for three byte unsolicited message 628 // starting with "A" 0x61. The second byte contains the AIU number + 629 // 0x30. The third byte contains the sensors, 0x41 < s < 0x6F 630 // This code is problematic, it is data sensitive. 631 // We can also incorrectly forward an AIU broadcast to a routine 632 // that is waiting for a reply 633 if (replyLen == 0 && getCommandOptions() < OPTION_2006) { 634 if (msg.getNumDataElements() == 1 && msg.getElement(0) == 0x61) { 635 return false; 636 } 637 if (msg.getNumDataElements() == 2 && msg.getElement(0) == 0x61 638 && msg.getElement(1) >= 0x30) { 639 return false; 640 } 641 if (msg.getNumDataElements() == 3 && msg.getElement(0) == 0x61 642 && msg.getElement(1) >= 0x30 643 && msg.getElement(2) >= 0x41 644 && msg.getElement(2) <= 0x6F) { 645 return true; 646 } 647 } 648 if (msg.getNumDataElements() >= replyLen) { 649 // reset reply length so we can detect an unsolicited AIU message 650 replyLen = 0; 651 return true; 652 } else { 653 return false; 654 } 655 } else { 656 // detect that the reply buffer ends with "COMMAND: " (note ending 657 // space) 658 int num = msg.getNumDataElements(); 659 // ptr is offset of last element in NceReply 660 int ptr = num - 1; 661 if ((num >= 9) 662 && (msg.getElement(ptr) == ' ') 663 && (msg.getElement(ptr - 1) == ':') 664 && (msg.getElement(ptr - 2) == 'D')) { 665 return true; 666 } // this got harder with the new PROM at the beginning of 2005. 667 // It doesn't always send the "COMMAND: " prompt at the end 668 // of each response. Try for the error message: 669 else if ((num >= 19) 670 && // don't check space,NL at end of buffer 671 (msg.getElement(ptr - 2) == '*') 672 && (msg.getElement(ptr - 3) == '*') 673 && (msg.getElement(ptr - 4) == '*') 674 && (msg.getElement(ptr - 5) == '*') 675 && (msg.getElement(ptr - 6) == ' ') 676 && (msg.getElement(ptr - 7) == 'D') 677 && (msg.getElement(ptr - 8) == 'O') 678 && (msg.getElement(ptr - 9) == 'O') 679 && (msg.getElement(ptr - 10) == 'T') 680 && (msg.getElement(ptr - 11) == 'S') 681 && (msg.getElement(ptr - 12) == 'R')) { 682 return true; 683 } 684 685 // otherwise, it's not the end 686 return false; 687 } 688 } 689 690 @Override 691 public String getUserName() { 692 if (memo == null) { 693 return "NCE"; 694 } 695 return memo.getUserName(); 696 } 697 698 @Override 699 public String getSystemPrefix() { 700 if (memo == null) { 701 return "N"; 702 } 703 return memo.getSystemPrefix(); 704 } 705 706 /* 707 * the command station memory object 708 */ 709 public NceCmdStationMemory csm; 710 711 private final static Logger log = LoggerFactory.getLogger(NceTrafficController.class); 712 713}