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 /** 381 * Format byte for decimal + (optional) hex display 382 * 383 */ 384 public static String toHexStr(int value) { 385 if (value > 9) { 386 return value + " (" + String.format("0x%02X", value) + ")"; 387 } else { 388 return String.valueOf(value); 389 } 390 } 391 392 /** 393 * Format byte for hex display 394 * 395 */ 396 public static String toHexComposite(int low, int high) { 397 String res = String.valueOf(low); 398 if (high == 0) { 399 if (low > 9) { 400 res += " (" + String.format("0x%02X", low) + ")"; 401 } 402 return res; 403 } 404 res += "/" + high; 405 if (low > 9 || high > 9) { 406 res += " ("; 407 res += (low > 9 ? String.format("0x%02X", low) : low); 408 res += "/"; 409 res += (high > 9 ? String.format("0x%02X", high) : high); 410 res += ")"; 411 } 412 return res; 413 } 414 415 /** 416 * 417 * @param possibleCmd integer to be compared to the command list 418 * @return true if the possibleCmd value is one of the supported SV 419 * Programming Format 1 commands 420 */ 421 public static boolean isSupportedSv1Command(int possibleCmd) { 422 switch (possibleCmd) { 423 case (SV_CMD_WRITE_ONE): 424 case (SV_CMD_READ_ONE): 425 return true; 426 default: 427 return false; 428 } 429 } 430 431 /** 432 * Confirm a message specifies a valid (known) SV Programming Format 1 command. 433 * 434 * @return true if the SV1 message specifies a valid (known) SV Programming 435 * Format 1 command. 436 */ 437 public boolean isSupportedSv1Command() { 438 return isSupportedSv1Command(sv_cmd); 439 } 440 441 /** 442 * 443 * @return true if the SV1 message is a SV1 Read One Reply message 444 */ 445 public boolean isSupportedSv1ReadOneReply() { 446 return (sv_cmd == SV_CMD_READ_ONE && src_l != 0x50 && vrs != 0); 447 } 448 449 /** 450 * Get the data from a SVs READ_ONE Reply message. May also be used to 451 * return the effective SV value reported in an SV1 WRITE_ONE Reply message (or is that returned in d8?). 452 * 453 * @return the {@code <D6>} value from the SV1 message 454 */ 455 public int getSingleReadReportData() { 456 return d6; 457 } 458 459 public int getSrcL() { 460 return src_l; 461 } 462 463 public int getDstL() { 464 return dst_l; 465 } 466 467 /** Used to check message. LNSV1 messages do not use the DST_H field for high address */ 468 public int getDstH() { 469 return dst_h; 470 } 471 472 /** Not returning a valid address because LNSV1 messages do not use the DST_H field for high address. 473 * and a composite address is not used. 474 * - LocoBuffer subaddress is always 1. 475 * - LocoIO subaddress is stored and fetched from PEER_XFER element SV1_SV_SUBADR_ELEMENT_INDEX (11). 476 * - JMRI LocoIO decoder address as stored in the Roster is calculated as a 14-bit number 477 * in jmri.jmrix.loconet.swing.lnsv1prog.Lnsv1ProgPane 478 */ 479 public int getDestAddr() { 480 return -1; 481 } 482 483 public int getSubAddress() { 484 return sub_adr; 485 } 486 487 public int getCmd() { 488 return sv_cmd; 489 } 490 491 public int getSvNum() { 492 if ((sv_cmd == Sv1Command.SV1_READ.sv_cmd) || 493 (sv_cmd == Sv1Command.SV1_WRITE.sv_cmd)) { 494 return sv_num; 495 } 496 return -1; 497 } 498 499 public int getSvValue() { 500 if (sv_cmd == Sv1Command.SV1_READ.sv_cmd) { 501 if (vrs > 0) { // Read reply 502 return d6; 503 } else { 504 return d4; // Read request 505 } 506 } else if (sv_cmd == Sv1Command.SV1_WRITE.sv_cmd) { 507 if (vrs > 0) { 508 return d8; // Write reply 509 } else { 510 return d4; // Write request 511 } 512 } 513 return -1; 514 } 515 516 public int getVersionNum() { 517 if (vrs > 0) { 518 return vrs; 519 } 520 return -1; 521 } 522 523 /** 524 * Get the d4 value 525 * @return d4 element contents 526 */ 527 public int getSv1D4() { 528 return d4; 529 } 530 531 /** 532 * Get the d6 value 533 * @return d6 element contents 534 */ 535 public int getSv1D6() { 536 return d6; 537 } 538 539 /** 540 * Get the d7 value 541 * @return d7 element contents 542 */ 543 public int getSv1D7() { 544 return d7; 545 } 546 547 /** 548 * Get the d8 value 549 * @return d8 element contents 550 */ 551 public int getSv1D8() { 552 return d8; 553 } 554 555 // ****** Create LNSV1 messages ***** // 556 557 /** 558 * Create a LocoNet message containing an SV Programming Format 0 message. 559 * Used only to simulate replies from LocoIO. Uses LNSV1_PEER_CODE_SV_VER0. 560 * <p> 561 * See Programmer message code in {@link jmri.jmrix.loconet.LnOpsModeProgrammer} loadSV1MessageFormat 562 * 563 * @param source source device address (for <SRC_L>) 564 * @param destination = SV format 1 7-bit destination address (for <DST_L>) 565 * @param subAddress = SV format 1 7-bit destination subaddress (for <DST_H>) 566 * @param command SV Programming Format 1 command number (for <SV_CMD>) 567 * @param svNum SV Programming Format 1 8-bit SV number 568 * @param newVal (d4) SV first 8-bit data value to write (for <D4>) 569 * @param version Programming Format 1 8-bit firmware version number; 0 in request,{@literal >0} in replies 570 * @param d6 second 8-bit data value (for <D6>) 571 * @param d7 third 8-bit data value (for <D7>) 572 * @param d8 fourth 8-bit data value (for <D8>) 573 * @return LocoNet message for the requested message 574 * @throws IllegalArgumentException if command is not a valid SV Programming Format 1 <SV_CMD> value 575 */ 576 public static LocoNetMessage createSv0Message ( 577 int source, 578 int destination, 579 int subAddress, 580 int command, 581 int svNum, 582 int version, 583 int newVal, 584 int d6, 585 int d7, 586 int d8) 587 throws IllegalArgumentException { 588 589 if (! isSupportedSv1Command(command)) { 590 throw new IllegalArgumentException("Command is not a supported SV1 command"); // NOI18N 591 } 592 int[] contents = {command, svNum, version, newVal, subAddress, d6, d7, d8}; 593 log.debug("createSv1Message src={} dst={} subAddr={} data[]={}", source, destination, subAddress, contents); 594 return LocoNetMessage.makePeerXfr( 595 source, 596 destination, 597 contents, 598 LNSV1_PEER_CODE_SV_VER0 599 ); 600 } 601 602 /** 603 * Create a LocoNet message containing an SV Programming Format 1 message. 604 * <p> 605 * See Programmer message code in {@link jmri.jmrix.loconet.LnOpsModeProgrammer} loadSV1MessageFormat 606 * 607 * @param source source device address (for <SRC_L>) 608 * @param destination = SV format 1 7-bit destination address (for <DST_L>) 609 * @param subAddress = SV format 1 7-bit destination subaddress (for <DST_H>) 610 * @param command SV Programming Format 1 command number (for <SV_CMD>) 611 * @param svNum SV Programming Format 1 8-bit SV number 612 * @param newVal (d4) SV first 8-bit data value to write (for <D4>) 613 * @param version Programming Format 1 8-bit firmware version number; 0 in request,{@literal >0} in replies 614 * @param d6 second 8-bit data value (for <D6>) 615 * @param d7 third 8-bit data value (for <D7>) 616 * @param d8 fourth 8-bit data value (for <D8>) 617 * @return LocoNet message for the requested message 618 * @throws IllegalArgumentException if command is not a valid SV Programming Format 1 <SV_CMD> value 619 */ 620 public static LocoNetMessage createSv1Message ( 621 int source, 622 int destination, 623 int subAddress, 624 int command, 625 int svNum, 626 int version, 627 int newVal, 628 int d6, 629 int d7, 630 int d8) 631 throws IllegalArgumentException { 632 633 if (! isSupportedSv1Command(command)) { 634 throw new IllegalArgumentException("Command is not a supported SV1 command"); // NOI18N 635 } 636 int[] contents = {command, svNum, version, newVal, subAddress, d6, d7, d8}; 637 log.debug("createSv1Message src={} dst={} subAddr={} data[]={}", source, destination, subAddress, contents); 638 return LocoNetMessage.makePeerXfr( 639 source, 640 destination, 641 contents, 642 LNSV1_PEER_CODE_SV_VER1 643 ); 644 } 645 646 /** 647 * Create LocoNet message for a query of an SV of this object. 648 * 649 * @param dst address of the device to read from 650 * @param subAddress subaddress of the device to read from 651 * @param svNum SV number to read 652 * @return LocoNet message 653 */ 654public static LocoNetMessage createSv1ReadRequest(int dst, int subAddress, int svNum) { 655 int dstExtr = dst | 0x0100; // force version 1 tag, cf. LnOpsModeProgrammer 656 log.debug("createSv1ReadRequest dst={} dstExtr={} subAddr={}", dst, dstExtr, subAddress); 657 return createSv1Message(LNSV1_LOCOBUFFER_ADDRESS, dstExtr, subAddress, 658 Sv1Command.SV1_READ.sv_cmd, svNum, 0,0, 0, 0, 0); 659 } 660 661 /** 662 * Simulate a read/probe reply for testing/developing. 663 * 664 * @param src board low address 665 * @param dst dest high address, usually 0x50 for LocoBuffer/PC 666 * @param subAddress board high address 667 * @param version fictional firmware version number to add 668 * @param svNum SV read 669 * @return LocoNet message containing the reply 670 */ 671 public static LocoNetMessage createSv1ReadReply(int src, int dst, int subAddress, int version, int svNum, int returnValue) { 672 log.debug("createSv0ReadReply"); 673 int dstExtr = dst | 0x0100; // force version 1 tag, cf. LnOpsModeProgrammer 674 return createSv0Message(src, dstExtr, subAddress, 675 Sv1Command.SV1_READ.sv_cmd, 676 svNum, version, 0, returnValue, 0, 0); 677 } 678 679 public static LocoNetMessage createSv1WriteRequest(int dst, int subAddress, int svNum, int newValue) { 680 log.debug("createSv1WriteRequest"); 681 int dstExtr = dst | 0x0100; // force version 1 tag, cf. LnOpsModeProgrammer 682 return createSv1Message(LNSV1_LOCOBUFFER_ADDRESS, dstExtr, subAddress, 683 Sv1Command.SV1_WRITE.sv_cmd, svNum, 0, newValue, 0, 0, 0); 684 } 685 686 /** 687 * Simulate a read/probe reply for testing/developing. 688 * 689 * @param src board low address 690 * @param subAddress board high address 691 * @param dst dest high address, usually 0x1050 for LocoBuffer/PC 692 * @param version fictional firmware version number to add 693 * @param svNum SV read 694 * @return LocoNet message containing the reply 695 */ 696 public static LocoNetMessage createSv1WriteReply(int src, int dst, int subAddress, int version, int svNum, int returnValue) { 697 log.debug("createSv0WriteReply"); 698 int dstExtr = dst | 0x0100; // force version 1 tag, cf. LnOpsModeProgrammer 699 return createSv0Message(src, dstExtr, subAddress, 700 Sv1Command.SV1_WRITE.sv_cmd, 701 svNum, version, 0, 0, 0, returnValue); 702 } 703 704 /** 705 * Compose a message that changes the hardware board address of ALL connected 706 * LNSV1 (LocoIO) boards. 707 * 708 * @param address the new base address of the LocoIO board to change 709 * @param subAddress the new subAddress of the board 710 * @return an array containing one or two LocoNet messages 711 */ 712 public static LocoNetMessage[] createBroadcastSetAddress(int address, int subAddress) { 713 LocoNetMessage[] messages = new LocoNetMessage[2]; 714 messages[0] = createSv1WriteRequest(LNSV1_BROADCAST_ADDRESS, 0, 1, address & 0xFF); 715 if (subAddress != 0) { 716 messages[1] = createSv1WriteRequest(LNSV1_BROADCAST_ADDRESS, 0, 2, subAddress); 717 } 718 return messages; 719 } 720 721 /** 722 * Create a message to probe all connected LocoIO (LNSV1) units on a given LocoNet connection. 723 * 724 * @return the complete LocoNet message 725 */ 726 public static LocoNetMessage createBroadcastProbeAll() { 727 return createSv1ReadRequest(LNSV1_BROADCAST_ADDRESS, 0, 2); 728 } 729 730 public enum Sv1Command { 731 SV1_WRITE (0x01), 732 SV1_READ (0x02); 733 734 private final int sv_cmd; 735 736 Sv1Command(int sv_cmd) { 737 this.sv_cmd = sv_cmd; 738 } 739 740 int getCmd() {return sv_cmd;} 741 742 public static int getCmd(Sv1Command mt) { 743 return mt.getCmd(); 744 } 745 } 746 747 // initialize logging 748 private final static Logger log = LoggerFactory.getLogger(Lnsv1MessageContents.class); 749 750}