001package jmri.jmrix.loconet.lnsvf2; 002 003import java.util.Locale; 004import java.util.Objects; 005 006import jmri.jmrix.loconet.LnConstants; 007import jmri.jmrix.loconet.LocoNetMessage; 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011/** 012 * Supporting class for LocoNet SV Programming Format 2 messaging. 013 * 014 * Some of the message formats used in this class are Copyright Digitrax, Inc. 015 * and used with permission as part of the JMRI project. That permission does 016 * not extend to uses in other software products. If you wish to use this code, 017 * algorithm or these message formats outside of JMRI, please contact Digitrax 018 * Inc for separate permission. 019 020 * @author B. Milhaupt Copyright (C) 2015 021 */ 022public class Lnsv2MessageContents { 023 private int src; 024 private int sv_cmd; 025 private int dst_l; 026 private int dst_h; 027 private int dst; 028 private int sv_adrl; 029 private int sv_adrh; 030 private int sv_adr; 031 private int d1; 032 private int d2; 033 private int d3; 034 private int d4; 035 036 // LocoNet "SV 2 format" helper definitions: length byte value for OPC_PEER_XFER message 037 public final static int SV2_LENGTH_ELEMENT_VALUE = 0x10; 038 039 // LocoNet "SV 2 format" helper definitions: indexes into the LocoNet message 040 public final static int SV2_LENGTH_ELEMENT_INDEX = 1; 041 public final static int SV2_SRC_ELEMENT_INDEX = 2; 042 public final static int SV2_SV_CMD_ELEMENT_INDEX = 3; 043 public final static int SV2_SV_TYPE_ELEMENT_INDEX = 4; 044 public final static int SV2_SVX1_ELEMENT_INDEX = 5; 045 public final static int SV2_SV_DST_L_ELEMENT_INDEX = 6; 046 public final static int SV2_SV_DST_H_ELEMENT_INDEX = 7; 047 public final static int SV2_SV_ADRL_ELEMENT_INDEX = 8; 048 public final static int SV2_SV_ADRH_ELEMENT_INDEX = 9; 049 public final static int SV2_SVX2_ELEMENT_INDEX = 10; 050 public final static int SV2_SVD1_ELEMENT_INDEX = 11; 051 public final static int SV2_SVD2_ELEMENT_INDEX = 12; 052 public final static int SV2_SVD3_ELEMENT_INDEX = 13; 053 public final static int SV2_SVD4_ELEMENT_INDEX = 14; 054 055 // helpers for decoding SV format 2 messages (versus other OCP_PEER_XFER messages with length 0x10) 056 public final static int SV2_SRC_ELEMENT_MASK = 0x7f; 057 public final static int SV2_SVX1_ELEMENT_VALIDITY_CHECK_MASK = 0x70; 058 public final static int SV2_SVX1_ELEMENT_VALIDITY_CHECK_VALUE = 0x10; 059 public final static int SV2_SV_DST_L_DSTLX7_CHECK_MASK = 0x01; 060 public final static int SV2_SV_DST_H_DSTHX7_CHECK_MASK = 0x02; 061 public final static int SV2_SV_ADRL_SVADRL7_CHECK_MASK = 0x04; 062 public final static int SV2_SV_ADRH_SVADRH7_CHECK_MASK = 0x08; 063 public final static int SV2_SVX2_ELEMENT_VALIDITY_CHECK_MASK = 0x70; 064 public final static int SV2_SVX2_ELEMENT_VALIDITY_CHECK_VALUE = 0x10; 065 public final static int SV2_SV_D1_D1X7_CHECK_MASK = 0x01; 066 public final static int SV2_SV_D2_D2X7_CHECK_MASK = 0x02; 067 public final static int SV2_SV_D3_D3X7_CHECK_MASK = 0x04; 068 public final static int SV2_SV_D4_D4X7_CHECK_MASK = 0x08; 069 public final static int SV2_SV_TYPE_ELEMENT_VALIDITY_CHECK_MASK = 0x7F; 070 public final static int SV2_SV_TYPE_ELEMENT_VALIDITY_CHECK_VALUE = 0x02; 071 072 // helpers for decoding SV_CMD 073 public final static int SV_CMD_WRITE_ONE = 0x01; 074 public final static int SV_CMD_WRITE_ONE_REPLY = 0x41; // reply to SV_CMD_WRITE_ONE 075 076 public final static int SV_CMD_QUERY_ONE = 0x02; 077 public final static int SV_CMD_REPORT_ONE = 0x42; // reply to SV_CMD_QUERY_ONE 078 079 public final static int SV_CMD_WRITE_ONE_MASKED = 0x03; 080 public final static int SV_CMD_WRITE_ONE_MASKED_REPLY = 0x43; // reply to SV_CMD_WRITE_ONE_MASKED 081 082 public final static int SV_CMD_WRITE_FOUR = 0x05; 083 public final static int SV_CMD_WRITE_FOUR_REPLY = 0x45; // reply to SV_CMD_WRITE_FOUR 084 085 public final static int SV_CMD_QUERY_FOUR = 0x06; 086 public final static int SV_CMD_REPORT_FOUR = 0x46; // reply to SV_CMD_QUERY_FOUR 087 088 public final static int SV_CMD_DISCOVER_DEVICES_QUERY = 0x07; 089 public final static int SV_CMD_DISCOVER_DEVICE_REPORT = 0x47; // reply to SV_CMD_DISCOVER_DEVICES_QUERY 090 091 public final static int SV_CMD_IDENTIFY_DEVICE_BY_DEVICE_ADDRESS = 0x08; 092 public final static int SV_CMD_IDENTIFY_DEVICE_BY_DEVICE_ADDRESS_REPLY = 0x48; // reply to SV_CMD_IDENTIFY_DEVICE_BY_DEVICE_ADDRESS 093 094 public final static int SV_CMD_CHANGE_ADDRESS_REQUEST = 0x09; 095 public final static int SV_CMD_CHANGE_ADDRESS_REPLY = 0x49; // reply to SV_CMD_CHANGE_ADDRESS_REQUEST 096 097 public final static int SV_CMD_RECONFIGURE_REQUEST = 0x0F; 098 public final static int SV_CMD_RECONFIGURE_REPLY = 0x4F; // reply to SV_CMD_RECONFIGURE_REQUEST 099 100 // LocoNet "SV 2 format" helper definitions: SV_CMD "reply" bit 101 public final static int SV2_SV_CMD_REPLY_BIT_NUMBER = 0x6; 102 public final static int SV2_SV_CMD_REPLY_BIT_MASK = (2^SV2_SV_CMD_REPLY_BIT_NUMBER); 103 104 // LocoNet "SV 2 format" helper definitions for data 105 public final static int SV2_SV_DATA_INDEX_EEPROM_SIZE = 1; 106 public final static int SV2_SV_DATA_INDEX_SOFTWARE_VERSION = 2; 107 public final static int SV2_SV_DATA_INDEX_SERIAL_NUMBER_LOW = 3; 108 public final static int SV2_SV_DATA_INDEX_SERIAL_NUMBER_HIGH = 4; 109 110 111 /** 112 * Create a new Lnsv2MessageContents object from a LocoNet message. 113 * 114 * @param m LocoNet message containing an SV Programming Format 2 message 115 * @throws IllegalArgumentException if the LocoNet message is not a valid, supported 116 * SV Programming Format 2 message 117 */ 118 public Lnsv2MessageContents(LocoNetMessage m) 119 throws java.lang.IllegalArgumentException { 120 121 log.debug("interpreting a LocoNet message - may be an SV2 message"); // NOI18N 122 if (!isSupportedSv2Message(m)) { 123 log.debug("interpreting a LocoNet message - is NOT an SV2 message"); // NOI18N 124 throw new java.lang.IllegalArgumentException("LocoNet message is not an SV2 message"); // NOI18N 125 } 126 src = m.getElement(SV2_SRC_ELEMENT_INDEX); 127 int svx1 = m.getElement(SV2_SVX1_ELEMENT_INDEX); 128 int svx2 = m.getElement(SV2_SVX2_ELEMENT_INDEX); 129 sv_cmd = m.getElement(SV2_SV_CMD_ELEMENT_INDEX); 130 dst_l = m.getElement(SV2_SV_DST_L_ELEMENT_INDEX) 131 + (((svx1 & SV2_SV_DST_L_DSTLX7_CHECK_MASK) == SV2_SV_DST_L_DSTLX7_CHECK_MASK) 132 ? 0x80 : 0); 133 dst_h = m.getElement(SV2_SV_DST_H_ELEMENT_INDEX) 134 + (((svx1 & SV2_SV_DST_H_DSTHX7_CHECK_MASK) == SV2_SV_DST_H_DSTHX7_CHECK_MASK) 135 ? 0x80 : 0); 136 dst = dst_l + (256 * dst_h); 137 138 sv_adrl = m.getElement(SV2_SV_ADRL_ELEMENT_INDEX) 139 + (((svx1 & SV2_SV_ADRL_SVADRL7_CHECK_MASK) == SV2_SV_ADRL_SVADRL7_CHECK_MASK) 140 ? 0x80 : 0); 141 sv_adrh = m.getElement(SV2_SV_ADRH_ELEMENT_INDEX) 142 + (((svx1 & SV2_SV_ADRH_SVADRH7_CHECK_MASK) == SV2_SV_ADRH_SVADRH7_CHECK_MASK) 143 ? 0x80 : 0); 144 sv_adr = sv_adrl + (256 * sv_adrh); 145 146 d1 = m.getElement(SV2_SVD1_ELEMENT_INDEX) 147 + (((svx2 & SV2_SV_D1_D1X7_CHECK_MASK) == SV2_SV_D1_D1X7_CHECK_MASK) 148 ? 0x80 : 0); 149 150 d2 = m.getElement(SV2_SVD2_ELEMENT_INDEX) 151 + (((svx2 & SV2_SV_D2_D2X7_CHECK_MASK) == SV2_SV_D2_D2X7_CHECK_MASK) 152 ? 0x80 : 0); 153 154 d3 = m.getElement(SV2_SVD3_ELEMENT_INDEX) 155 + (((svx2 & SV2_SV_D3_D3X7_CHECK_MASK) == SV2_SV_D3_D3X7_CHECK_MASK) 156 ? 0x80 : 0); 157 158 d4 = m.getElement(SV2_SVD4_ELEMENT_INDEX) 159 + (((svx2 & SV2_SV_D4_D4X7_CHECK_MASK) == SV2_SV_D4_D4X7_CHECK_MASK) 160 ? 0x80 : 0); 161 } 162 163 /** 164 * Check a LocoNet message to determine if it is a valid SV Programming Format 2 165 * message. 166 * 167 * @param m LocoNet message to check 168 * @return true if LocoNet message m is a supported SV Programming Format 2 169 * message, else false. 170 */ 171 public static boolean isSupportedSv2Message(LocoNetMessage m) { 172 // must be OPC_PEER_XFER opcode 173 if (m.getOpCode() != LnConstants.OPC_PEER_XFER) { 174 log.debug ("cannot be SV2 message because not OPC_PEER_XFER"); // NOI18N 175 return false; 176 } 177 178 // length of OPC_PEER_XFER must be 0x10 179 if (m.getElement(1) != 0x10) { 180 log.debug ("cannot be SV2 message because not length 0x10"); // NOI18N 181 return false; 182 } 183 184 // <SV_TYPE> must be correct 185 if ((m.getElement(SV2_SV_TYPE_ELEMENT_INDEX) 186 & SV2_SV_TYPE_ELEMENT_VALIDITY_CHECK_MASK) 187 != SV2_SV_TYPE_ELEMENT_VALIDITY_CHECK_VALUE) { 188 log.debug ("cannot be SV2 message because type byte not correct"); // NOI18N 189 return false; 190 } 191 192 // "extended command" identifier must be correct. Check part of the 193 // "extended command" identifier 194 if ((m.getElement(SV2_SVX1_ELEMENT_INDEX) 195 & SV2_SVX1_ELEMENT_VALIDITY_CHECK_MASK) 196 != SV2_SVX1_ELEMENT_VALIDITY_CHECK_VALUE) { 197 log.debug ("cannot be SV2 message because SVX1 upper nibble wrong"); // NOI18N 198 return false; 199 } 200 // "extended command" identifier must be correct. Check the rest 201 // of the "extended command" identifier 202 if ((m.getElement(SV2_SVX2_ELEMENT_INDEX) 203 & SV2_SVX2_ELEMENT_VALIDITY_CHECK_MASK) 204 != SV2_SVX2_ELEMENT_VALIDITY_CHECK_VALUE) { 205 log.debug ("cannot be SV2 message because SVX2 upper nibble wrong"); // NOI18N 206 return false; 207 } 208 209 // check the <SV_CMD> value 210 if (isSupportedSv2Command(m.getElement(SV2_SV_CMD_ELEMENT_INDEX))) { 211 log.debug("LocoNet message is a supported SV Format 2 message"); 212 return true; 213 } 214 log.debug("LocoNet message is not a supported SV Format 2 message"); // NOI18N 215 return false; 216 } 217 218 /** 219 * Compare reply message against a specific SV Programming Format 2 message type. 220 * 221 * @param m LocoNet message to be verified as an SV Programming Format 2 message 222 * with the specified <SV_CMD> value 223 * @param svCmd SV Programming Format 2 command to expect 224 * @return true if message is an SV Programming Format 2 message of the specified <SV_CMD>, 225 * else false. 226 */ 227 public static boolean isLnMessageASpecificSv2Command(LocoNetMessage m, Sv2Command svCmd) { 228 // must be OPC_PEER_XFER opcode 229 if (m.getOpCode() != LnConstants.OPC_PEER_XFER) { 230 log.debug ("cannot be SV2 message because not OPC_PEER_XFER"); // NOI18N 231 return false; 232 } 233 234 // length of OPC_PEER_XFER must be 0x10 235 if (m.getElement(1) != 0x10) { 236 log.debug ("cannot be SV2 message because not length 0x10"); // NOI18N 237 return false; 238 } 239 240 // <SV_TYPE> must be correct 241 if ((m.getElement(SV2_SV_TYPE_ELEMENT_INDEX) 242 & SV2_SV_TYPE_ELEMENT_VALIDITY_CHECK_MASK) 243 != SV2_SV_TYPE_ELEMENT_VALIDITY_CHECK_VALUE) { 244 log.debug ("cannot be SV2 message because type byte not correct"); // NOI18N 245 return false; 246 } 247 248 // "extended command" identifier must be correct. Check part of the 249 // "extended command" identifier 250 if ((m.getElement(SV2_SVX1_ELEMENT_INDEX) 251 & SV2_SVX1_ELEMENT_VALIDITY_CHECK_MASK) 252 != SV2_SVX1_ELEMENT_VALIDITY_CHECK_VALUE) { 253 log.debug ("cannot be SV2 message because SVX1 upper nibble wrong"); // NOI18N 254 return false; 255 } 256 // "extended command" identifier must be correct. Check the rest 257 // of the "extended command" identifier 258 if ((m.getElement(SV2_SVX2_ELEMENT_INDEX) 259 & SV2_SVX2_ELEMENT_VALIDITY_CHECK_MASK) 260 != SV2_SVX2_ELEMENT_VALIDITY_CHECK_VALUE) { 261 log.debug ("cannot be SV2 message because SVX2 upper nibble wrong"); // NOI18N 262 return false; 263 } 264 265 // check the <SV_CMD> value 266 if (isSupportedSv2Command(m.getElement(SV2_SV_CMD_ELEMENT_INDEX))) { 267 log.debug("LocoNet message is a supported SV Format 2 message"); // NOI18N 268 if (Objects.equals(extractMessageType(m), svCmd)) { 269 log.debug("LocoNet message is the specified SV Format 2 message"); // NOI18N 270 return true; 271 } 272 } 273 log.debug("LocoNet message is not a supported SV Format 2 message"); // NOI18N 274 return false; 275 } 276 277 /** 278 * Interpret a LocoNet message to determine its SV Programming Format 2 <SV_CMD>. 279 * If the message is not an SV Programming Format 2 message, returns null. 280 * 281 * @param m LocoNet message containing SV Programming Format 2 message 282 * @return Sv2Command found in the SV Programming Format 2 message or null if not found 283 */ 284 public static Sv2Command extractMessageType(LocoNetMessage m) { 285 if (isSupportedSv2Message(m)) { 286 int msgCmd = m.getElement(SV2_SV_CMD_ELEMENT_INDEX); 287 for (Sv2Command s: Sv2Command.values()) { 288 if (s.getCmd() == msgCmd) { 289 log.debug("LocoNet message has SV2 message command {}", msgCmd); // NOI18N 290 return s; 291 } 292 } 293 } 294 return null; 295 } 296 297 /** 298 * Interpret the SV Programming Format 2 message into a human-readable string. 299 * 300 * @return String containing a human-readable version of the SV Programming 301 * Format 2 message 302 */ 303 @Override 304 public String toString() { 305 Locale l = Locale.getDefault(); 306 return Lnsv2MessageContents.this.toString(l); 307 } 308 309 /** 310 * Interpret the SV Programming Format 2 message into a human-readable string. 311 * 312 * @param locale locale to use for the human-readable string 313 * @return String containing a human-readable version of the SV Programming 314 * Format 2 message, in the language specified by the Locale if the 315 * properties have been translated to that Locale, else in the default 316 * English language. 317 */ 318 public String toString(Locale locale) { 319 String returnString; 320 log.debug("interpreting an SV2 message - cmd is {}", sv_cmd); // NOI18N 321 322 switch (sv_cmd) { 323 case (SV_CMD_WRITE_ONE): 324 returnString = Bundle.getMessage(locale, "SV2_WRITE_ONE_INTERPRETED", 325 src, 326 dst, 327 sv_adr, 328 d1); 329 break; 330 331 case (SV_CMD_WRITE_ONE_REPLY): 332 returnString = Bundle.getMessage(locale, "SV2_WRITE_ONE_REPLY_INTERPRETED", 333 src, 334 dst, 335 sv_adr, 336 d1); 337 break; 338 339 case (SV_CMD_QUERY_ONE): 340 returnString = Bundle.getMessage(locale, "SV2_READ_ONE_REQUEST_INTERPRETED", 341 src, 342 dst, 343 sv_adr); 344 break; 345 346 case (SV_CMD_REPORT_ONE): 347 returnString = Bundle.getMessage(locale, "SV2_READ_ONE_REPORT_INTERPRETED", 348 src, 349 dst, 350 sv_adr, 351 d1); 352 break; 353 354 case (SV_CMD_WRITE_ONE_MASKED): 355 returnString = Bundle.getMessage(locale, "SV2_WRITE_ONE_MASKED_INTERPRETED", 356 src, 357 dst, 358 sv_adr, 359 d1, 360 d2); 361 break; 362 363 case (SV_CMD_WRITE_ONE_MASKED_REPLY): 364 returnString = Bundle.getMessage(locale, "SV2_WRITE_ONE_MASKED_REPLY_INTERPRETED", 365 src, 366 dst, 367 sv_adr, 368 d1, 369 d2); 370 break; 371 372 case (SV_CMD_WRITE_FOUR): 373 /* Note: This code does not track total available SVs. Total 374 available SVs can vary by SV device type. So the simple 375 expedient used here is "last SV number is equal to first 376 SV number plus 3". 377 */ 378 returnString = Bundle.getMessage(locale, "SV2_WRITE_FOUR_INTERPRETED", 379 src, 380 dst, 381 sv_adr, 382 sv_adr+3, 383 d1, 384 d2, 385 d3, 386 d4); 387 break; 388 389 case (SV_CMD_WRITE_FOUR_REPLY): 390 /* Note: This code does not track total available SVs. Total 391 available SVs can vary by SV device type. So the simple 392 expedient used here is "last SV number is equal to first 393 SV number plus 3". 394 */ 395 returnString = Bundle.getMessage(locale, "SV2_WRITE_FOUR_REPLY_INTERPRETED", 396 src, 397 dst, 398 sv_adr, 399 sv_adr+3, 400 d1, 401 d2, 402 d3, 403 d4); 404 break; 405 406 case (SV_CMD_QUERY_FOUR): 407 /* Note: This code does not track total available SVs. Total 408 available SVs can vary by SV device type. So the simple 409 expedient used here is "last SV number is equal to first 410 SV number plus 3". 411 */ 412 returnString = Bundle.getMessage(locale, "SV2_READ_FOUR_REQUEST_INTERPRETED", 413 src, 414 dst, 415 sv_adr, 416 sv_adr+3); 417 break; 418 419 case (SV_CMD_REPORT_FOUR): 420 /* Note: This code does not track total available SVs. Total 421 available SVs can vary by SV device type. So the simple 422 expedient used here is "last SV number is equal to first 423 SV number plus 3". 424 */ 425 returnString = Bundle.getMessage(locale, "SV2_READ_FOUR_REPORT_INTERPRETED", 426 src, 427 dst, 428 sv_adr, 429 sv_adr+3, 430 d1, 431 d2, 432 d3, 433 d4); 434 break; 435 436 case (SV_CMD_DISCOVER_DEVICES_QUERY): 437 returnString = Bundle.getMessage(locale, "SV2_DISCOVER_DEVICES_INTERPRETED", 438 src); 439 break; 440 441 case (SV_CMD_DISCOVER_DEVICE_REPORT): 442 returnString = Bundle.getMessage(locale, "SV2_DEVICE_TYPE_REPORT_INTERPRETED", 443 src, 444 dst, 445 sv_adrl, 446 sv_adrh, 447 d1 + (256 * d2), 448 d3 + (256 * d4)); 449 break; 450 451 case (SV_CMD_IDENTIFY_DEVICE_BY_DEVICE_ADDRESS): 452 returnString = Bundle.getMessage(locale, "SV2_IDENTIFY_DEVICE_REQUEST_INTERPRETED", 453 src, 454 dst); 455 break; 456 457 case (SV_CMD_IDENTIFY_DEVICE_BY_DEVICE_ADDRESS_REPLY): 458 returnString = Bundle.getMessage(locale, "SV2_DEVICE_IDENTITY_REPORT_INTERPRETED", 459 src, 460 dst, // SV device address 461 sv_adrl, // manufacturer id 462 sv_adrh, // device id 463 d1 + (256 * d2), // product id 464 d3 + (256 * d4)); // serial number 465 break; 466 467 case (SV_CMD_CHANGE_ADDRESS_REQUEST): 468 returnString = Bundle.getMessage(locale, "SV2_CHANGE_ADDRESS_REQUEST_INTERPRETED", 469 src, 470 dst, // <new> SV device address 471 sv_adrl, // manufacturer id 472 sv_adrh, // device id 473 d1 + (256 * d2), // product id 474 d3 + (256 * d4)); // serial number 475 break; 476 477 case (SV_CMD_CHANGE_ADDRESS_REPLY): 478 /* 479 Using only a single SV2 Programming Format message, it is impossible 480 to correctly distinguish between a change address reply which indicates a 481 need for a "Reconfigure" message from a change address reply which indicates 482 that a reconfigure is not required. This code does a "best guess" by looking 483 at the other data fields. */ 484 if ((sv_adrl == 0) && 485 (sv_adrh == 0) && 486 (d1 == 0) && 487 (d2 == 0) && 488 (d3 == 0) && 489 (d4 == 0)) { 490 // this is probably a change address reply where a reconfigure is required 491 returnString = Bundle.getMessage(locale, 492 "SV2_CHANGE_ADDRESS_REPLY_NEEDS_RECONFIGURE_INTERPRETED", 493 src, 494 dst // old SV device address 495 ); 496 } else { 497 returnString = Bundle.getMessage(locale, 498 "SV2_CHANGE_ADDRESS_REPLY_INTERPRETED", 499 src, 500 dst, // new SV device address 501 sv_adrl, // manufacturer id 502 sv_adrh, // device id 503 d1 + (256 * d2), // product id 504 d3 + (256 * d4)); // serial number 505 } 506 break; 507 508 case (SV_CMD_RECONFIGURE_REQUEST): 509 returnString = Bundle.getMessage(locale, "SV2_RECONFIGURE_REQUEST_INTERPRETED", 510 src, 511 dst); 512 break; 513 514 case (SV_CMD_RECONFIGURE_REPLY): 515 returnString = Bundle.getMessage(locale, "SV2_DEVICE_RECONFIGURE_REPLY_INTERPRETED", 516 src, 517 dst, // SV device address 518 sv_adrl, // manufacturer id 519 sv_adrh, // device id 520 d1 + (256 * d2), // product id 521 d3 + (256 * d4)); // serial number 522 break; 523 524 default: 525 return Bundle.getMessage(locale, "SV2_UNDEFINED_MESSAGE") + "\n"; 526 } 527 528 log.debug("interpreted: {}", returnString); // NOI18N 529 return returnString + "\n"; // NOI18N 530 } 531 532 /** 533 * 534 * @param possibleCmd integer to be compared to the command list 535 * @return true if the possibleCmd value is one of the supported SV 536 * Programming Format 2 commands 537 */ 538 public static boolean isSupportedSv2Command(int possibleCmd) { 539 switch (possibleCmd) { 540 case (SV_CMD_WRITE_ONE): 541 case (SV_CMD_WRITE_ONE_REPLY): 542 case (SV_CMD_QUERY_ONE): 543 case (SV_CMD_REPORT_ONE): 544 case (SV_CMD_WRITE_ONE_MASKED): 545 case (SV_CMD_WRITE_ONE_MASKED_REPLY): 546 case (SV_CMD_WRITE_FOUR): 547 case (SV_CMD_WRITE_FOUR_REPLY): 548 case (SV_CMD_QUERY_FOUR): 549 case (SV_CMD_REPORT_FOUR): 550 case (SV_CMD_DISCOVER_DEVICES_QUERY): 551 case (SV_CMD_DISCOVER_DEVICE_REPORT): 552 case (SV_CMD_IDENTIFY_DEVICE_BY_DEVICE_ADDRESS): 553 case (SV_CMD_IDENTIFY_DEVICE_BY_DEVICE_ADDRESS_REPLY): 554 case (SV_CMD_CHANGE_ADDRESS_REQUEST): 555 case (SV_CMD_CHANGE_ADDRESS_REPLY): 556 case (SV_CMD_RECONFIGURE_REQUEST): 557 case (SV_CMD_RECONFIGURE_REPLY): 558 return true; 559 default: 560 return false; 561 } 562 } 563 564 /** 565 * Confirm a message specifies a valid (known) SV Programming Format 2 command. 566 * 567 * @return true if the SV2 message specifies a valid (known) SV Programming 568 * Format 2 command. 569 */ 570 public boolean isSupportedSv2Command() { 571 return isSupportedSv2Command(sv_cmd); 572 } 573 574 /** 575 * 576 * @return true if the SV2 message is a SV2 Read One Reply message 577 */ 578 public boolean isSupportedSv2ReadOneReply() { 579 return (sv_cmd == SV_CMD_REPORT_ONE); 580 } 581 582 /** 583 * 584 * @return true if the SV2 message is a SV2 Read Four Reply message 585 */ 586 public boolean isSupportedSv2ReadFourReply() { 587 return (sv_cmd == SV_CMD_REPORT_FOUR); 588 } 589 590 /** 591 * 592 * @return true if the SV2 message is a SV2 Read One Reply message or a SV2 593 * Read Four Reply message 594 */ 595 public boolean isSupportedSv2ReadOneReplyOrSv2ReadFourReply() { 596 return ((sv_cmd == SV_CMD_REPORT_ONE) 597 || 598 (sv_cmd == SV_CMD_REPORT_FOUR)); 599 } 600 601 /** 602 * Get the data from a SVs Single Read Reply message. May also be used to 603 * return the effective SV value reported in a SV2 Single Write Reply message. 604 * 605 * @return the {@code <D1>} value from the SV2 message 606 */ 607 public int getSingleReadReportData() { 608 return d1; 609 } 610 611 /** 612 * Create a LocoNet message containing an SV Programming Format 2 message. 613 * See Programmer message code in {@link jmri.jmrix.loconet.LnOpsModeProgrammer} loadSV2MessageFormat 614 * 615 * @param source source device address (7 bit, for <SRC>) 616 * @param command SV Programming Format 2 command number (for <SV_CMD>) 617 * @param destination = SV format 2 destination address (for <DST_L> and <DST_H>) 618 * @param svNum SV Programming Format 2 16-bit SV number (for <SVN_L> and <SVN_H>) 619 * @param d1 SV Programming Format 2 first data value (for <D1>) 620 * @param d2 SV Programming Format 2 second data value (for <D2>) 621 * @param d3 SV Programming Format 2 third data value (for <D3>) 622 * @param d4 SV Programming Format 2 fourth data value (for <D4>) 623 * @return LocoNet message for the requested message 624 * @throws IllegalArgumentException if command is not a valid SV Programming Format 2 <SV_CMD> value 625 */ 626 public static LocoNetMessage createSv2Message (int source, int command, 627 int destination, int svNum, int d1, int d2, int d3, int d4) 628 throws java.lang.IllegalArgumentException { 629 630 if ( ! isSupportedSv2Command(command)) { 631 throw new java.lang.IllegalArgumentException("Command is not a supported SV2 command"); // NOI18N 632 } 633 LocoNetMessage m = new LocoNetMessage(SV2_LENGTH_ELEMENT_VALUE); 634 m.setOpCode(LnConstants.OPC_PEER_XFER); 635 m.setElement(SV2_LENGTH_ELEMENT_INDEX, SV2_LENGTH_ELEMENT_VALUE); 636 m.setElement(SV2_SRC_ELEMENT_INDEX, (source & SV2_SRC_ELEMENT_MASK)); 637 m.setElement(SV2_SV_CMD_ELEMENT_INDEX, command); 638 m.setElement(SV2_SV_TYPE_ELEMENT_INDEX, SV2_SV_TYPE_ELEMENT_VALIDITY_CHECK_VALUE); 639 640 int svx1 = SV2_SVX1_ELEMENT_VALIDITY_CHECK_VALUE; 641 svx1 = svx1 + (((destination & 0x80) == 0x80) ? SV2_SV_DST_L_DSTLX7_CHECK_MASK : 0); 642 svx1 = svx1 + (((destination & 0x8000) == 0x8000) ? SV2_SV_DST_H_DSTHX7_CHECK_MASK : 0); 643 svx1 = svx1 + (((svNum & 0x80) == 0x80) ? SV2_SV_ADRL_SVADRL7_CHECK_MASK : 0); 644 svx1 = svx1 + (((svNum & 0x8000) == 0x8000) ? SV2_SV_ADRH_SVADRH7_CHECK_MASK : 0); 645 m.setElement(SV2_SVX1_ELEMENT_INDEX, svx1); 646 647 m.setElement(SV2_SV_DST_L_ELEMENT_INDEX, (destination & 0x7f)); 648 m.setElement(SV2_SV_DST_H_ELEMENT_INDEX, ((destination >> 8) & 0x7f)); 649 m.setElement(SV2_SV_ADRL_ELEMENT_INDEX, (svNum & 0x7f)); 650 m.setElement(SV2_SV_ADRH_ELEMENT_INDEX, ((svNum >> 8) & 0x7f)); 651 652 int svx2 = SV2_SVX2_ELEMENT_VALIDITY_CHECK_VALUE; 653 svx2 = svx2 + (((d1 & 0x80) == 0x80) ? SV2_SV_D1_D1X7_CHECK_MASK : 0); 654 svx2 = svx2 + (((d2 & 0x80) == 0x80) ? SV2_SV_D2_D2X7_CHECK_MASK : 0); 655 svx2 = svx2 + (((d3 & 0x80) == 0x80) ? SV2_SV_D3_D3X7_CHECK_MASK : 0); 656 svx2 = svx2 + (((d4 & 0x80) == 0x80) ? SV2_SV_D4_D4X7_CHECK_MASK : 0); 657 m.setElement(SV2_SVX2_ELEMENT_INDEX, svx2); 658 659 m.setElement(SV2_SVD1_ELEMENT_INDEX, (d1 & 0x7f)); 660 m.setElement(SV2_SVD2_ELEMENT_INDEX, (d2 & 0x7f)); 661 m.setElement(SV2_SVD3_ELEMENT_INDEX, (d3 & 0x7f)); 662 m.setElement(SV2_SVD4_ELEMENT_INDEX, (d4 & 0x7f)); 663 664 return m; 665 } 666 667 public int getDestAddr() { 668 if (sv_cmd != Sv2Command.SV2_DISCOVER_ALL.cmd) { 669 return dst_l + 256*dst_h; 670 } 671 return -1; 672 } 673 674 public int getSvNum() { 675 if ((sv_cmd != Sv2Command.SV2_DISCOVER_ALL.cmd) && 676 (sv_cmd != Sv2Command.SV2_IDENTIFY_DEVICES_BY_TYPE.cmd) && 677 (sv_cmd != Sv2Command.SV2_CHANGE_DEVICE_ADDRESS.cmd) && 678 (sv_cmd != Sv2Command.SV2_DISCOVER_DEVICE_REPORT.cmd) && 679 (sv_cmd != Sv2Command.SV2_DEVICE_TYPE_REPORT.cmd) && 680 (sv_cmd != Sv2Command.SV2_CHANGE_DEVICE_ADDRESS_REPLY.cmd) && 681 (sv_cmd != Sv2Command.SV2_RECONFIGURE_DEVICE_REPLY.cmd) && 682 (sv_cmd != Sv2Command.SV2_RECONFIGURE_DEVICE.cmd)) { 683 return sv_adrl + 256*sv_adrh; 684 } 685 return -1; 686 } 687 688 public int getSv2ManufacturerID() { 689 if ((sv_cmd == Sv2Command.SV2_DISCOVER_DEVICE_REPORT.cmd) || 690 (sv_cmd == Sv2Command.SV2_DEVICE_TYPE_REPORT.cmd)) { 691 return sv_adrl; 692 } 693 return -1; 694 } 695 696 public boolean isSvReconfigureReply() { 697 return (sv_cmd == Sv2Command.SV2_RECONFIGURE_DEVICE_REPLY.cmd); 698 } 699 700 public int getSv2DeveloperID() { 701 if ((sv_cmd == Sv2Command.SV2_CHANGE_DEVICE_ADDRESS.cmd) || 702 (sv_cmd == Sv2Command.SV2_DISCOVER_DEVICE_REPORT.cmd) || 703 (sv_cmd == Sv2Command.SV2_DEVICE_TYPE_REPORT.cmd)){ 704 return sv_adrh; 705 } 706 return -1; 707 } 708 709 public int getSv2ProductID() { 710 if ((sv_cmd == Sv2Command.SV2_CHANGE_DEVICE_ADDRESS.cmd) || 711 (sv_cmd == Sv2Command.SV2_DISCOVER_DEVICE_REPORT.cmd) || 712 (sv_cmd == Sv2Command.SV2_DEVICE_TYPE_REPORT.cmd) || 713 (sv_cmd == Sv2Command.SV2_RECONFIGURE_DEVICE_REPLY.cmd)){ 714 return d1 + d2 * 256; 715 } 716 return -1; 717 } 718 719 public int getSv2SerialNum() { 720 if ((sv_cmd == Sv2Command.SV2_CHANGE_DEVICE_ADDRESS.cmd) || 721 (sv_cmd == Sv2Command.SV2_DISCOVER_DEVICE_REPORT.cmd) || 722 (sv_cmd == Sv2Command.SV2_DEVICE_TYPE_REPORT.cmd) || 723 (sv_cmd == Sv2Command.SV2_RECONFIGURE_DEVICE_REPLY.cmd)){ 724 return d3 + d4 * 256; 725 } 726 return -1; 727 } 728 729 /** 730 * 731 * @param ida IDA number for "SRC" field of OPC_PEER_XFER 732 * @param currentDest the destination address of the device 733 * @param mfg the Manufacturer ID 734 * @param devel the developer ID 735 * @param prodID the product ID 736 * @param serial the serial number 737 * @return a LocoNet message containing the formatted reply 738 */ 739 public static LocoNetMessage createSv2DeviceDiscoveryReply(int ida, int currentDest, 740 int mfg, int devel, int prodID, int serial) { 741 742 return createSv2Message(ida, 743 Sv2Command.SV2_DISCOVER_DEVICE_REPORT.cmd, 744 currentDest, 745 mfg + (256*devel), 746 prodID % 256, 747 prodID / 256, 748 serial % 256, 749 serial/256) ; 750 } 751 752 /** 753 * Create a LocoNet message for the reply for an SV2 "Change Address" 754 * message where the device requires a reconfigure. 755 * 756 * @param ida IDA value, for the SRC field of the OPC_PEER_XFER 757 * @param destAddr the "old" SV2 destination address 758 * @return a LocoNet message 759 */ 760 public static LocoNetMessage createSv2ChangeAddressReply(int ida, int destAddr) { 761 return createSv2Message(ida, 762 Sv2Command.SV2_CHANGE_DEVICE_ADDRESS_REPLY.cmd, 763 destAddr, 764 0, 0, 0, 0, 0) ; 765 } 766 767 /** 768 * Create a LocoNet message for the reply for an SV2 "Change Address" 769 * message where the device requires a reconfigure. 770 * 771 * @param ida IDA value, for the SRC field of the OPC_PEER_XFER 772 * @param newDestAddr the "new" SV2 destination address 773 * @param mfg manufacturer ID 774 * @param developer device ID 775 * @param productId product ID 776 * @param serialNum serial Number 777 * @return a LocoNet message 778 */ 779 public static LocoNetMessage createSv2ChangeAddressReply(int ida, int newDestAddr, 780 int mfg, int developer, int productId, int serialNum) { 781 return createSv2Message(ida, 782 Sv2Command.SV2_CHANGE_DEVICE_ADDRESS_REPLY.cmd, 783 newDestAddr, 784 mfg + (256 * developer), productId % 256, productId / 256, 785 serialNum % 256, serialNum / 256); 786 } 787 788 /** 789 * Create a LocoNet message for the reply for an SV2 "Reconfigure Reply" 790 * 791 * @param ida IDA value, for the SRC field of the OPC_PEER_XFER 792 * @param newDestAddr the "new" SV2 destination address 793 * @param mfg manufacturer ID 794 * @param developer device ID 795 * @param productId product ID 796 * @param serialNum serial Number 797 * @return a LocoNet message 798 */ 799 public static LocoNetMessage createSv2ReconfigureReply(int ida, int newDestAddr, 800 int mfg, int developer, int productId, int serialNum) { 801 return createSv2Message(ida, 802 Sv2Command.SV2_RECONFIGURE_DEVICE_REPLY.cmd, 803 newDestAddr, 804 mfg + (256 * developer), productId % 256, productId / 256, 805 serialNum % 256, serialNum / 256); 806 } 807 /** 808 * 809 * @param m the preceding LocoNet message 810 * @param svValues array containing the SV values; only one value is used 811 * when m contains an SV_QUERY_ONE, else contains 4 values. 812 * @return LocoNet message containing the reply, or null if preceding 813 * message isn't a query 814 */ 815 public static LocoNetMessage createSvReadReply(LocoNetMessage m, int[] svValues) { 816 if (!isSupportedSv2Message(m)) { 817 return null; 818 } 819 if ((m.getElement(3) != Sv2Command.SV2_QUERY_ONE.cmd) && 820 (m.getElement(3) != Sv2Command.SV2_QUERY_FOUR.cmd)) { 821 return null; 822 } 823 LocoNetMessage n = m; 824 n.setElement(3, n.getElement(3) + 0x40); 825 n.setElement(11, svValues[0] & 0x7F); 826 if (n.getElement(3) == Sv2Command.SV2_QUERY_ONE.cmd) { 827 n.setElement(12, 0); 828 n.setElement(13, 0); 829 n.setElement(14, 0); 830 int a = n.getElement(10); 831 a &= 0x70; 832 if ((svValues[0] & 0xFF) > 0x7f) { 833 a |= 1; 834 } 835 n.setElement(10, a); 836 return n; 837 } 838 n.setElement(12, svValues[1] & 0x7F); 839 n.setElement(13, svValues[2] & 0x7F); 840 n.setElement(14, svValues[3] & 0x7F); 841 int a = n.getElement(10); 842 a &= 0x70; 843 a |= ((svValues[1] & 0x80) >> 6); 844 a |= ((svValues[2] & 0x80) >> 5); 845 a |= ((svValues[3] & 0x80) >> 5); 846 n.setElement(10, a); 847 return n; 848 } 849 850 /** 851 * 852 * @param m the preceding LocoNet message 853 * @param svValue value of one SV register 854 * @return LocoNet message containing the reply, or null if preceding 855 * message isn't a query 856 */ 857 public static LocoNetMessage createSvReadReply(LocoNetMessage m, int svValue) { 858 return createSvReadReply(m, new int[] {svValue}); 859 } 860 861 /** 862 * Get the d1 value 863 * @return d1 element contents 864 */ 865 public int getSv2D1() { 866 return d1; 867 } 868 869 /** 870 * Get the d2 value 871 * @return d2 element contents 872 */ 873 public int getSv2D2() { 874 return d2; 875 } 876 877 /** 878 * Get the d3 value 879 * @return d3 element contents 880 */ 881 public int getSv2D3() { 882 return d3; 883 } 884 885 /** 886 * Get the d4 value 887 * @return d4 element contents 888 */ 889 public int getSv2D4() { 890 return d4; 891 } 892 893 public boolean isSvChangeAddressReply() { 894 return (sv_cmd == Sv2Command.SV2_CHANGE_DEVICE_ADDRESS_REPLY.cmd); 895 } 896 897 public static LocoNetMessage createSvDiscoverQueryMessage() { 898 return createSv2Message(1, 899 Sv2Command.SV2_DISCOVER_ALL.cmd, 900 0, 0, 0, 0, 0, 0); 901 } 902 903 public static LocoNetMessage createSvReadRequest() { 904 return createSv2Message(1, 905 Sv2Command.SV2_DISCOVER_ALL.cmd, 906 0, 0, 0, 0, 0, 0); 907 } 908 909 /** 910 * Create LocoNet message for another query of an SV of this object. 911 * 912 * @param deviceAddress address of the device 913 * @param svNum SV number 914 * @return LocoNet message 915 */ 916 public static LocoNetMessage createSvReadRequest(int deviceAddress, int svNum) { 917 return createSv2Message(1, Sv2Command.SV2_QUERY_ONE.cmd, 918 deviceAddress, svNum, 0, 0, 0, 0); 919 } 920 921 public enum Sv2Command { 922 SV2_WRITE_ONE (0x01), 923 SV2_QUERY_ONE (0x02), 924 SV2_WRITE_ONE_MASKED (0x03), 925 SV2_WRITE_FOUR (0x05), 926 SV2_QUERY_FOUR (0x06), 927 SV2_DISCOVER_ALL (0x07), 928 SV2_IDENTIFY_DEVICES_BY_TYPE (0x08), 929 SV2_CHANGE_DEVICE_ADDRESS (0x09), 930 SV2_RECONFIGURE_DEVICE (0x0f), 931 SV2_WRITE_ONE_REPLY (0x41), 932 SV2_REPORT_ONE (0x42), 933 SV2_WRITE_ONE_MASKED_REPLYL (0x43), 934 SV2_WRITE_FOUR_REPLY (0x45), 935 SV2_REPORT_FOUR (0x46), 936 SV2_DISCOVER_DEVICE_REPORT (0x47), 937 SV2_DEVICE_TYPE_REPORT (0x48), 938 SV2_CHANGE_DEVICE_ADDRESS_REPLY (0x49), 939 SV2_RECONFIGURE_DEVICE_REPLY (0x4f); 940 941 private final int cmd; 942 943 Sv2Command(int cmd) { 944 this.cmd = cmd; 945 } 946 947 int getCmd() {return cmd;} 948 949 public static int getCmd(Sv2Command mt) { 950 return mt.getCmd(); 951 } 952 } 953 954 // initialize logging 955 private final static Logger log = LoggerFactory.getLogger(Lnsv2MessageContents.class); 956 957}