001package jmri.jmrix.lenz; 002 003import java.util.Optional; 004import java.util.function.Function; 005import javax.annotation.CheckForNull; 006import javax.annotation.Nonnull; 007 008/** 009 * Represents a single response from the XpressNet. 010 * 011 * @author Paul Bender Copyright (C) 2004 012 * 013 */ 014public class XNetReply extends jmri.jmrix.AbstractMRReply { 015 016 private static final String RS_TYPE = "rsType"; 017 private static final String X_NET_REPLY_LI_BAUD = "XNetReplyLIBaud"; 018 private static final String COLUMN_STATE = "ColumnState"; 019 private static final String MAKE_LABEL = "MakeLabel"; 020 private static final String POWER_STATE_ON = "PowerStateOn"; 021 private static final String POWER_STATE_OFF = "PowerStateOff"; 022 private static final String FUNCTION_MOMENTARY = "FunctionMomentary"; 023 private static final String FUNCTION_CONTINUOUS = "FunctionContinuous"; 024 private static final String BEAN_NAME_TURNOUT = "BeanNameTurnout"; 025 private static final String X_NET_REPLY_NOT_OPERATED = "XNetReplyNotOperated"; 026 private static final String X_NET_REPLY_THROWN_LEFT = "XNetReplyThrownLeft"; 027 private static final String X_NET_REPLY_THROWN_RIGHT = "XNetReplyThrownRight"; 028 private static final String X_NET_REPLY_INVALID = "XNetReplyInvalid"; 029 private static final String X_NET_REPLY_CONTACT_LABEL = "XNetReplyContactLabel"; 030 private static final String SPEED_STEP_MODE_X = "SpeedStepModeX"; 031 // unsolicited by message type. 032 033 // Create a new reply. 034 public XNetReply() { 035 super(); 036 setBinary(true); 037 } 038 039 // Create a new reply from an existing reply 040 public XNetReply(XNetReply reply) { 041 super(reply); 042 setBinary(true); 043 } 044 045 /** 046 * Create a reply from an XNetMessage. 047 * @param message existing message. 048 */ 049 public XNetReply(XNetMessage message) { 050 super(); 051 setBinary(true); 052 for (int i = 0; i < message.getNumDataElements(); i++) { 053 setElement(i, message.getElement(i)); 054 } 055 } 056 057 /** 058 * Create a reply from a string of hex characters. 059 * @param message hex string of message. 060 */ 061 public XNetReply(String message) { 062 super(); 063 setBinary(true); 064 // gather bytes in result 065 byte[] b= jmri.util.StringUtil.bytesFromHexString(message); 066 if (b.length == 0) { 067 // no such thing as a zero-length message 068 _nDataChars = 0; 069 _dataChars = null; 070 return; 071 } 072 _nDataChars = b.length; 073 _dataChars = new int[_nDataChars]; 074 for (int i = 0; i < b.length; i++) { 075 setElement(i, ( b[i] & 0xff) ); 076 } 077 } 078 079 /** 080 * Get the opcode as a string in hex format. 081 * @return 0x hex string of OpCode. 082 */ 083 public String getOpCodeHex() { 084 return "0x" + Integer.toHexString(this.getOpCode()); 085 } 086 087 /** 088 * Check whether the message has a valid parity. 089 * @return true if parity valid, else false. 090 */ 091 public boolean checkParity() { 092 int len = getNumDataElements(); 093 int chksum = 0x00; /* the seed */ 094 095 int loop; 096 097 for (loop = 0; loop < len - 1; loop++) { // calculate contents for data part 098 chksum ^= getElement(loop); 099 } 100 return ((chksum & 0xFF) == getElement(len - 1)); 101 } 102 103 public void setParity() { 104 int len = getNumDataElements(); 105 int chksum = 0x00; /* the seed */ 106 107 int loop; 108 109 for (loop = 0; loop < len - 1; loop++) { // calculate contents for data part 110 chksum ^= getElement(loop); 111 } 112 setElement(len - 1, chksum & 0xFF); 113 } 114 115 /** 116 * Get an integer representation of a BCD value. 117 * 118 * @param n byte in message to convert 119 * @return Integer value of BCD byte. 120 */ 121 public Integer getElementBCD(int n) { 122 return Integer.decode(Integer.toHexString(getElement(n))); 123 } 124 125 /** 126 * skipPrefix is not used at this point in time, but is 127 * defined as abstract in AbstractMRReply 128 */ 129 @Override 130 protected int skipPrefix(int index) { 131 return -1; 132 } 133 134 // decode messages of a particular form 135 136 /* 137 * The next group of routines are used by Feedback and/or turnout 138 * control code. These are used in multiple places within the code, 139 * so they appear here. 140 */ 141 142 /** 143 * If this is a feedback response message for a turnout, return the address. 144 * Otherwise return -1. 145 * 146 * @return the integer address or -1 if not a turnout message 147 */ 148 public int getTurnoutMsgAddr() { 149 if (this.isFeedbackMessage()) { 150 return getTurnoutAddrFromData( 151 getElement(1), 152 getElement(2)); 153 } 154 return -1; 155 } 156 157 private int getTurnoutAddrFromData(int a1, int a2) { 158 if (getFeedbackMessageType() > 1) { 159 return -1; 160 } 161 int address = (a1 & 0xff) * 4 + 1; 162 if ((a2 & 0x10) != 0) { 163 address += 2; 164 } 165 return address; 166 } 167 168 /** 169 * If this is a feedback broadcast message and the specified startbyte is 170 * the address byte of an addres byte data byte pair for a turnout, return 171 * the address. Otherwise return -1. 172 * 173 * @param startByte address byte of the address byte/data byte pair. 174 * @return the integer address or -1 if not a turnout message 175 */ 176 public int getTurnoutMsgAddr(int startByte) { 177 if (this.isFeedbackBroadcastMessage()) { 178 int a1 = this.getElement(startByte); 179 int a2 = this.getElement(startByte + 1); 180 return getTurnoutAddrFromData(a1, a2); 181 } else { 182 return -1; 183 } 184 } 185 186 /** 187 * Parse the feedback message for a turnout, and return the status for the 188 * even or odd half of the nibble (upper or lower part). 189 * 190 * @param turnout <ul> 191 * <li>0 for the even turnout associated with the pair. This is the upper 192 * half of the data nibble asociated with the pair </li> 193 * <li>1 for the odd turnout associated with the pair. This is the lower 194 * half of the data nibble asociated with the pair </li> 195 * </ul> 196 * @return THROWN/CLOSED as defined in {@link jmri.Turnout} 197 */ 198 public int getTurnoutStatus(int turnout) { 199 if (this.isFeedbackMessage() && (turnout == 0 || turnout == 1)) { 200 int a2 = this.getElement(2); 201 // fake turnout id, used just internally. Just odd/even matters. 202 return createFeedbackItem(turnout, a2).getTurnoutStatus(); 203 } 204 return (-1); 205 } 206 207 /** 208 * Parse the specified address byte/data byte pair in a feedback broadcast 209 * message and see if it is for a turnout. If it is, return the status for 210 * the even or odd half of the nibble (upper or lower part) 211 * 212 * @param startByte address byte of the address byte/data byte pair. 213 * @param turnout <ul> 214 * <li>0 for the even turnout associated with the pair. This is the upper 215 * half of the data nibble asociated with the pair </li> 216 * <li>1 for the odd turnout associated with the pair. This is the lower 217 * half of the data nibble asociated with the pair </li> 218 * </ul> 219 * @return THROWN/CLOSED as defined in {@link jmri.Turnout} 220 */ 221 public int getTurnoutStatus(int startByte, int turnout) { 222 if (this.isFeedbackBroadcastMessage() && (turnout == 0 || turnout == 1)) { 223 int a2 = this.getElement(startByte + 1); 224 // fake turnout id, used just internally. Just odd/even matters. 225 return createFeedbackItem(turnout, a2).getTurnoutStatus(); 226 } 227 return (-1); 228 } 229 230 /** 231 * If this is a feedback response message for a feedback encoder, return the 232 * address. Otherwise return -1. 233 * 234 * @return the integer address or -1 if not a feedback message 235 */ 236 public int getFeedbackEncoderMsgAddr() { 237 if (this.isFeedbackMessage()) { 238 int a1 = this.getElement(1); 239 int messagetype = this.getFeedbackMessageType(); 240 if (messagetype == 2) { 241 // This is a feedback encoder message 242 return (a1 & 0xff); 243 } else { 244 return -1; 245 } 246 } else { 247 return -1; 248 } 249 } 250 251 /** 252 * Returns the number of feedback items in the messages. 253 * For accessory info replies, always returns 1. For broadcast, it returns the 254 * number of feedback pairs. Returns 0 for non-feedback messages. 255 * 256 * @return number of feedback pair items. 257 */ 258 public final int getFeedbackMessageItems() { 259 if (isFeedbackMessage()) { 260 return 1; 261 } else if (isFeedbackBroadcastMessage()) { 262 return (this.getElement(0) & 0x0F) / 2; 263 } 264 return 0; 265 } 266 267 /** 268 * If this is a feedback broadcast message and the specified startByte is 269 * the address byte of an address byte/data byte pair for a feedback 270 * encoder, return the address. Otherwise return -1. 271 * 272 * @param startByte address byte of the address byte data byte pair. 273 * @return the integer address or -1 if not a feedback message 274 */ 275 public int getFeedbackEncoderMsgAddr(int startByte) { 276 if (this.isFeedbackBroadcastMessage()) { 277 int a1 = this.getElement(startByte); 278 int messagetype = this.getFeedbackMessageType(startByte); 279 if (messagetype == 2) { 280 // This is a feedback encoder message 281 return (a1 & 0xff); 282 } else { 283 return -1; 284 } 285 } else { 286 return -1; 287 } 288 } 289 290 /** 291 * Is this a feedback response message? 292 * @return true if a feedback response, else false. 293 */ 294 public boolean isFeedbackMessage() { 295 return (this.getElement(0) == XNetConstants.ACC_INFO_RESPONSE); 296 } 297 298 /** 299 * Is this a feedback broadcast message? 300 * @return true if a feedback broadcast message, else false. 301 */ 302 public boolean isFeedbackBroadcastMessage() { 303 return ((this.getElement(0) & 0xF0) == XNetConstants.BC_FEEDBACK); 304 } 305 306 /** 307 * Extract the feedback message type from a feedback message this is the 308 * middle two bits of the upper byte of the second data byte. 309 * 310 * @return message type, values are: 311 * <ul> 312 * <li>0 for a turnout with no feedback</li> 313 * <li>1 for a turnout with feedback</li> 314 * <li>2 for a feedback encoder</li> 315 * <li>3 is reserved by Lenz for future use.</li> 316 * </ul> 317 */ 318 public int getFeedbackMessageType() { 319 if (this.isFeedbackMessage()) { 320 int a2 = this.getElement(2); 321 return ((a2 & 0x60) / 32); 322 } else { 323 return -1; 324 } 325 } 326 327 /** 328 * Extract the feedback message type from the data byte of associated with 329 * the specified address byte specified by startByte. 330 * <p> 331 * The return value is the middle two bits of the upper byte of the data 332 * byte of an address byte/data byte pair. 333 * 334 * @param startByte The address byte for this addres byte data byte pair. 335 * @return message type, values are: 336 * <ul> 337 * <li>0 for a turnout with no feedback</li> 338 * <li>1 for a turnout with feedback</li> 339 * <li>2 for a feedback encoder</li> 340 * <li>3 is reserved by Lenz for future use.</li> 341 * </ul> 342 */ 343 public int getFeedbackMessageType(int startByte) { 344 if (this.isFeedbackBroadcastMessage()) { 345 int a2 = this.getElement(startByte + 1); 346 return ((a2 & 0x60) / 32); 347 } else { 348 return -1; 349 } 350 } 351 352 public boolean isFeedbackMotionComplete(int startByte) { 353 int messageType = getFeedbackMessageType(startByte); 354 if (messageType == 1) { 355 int a2 = getElement(startByte + 1); 356 return ((a2 & 0x80) != 0x80); 357 } 358 return false; 359 } 360 361 /* 362 * Next we have a few throttle related messages 363 */ 364 365 /** 366 * If this is a throttle-type message, return address. 367 * Otherwise return -1. 368 * <p> 369 * Note we only identify the command now; 370 * the response to a request for status is not yet seen here. 371 * @return address if throttle-type message, else -1. 372 */ 373 public int getThrottleMsgAddr() { 374 if (this.isThrottleMessage()) { 375 int a1 = this.getElement(2); 376 int a2 = this.getElement(3); 377 if (a1 == 0) { 378 return (a2); 379 } else { 380 return (((a1 * 256) & 0xFF00) + (a2 & 0xFF) - 0xC000); 381 } 382 } else { 383 return -1; 384 } 385 } 386 387 /** 388 * Is this a throttle message? 389 * @return true if throttle message. else false. 390 */ 391 public boolean isThrottleMessage() { 392 int message = this.getElement(0); 393 return (message == XNetConstants.LOCO_INFO_NORMAL_UNIT 394 || message == XNetConstants.LOCO_INFO_RESPONSE 395 || message == XNetConstants.LOCO_INFO_MUED_UNIT 396 || message == XNetConstants.LOCO_INFO_MU_ADDRESS 397 || message == XNetConstants.LOCO_INFO_DH_UNIT 398 || message == XNetConstants.LOCO_AVAILABLE_V1 399 || message == XNetConstants.LOCO_AVAILABLE_V2 400 || message == XNetConstants.LOCO_NOT_AVAILABLE_V1 401 || message == XNetConstants.LOCO_NOT_AVAILABLE_V2); 402 } 403 404 /** 405 * Does this message indicate the locomotive has been taken over by another 406 * device? 407 * @return true if take over message, else false. 408 */ 409 public boolean isThrottleTakenOverMessage() { 410 return (this.getElement(0) == XNetConstants.LOCO_INFO_RESPONSE 411 && this.getElement(1) == XNetConstants.LOCO_NOT_AVAILABLE); 412 } 413 414 /** 415 * Is this a consist message? 416 * @return true if consist message, else false. 417 */ 418 public boolean isConsistMessage() { 419 int message = this.getElement(0); 420 return (message == XNetConstants.LOCO_MU_DH_ERROR 421 || message == XNetConstants.LOCO_DH_INFO_V1 422 || message == XNetConstants.LOCO_DH_INFO_V2); 423 } 424 425 /* 426 * Finally, we have some commonly used routines that are used for 427 * checking specific, generic, response messages. 428 */ 429 430 /** 431 * In the interest of code reuse, the following function checks to see 432 * if an XpressNet Message is the OK message (01 04 05). 433 * @return true if an OK message, else false. 434 */ 435 public boolean isOkMessage() { 436 return (this.getElement(0) == XNetConstants.LI_MESSAGE_RESPONSE_HEADER 437 && this.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_SEND_SUCCESS); 438 } 439 440 /** 441 * In the interest of code reuse, the following function checks to see 442 * if an XpressNet Message is the timeslot restored message (01 07 06). 443 * @return true if a time-slot restored message. 444 */ 445 public boolean isTimeSlotRestored() { 446 return (this.getElement(0) == XNetConstants.LI_MESSAGE_RESPONSE_HEADER 447 && this.getElement(1) == XNetConstants.LIUSB_TIMESLOT_RESTORED); 448 } 449 450 /** 451 * In the interest of code reuse, the following function checks to see 452 * if an XpressNet Message is the Command Station no longer providing a 453 * timeslot message (01 05 04). 454 * @return true if a time-slot revoked message, else false. 455 */ 456 public boolean isTimeSlotRevoked() { 457 return (this.getElement(0) == XNetConstants.LI_MESSAGE_RESPONSE_HEADER 458 && this.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_TIMESLOT_ERROR); 459 } 460 461 /** 462 * In the interest of code reuse, the following function checks to see 463 * if an XpressNet Message is the Command Station Busy message (61 81 e3). 464 * @return true if is a CS Busy message, else false. 465 */ 466 public boolean isCSBusyMessage() { 467 return (this.getElement(0) == XNetConstants.CS_INFO 468 && this.getElement(1) == XNetConstants.CS_BUSY); 469 } 470 471 472 /** 473 * In the interest of code reuse, the following function checks to see 474 * if an XpressNet Message is the Command Station Transfer Error 475 * message (61 80 e1). 476 * @return if CS Transfer error, else false. 477 */ 478 public boolean isCSTransferError() { 479 return (this.getElement(0) == XNetConstants.CS_INFO 480 && this.getElement(1) == XNetConstants.CS_TRANSFER_ERROR); 481 } 482 483 /** 484 * In the interest of code reuse, the following function checks to see 485 * if an XpressNet Message is the not supported Error 486 * message (61 82 e3). 487 * @return true if unsupported error, else false. 488 */ 489 public boolean isUnsupportedError() { 490 return (this.getElement(0) == XNetConstants.CS_INFO 491 && this.getElement(1) == XNetConstants.CS_NOT_SUPPORTED); 492 } 493 494 /** 495 * In the interest of code reuse, the following function checks to see 496 * if an XpressNet Message is a communications error message. 497 * <p> 498 * The errors handled are: 499 * 01 01 00 -- Error between interface and the PC 500 * 01 02 03 -- Error between interface and the Command Station 501 * 01 03 02 -- Unknown Communications Error 502 * 01 06 07 -- LI10x Buffer Overflow 503 * 01 0A 0B -- LIUSB only. Request resend of data. 504 * @return true if comm error message, else false. 505 */ 506 public boolean isCommErrorMessage() { 507 return (this.getElement(0) == XNetConstants.LI_MESSAGE_RESPONSE_HEADER 508 && (this.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_UNKNOWN_DATA_ERROR 509 || this.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_CS_DATA_ERROR 510 || this.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_PC_DATA_ERROR 511 || this.getElement(1) == XNetConstants.LIUSB_RETRANSMIT_REQUEST 512 || this.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_BUFFER_OVERFLOW) 513 || this.isTimeSlotErrorMessage()); 514 } 515 516 /** 517 * In the interest of code reuse, the following function checks to see 518 * if an XpressNet Message is a communications error message. 519 * <p> 520 * The errors handled are: 521 * 01 05 04 -- Timeslot Error 522 * 01 07 06 -- Timeslot Restored 523 * 01 08 09 -- Data sent while there is no Timeslot 524 * @return true if time slot error, else false. 525 */ 526 public boolean isTimeSlotErrorMessage() { 527 return (this.getElement(0) == XNetConstants.LI_MESSAGE_RESPONSE_HEADER 528 && (this.getElement(1) == XNetConstants.LIUSB_REQUEST_SENT_WHILE_NO_TIMESLOT 529 || this.getElement(1) == XNetConstants.LIUSB_TIMESLOT_RESTORED 530 || this.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_TIMESLOT_ERROR)); 531 } 532 533 534 /** 535 * Is this message a service mode response? 536 * @return true if a service mode response, else false. 537 */ 538 public boolean isServiceModeResponse() { 539 return (getElement(0) == XNetConstants.CS_SERVICE_MODE_RESPONSE 540 && (getElement(1) == XNetConstants.CS_SERVICE_DIRECT_RESPONSE 541 || getElement(1) == (XNetConstants.CS_SERVICE_DIRECT_RESPONSE + 1) 542 || getElement(1) == (XNetConstants.CS_SERVICE_DIRECT_RESPONSE + 2) 543 || getElement(1) == (XNetConstants.CS_SERVICE_DIRECT_RESPONSE + 3) 544 || getElement(1) == XNetConstants.CS_SERVICE_REG_PAGE_RESPONSE)); 545 } 546 547 /** 548 * Is this message a register or paged mode programming response? 549 * @return true if register or paged mode programming response, else false. 550 */ 551 public boolean isPagedModeResponse() { 552 return (getElement(0) == XNetConstants.CS_SERVICE_MODE_RESPONSE 553 && getElement(1) == XNetConstants.CS_SERVICE_REG_PAGE_RESPONSE); 554 } 555 556 /** 557 * Is this message a direct CV mode programming response? 558 * @return true if direct CV mode programming response, else false. 559 */ 560 public boolean isDirectModeResponse() { 561 return (getElement(0) == XNetConstants.CS_SERVICE_MODE_RESPONSE 562 && (getElement(1) == XNetConstants.CS_SERVICE_DIRECT_RESPONSE 563 || getElement(1) == (XNetConstants.CS_SERVICE_DIRECT_RESPONSE + 1) 564 || getElement(1) == (XNetConstants.CS_SERVICE_DIRECT_RESPONSE + 2) 565 || getElement(1) == (XNetConstants.CS_SERVICE_DIRECT_RESPONSE + 3))); 566 } 567 568 /** 569 * @return the CV value associated with a service mode reply 570 * return -1 if not a service mode message. 571 */ 572 public int getServiceModeCVNumber() { 573 int cv = -1; 574 if (isServiceModeResponse()) { 575 if ((getElement(1) & XNetConstants.CS_SERVICE_DIRECT_RESPONSE) == XNetConstants.CS_SERVICE_DIRECT_RESPONSE) { 576 cv = (getElement(1) - XNetConstants.CS_SERVICE_DIRECT_RESPONSE) * 256 + getElement(2); 577 } else { 578 cv = getElement(2); 579 } 580 } 581 return (cv); 582 } 583 584 /** 585 * @return the value returned by the DCC system associated with a 586 * service mode reply. 587 * return -1 if not a service mode message. 588 */ 589 public int getServiceModeCVValue() { 590 int value = -1; 591 if (isServiceModeResponse()) { 592 value = getElement(3); 593 } 594 return (value); 595 } 596 597 /** 598 * @return true if the message is an error message indicating 599 * we should retransmit. 600 */ 601 @Override 602 public boolean isRetransmittableErrorMsg() { 603 return (this.isCSBusyMessage() 604 || this.isCommErrorMessage() 605 || this.isCSTransferError()); 606 } 607 608 /** 609 * @return true if the message is an unsolicited message 610 */ 611 @Override 612 public boolean isUnsolicited() { 613 // The message may be set as an unsolicited message else where 614 // or it may be classified as unsolicited based on the type of 615 // message received. 616 // NOTE: The feedback messages may be received in either solicited 617 // or unsolicited form. requesting code can mark the reply as solicited 618 // by calling the resetUnsolicited function. 619 return (super.isUnsolicited() 620 || this.isThrottleTakenOverMessage()); 621 } 622 623 /** 624 * Mask to identify a turnout feedback + correct nibble. Turnout types differ in 625 * 6th bit, so it's left out (is variable). 626 */ 627 private static final int FEEDBACK_TURNOUT_MASK = 0b0101_0000; 628 629 /** 630 * Mask to identify a feedback module + correct nibble. Turnout modules have 631 * type exactly 2. 632 */ 633 private static final int FEEDBACK_MODULE_MASK = 0b0111_0000; 634 635 /** 636 * The value of "feedback module" type. 637 */ 638 private static final int FEEDBACK_TYPE_FBMODULE = 0b0100_0000; 639 640 /** 641 * Bit that indicates the higher nibble in module or turnout feedback 642 */ 643 private static final int FEEDBACK_HIGH_NIBBLE = 0b0001_0000; 644 645 private int findFeedbackData(int baseAddress, int selector, int mask) { 646 if (isFeedbackMessage()) { 647 // shorctcut for single-item msg 648 int data = getElement(2); 649 if (getElement(1) == baseAddress && 650 (data & mask) == selector) { 651 return data; 652 } 653 } else { 654 int start = 1; 655 for (int cnt = getFeedbackMessageItems(); cnt > 0; cnt--, start += 2) { 656 int data = getElement(start + 1); 657 if (getElement(start) == baseAddress && 658 (data & mask) == selector) { 659 return data; 660 } 661 } 662 } 663 return -1; 664 } 665 666 /** 667 * Returns value of the given feedback module bit. Returns {@link Optional} 668 * that is non-empty, if the feedback was present. The Optional's value indicates the 669 * feedback state. 670 * 671 * @param sensorNumber the sensor bit ID 672 * @return optional sensor state. 673 */ 674 @CheckForNull 675 public Boolean selectModuleFeedback(int sensorNumber) { 676 if (!isFeedbackBroadcastMessage() || sensorNumber == 0 || sensorNumber >= 1024) { 677 return null; 678 } 679 // feedback address directly addresses 8-bit module, XpressNet spec 3.0:2.1.11. 680 int s = sensorNumber - 1; 681 int baseAddress = (s / 8); 682 int selector2 = (s & 0x04) != 0 ? 683 FEEDBACK_TYPE_FBMODULE | FEEDBACK_HIGH_NIBBLE : 684 FEEDBACK_TYPE_FBMODULE; 685 int res = findFeedbackData(baseAddress, selector2, FEEDBACK_MODULE_MASK); 686 return res == -1 ? null : 687 (res & (1 << (s % 4))) > 0; 688 } 689 690 /** 691 * Calls processor for turnout's feedback, returns the processor's outcome. 692 * Searches for the turnout feedback for the given accessory. If found, 693 * runs a processor on the feedback item, and returns its Boolean result. 694 * <p> 695 * Returns {@code false}, if matching feedback is not found. 696 * @param accessoryNumber the turnout number 697 * @param proc the processor 698 * @return {@code false} if feedback was not found, or a result of {@code proc()}. 699 */ 700 public boolean onTurnoutFeedback(int accessoryNumber, Function<FeedbackItem, Boolean> proc) { 701 return selectTurnoutFeedback(accessoryNumber).map(proc).orElse(false); 702 } 703 704 /** 705 * Selects a matching turnout feedback. Finds turnout feedback for the given {@code accessoryNumber}. 706 * Returns an encapsulated feedback, that can be inspected. If no matching feedback is 707 * present, returns empty {@link Optional}. 708 * @param accessoryNumber the turnout number 709 * @return optional feedback item. 710 */ 711 @Nonnull 712 public Optional<FeedbackItem> selectTurnoutFeedback(int accessoryNumber) { 713 // shortcut for single-item messages. 714 if (!isFeedbackBroadcastMessage() || accessoryNumber <= 0 || accessoryNumber >= 1024) { 715 return Optional.empty(); 716 } 717 int a = accessoryNumber - 1; 718 int base = (a / 4); 719 // the mask makes the turnout feedback type bit irrelevant 720 int selector2 = (a & 0x02) != 0 ? FEEDBACK_HIGH_NIBBLE : 0; 721 int r = findFeedbackData(base, selector2, FEEDBACK_TURNOUT_MASK); 722 if (r == -1) { 723 return Optional.empty(); 724 } 725 FeedbackItem item = new FeedbackItem(this, accessoryNumber, r); 726 return Optional.of(item); 727 } 728 729 protected final FeedbackItem createFeedbackItem(int n, int d) { 730 return new FeedbackItem(this, n, d); 731 } 732 733 /** 734 * @return a string representation of the reply suitable for display in the 735 * XpressNet monitor. 736 */ 737 @Override 738 public String toMonitorString(){ 739 StringBuilder text; 740 // First, Decode anything that is sent by the LI10x, and 741 // not the command station 742 743 if(getElement(0) == XNetConstants.LI_MESSAGE_RESPONSE_HEADER){ 744 switch(this.getElement(1)) { 745 case XNetConstants.LI_MESSAGE_RESPONSE_PC_DATA_ERROR: 746 text = new StringBuilder(Bundle.getMessage("XNetReplyErrorPCtoLI")); 747 break; 748 case XNetConstants.LI_MESSAGE_RESPONSE_CS_DATA_ERROR: 749 text = new StringBuilder(Bundle.getMessage("XNetReplyErrorLItoCS")); 750 break; 751 case XNetConstants.LI_MESSAGE_RESPONSE_UNKNOWN_DATA_ERROR: 752 text = new StringBuilder(Bundle.getMessage("XNetReplyErrorUnknown")); 753 break; 754 case XNetConstants.LI_MESSAGE_RESPONSE_SEND_SUCCESS: 755 text = new StringBuilder(Bundle.getMessage("XNetReplyOkMessage")); 756 break; 757 case XNetConstants.LI_MESSAGE_RESPONSE_TIMESLOT_ERROR: 758 text = new StringBuilder(Bundle.getMessage("XNetReplyErrorNoTimeSlot")); 759 break; 760 case XNetConstants.LI_MESSAGE_RESPONSE_BUFFER_OVERFLOW: 761 text = new StringBuilder(Bundle.getMessage("XNetReplyErrorBufferOverflow")); 762 break; 763 case XNetConstants.LIUSB_TIMESLOT_RESTORED: 764 text = new StringBuilder(Bundle.getMessage("XNetReplyTimeSlotRestored")); 765 break; 766 case XNetConstants.LIUSB_REQUEST_SENT_WHILE_NO_TIMESLOT: 767 text = new StringBuilder(Bundle.getMessage("XNetReplyRequestSentWhileNoTimeslot")); 768 break; 769 case XNetConstants.LIUSB_BAD_DATA_IN_REQUEST: 770 text = new StringBuilder(Bundle.getMessage("XNetReplyBadDataInRequest")); 771 break; 772 case XNetConstants.LIUSB_RETRANSMIT_REQUEST: 773 text = new StringBuilder(Bundle.getMessage("XNetReplyRetransmitRequest")); 774 break; 775 default: 776 text = new StringBuilder(toString()); 777 } 778 } else if (getElement(0) == XNetConstants.LI_VERSION_RESPONSE) { 779 text = new StringBuilder(Bundle.getMessage("XNetReplyLIVersion", (getElementBCD(1).floatValue()) / 10, (getElementBCD(2).floatValue()) / 10)); 780 } else if (getElement(0) == XNetConstants.LI101_REQUEST) { 781 // The request and response for baud rate look the same, 782 // so we need this for both incoming and outgoing directions 783 switch (getElement(1)) { 784 case XNetConstants.LI101_REQUEST_ADDRESS: 785 text = new StringBuilder(Bundle.getMessage("XNetReplyLIAddress", getElement(2))); 786 break; 787 case XNetConstants.LI101_REQUEST_BAUD: 788 switch (getElement(2)) { 789 case 1: 790 text = new StringBuilder(Bundle.getMessage(X_NET_REPLY_LI_BAUD, Bundle.getMessage("LIBaud19200"))); 791 break; 792 case 2: 793 text = new StringBuilder(Bundle.getMessage(X_NET_REPLY_LI_BAUD, Bundle.getMessage("Baud38400"))); 794 break; 795 case 3: 796 text = new StringBuilder(Bundle.getMessage(X_NET_REPLY_LI_BAUD, Bundle.getMessage("Baud57600"))); 797 break; 798 case 4: 799 text = new StringBuilder(Bundle.getMessage(X_NET_REPLY_LI_BAUD, Bundle.getMessage("Baud115200"))); 800 break; 801 default: 802 text = new StringBuilder(Bundle.getMessage(X_NET_REPLY_LI_BAUD, Bundle.getMessage("BaudOther"))); 803 } 804 break; 805 default: 806 text = new StringBuilder(toString()); 807 } 808 /* Next, check the "CS Info" messages */ 809 } else if (getElement(0) == XNetConstants.CS_INFO) { 810 switch (getElement(1)) { 811 case XNetConstants.BC_NORMAL_OPERATIONS: 812 text = new StringBuilder(Bundle.getMessage("XNetReplyBCNormalOpsResumed")); 813 break; 814 case XNetConstants.BC_EVERYTHING_OFF: 815 text = new StringBuilder(Bundle.getMessage("XNetReplyBCEverythingOff")); 816 break; 817 case XNetConstants.BC_SERVICE_MODE_ENTRY: 818 text = new StringBuilder(Bundle.getMessage("XNetReplyBCServiceEntry")); 819 break; 820 case XNetConstants.PROG_SHORT_CIRCUIT: 821 text = new StringBuilder(Bundle.getMessage("XNetReplyServiceModeShort")); 822 break; 823 case XNetConstants.PROG_BYTE_NOT_FOUND: 824 text = new StringBuilder(Bundle.getMessage("XNetReplyServiceModeDataByteNotFound")); 825 break; 826 case XNetConstants.PROG_CS_BUSY: 827 text = new StringBuilder(Bundle.getMessage("XNetReplyServiceModeCSBusy")); 828 break; 829 case XNetConstants.PROG_CS_READY: 830 text = new StringBuilder(Bundle.getMessage("XNetReplyServiceModeCSReady")); 831 break; 832 case XNetConstants.CS_BUSY: 833 text = new StringBuilder(Bundle.getMessage("XNetReplyCSBusy")); 834 break; 835 case XNetConstants.CS_NOT_SUPPORTED: 836 text = new StringBuilder(Bundle.getMessage("XNetReplyCSNotSupported")); 837 break; 838 case XNetConstants.CS_TRANSFER_ERROR: 839 text = new StringBuilder(Bundle.getMessage("XNetReplyCSTransferError")); 840 break; 841 /* The remaining cases are for a Double Header or MU Error */ 842 case XNetConstants.CS_DH_ERROR_NON_OP: 843 text = new StringBuilder(Bundle.getMessage("XNetReplyV1DHErrorNotOperated")); 844 break; 845 case XNetConstants.CS_DH_ERROR_IN_USE: 846 text = new StringBuilder(Bundle.getMessage("XNetReplyV1DHErrorInUse")); 847 break; 848 case XNetConstants.CS_DH_ERROR_ALREADY_DH: 849 text = new StringBuilder(Bundle.getMessage("XNetReplyV1DHErrorAlreadyDH")); 850 break; 851 case XNetConstants.CS_DH_ERROR_NONZERO_SPD: 852 text = new StringBuilder(Bundle.getMessage("XNetReplyV1DHErrorNonZeroSpeed")); 853 break; 854 default: 855 text = new StringBuilder(toString()); 856 } 857 } else if (getElement(0) == XNetConstants.BC_EMERGENCY_STOP 858 && getElement(1) == XNetConstants.BC_EVERYTHING_STOP) { 859 text = new StringBuilder(Bundle.getMessage("XNetReplyBCEverythingStop")); 860 /* Followed by Service Mode responses */ 861 } else if (getElement(0) == XNetConstants.CS_SERVICE_MODE_RESPONSE) { 862 if (isDirectModeResponse()) { 863 text = new StringBuilder(Bundle.getMessage("XNetReplyServiceModeDirectResponse", getServiceModeCVNumber(), getServiceModeCVValue())); 864 } else if (isPagedModeResponse()) { 865 text = new StringBuilder(Bundle.getMessage("XNetReplyServiceModePagedResponse", getServiceModeCVNumber(), getServiceModeCVValue())); 866 } else if (getElement(1) == XNetConstants.CS_SOFTWARE_VERSION) { 867 String typeString; 868 switch (getElement(3)) { 869 case 0x00: 870 typeString = Bundle.getMessage("CSTypeLZ100"); 871 break; 872 case 0x01: 873 typeString = Bundle.getMessage("CSTypeLH200"); 874 break; 875 case 0x02: 876 typeString = Bundle.getMessage("CSTypeCompact"); 877 break; 878 // GT 2007/11/6 - Added multiMaus 879 case 0x10: 880 typeString = Bundle.getMessage("CSTypeMultiMaus"); 881 break; 882 default: 883 typeString = "" + getElement(3); 884 } 885 text = new StringBuilder(Bundle.getMessage("XNetReplyCSVersion", (getElementBCD(2).floatValue()) / 10, typeString)); 886 } else { 887 text = new StringBuilder(toString()); 888 } 889 /* We want to look at responses to specific requests made to the Command Station */ 890 } else if (getElement(0) == XNetConstants.CS_REQUEST_RESPONSE) { 891 if (getElement(1) == XNetConstants.CS_STATUS_RESPONSE) { 892 text = new StringBuilder(Bundle.getMessage("XNetReplyCSStatus") + " "); 893 int statusByte = getElement(2); 894 if ((statusByte & 0x01) == 0x01) { 895 // Command station is in Emergency Off Mode 896 text.append(Bundle.getMessage("XNetCSStatusEmergencyOff")).append("; "); 897 } 898 if ((statusByte & 0x02) == 0x02) { 899 // Command station is in Emergency Stop Mode 900 text.append(Bundle.getMessage("XNetCSStatusEmergencyStop")).append("; "); 901 } 902 if ((statusByte & 0x08) == 0x08) { 903 // Command station is in Service Mode 904 text.append(Bundle.getMessage("XNetCSStatusServiceMode")).append("; "); 905 } 906 if ((statusByte & 0x40) == 0x40) { 907 // Command station is in Power Up Mode 908 text.append(Bundle.getMessage("XNetCSStatusPoweringUp")).append("; "); 909 } 910 if ((statusByte & 0x04) == 0x04) { 911 text.append(Bundle.getMessage("XNetCSStatusPowerModeAuto")).append("; "); 912 } else { 913 text.append(Bundle.getMessage("XNetCSStatusPowerModeManual")).append("; "); 914 } 915 if ((statusByte & 0x80) == 0x80) { 916 // Command station has a experienced a ram check error 917 text.append(Bundle.getMessage("XNetCSStatusRamCheck")); 918 } 919 } else if (getElement(1) == XNetConstants.CS_SOFTWARE_VERSION) { 920 /* This is a Software version response for XpressNet 921 Version 1 or 2 */ 922 text = new StringBuilder(Bundle.getMessage("XNetReplyCSVersionV1", (getElementBCD(2).floatValue()) / 10)); 923 } else { 924 text = new StringBuilder(toString()); 925 } 926 927 // MU and Double Header Related Responses 928 } else if (getElement(0) == XNetConstants.LOCO_MU_DH_ERROR) { 929 switch (getElement(1)) { 930 case 0x81: 931 text = new StringBuilder(Bundle.getMessage("XNetReplyDHErrorNotOperated")); 932 break; 933 case 0x82: 934 text = new StringBuilder(Bundle.getMessage("XNetReplyDHErrorInUse")); 935 break; 936 case 0x83: 937 text = new StringBuilder(Bundle.getMessage("XNetReplyDHErrorAlreadyDH")); 938 break; 939 case 0x84: 940 text = new StringBuilder(Bundle.getMessage("XNetReplyDHErrorNonZeroSpeed")); 941 break; 942 case 0x85: 943 text = new StringBuilder(Bundle.getMessage("XNetReplyDHErrorLocoNotMU")); 944 break; 945 case 0x86: 946 text = new StringBuilder(Bundle.getMessage("XNetReplyDHErrorLocoNotMUBase")); 947 break; 948 case 0x87: 949 text = new StringBuilder(Bundle.getMessage("XNetReplyDHErrorCanNotDelete")); 950 break; 951 case 0x88: 952 text = new StringBuilder(Bundle.getMessage("XNetReplyDHErrorStackFull")); 953 break; 954 default: 955 text = new StringBuilder(Bundle.getMessage("XNetReplyDHErrorOther", (getElement(1) - 0x80))); 956 } 957 // Loco Information Response Messages 958 } else if (getElement(0) == XNetConstants.LOCO_INFO_NORMAL_UNIT) { 959 if (getElement(1) == XNetConstants.LOCO_FUNCTION_STATUS_HIGH_MOM) { 960 text = new StringBuilder(Bundle.getMessage("XNetReplyLocoStatus13Label") + " "); 961 // message byte 3, contains F20,F19,F18,F17,F16,F15,F14,F13 962 int element3 = getElement(2); 963 // message byte 4, contains F28,F27,F26,F25,F24,F23,F22,F21 964 int element4 = getElement(3); 965 text.append(parseFunctionHighMomentaryStatus(element3, element4)); 966 } else { 967 text = new StringBuilder(Bundle.getMessage("XNetReplyLocoNormalLabel") + ","); 968 text.append(parseSpeedAndDirection(getElement(1), getElement(2))).append(" "); 969 // message byte 4, contains F0,F1,F2,F3,F4 970 int element3 = getElement(3); 971 // message byte 5, contains F12,F11,F10,F9,F8,F7,F6,F5 972 int element4 = getElement(4); 973 text.append(parseFunctionStatus(element3, element4)); 974 } 975 } else if (getElement(0) == XNetConstants.LOCO_INFO_MUED_UNIT) { 976 if (getElement(1) == 0xF8) { 977 // This message is a Hornby addition to the protocol 978 // indicating the speed and direction of a locomoitve 979 // controlled by the elite's built in throttles 980 text = new StringBuilder(Bundle.getMessage("XNetReplyLocoEliteSLabel") + " "); 981 text.append(LenzCommandStation.calcLocoAddress(getElement(2), getElement(3))); 982 text.append(",").append(parseSpeedAndDirection(getElement(4), getElement(5))).append(" "); 983 } else if (getElement(1) == 0xF9) { 984 // This message is a Hornby addition to the protocol 985 // indicating the function on/off status of a locomoitve 986 // controlled by the elite's built in throttles 987 text = new StringBuilder(Bundle.getMessage("XNetReplyLocoEliteFLabel") + " "); 988 text.append(LenzCommandStation.calcLocoAddress(getElement(2), getElement(3))).append(" "); 989 // message byte 5, contains F0,F1,F2,F3,F4 990 int element4 = getElement(4); 991 // message byte 5, contains F12,F11,F10,F9,F8,F7,F6,F5 992 int element5 = getElement(5); 993 text.append(parseFunctionStatus(element4, element5)); 994 } else { 995 text = new StringBuilder(Bundle.getMessage("XNetReplyLocoMULabel") + ","); 996 text.append(parseSpeedAndDirection(getElement(1), getElement(2))); 997 // message byte 4, contains F0,F1,F2,F3,F4 998 int element3 = getElement(3); 999 // message byte 5, contains F12,F11,F10,F9,F8,F7,F6,F5 1000 int element4 = getElement(4); 1001 text.append(parseFunctionStatus(element3, element4)); 1002 } 1003 } else if (getElement(0) == XNetConstants.LOCO_INFO_MU_ADDRESS) { 1004 text = new StringBuilder(Bundle.getMessage("XNetReplyLocoMUBaseLabel") + ","); 1005 text.append(parseSpeedAndDirection(getElement(1), getElement(2))).append(" "); 1006 } else if (getElement(0) == XNetConstants.LOCO_INFO_DH_UNIT) { 1007 text = new StringBuilder(Bundle.getMessage("XNetReplyLocoDHLabel") + ","); 1008 text.append(parseSpeedAndDirection(getElement(1), getElement(2))).append(" "); 1009 // message byte 4, contains F0,F1,F2,F3,F4 1010 int element3 = getElement(3); 1011 // message byte 5, contains F12,F11,F10,F9,F8,F7,F6,F5 1012 int element4 = getElement(4); 1013 text.append(parseFunctionStatus(element3, element4)); 1014 text.append(" ").append(Bundle.getMessage("XNetReplyLoco2DHLabel")).append(" "); 1015 text.append(LenzCommandStation.calcLocoAddress(getElement(5), getElement(6))); 1016 } else if (getElement(0) == XNetConstants.LOCO_INFO_RESPONSE) { 1017 text = new StringBuilder(Bundle.getMessage("XNetReplyLocoLabel") + " "); 1018 switch (getElement(1)) { 1019 case XNetConstants.LOCO_SEARCH_RESPONSE_N: 1020 text.append(Bundle.getMessage("XNetReplySearchNormalLabel")).append(" "); 1021 text.append(getThrottleMsgAddr()); 1022 break; 1023 case XNetConstants.LOCO_SEARCH_RESPONSE_DH: 1024 text.append(Bundle.getMessage("XNetReplySearchDHLabel")).append(" "); 1025 text.append(getThrottleMsgAddr()); 1026 break; 1027 case XNetConstants.LOCO_SEARCH_RESPONSE_MU_BASE: 1028 text.append(Bundle.getMessage("XNetReplySearchMUBaseLabel")).append(" "); 1029 text.append(getThrottleMsgAddr()); 1030 break; 1031 case XNetConstants.LOCO_SEARCH_RESPONSE_MU: 1032 text.append(Bundle.getMessage("XNetReplySearchMULabel")).append(" "); 1033 text.append(getThrottleMsgAddr()); 1034 break; 1035 case XNetConstants.LOCO_SEARCH_NO_RESULT: 1036 text.append(Bundle.getMessage("XNetReplySearchFailedLabel")).append(" "); 1037 text.append(getThrottleMsgAddr()); 1038 break; 1039 case XNetConstants.LOCO_NOT_AVAILABLE: 1040 text.append(Bundle.getMessage(RS_TYPE)).append(" "); 1041 text.append(getThrottleMsgAddr()).append(" "); 1042 text.append(Bundle.getMessage("XNetReplyLocoOperated")); 1043 break; 1044 case XNetConstants.LOCO_FUNCTION_STATUS: 1045 locoFunctionStatusText(text); 1046 break; 1047 case XNetConstants.LOCO_FUNCTION_STATUS_HIGH: 1048 locoFunctionStatusHighText(text); 1049 break; 1050 default: 1051 text = new StringBuilder(toString()); 1052 } 1053 // Feedback Response Messages 1054 } else if (isFeedbackBroadcastMessage()) { 1055 text = new StringBuilder().append(Bundle.getMessage("XNetReplyFeedbackLabel")).append(" "); 1056 int numDataBytes = getElement(0) & 0x0f; 1057 for (int i = 1; i < numDataBytes; i += 2) { 1058 switch (getFeedbackMessageType(i)) { 1059 case 0: 1060 text.append(getTurnoutReplyMonitorString(i, "TurnoutWoFeedback")); 1061 break; 1062 case 1: 1063 text.append(getTurnoutReplyMonitorString(i, "TurnoutWFeedback")); 1064 break; 1065 case 2: 1066 text.append(Bundle.getMessage("XNetReplyFeedbackEncoder")).append(" ").append(getFeedbackEncoderMsgAddr(i)); 1067 boolean highnibble = ((getElement(i + 1) & 0x10) == 0x10); 1068 text.append(" ").append(Bundle.getMessage(X_NET_REPLY_CONTACT_LABEL)).append(" ").append(highnibble ? 5 : 1); 1069 1070 text.append(" ").append(Bundle.getMessage(MAKE_LABEL, Bundle.getMessage(COLUMN_STATE))).append(" ") 1071 .append(((getElement(i + 1) & 0x01) == 0x01) ? Bundle.getMessage(POWER_STATE_ON) : Bundle.getMessage(POWER_STATE_OFF)); 1072 text.append("; ").append(Bundle.getMessage(X_NET_REPLY_CONTACT_LABEL)).append(" ").append(highnibble ? 6 : 2); 1073 1074 text.append(" ").append(Bundle.getMessage(MAKE_LABEL, Bundle.getMessage(COLUMN_STATE))).append(" ") 1075 .append(((getElement(i + 1) & 0x02) == 0x02) ? Bundle.getMessage(POWER_STATE_ON) : Bundle.getMessage(POWER_STATE_OFF)); 1076 text.append("; ").append(Bundle.getMessage(X_NET_REPLY_CONTACT_LABEL)).append(" ").append(highnibble ? 7 : 3); 1077 1078 text.append(" ").append(Bundle.getMessage(MAKE_LABEL, Bundle.getMessage(COLUMN_STATE))).append(" ") 1079 .append(((getElement(i + 1) & 0x04) == 0x04) ? Bundle.getMessage(POWER_STATE_ON) : Bundle.getMessage(POWER_STATE_OFF)); 1080 text.append("; ").append(Bundle.getMessage(X_NET_REPLY_CONTACT_LABEL)).append(" ").append(highnibble ? 8 : 4); 1081 1082 text.append(" ").append(Bundle.getMessage(MAKE_LABEL, Bundle.getMessage(COLUMN_STATE))).append(" ") 1083 .append(((getElement(i + 1) & 0x08) == 0x08) ? Bundle.getMessage(POWER_STATE_ON) : Bundle.getMessage(POWER_STATE_OFF)); 1084 text.append("; "); 1085 break; 1086 default: 1087 text.append(getElement(i)).append(" ").append(getElement(i + 1)); 1088 } 1089 } 1090 } else { 1091 text = new StringBuilder(toString()); 1092 } 1093 return text.toString(); 1094 } 1095 1096 private void locoFunctionStatusHighText(StringBuilder text) { 1097 text.append(Bundle.getMessage(RS_TYPE)).append(" "); 1098 text.append(Bundle.getMessage("XNetReplyF13StatusLabel")).append(" "); 1099 // message byte 3, contains F20,F19,F18,F17,F16,F15,F14,F13 1100 int element3 = getElement(2); 1101 // message byte 4, contains F28,F27,F26,F25,F24,F23,F22,F21 1102 int element4 = getElement(3); 1103 text.append(parseFunctionHighStatus(element3, element4)); 1104 } 1105 1106 private void locoFunctionStatusText(StringBuilder text) { 1107 text.append(Bundle.getMessage(RS_TYPE)).append(" "); // "Locomotive", key in NBBundle, shared with Operations 1108 text.append(Bundle.getMessage("XNetReplyFStatusLabel")).append(" "); 1109 // message byte 3, contains F0,F1,F2,F3,F4 1110 int element3 = getElement(2); 1111 // message byte 4, contains F12,F11,F10,F9,F8,F7,F6,F5 1112 int element4 = getElement(3); 1113 text.append(parseFunctionMomentaryStatus(element3, element4)); 1114 } 1115 1116 private String getTurnoutReplyMonitorString(int startByte, String typeBundleKey) { 1117 StringBuilder text = new StringBuilder(); 1118 int turnoutMsgAddr = getTurnoutMsgAddr(startByte); 1119 Optional<FeedbackItem> feedBackOdd = selectTurnoutFeedback(turnoutMsgAddr); 1120 if(feedBackOdd.isPresent()){ 1121 FeedbackItem feedbackItem = feedBackOdd.get(); 1122 text.append(singleTurnoutMonitorMessage(Bundle.getMessage(typeBundleKey), turnoutMsgAddr, feedbackItem)); 1123 text.append(";"); 1124 FeedbackItem pairedItem = feedbackItem.pairedAccessoryItem(); 1125 text.append(singleTurnoutMonitorMessage("", turnoutMsgAddr + 1, pairedItem)); 1126 1127 } 1128 return text.toString(); 1129 } 1130 1131 private String singleTurnoutMonitorMessage(String prefix, int turnoutMsgAddr, FeedbackItem feedbackItem) { 1132 StringBuilder outputBuilder = new StringBuilder(); 1133 outputBuilder.append(prefix).append(" ") 1134 .append(Bundle.getMessage(MAKE_LABEL, Bundle.getMessage(BEAN_NAME_TURNOUT))).append(" ") 1135 .append(turnoutMsgAddr).append(" ").append(Bundle.getMessage(MAKE_LABEL, Bundle.getMessage(COLUMN_STATE))).append(" "); 1136 switch (feedbackItem.getAccessoryStatus()){ 1137 case 0: 1138 outputBuilder.append(Bundle.getMessage(X_NET_REPLY_NOT_OPERATED)); // last items on line, no trailing space 1139 break; 1140 case 1: 1141 outputBuilder.append(Bundle.getMessage(X_NET_REPLY_THROWN_LEFT)); 1142 break; 1143 case 2: 1144 outputBuilder.append(Bundle.getMessage(X_NET_REPLY_THROWN_RIGHT)); 1145 break; 1146 default: 1147 outputBuilder.append(Bundle.getMessage(X_NET_REPLY_INVALID)); 1148 } 1149 if(feedbackItem.getType()==1){ 1150 outputBuilder.append(" "); 1151 if(feedbackItem.isMotionComplete()){ 1152 outputBuilder.append(Bundle.getMessage("XNetReplyMotionComplete")); 1153 } else { 1154 outputBuilder.append(Bundle.getMessage("XNetReplyMotionIncomplete")); 1155 } 1156 } 1157 return outputBuilder.toString(); 1158 } 1159 1160 /** 1161 * Parse the speed step and the direction information for a locomotive. 1162 * 1163 * @param element1 contains the speed step mode designation and 1164 * availability information 1165 * @param element2 contains the data byte including the step mode and 1166 * availability information 1167 * @return readable version of message 1168 */ 1169 protected String parseSpeedAndDirection(int element1, int element2) { 1170 String text = ""; 1171 int speedVal; 1172 if ((element2 & 0x80) == 0x80) { 1173 text += Bundle.getMessage("Forward") + ","; 1174 } else { 1175 text += Bundle.getMessage("Reverse") + ","; 1176 } 1177 1178 if ((element1 & 0x04) == 0x04) { 1179 // We're in 128 speed step mode 1180 speedVal = element2 & 0x7f; 1181 // The first speed step used is actually at 2 for 128 1182 // speed step mode. 1183 if (speedVal >= 1) { 1184 speedVal -= 1; 1185 } else { 1186 speedVal = 0; 1187 } 1188 text += Bundle.getMessage(SPEED_STEP_MODE_X, 128) + ","; 1189 } else if ((element1 & 0x02) == 0x02) { 1190 // We're in 28 speed step mode 1191 // We have to re-arange the bits, since bit 4 is the LSB, 1192 // but other bits are in order from 0-3 1193 speedVal = ((element2 & 0x0F) << 1) + ((element2 & 0x10) >> 4); 1194 // The first speed step used is actually at 4 for 28 1195 // speed step mode. 1196 if (speedVal >= 3) { 1197 speedVal -= 3; 1198 } else { 1199 speedVal = 0; 1200 } 1201 text += Bundle.getMessage(SPEED_STEP_MODE_X, 28) + ","; 1202 } else if ((element1 & 0x01) == 0x01) { 1203 // We're in 27 speed step mode 1204 // We have to re-arange the bits, since bit 4 is the LSB, 1205 // but other bits are in order from 0-3 1206 speedVal = ((element2 & 0x0F) << 1) + ((element2 & 0x10) >> 4); 1207 // The first speed step used is actually at 4 for 27 1208 // speed step mode. 1209 if (speedVal >= 3) { 1210 speedVal -= 3; 1211 } else { 1212 speedVal = 0; 1213 } 1214 text += Bundle.getMessage(SPEED_STEP_MODE_X, 27) + ","; 1215 } else { 1216 // Assume we're in 14 speed step mode. 1217 speedVal = (element2 & 0x0F); 1218 if (speedVal >= 1) { 1219 speedVal -= 1; 1220 } else { 1221 speedVal = 0; 1222 } 1223 text += Bundle.getMessage(SPEED_STEP_MODE_X, 14) + ","; 1224 } 1225 1226 text += Bundle.getMessage("SpeedStepLabel") + " " + speedVal + ". "; 1227 1228 if ((element1 & 0x08) == 0x08) { 1229 text += "" + Bundle.getMessage("XNetReplyAddressInUse"); 1230 } else { 1231 text += "" + Bundle.getMessage("XNetReplyAddressFree"); 1232 } 1233 return (text); 1234 } 1235 1236 /** 1237 * Parse the status of functions F0-F12. 1238 * 1239 * @param element3 contains the data byte including F0,F1,F2,F3,F4 1240 * @param element4 contains F12,F11,F10,F9,F8,F7,F6,F5 1241 * @return readable version of message 1242 */ 1243 protected String parseFunctionStatus(int element3, int element4) { 1244 String text = ""; 1245 if ((element3 & 0x10) != 0) { 1246 text += "F0 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1247 } else { 1248 text += "F0 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1249 } 1250 if ((element3 & 0x01) != 0) { 1251 text += "F1 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1252 } else { 1253 text += "F1 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1254 } 1255 if ((element3 & 0x02) != 0) { 1256 text += "F2 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1257 } else { 1258 text += "F2 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1259 } 1260 if ((element3 & 0x04) != 0) { 1261 text += "F3 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1262 } else { 1263 text += "F3 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1264 } 1265 if ((element3 & 0x08) != 0) { 1266 text += "F4 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1267 } else { 1268 text += "F4 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1269 } 1270 if ((element4 & 0x01) != 0) { 1271 text += "F5 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1272 } else { 1273 text += "F5 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1274 } 1275 if ((element4 & 0x02) != 0) { 1276 text += "F6 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1277 } else { 1278 text += "F6 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1279 } 1280 if ((element4 & 0x04) != 0) { 1281 text += "F7 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1282 } else { 1283 text += "F7 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1284 } 1285 if ((element4 & 0x08) != 0) { 1286 text += "F8 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1287 } else { 1288 text += "F8 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1289 } 1290 if ((element4 & 0x10) != 0) { 1291 text += "F9 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1292 } else { 1293 text += "F9 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1294 } 1295 if ((element4 & 0x20) != 0) { 1296 text += "F10 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1297 } else { 1298 text += "F10 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1299 } 1300 if ((element4 & 0x40) != 0) { 1301 text += "F11 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1302 } else { 1303 text += "F11 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1304 } 1305 if ((element4 & 0x80) != 0) { 1306 text += "F12 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1307 } else { 1308 text += "F12 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1309 } 1310 return (text); 1311 } 1312 1313 /** 1314 * Parse the status of functions F13-F28. 1315 * 1316 * @param element3 contains F20,F19,F18,F17,F16,F15,F14,F13 1317 * @param element4 contains F28,F27,F26,F25,F24,F23,F22,F21 1318 * @return readable version of message 1319 */ 1320 protected String parseFunctionHighStatus(int element3, int element4) { 1321 String text = ""; 1322 if ((element3 & 0x01) != 0) { 1323 text += "F13 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1324 } else { 1325 text += "F13 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1326 } 1327 if ((element3 & 0x02) != 0) { 1328 text += "F14 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1329 } else { 1330 text += "F14 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1331 } 1332 if ((element3 & 0x04) != 0) { 1333 text += "F15 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1334 } else { 1335 text += "F15 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1336 } 1337 if ((element3 & 0x08) != 0) { 1338 text += "F16 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1339 } else { 1340 text += "F16 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1341 } 1342 if ((element3 & 0x10) != 0) { 1343 text += "F17 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1344 } else { 1345 text += "F17 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1346 } 1347 if ((element3 & 0x20) != 0) { 1348 text += "F18 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1349 } else { 1350 text += "F18 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1351 } 1352 if ((element3 & 0x40) != 0) { 1353 text += "F19 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1354 } else { 1355 text += "F19 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1356 } 1357 if ((element3 & 0x80) != 0) { 1358 text += "F20 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1359 } else { 1360 text += "F20 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1361 } 1362 if ((element4 & 0x01) != 0) { 1363 text += "F21 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1364 } else { 1365 text += "F21 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1366 } 1367 if ((element4 & 0x02) != 0) { 1368 text += "F22 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1369 } else { 1370 text += "F22 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1371 } 1372 if ((element4 & 0x04) != 0) { 1373 text += "F23 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1374 } else { 1375 text += "F23 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1376 } 1377 if ((element4 & 0x08) != 0) { 1378 text += "F24 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1379 } else { 1380 text += "F24 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1381 } 1382 if ((element4 & 0x10) != 0) { 1383 text += "F25 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1384 } else { 1385 text += "F25 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1386 } 1387 if ((element4 & 0x20) != 0) { 1388 text += "F26 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1389 } else { 1390 text += "F26 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1391 } 1392 if ((element4 & 0x40) != 0) { 1393 text += "F27 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1394 } else { 1395 text += "F27 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1396 } 1397 if ((element4 & 0x80) != 0) { 1398 text += "F28 " + Bundle.getMessage(POWER_STATE_ON) + "; "; 1399 } else { 1400 text += "F28 " + Bundle.getMessage(POWER_STATE_OFF) + "; "; 1401 } 1402 return (text); 1403 } 1404 /** 1405 * Parse the Momentary status of functions. 1406 * 1407 * @param element3 contains the data byte including F0,F1,F2,F3,F4 1408 * @param element4 contains F12,F11,F10,F9,F8,F7,F6,F5 1409 * @return readable version of message 1410 */ 1411 protected String parseFunctionMomentaryStatus(int element3, int element4) { 1412 String text = ""; 1413 if ((element3 & 0x10) != 0) { 1414 text += "F0 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1415 } else { 1416 text += "F0 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1417 } 1418 if ((element3 & 0x01) != 0) { 1419 text += "F1 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1420 } else { 1421 text += "F1 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1422 } 1423 if ((element3 & 0x02) != 0) { 1424 text += "F2 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1425 } else { 1426 text += "F2 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1427 } 1428 if ((element3 & 0x04) != 0) { 1429 text += "F3 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1430 } else { 1431 text += "F3 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1432 } 1433 if ((element3 & 0x08) != 0) { 1434 text += "F4 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1435 } else { 1436 text += "F4 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1437 } 1438 if ((element4 & 0x01) != 0) { 1439 text += "F5 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1440 } else { 1441 text += "F5 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1442 } 1443 if ((element4 & 0x02) != 0) { 1444 text += "F6 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1445 } else { 1446 text += "F6 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1447 } 1448 if ((element4 & 0x04) != 0) { 1449 text += "F7 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1450 } else { 1451 text += "F7 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1452 } 1453 if ((element4 & 0x08) != 0) { 1454 text += "F8 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1455 } else { 1456 text += "F8 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1457 } 1458 if ((element4 & 0x10) != 0) { 1459 text += "F9 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1460 } else { 1461 text += "F9 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1462 } 1463 if ((element4 & 0x20) != 0) { 1464 text += "F10 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1465 } else { 1466 text += "F10 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1467 } 1468 if ((element4 & 0x40) != 0) { 1469 text += "F11 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1470 } else { 1471 text += "F11 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1472 } 1473 if ((element4 & 0x80) != 0) { 1474 text += "F12 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1475 } else { 1476 text += "F12 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1477 } 1478 return (text); 1479 } 1480 1481 /** 1482 * Parse the Momentary sytatus of functions F13-F28. 1483 * 1484 * @param element3 contains F20,F19,F18,F17,F16,F15,F14,F13 1485 * @param element4 contains F28,F27,F26,F25,F24,F23,F22,F21 1486 * @return readable version of message 1487 */ 1488 protected String parseFunctionHighMomentaryStatus(int element3, int element4) { 1489 String text = ""; 1490 if ((element3 & 0x01) != 0) { 1491 text += "F13 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1492 } else { 1493 text += "F13 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1494 } 1495 if ((element3 & 0x02) != 0) { 1496 text += "F14 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1497 } else { 1498 text += "F14 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1499 } 1500 if ((element3 & 0x04) != 0) { 1501 text += "F15 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1502 } else { 1503 text += "F15 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1504 } 1505 if ((element3 & 0x08) != 0) { 1506 text += "F16 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1507 } else { 1508 text += "F16 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1509 } 1510 if ((element3 & 0x10) != 0) { 1511 text += "F17 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1512 } else { 1513 text += "F17 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1514 } 1515 if ((element3 & 0x20) != 0) { 1516 text += "F18 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1517 } else { 1518 text += "F18 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1519 } 1520 if ((element3 & 0x40) != 0) { 1521 text += "F19 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1522 } else { 1523 text += "F19 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1524 } 1525 if ((element3 & 0x80) != 0) { 1526 text += "F20 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1527 } else { 1528 text += "F20 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1529 } 1530 if ((element4 & 0x01) != 0) { 1531 text += "F21 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1532 } else { 1533 text += "F21 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1534 } 1535 if ((element4 & 0x02) != 0) { 1536 text += "F22 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1537 } else { 1538 text += "F22 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1539 } 1540 if ((element4 & 0x04) != 0) { 1541 text += "F23 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1542 } else { 1543 text += "F23 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1544 } 1545 if ((element4 & 0x08) != 0) { 1546 text += "F24 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1547 } else { 1548 text += "F24 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1549 } 1550 if ((element4 & 0x10) != 0) { 1551 text += "F25 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1552 } else { 1553 text += "F25 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1554 } 1555 if ((element4 & 0x20) != 0) { 1556 text += "F26 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1557 } else { 1558 text += "F26 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1559 } 1560 if ((element4 & 0x40) != 0) { 1561 text += "F27 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1562 } else { 1563 text += "F27 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1564 } 1565 if ((element4 & 0x80) != 0) { 1566 text += "F28 " + Bundle.getMessage(FUNCTION_MOMENTARY) + "; "; 1567 } else { 1568 text += "F28 " + Bundle.getMessage(FUNCTION_CONTINUOUS) + "; "; 1569 } 1570 return (text); 1571 } 1572 1573}