001package jmri.jmrix.loconet.lnsvf1; 002 003import jmri.jmrix.loconet.LnConstants; 004import jmri.jmrix.loconet.LocoNetMessage; 005import jmri.jmrix.loconet.messageinterp.LocoNetMessageInterpret; 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008 009import java.util.Locale; 010import java.util.Objects; 011 012/** 013 * Supporting class for LocoNet SV Programming Format 1 (LocoIO) messaging. 014 * <p> 015 * Some of the message formats used in this class are Copyright Digitrax, Inc. 016 * and used with permission as part of the JMRI project. That permission does 017 * not extend to uses in other software products. If you wish to use this code, 018 * algorithm or these message formats outside of JMRI, please contact Digitrax 019 * Inc for separate permission. 020 * <p> 021 * Uses the LOCONETSV1MODE programming mode. 022 * <p> 023 * Uses LnProgrammer LOCOIO_PEER_CODE_SV_VER1 message format, comparable to DecoderPro LOCONETSV1MODE 024 * The DecoderPro decoder definition is recommended for all LocoIO versions. Requires JMRI 4.12 or later. 025 * 026 * @see jmri.jmrix.loconet.LnOpsModeProgrammer#message(LocoNetMessage) 027 * 028 * Programming SV's 029 * <p> 030 * The SV's in a LocoIO hardware module can be programmed using LocoNet OPC_PEER_XFER messages. 031 * <p> 032 * Commands for setting SV's: 033 * <p> 034 * PC to LocoIO LocoNet message (OPC_PEER_XFER) 035 * <pre><code> 036 * Code LNSV1_SV_READ LNSV1_SV_WRITE 037 * 0xE5 OPC_PEER_XFER 038 * 0x10 2nd part of OpCode 039 * SRCL 0x50 0x50 // low address of LocoBuffer, high address assumed 1 040 * DSTL LocoIO address 041 * DSTH always 0x01 042 * PXCT1 043 * D1 LNSV1_SV_READ _or_ LNSV1_SV_WRITE // Read/Write command 044 * D2 SV number SV number 045 * D3 0x00 0x00 046 * D4 0x00 New value byte to Write 047 * PXCT2 048 * D5 LocoIO sub-address LocoIO sub-address 049 * D6 0x00 0x00 050 * D7 0x00 0x00 051 * D8 0x00 0x00 052 * CHK Checksum Checksum 053 * </code></pre> 054 * 055 * LocoIO to PC reply message (OPC_PEER_XFER) 056 * <pre><code> 057 * Code LNSV1__SV_READ LNSV1__SV_WRITE 058 * 0xE5 OPC_PEER_XFER 059 * 0x10 2nd part of OpCode 060 * SRCL LocoIO low address LocoIO low address 061 * DSTL 0x50 0x50 // address of LocoBuffer 062 * DSTH always 0x01 always 0x01 063 * PXCT1 MSB SV + version // High order bits of SV and LocoIO version 064 * D1 LNSV1_READ _or_ LNSV1_WRITE // Copy of original Command 065 * D2 SV number requested SV number requested 066 * D3 LSBs LocoIO version // Lower 7 bits of LocoIO version 067 * D4 0x00 0x00 068 * PXCT2 MSB Requested Data // High order bits of written cvValue 069 * D5 LocoIO Sub-address LocoIO Sub-address 070 * D6 Requested Data 0x00 071 * D7 Requested Data + 1 0x00 072 * D8 Requested Data + 2 Written cvValue confirmed 073 * CHK Checksum Checksum 074 * </code></pre> 075 * 076 * @author John Plocher 2006, 2007 077 * @author B. Milhaupt Copyright (C) 2015 078 * @author E. Broerse Copyright (C) 2025 079 */ 080public class Lnsv1MessageContents { 081 public static final int LNSV1_BROADCAST_ADDRESS = 0x00; // LocoIO broadcast (addr_H = 1) 082 public static final int LNSV1_LOCOBUFFER_ADDRESS = 0x50; // LocoBuffer reserved address (addr_H = 1) 083 public static final int LNSV1_PEER_CODE_SV_VER0 = 0x00; // observed in read and write replies from LocoIO 084 public static final int LNSV1_PEER_CODE_SV_VER1 = 0x08; // for read and write requests, not for replies 085 086 private final int src_l; 087 private final int sv_cmd; 088 private final int dst_l; 089 private final int dst_h; 090 private final int sub_adr; 091 private final int sv_num; 092 private final int vrs; 093 private final int d4; 094 private final int d6; 095 private final int d7; 096 private final int d8; 097 098 // Helper to calculate LocoIO Sensor address from returned data is in LocoNetMessage 099 100 // LocoNet "SV 1 format" helper definitions: indexes into the LocoNet message 101 public final static int SV1_SV_SRC_L_ELEMENT_INDEX = 2; 102 public final static int SV1_SV_DST_L_ELEMENT_INDEX = 3; 103 public final static int SV1_SV_DST_H_ELEMENT_INDEX = 4; 104 public final static int SV1_SVX1_ELEMENT_INDEX = 5; 105 public final static int SV1_SV_CMD_ELEMENT_INDEX = 6; 106 public final static int SV1_SVX2_ELEMENT_INDEX = 10; 107 108 // decoding SV format 1 messages (versus other OCP_PEER_XFER messages with length 0x10) uses m.getPeerXfrData() 109 public final static int SV1_SVX1_ELEMENT_VALIDITY_CHECK_MASK = 0x70; 110 public final static int SV1_SVX1_ELEMENT_VALIDITY_CHECK_VALUE = 0x00; 111 public final static int SV1_SVX2_ELEMENT_VALIDITY_CHECK_MASK = 0x70; 112 public final static int SV0_SVX2_ELEMENT_VALIDITY_CHECK_VALUE = 0x10; // LNSV0 mask 113 public final static int SV1_SVX2_ELEMENT_VALIDITY_CHECK_VALUE = 0x00; // LNSV1 mask 114 115 // helpers for decoding SV_CMD, compare to ENUM Sv1Command below 116 public final static int SV_CMD_WRITE_ONE = 0x01; 117 public final static int SV_CMD_READ_ONE = 0x02; 118 119 /** 120 * Create a new Lnsv1MessageContents object from a LocoNet message. 121 * 122 * @param m LocoNet message containing an SV Programming Format 1 message 123 * @throws IllegalArgumentException if the LocoNet message is not a valid, supported 124 * SV Programming Format 1 message 125 */ 126 public Lnsv1MessageContents(LocoNetMessage m) 127 throws IllegalArgumentException { 128 129 log.debug("interpreting a LocoNet message - may be an SV1 message"); // NOI18N 130 if (!isSupportedSv1Message(m)) { 131 log.debug("interpreting a LocoNet message - is NOT an SV1 message"); // NOI18N 132 throw new IllegalArgumentException("LocoNet message is not an SV1 message"); // NOI18N 133 } 134 src_l = m.getElement(SV1_SV_SRC_L_ELEMENT_INDEX); 135 dst_l = m.getElement(SV1_SV_DST_L_ELEMENT_INDEX); 136 dst_h = m.getElement(SV1_SV_DST_H_ELEMENT_INDEX); // should always be 0x01 137 138 int[] d = m.getPeerXfrData(); 139 sv_cmd = d[0]; 140 sv_num = d[1]; 141 vrs = d[2]; 142 d4 = d[3]; 143 sub_adr = d[4]; 144 d6 = d[5]; 145 d7 = d[6]; 146 d8 = d[7]; 147 } 148 149 /** 150 * Check a LocoNet message to determine if it is a valid SV Programming Format 1 151 * message. 152 * 153 * @param m LocoNet message to check 154 * @return true if LocoNet message m is a supported SV Programming Format 1 155 * message, else false. 156 */ 157 public static boolean isSupportedSv1Message(LocoNetMessage m) { 158 // must be OPC_PEER_XFER opcode 159 if (m.getOpCode() != LnConstants.OPC_PEER_XFER) { 160 log.debug ("cannot be SV1 message because not OPC_PEER_XFER"); // NOI18N 161 return false; 162 } 163 164 // Element 1 must be 0x10 165 if (m.getElement(1) != 0x10) { 166 log.debug ("cannot be SV1 message because elem. 1 not 0x10"); // NOI18N 167 return false; 168 } 169 170 if (m.getElement(SV1_SV_DST_H_ELEMENT_INDEX) != 1) { 171 log.debug ("cannot be SV1 message because elem. 4 not 0x01"); // NOI18N 172 return false; 173 } 174 175 // Check PXCT1 176 if ((m.getElement(SV1_SVX1_ELEMENT_INDEX) 177 & SV1_SVX1_ELEMENT_VALIDITY_CHECK_MASK) 178 != SV1_SVX1_ELEMENT_VALIDITY_CHECK_VALUE) { 179 log.debug ("cannot be SV1 message because SVX1 upper nibble wrong"); // NOI18N 180 return false; 181 } 182 // Check PXCT2 183 if ((m.getElement(SV1_SVX2_ELEMENT_INDEX) // SV0 Broadcast/Write from LocoBuffer 184 & SV1_SVX2_ELEMENT_VALIDITY_CHECK_MASK) 185 != SV0_SVX2_ELEMENT_VALIDITY_CHECK_VALUE) { 186 if ((m.getElement(SV1_SVX2_ELEMENT_INDEX) // SV1 Read/Write reply from LocoIO 187 & SV1_SVX2_ELEMENT_VALIDITY_CHECK_MASK) != SV1_SVX2_ELEMENT_VALIDITY_CHECK_VALUE) { 188 log.debug("cannot be SV1 message because SVX2 upper nibble wrong: {}", // extra CHECK_VALUE for replies? 189 m.getElement(SV1_SVX2_ELEMENT_INDEX) & SV1_SVX2_ELEMENT_VALIDITY_CHECK_MASK); // NOI18N 190 return false; 191 } 192 } 193 194 // check the <SV_CMD> value 195 if (isSupportedSv1Command(m.getElement(SV1_SV_CMD_ELEMENT_INDEX))) { 196 log.debug("LocoNet message is a supported SV Format 1 message"); 197 return true; 198 } 199 log.debug("LocoNet message is not a supported SV Format 1 message"); // NOI18N 200 return false; 201 } 202 203 /** 204 * Compare reply message against a specific SV Programming Format 1 message type. 205 * 206 * @param m LocoNet message to be verified as an SV Programming Format 1 message 207 * with the specified <SV_CMD> value 208 * @param svCmd SV Programming Format 1 command to expect 209 * @return true if message is an SV Programming Format 1 message of the specified <SV_CMD>, 210 * else false. 211 */ 212 public static boolean isLnMessageASpecificSv1Command(LocoNetMessage m, Sv1Command svCmd) { 213 // must be OPC_PEER_XFER opcode 214 if (m.getOpCode() != LnConstants.OPC_PEER_XFER) { 215 log.debug ("cannot be SV1 message because not OPC_PEER_XFER"); // NOI18N 216 return false; 217 } 218 219 // length of OPC_PEER_XFER must be 0x10 220 if (m.getElement(1) != 0x10) { 221 log.debug ("cannot be SV1 message because not length 0x10"); // NOI18N 222 return false; 223 } 224 225 // The upper nibble of PXCT1 must be 0, and the upper nibble of PXCT2 must be 1 or 2. 226 // Check PCX1 227 if ((m.getElement(SV1_SVX1_ELEMENT_INDEX) 228 & SV1_SVX1_ELEMENT_VALIDITY_CHECK_MASK) 229 != SV1_SVX1_ELEMENT_VALIDITY_CHECK_VALUE) { 230 log.debug ("cannot be SV1 message because SVX1 upper nibble wrong"); // NOI18N 231 return false; 232 } 233 // Check PCX2 234 if ((m.getElement(SV1_SVX2_ELEMENT_INDEX) // SV0 Broadcast/Write from LocoBuffer 235 & SV1_SVX2_ELEMENT_VALIDITY_CHECK_MASK) 236 != SV0_SVX2_ELEMENT_VALIDITY_CHECK_VALUE) { 237 if ((m.getElement(SV1_SVX2_ELEMENT_INDEX) // SV1 Read/Write reply from LocoIO 238 & SV1_SVX2_ELEMENT_VALIDITY_CHECK_MASK) 239 != SV1_SVX2_ELEMENT_VALIDITY_CHECK_VALUE) { 240 log.debug ("cannot be SV1 message because SVX2 upper nibble wrong {}", 241 m.getElement(SV1_SVX2_ELEMENT_INDEX) & SV1_SVX2_ELEMENT_VALIDITY_CHECK_MASK); // NOI18N 242 return false; 243 } 244 } 245 246 // check the <SV_CMD> value 247 if (isSupportedSv1Command(m.getElement(SV1_SV_CMD_ELEMENT_INDEX))) { 248 log.debug("LocoNet message is a supported SV Format 1 message"); // NOI18N 249 if (Objects.equals(extractMessageType(m), svCmd)) { 250 log.debug("LocoNet message is the specified SV Format 1 message"); // NOI18N 251 return true; 252 } 253 } 254 log.debug("LocoNet message is not a supported SV Format 1 message"); // NOI18N 255 return false; 256 } 257 258 /** 259 * Interpret a LocoNet message to extract its SV Programming Format 1 <SV_CMD>. 260 * If the message is not an SV Programming Format 1 message, returns null. 261 * 262 * @param m LocoNet message containing SV Programming Format 1 message 263 * @return Sv1Command found in the SV Programming Format 1 message or null if not found 264 */ 265 public static Sv1Command extractMessageType(LocoNetMessage m) { 266 if (isSupportedSv1Message(m)) { 267 int msgCmd = m.getPeerXfrData()[0]; 268 for (Sv1Command s: Sv1Command.values()) { 269 if (s.getCmd() == msgCmd) { 270 log.debug("LocoNet message has SV1 message command {}", msgCmd); // NOI18N 271 return s; 272 } 273 } 274 } 275 return null; 276 } 277 278 /** 279 * Interpret a LocoNet message to extract its SV Programming Format 1 <SV_CMD>. 280 * If the message is not an SV Programming Format 1 message, return null. 281 * 282 * @param m LocoNet message containing SV Programming Format 1 version field 283 * @return Version found in the SV Programming Format 1 message or -1 if not found 284 */ 285 public static int extractMessageVersion(LocoNetMessage m) { 286 if (isSupportedSv1Message(m)) { 287 int v = m.getPeerXfrData()[2]; 288 log.debug("LocoNet LNSV1 message contains version {}", v); // NOI18N 289 return (v > 0 ? v : -1); 290 } 291 return -1; 292 } 293 294 /** 295 * Interpret the SV Programming Format 1 message into a human-readable string. 296 * 297 * @return String containing a human-readable version of the SV Programming 298 * Format 1 message 299 */ 300 @Override 301 public String toString() { 302 Locale l = Locale.getDefault(); 303 return Lnsv1MessageContents.this.toString(l); 304 } 305 306 /** 307 * Interpret the SV Programming Format 1 message into a human-readable string. 308 * 309 * @param locale locale to use for the human-readable string 310 * @return String containing a human-readable version of the SV Programming 311 * Format 1 message, in the language specified by the Locale if the 312 * properties have been translated to that Locale, else in the default 313 * English language. 314 */ 315 public String toString(Locale locale) { 316 String returnString; 317 log.debug("interpreting an SV1 message - sv_cmd is {}", sv_cmd); // NOI18N 318 319 // use Integer.toHexString(i) and/or String.format("0x%02X", i)) 320 switch (sv_cmd) { 321 case (SV_CMD_WRITE_ONE): 322 if (src_l == 0x50) { 323 if (dst_l == 0) { 324 returnString = Bundle.getMessage(locale, "SV1_WRITE_ALL_INTERPRETED", 325 (sv_num == 1 ? "" : "sub"), // makes sub+address // NOI18N 326 toHexStr(sv_num), 327 toHexStr(d4)); 328 } else if (dst_l == 0x50) { 329 returnString = Bundle.getMessage(locale, "SV1_WRITE_LB_INTERPRETED", 330 toHexStr(sv_num), 331 toHexStr(d4), 332 (vrs > 0) ? " Firmware rev " + LocoNetMessageInterpret.dotme(vrs) : ""); // in test, useful? 333 } else { 334 returnString = Bundle.getMessage(locale, "SV1_WRITE_INTERPRETED", 335 toHexComposite(dst_l, sub_adr), 336 toHexStr(sv_num), 337 toHexStr(d4), 338 (vrs > 0) ? " Firmware rev " + LocoNetMessageInterpret.dotme(vrs) : ""); // NOI18N 339 } 340 } else { 341 returnString = Bundle.getMessage(locale, "SV1_WRITE_REPLY_INTERPRETED", 342 toHexComposite(src_l, sub_adr), 343 toHexStr(sv_num), 344 toHexStr(d8), 345 (vrs > 0) ? " Firmware rev " + LocoNetMessageInterpret.dotme(vrs) : ""); // NOI18N 346 } 347 break; 348 349 case (SV_CMD_READ_ONE): 350 if (src_l == 0x50) { 351 if (dst_l == 0) { 352 returnString = Bundle.getMessage(locale, "SV1_PROBE_ALL_INTERPRETED"); 353 } else if (dst_l == 0x50) { 354 returnString = Bundle.getMessage(locale, "SV1_READ_LB_INTERPRETED", 355 toHexStr(sv_num), 356 (vrs > 0) ? " Firmware rev " + LocoNetMessageInterpret.dotme(vrs) : ""); // in test, useful? 357 } else { 358 returnString = Bundle.getMessage(locale, "SV1_READ_INTERPRETED", 359 toHexComposite(dst_l, sub_adr), 360 toHexStr(sv_num), 361 (vrs > 0) ? " Firmware rev " + LocoNetMessageInterpret.dotme(vrs) : ""); 362 } 363 } else { 364 returnString = Bundle.getMessage(locale, "SV1_READ_REPLY_INTERPRETED", 365 toHexComposite(src_l, sub_adr), 366 toHexStr(sv_num), 367 toHexStr(d6), 368 (vrs > 0) ? " Firmware rev " + LocoNetMessageInterpret.dotme(vrs) : ""); // NOI18N 369 } 370 break; 371 372 default: 373 return Bundle.getMessage(locale, "SV1_UNDEFINED_MESSAGE") + "\n"; 374 } 375 376 log.debug("interpreted: {}", returnString); // NOI18N 377 return returnString + "\n"; // NOI18N 378 } 379 380 private static final String HEX_FORMAT = "0x%02X"; 381 382 /** 383 * Format byte for decimal + (optional) hex display 384 * 385 */ 386 public static String toHexStr(int value) { 387 if (value > 9) { 388 return value + " (" + String.format(HEX_FORMAT, value) + ")"; 389 } else { 390 return String.valueOf(value); 391 } 392 } 393 394 /** 395 * Format byte for hex display 396 * 397 */ 398 public static String toHexComposite(int low, int high) { 399 String res = String.valueOf(low); 400 if (high == 0) { 401 if (low > 9) { 402 res += " (" + String.format(HEX_FORMAT, low) + ")"; 403 } 404 return res; 405 } 406 res += "/" + high; 407 if (low > 9 || high > 9) { 408 res += " ("; 409 res += (low > 9 ? String.format(HEX_FORMAT, low) : low); 410 res += "/"; 411 res += (high > 9 ? String.format(HEX_FORMAT, high) : high); 412 res += ")"; 413 } 414 return res; 415 } 416 417 /** 418 * 419 * @param possibleCmd integer to be compared to the command list 420 * @return true if the possibleCmd value is one of the supported SV 421 * Programming Format 1 commands 422 */ 423 public static boolean isSupportedSv1Command(int possibleCmd) { 424 switch (possibleCmd) { 425 case (SV_CMD_WRITE_ONE): 426 case (SV_CMD_READ_ONE): 427 return true; 428 default: 429 return false; 430 } 431 } 432 433 /** 434 * Confirm a message specifies a valid (known) SV Programming Format 1 command. 435 * 436 * @return true if the SV1 message specifies a valid (known) SV Programming 437 * Format 1 command. 438 */ 439 public boolean isSupportedSv1Command() { 440 return isSupportedSv1Command(sv_cmd); 441 } 442 443 /** 444 * 445 * @return true if the SV1 message is a SV1 Read One Reply message 446 */ 447 public boolean isSupportedSv1ReadOneReply() { 448 return (sv_cmd == SV_CMD_READ_ONE && src_l != 0x50 && vrs != 0); 449 } 450 451 /** 452 * Get the data from a SVs READ_ONE Reply message. May also be used to 453 * return the effective SV value reported in an SV1 WRITE_ONE Reply message (or is that returned in d8?). 454 * 455 * @return the {@code <D6>} value from the SV1 message 456 */ 457 public int getSingleReadReportData() { 458 return d6; 459 } 460 461 public int getSrcL() { 462 return src_l; 463 } 464 465 public int getDstL() { 466 return dst_l; 467 } 468 469 /** Used to check message. LNSV1 messages do not use the DST_H field for high address */ 470 public int getDstH() { 471 return dst_h; 472 } 473 474 /** Not returning a valid address because LNSV1 messages do not use the DST_H field for high address. 475 * and a composite address is not used. 476 * - LocoBuffer subaddress is always 1. 477 * - LocoIO subaddress is stored and fetched from PEER_XFER element SV1_SV_SUBADR_ELEMENT_INDEX (11). 478 * - JMRI LocoIO decoder address as stored in the Roster is calculated as a 14-bit number 479 * in jmri.jmrix.loconet.swing.lnsv1prog.Lnsv1ProgPane 480 */ 481 public int getDestAddr() { 482 return -1; 483 } 484 485 public int getSubAddress() { 486 return sub_adr; 487 } 488 489 public int getCmd() { 490 return sv_cmd; 491 } 492 493 public int getSvNum() { 494 if ((sv_cmd == Sv1Command.SV1_READ.sv_cmd) || 495 (sv_cmd == Sv1Command.SV1_WRITE.sv_cmd)) { 496 return sv_num; 497 } 498 return -1; 499 } 500 501 public int getSvValue() { 502 if (sv_cmd == Sv1Command.SV1_READ.sv_cmd) { 503 if (vrs > 0) { // Read reply 504 return d6; 505 } else { 506 return d4; // Read request 507 } 508 } else if (sv_cmd == Sv1Command.SV1_WRITE.sv_cmd) { 509 if (vrs > 0) { 510 return d8; // Write reply 511 } else { 512 return d4; // Write request 513 } 514 } 515 return -1; 516 } 517 518 public int getVersionNum() { 519 if (vrs > 0) { 520 return vrs; 521 } 522 return -1; 523 } 524 525 /** 526 * Get the d4 value 527 * @return d4 element contents 528 */ 529 public int getSv1D4() { 530 return d4; 531 } 532 533 /** 534 * Get the d6 value 535 * @return d6 element contents 536 */ 537 public int getSv1D6() { 538 return d6; 539 } 540 541 /** 542 * Get the d7 value 543 * @return d7 element contents 544 */ 545 public int getSv1D7() { 546 return d7; 547 } 548 549 /** 550 * Get the d8 value 551 * @return d8 element contents 552 */ 553 public int getSv1D8() { 554 return d8; 555 } 556 557 // ****** Create LNSV1 messages ***** // 558 559 /** 560 * Create a LocoNet message containing an SV Programming Format 0 message. 561 * Used only to simulate replies from LocoIO. Uses LNSV1_PEER_CODE_SV_VER0. 562 * <p> 563 * See Programmer message code in {@link jmri.jmrix.loconet.LnOpsModeProgrammer} loadSV1MessageFormat 564 * 565 * @param source source device address (for <SRC_L>) 566 * @param destination = SV format 1 7-bit destination address (for <DST_L>) 567 * @param subAddress = SV format 1 7-bit destination subaddress (for <DST_H>) 568 * @param command SV Programming Format 1 command number (for <SV_CMD>) 569 * @param svNum SV Programming Format 1 8-bit SV number 570 * @param newVal (d4) SV first 8-bit data value to write (for <D4>) 571 * @param version Programming Format 1 8-bit firmware version number; 0 in request,{@literal >0} in replies 572 * @param d6 second 8-bit data value (for <D6>) 573 * @param d7 third 8-bit data value (for <D7>) 574 * @param d8 fourth 8-bit data value (for <D8>) 575 * @return LocoNet message for the requested message 576 * @throws IllegalArgumentException if command is not a valid SV Programming Format 1 <SV_CMD> value 577 */ 578 public static LocoNetMessage createSv0Message ( 579 int source, 580 int destination, 581 int subAddress, 582 int command, 583 int svNum, 584 int version, 585 int newVal, 586 int d6, 587 int d7, 588 int d8) 589 throws IllegalArgumentException { 590 591 if (! isSupportedSv1Command(command)) { 592 throw new IllegalArgumentException("Command is not a supported SV1 command"); // NOI18N 593 } 594 int[] contents = {command, svNum, version, newVal, subAddress, d6, d7, d8}; 595 log.debug("createSv1Message src={} dst={} subAddr={} data[]={}", source, destination, subAddress, contents); 596 return LocoNetMessage.makePeerXfr( 597 source, 598 destination, 599 contents, 600 LNSV1_PEER_CODE_SV_VER0 601 ); 602 } 603 604 /** 605 * Create a LocoNet message containing an SV Programming Format 1 message. 606 * <p> 607 * See Programmer message code in {@link jmri.jmrix.loconet.LnOpsModeProgrammer} loadSV1MessageFormat 608 * 609 * @param source source device address (for <SRC_L>) 610 * @param destination = SV format 1 7-bit destination address (for <DST_L>) 611 * @param subAddress = SV format 1 7-bit destination subaddress (for <DST_H>) 612 * @param command SV Programming Format 1 command number (for <SV_CMD>) 613 * @param svNum SV Programming Format 1 8-bit SV number 614 * @param newVal (d4) SV first 8-bit data value to write (for <D4>) 615 * @param version Programming Format 1 8-bit firmware version number; 0 in request,{@literal >0} in replies 616 * @param d6 second 8-bit data value (for <D6>) 617 * @param d7 third 8-bit data value (for <D7>) 618 * @param d8 fourth 8-bit data value (for <D8>) 619 * @return LocoNet message for the requested message 620 * @throws IllegalArgumentException if command is not a valid SV Programming Format 1 <SV_CMD> value 621 */ 622 public static LocoNetMessage createSv1Message ( 623 int source, 624 int destination, 625 int subAddress, 626 int command, 627 int svNum, 628 int version, 629 int newVal, 630 int d6, 631 int d7, 632 int d8) 633 throws IllegalArgumentException { 634 635 if (! isSupportedSv1Command(command)) { 636 throw new IllegalArgumentException("Command is not a supported SV1 command"); // NOI18N 637 } 638 int[] contents = {command, svNum, version, newVal, subAddress, d6, d7, d8}; 639 log.debug("createSv1Message src={} dst={} subAddr={} data[]={}", source, destination, subAddress, contents); 640 return LocoNetMessage.makePeerXfr( 641 source, 642 destination, 643 contents, 644 LNSV1_PEER_CODE_SV_VER1 645 ); 646 } 647 648 /** 649 * Create LocoNet message for a query of an SV of this object. 650 * 651 * @param dst address of the device to read from 652 * @param subAddress subaddress of the device to read from 653 * @param svNum SV number to read 654 * @return LocoNet message 655 */ 656public static LocoNetMessage createSv1ReadRequest(int dst, int subAddress, int svNum) { 657 int dstExtr = dst | 0x0100; // force version 1 tag, cf. LnOpsModeProgrammer 658 log.debug("createSv1ReadRequest dst={} dstExtr={} subAddr={}", dst, dstExtr, subAddress); 659 return createSv1Message(LNSV1_LOCOBUFFER_ADDRESS, dstExtr, subAddress, 660 Sv1Command.SV1_READ.sv_cmd, svNum, 0,0, 0, 0, 0); 661 } 662 663 /** 664 * Simulate a read/probe reply for testing/developing. 665 * 666 * @param src board low address 667 * @param dst dest high address, usually 0x50 for LocoBuffer/PC 668 * @param subAddress board high address 669 * @param version fictional firmware version number to add 670 * @param svNum SV read 671 * @return LocoNet message containing the reply 672 */ 673 public static LocoNetMessage createSv1ReadReply(int src, int dst, int subAddress, int version, int svNum, int returnValue) { 674 log.debug("createSv0ReadReply"); 675 int dstExtr = dst | 0x0100; // force version 1 tag, cf. LnOpsModeProgrammer 676 return createSv0Message(src, dstExtr, subAddress, 677 Sv1Command.SV1_READ.sv_cmd, 678 svNum, version, 0, returnValue, 0, 0); 679 } 680 681 public static LocoNetMessage createSv1WriteRequest(int dst, int subAddress, int svNum, int newValue) { 682 log.debug("createSv1WriteRequest"); 683 int dstExtr = dst | 0x0100; // force version 1 tag, cf. LnOpsModeProgrammer 684 return createSv1Message(LNSV1_LOCOBUFFER_ADDRESS, dstExtr, subAddress, 685 Sv1Command.SV1_WRITE.sv_cmd, svNum, 0, newValue, 0, 0, 0); 686 } 687 688 /** 689 * Simulate a read/probe reply for testing/developing. 690 * 691 * @param src board low address 692 * @param subAddress board high address 693 * @param dst dest high address, usually 0x1050 for LocoBuffer/PC 694 * @param version fictional firmware version number to add 695 * @param svNum SV read 696 * @return LocoNet message containing the reply 697 */ 698 public static LocoNetMessage createSv1WriteReply(int src, int dst, int subAddress, int version, int svNum, int returnValue) { 699 log.debug("createSv0WriteReply"); 700 int dstExtr = dst | 0x0100; // force version 1 tag, cf. LnOpsModeProgrammer 701 return createSv0Message(src, dstExtr, subAddress, 702 Sv1Command.SV1_WRITE.sv_cmd, 703 svNum, version, 0, 0, 0, returnValue); 704 } 705 706 /** 707 * Compose a message that changes the hardware board address of ALL connected 708 * LNSV1 (LocoIO) boards. 709 * 710 * @param address the new base address of the LocoIO board to change 711 * @param subAddress the new subAddress of the board 712 * @return an array containing one or two LocoNet messages 713 */ 714 public static LocoNetMessage[] createBroadcastSetAddress(int address, int subAddress) { 715 LocoNetMessage[] messages = new LocoNetMessage[2]; 716 messages[0] = createSv1WriteRequest(LNSV1_BROADCAST_ADDRESS, 0, 1, address & 0xFF); 717 if (subAddress != 0) { 718 messages[1] = createSv1WriteRequest(LNSV1_BROADCAST_ADDRESS, 0, 2, subAddress); 719 } 720 return messages; 721 } 722 723 /** 724 * Create a message to probe all connected LocoIO (LNSV1) units on a given LocoNet connection. 725 * 726 * @return the complete LocoNet message 727 */ 728 public static LocoNetMessage createBroadcastProbeAll() { 729 return createSv1ReadRequest(LNSV1_BROADCAST_ADDRESS, 0, 2); 730 } 731 732 public enum Sv1Command { 733 SV1_WRITE (0x01), 734 SV1_READ (0x02); 735 736 private final int sv_cmd; 737 738 Sv1Command(int sv_cmd) { 739 this.sv_cmd = sv_cmd; 740 } 741 742 int getCmd() {return sv_cmd;} 743 744 public static int getCmd(Sv1Command mt) { 745 return mt.getCmd(); 746 } 747 } 748 749 // initialize logging 750 private final static Logger log = LoggerFactory.getLogger(Lnsv1MessageContents.class); 751 752}