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