001package jmri.jmrix.loconet.duplexgroup.swing; 002 003import jmri.jmrix.loconet.LnConstants; 004import jmri.jmrix.loconet.LocoNetMessage; 005import jmri.jmrix.loconet.LocoNetSystemConnectionMemo; 006import jmri.jmrix.loconet.duplexgroup.DuplexGroupMessageType; 007import jmri.jmrix.loconet.duplexgroup.LnDplxGrpInfoImplConstants; 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010import jmri.util.StringUtil; 011 012/** 013 * Provides a low-level interface to Digitrax Duplex Group Identity information. 014 * <p> 015 * Implements the following "Property Change" events, which are defined as 016 * static strings in {@link jmri.jmrix.loconet.duplexgroup.LnDplxGrpInfoImplConstants}: 017 * <ul> 018 * <li> 019 * DPLX_PC_STAT_LN_UPDATE - 020 * Indicates that a GUI status line could be updated using provided string. 021 * <li> 022 * DPLX_PC_STAT_LN_UPDATE_IF_NOT_CURRENTLY_ERROR - Indicates that a GUI status 023 * line could be updated using the provided string UNLESS the status line is 024 * currently showing an error. 025 * <li> 026 * NumberOfUr92sUpdate - Indicates that the class has counted the number of UR92 027 * devices 028 * <li> 029 * DPLX_PC_NAME_UPDATE - Indicates that a LocoNet message has reported the 030 * Duplex Group Name 031 * <li> 032 * DPLX_PC_CHANNEL_UPDATE - Indicates that a LocoNet message has reported the 033 * Duplex Group Channel 034 * <li> 035 * DPLX_PC_PASSWORD_UPDATE - Indicates that a LocoNet message has reported the 036 * Duplex Group Password 037 * <li> 038 * DPLX_PC_ID_UPDATE - Indicates that a LocoNet message has reported the Duplex 039 * Group Id 040 * <li> 041 * DPLX_PC_NAME_VALIDITY - Indicates that the validity of GUI field showing the 042 * Duplex Group Name should be changed. NewValue() is true if a valid Duplex 043 * Group Name is available; is false if the Duplex Group Name should be 044 * considered invalid. 045 * <li> 046 * DPLX_PC_CHANNEL_VALIDITY - Indicates that the validity of GUI field showing 047 * the Duplex Group Channel should be changed. NewValue() is true if a valid 048 * Duplex Group Channel is available; is false if the Duplex Group Channel 049 * should be considered invalid. 050 * <li> 051 * DPLX_PC_PASSWORD_VALIDITY - Indicates that the validity of GUI field showing 052 * the Duplex Group Password should be changed. NewValue() is true if a valid 053 * Duplex Group Password is available; is false if the Duplex Group Password 054 * should be considered invalid. 055 * <li> 056 * DPLX_PC_ID_VALIDITY - Indicates that the validity of GUI field showing the 057 * Duplex Group Id should be changed. NewValue() is true if a valid Duplex Group 058 * Id is available; is false if the Duplex Group Id should be considered 059 * invalid. 060 * <li> 061 * DPLX_PC_RCD_DPLX_IDENTITY_QUERY - Indicates that a LocoNet message which 062 * queries the Duplex Group identity has been received. 063 * <li> 064 * DPLX_PC_RCD_DPLX_IDENTITY_REPORT - Indicates that a LocoNet message which 065 * reports the Duplex Group identity has been received. 066 * </ul> 067 * This tool works equally well with UR92 and UR92CE devices. The UR92 and 068 * UR92CE behave identically with respect to this tool. For the purpose of 069 * clarity, only the term UR92 is used herein. 070 * 071 * @author B. Milhaupt Copyright 2011 072 */ 073public class LnDplxGrpInfoImpl extends javax.swing.JComponent implements jmri.jmrix.loconet.LocoNetListener { 074 075 static boolean limitPasswordToNumericCharacters = false; // not final to allow override by script 076 private LocoNetSystemConnectionMemo memo; 077 private Integer numUr92CompatibleType; 078 private javax.swing.Timer swingTmrIplQuery; 079 private javax.swing.Timer swingTmrDuplexInfoQuery; 080 private boolean waitingForIplReply; 081 private boolean gotQueryReply; 082 private int messagesHandled; 083 084 LnDplxGrpInfoImpl thisone; 085 086 public LnDplxGrpInfoImpl(LocoNetSystemConnectionMemo LNCMemo) { 087 super(); 088 thisone = this; 089 090 memo = LNCMemo; 091 092 messagesHandled = 0; 093 094 // connect to the LnTrafficController 095 connect(memo.getLnTrafficController()); 096 097 numUr92CompatibleType = 0; // assume 0 UR92 devices available 098 waitingForIplReply = false; 099 100 swingTmrIplQuery = new javax.swing.Timer(LnDplxGrpInfoImplConstants.IPL_QUERY_DELAY, new java.awt.event.ActionListener() { 101 @Override 102 public void actionPerformed(java.awt.event.ActionEvent e) { 103 swingTmrIplQuery.stop(); 104 waitingForIplReply = false; 105 int oldvalue = 9999; 106 int newvalue = 0; 107 if (numUr92CompatibleType > 0) { 108 newvalue = numUr92CompatibleType; 109 thisone.firePropertyChange("NumberOfUr92sUpdate", oldvalue, newvalue); // NOI18N 110 invalidateDataAndQueryDuplexInfo(); 111 } else { 112 thisone.firePropertyChange("NumberOfUr92sUpdate", oldvalue, newvalue); // NOI18N 113 thisone.firePropertyChange(DPLX_PC_STAT_LN_UPDATE, " ", "ErrorNoUR92Found"); // NOI18N 114 } 115 } 116 }); 117 swingTmrDuplexInfoQuery = new javax.swing.Timer(LnDplxGrpInfoImplConstants.DPLX_QUERY_DELAY, new java.awt.event.ActionListener() { 118 @Override 119 public void actionPerformed(java.awt.event.ActionEvent e) { 120 swingTmrDuplexInfoQuery.stop(); 121 waitingForIplReply = false; 122 if (gotQueryReply == true) { 123 // do not want to erase any status message other than the "Processing" message. 124 thisone.firePropertyChange(DPLX_PC_STAT_LN_UPDATE_IF_NOT_CURRENTLY_ERROR, "", " "); // NOI18N 125 gotQueryReply = false; 126 } else { 127 thisone.firePropertyChange(DPLX_PC_STAT_LN_UPDATE, " ", "ErrorNoQueryResponse"); // NOI18N 128 numUr92CompatibleType = 0; 129 int oldvalue = 9999; 130 int newvalue = 0; 131 thisone.firePropertyChange("NumberOfUr92sUpdate", oldvalue, newvalue); // NOI18N 132 } 133 } 134 }); 135 136 acceptedGroupName = ""; 137 acceptedGroupChannel = ""; 138 acceptedGroupPassword = ""; 139 acceptedGroupId = ""; 140 141 } 142 143 /** 144 * Report whether Duplex Group Password must only be numeric, or if 145 * Password is allowed to include characters 'A', 'B', and/or 'C'. 146 * 147 * @return true if Password may only include digits. 148 */ 149 public static final boolean isPasswordLimitedToNumbers() { 150 return limitPasswordToNumericCharacters; 151 } 152 153 /** 154 * Validate a Duplex Group Name. 155 * <p> 156 * A valid Duplex Group Name is an 8 character string. The calling method 157 * should append spaces or truncate to give correct length if necessary. 158 * 159 * @param sGroupName string containing group name to be validated 160 * @return true if and only if groupName is a valid Duplex Group Name 161 */ 162 public static final boolean validateGroupName(String sGroupName) { 163 // Digitrax seems to allow use of any 8-bit character. So only 164 // requirement seems to be that the name must be 8 characters long. 165 return sGroupName.length() == 8; 166 } 167 168 /** 169 * Validate a Duplex Group Password. 170 * <p> 171 * Note that the password must be four digits if only numeric values are 172 * allowed, or must be four characters, each of pattern [0-9A-C] if 173 * alphanumeric values are allowed. (See private field 174 * limitPasswordToNumericCharacters.) 175 * 176 * @param sGroupPassword Duplex Group Password to be validated 177 * @return true if and only if sGroupPassword is a valid Duplex Group 178 * Password. 179 */ 180 // TODO: There is no way currently to set limitPasswordToNumericCharacters to true 181 public static final boolean validateGroupPassword(String sGroupPassword) { 182 // force the value to uppercase 183 if (sGroupPassword.length() == 0) { 184 return false; 185 } 186 // Return whether or not the password matches 187 return (limitPasswordToNumericCharacters && sGroupPassword.matches("^[0-9][0-9][0-9][0-9]$")) // NOI18N 188 || sGroupPassword.matches("^[0-9A-C][0-9A-C][0-9A-C][0-9A-C]$"); // NOI18N 189 } 190 191 /** 192 * Validate a Duplex Group Channel Number. 193 * 194 * @param iGroupChannel Duplex Group Channel number to be validated 195 * @return true if and only if iGroupChannel is a valid Duplex Group 196 * Channel. 197 */ 198 public static final boolean validateGroupChannel(Integer iGroupChannel) { 199 if ((iGroupChannel < LnDplxGrpInfoImplConstants.DPLX_MIN_CH) 200 || (iGroupChannel > LnDplxGrpInfoImplConstants.DPLX_MAX_CH)) { 201 return false; 202 } else { 203 return true; 204 } 205 } 206 207 /** 208 * Validate the parameter as a Duplex Group ID number. 209 * 210 * @param iGroupId Duplex Group ID number to be validated 211 * @return true if and only if iGroupId is a valid Duplex Group ID. 212 */ 213 public static final boolean validateGroupID(Integer iGroupId) { 214 215 if ((iGroupId < LnDplxGrpInfoImplConstants.DPLX_MIN_ID) 216 || (iGroupId > LnDplxGrpInfoImplConstants.DPLX_MAX_ID)) { 217 return false; 218 } else { 219 return true; 220 } 221 } 222 223 /** 224 * Create a LocoNet packet which queries UR92(s) for Duplex group 225 * identification information. The invoking method is responsible for 226 * sending the message to LocoNet. 227 * 228 * @return LocoNetMessage containing IPL query of UR92s 229 */ 230 public static final LocoNetMessage createUr92GroupIdentityQueryPacket() { 231 // format packet 232 LocoNetMessage m = new LocoNetMessage(LnConstants.RE_DPLX_OP_LEN); 233 Integer i; 234 i = 0; 235 m.setElement(i++, LnConstants.OPC_PEER_XFER); 236 m.setElement(i++, LnConstants.RE_DPLX_OP_LEN); // 20-byte message 237 m.setElement(i++, LnConstants.RE_DPLX_GP_NAME_TYPE); // Group Name Operation 238 m.setElement(i++, LnConstants.RE_DPLX_OP_TYPE_QUERY); // Query Operation 239 for (; i < (LnConstants.RE_DPLX_OP_LEN - 1); i++) { 240 m.setElement(i, 0); // always 0 for duplex group name write 241 } 242 return m; 243 } 244 245 /** 246 * Create a LocoNet packet to set the Duplex group name. 247 * <p> 248 * Throws an exception if s provides a 0-length group name string. If s is 249 * too short, it is padded with spaces at the end of the string. 250 * 251 * @param sGroupName is the desired group name value as a string 252 * @return The LocoNet packet which writes the Group Name to the UR92 253 * device(s) 254 * @throws jmri.jmrix.loconet.LocoNetException if sGroupName is not a valid 255 * Duplex Group Name 256 */ 257 public static final LocoNetMessage createSetUr92GroupNamePacket(String sGroupName) throws jmri.jmrix.loconet.LocoNetException { 258 int gr_msb1 = 0; 259 int gr_msb2 = 0; 260 int i; 261 262 if (validateGroupName(sGroupName) == false) { 263 throw new jmri.jmrix.loconet.LocoNetException("Invalid Duplex Group Name - must be exactly 8 characters"); // NOI18N 264 } 265 266 // format packet 267 LocoNetMessage m = new LocoNetMessage(LnConstants.RE_DPLX_OP_LEN); 268 269 // update extended data storage for most-significant bits of each character 270 gr_msb1 += (Character.valueOf(sGroupName.charAt(0)) > LnConstants.RE_DPLX_7BITS_MAX) ? LnConstants.RE_DPLX_MSB1_BIT : 0; 271 gr_msb1 += (Character.valueOf(sGroupName.charAt(1)) > LnConstants.RE_DPLX_7BITS_MAX) ? LnConstants.RE_DPLX_MSB2_BIT : 0; 272 gr_msb1 += (Character.valueOf(sGroupName.charAt(2)) > LnConstants.RE_DPLX_7BITS_MAX) ? LnConstants.RE_DPLX_MSB3_BIT : 0; 273 gr_msb1 += (Character.valueOf(sGroupName.charAt(3)) > LnConstants.RE_DPLX_7BITS_MAX) ? LnConstants.RE_DPLX_MSB4_BIT : 0; 274 gr_msb2 += (Character.valueOf(sGroupName.charAt(4)) > LnConstants.RE_DPLX_7BITS_MAX) ? LnConstants.RE_DPLX_MSB1_BIT : 0; 275 gr_msb2 += (Character.valueOf(sGroupName.charAt(5)) > LnConstants.RE_DPLX_7BITS_MAX) ? LnConstants.RE_DPLX_MSB2_BIT : 0; 276 gr_msb2 += (Character.valueOf(sGroupName.charAt(6)) > LnConstants.RE_DPLX_7BITS_MAX) ? LnConstants.RE_DPLX_MSB3_BIT : 0; 277 gr_msb2 += (Character.valueOf(sGroupName.charAt(7)) > LnConstants.RE_DPLX_7BITS_MAX) ? LnConstants.RE_DPLX_MSB4_BIT : 0; 278 279 i = 0; 280 m.setElement(i++, LnConstants.OPC_PEER_XFER); 281 m.setElement(i++, LnConstants.RE_DPLX_OP_LEN); // 20-byte message 282 m.setElement(i++, LnConstants.RE_DPLX_GP_NAME_TYPE); // Group Name Operation 283 m.setElement(i++, LnConstants.RE_DPLX_OP_TYPE_WRITE); // Write Operation 284 m.setElement(i++, gr_msb1); // MSB1 285 m.setElement(i++, Character.valueOf(sGroupName.charAt(0)) 286 & LnConstants.RE_DPLX_MAX_NOT_OPC); // 7 LSBs of leftmost character 287 m.setElement(i++, Character.valueOf(sGroupName.charAt(1)) 288 & LnConstants.RE_DPLX_MAX_NOT_OPC); // 7 LSBs of next character 289 m.setElement(i++, Character.valueOf(sGroupName.charAt(2)) 290 & LnConstants.RE_DPLX_MAX_NOT_OPC); // 7 LSBs of next character 291 m.setElement(i++, Character.valueOf(sGroupName.charAt(3)) 292 & LnConstants.RE_DPLX_MAX_NOT_OPC); // 7 LSBs of next character 293 m.setElement(i++, gr_msb2); // MSB2 294 m.setElement(i++, Character.valueOf(sGroupName.charAt(4)) 295 & LnConstants.RE_DPLX_MAX_NOT_OPC); // 7 LSBs of next character 296 m.setElement(i++, Character.valueOf(sGroupName.charAt(5)) 297 & LnConstants.RE_DPLX_MAX_NOT_OPC); // 7 LSBs of next character 298 m.setElement(i++, Character.valueOf(sGroupName.charAt(6)) 299 & LnConstants.RE_DPLX_MAX_NOT_OPC); // 7 LSBs of next character 300 m.setElement(i++, Character.valueOf(sGroupName.charAt(7)) 301 & LnConstants.RE_DPLX_MAX_NOT_OPC); // 7 LSBs of rightmost character 302 for (; i < (LnConstants.RE_DPLX_OP_LEN - 1); i++) { 303 m.setElement(i, 0); // always 0 for duplex group name write 304 } 305 // Note: LocoNet send process will compute and add checksum byte in correct location 306 307 return m; 308 } 309 310 /** 311 * Create a LocoNet packet to set the Duplex group channel number. 312 * <p> 313 * If s provides a 0-length group name, a bogus LocoNet message is returned. 314 * If s does not define an integer is too short, it is padded with spaces at 315 * the end of the string. 316 * 317 * @param iChannelNumber The desired group channel number value as an 318 * integer 319 * @return The packet which writes the Group Channel Number to the UR92 320 * device(s) 321 * @throws jmri.jmrix.loconet.LocoNetException if sGroupName is not a valid 322 * Duplex Group Name 323 */ 324 public static final LocoNetMessage createSetUr92GroupChannelPacket(Integer iChannelNumber) throws jmri.jmrix.loconet.LocoNetException { 325 int i; 326 327 if (validateGroupChannel(iChannelNumber) == false) { 328 throw new jmri.jmrix.loconet.LocoNetException("Invalid Duplex Group Channel - must be between 11 and 26, inclusive"); // NOI18N 329 } 330 331 // format packet 332 LocoNetMessage m = new LocoNetMessage(LnConstants.RE_DPLX_OP_LEN); 333 334 i = 0; 335 m.setElement(i++, LnConstants.OPC_PEER_XFER); 336 m.setElement(i++, LnConstants.RE_DPLX_OP_LEN); // 20-byte message 337 m.setElement(i++, LnConstants.RE_DPLX_GP_CHAN_TYPE); // Group Channel Operation 338 m.setElement(i++, LnConstants.RE_DPLX_OP_TYPE_WRITE); // Write Operation 339 m.setElement(i++, 0); // always 0 for duplex group channel write 340 m.setElement(i++, iChannelNumber); // Group Channel Number 341 for (; i < (LnConstants.RE_DPLX_OP_LEN - 1); i++) { 342 m.setElement(i, 0); // always 0 for duplex group channel write 343 } 344 // Note: LocoNet send process will compute and add checksum byte in correct location 345 346 return m; 347 } 348 349 /** 350 * Create a LocoNet packet to set the Duplex group password. 351 * <p> 352 * If s provides anything other than a 4 character length group password 353 * which uses only valid group ID characters (0-9, A-C), a bogus 354 * LocoNet message is returned. 355 * 356 * @param sGroupPassword The desired group password as a string 357 * @return The packet which writes the Group Password to the UR92 device(s) 358 * @throws jmri.jmrix.loconet.LocoNetException in case of invalid sGrooupPassword 359 */ 360 public static final LocoNetMessage createSetUr92GroupPasswordPacket(String sGroupPassword) throws jmri.jmrix.loconet.LocoNetException { 361 362 int gr_p1 = sGroupPassword.toUpperCase().charAt(0); 363 int gr_p2 = sGroupPassword.toUpperCase().charAt(1); 364 int gr_p3 = sGroupPassword.toUpperCase().charAt(2); 365 int gr_p4 = sGroupPassword.toUpperCase().charAt(3); 366 int i; 367 368 if (validateGroupPassword(sGroupPassword) == false) { 369 if (isPasswordLimitedToNumbers() == true) { 370 throw new jmri.jmrix.loconet.LocoNetException("Invalid Duplex Group Password - must be a 4 digit number between 0000 and 9999, inclusive"); // NOI18N 371 } else { 372 throw new jmri.jmrix.loconet.LocoNetException("Invalid Duplex Group Password - must be a 4 character value using only digits, 'A', 'B', and/or 'C'"); // NOI18N 373 } 374 } 375 376 // re-code individual characters when an alphabetic character is used 377 gr_p1 -= (gr_p1 > '9') ? ('A' - '9' - 1) : 0; 378 gr_p2 -= (gr_p2 > '9') ? ('A' - '9' - 1) : 0; 379 gr_p3 -= (gr_p3 > '9') ? ('A' - '9' - 1) : 0; 380 gr_p4 -= (gr_p4 > '9') ? ('A' - '9' - 1) : 0; 381 382 // format packet 383 LocoNetMessage m = new LocoNetMessage(LnConstants.RE_DPLX_OP_LEN); 384 385 i = 0; 386 m.setElement(i++, LnConstants.OPC_PEER_XFER); 387 m.setElement(i++, LnConstants.RE_DPLX_OP_LEN); // 20-byte message 388 m.setElement(i++, LnConstants.RE_DPLX_GP_PW_TYPE); // Group password Operation 389 m.setElement(i++, LnConstants.RE_DPLX_OP_TYPE_WRITE); // Write Operation 390 m.setElement(i++, 0); // always 0 for duplex group password write 391 m.setElement(i++, gr_p1); // Group password leftmost value + 0x30 392 m.setElement(i++, gr_p2); // Group password next value + 0x30 393 m.setElement(i++, gr_p3); // Group password next value + 0x30 394 m.setElement(i++, gr_p4); // Group password rightmost value + 0x30 395 for (; i < (LnConstants.RE_DPLX_OP_LEN - 1); i++) { 396 m.setElement(i, 0); // always 0 for duplex group password write 397 } 398 // Note: LocoNet send process will compute and add checksum byte in correct location 399 400 return m; 401 } 402 403 /** 404 * Create a LocoNet packet to set the Duplex group ID number. 405 * <p> 406 * If s provides anything other than a numeric value between 0 and 127, a 407 * LocoNetException is thrown. 408 * 409 * @param s The desired group ID number as a string 410 * @return The packet which writes the Group ID Number to the UR92 device(s) 411 * @throws jmri.jmrix.loconet.LocoNetException when an invalid id is provided 412 */ 413 public static final LocoNetMessage createSetUr92GroupIDPacket(String s) throws jmri.jmrix.loconet.LocoNetException { 414 int gr_id = Integer.parseInt(s, 10); 415 416 if ((gr_id >= LnDplxGrpInfoImplConstants.DPLX_MIN_ID) && (gr_id <= LnDplxGrpInfoImplConstants.DPLX_MAX_ID)) { 417 // format packet 418 int i = 0; 419 LocoNetMessage m = new LocoNetMessage(LnConstants.RE_DPLX_OP_LEN); 420 421 m.setElement(i++, LnConstants.OPC_PEER_XFER); 422 m.setElement(i++, LnConstants.RE_DPLX_OP_LEN); // 20-byte message 423 m.setElement(i++, LnConstants.RE_DPLX_GP_ID_TYPE); // Group ID Operation 424 m.setElement(i++, LnConstants.RE_DPLX_OP_TYPE_WRITE); // Write Operation 425 m.setElement(i++, 0); // always 0 for duplex group ID write 426 m.setElement(i++, gr_id); // Group ID Number 427 for (; i < (LnConstants.RE_DPLX_OP_LEN - 1); i++) { 428 m.setElement(i, 0); // always 0 for duplex group ID write 429 } 430 // Note: LocoNet send process will compute and add checksum byte in correct location 431 return m; 432 } else { 433 /* in case param s encodes something other than a valid Duplex 434 * ID number, throw an exception. 435 */ 436 throw new jmri.jmrix.loconet.LocoNetException("Illegal Duplex Group ID number"); // NOI18N 437 } 438 } 439 440 /** 441 * Checks message m to determine if it contains a Duplex Group Identity 442 * message, including queries, reports, and writes, for Name, Channel, 443 * Password, and ID. 444 * 445 * @param m LocoNet message to check 446 * @return true if message is query, report, or write of Duplex Group Name, 447 * Channel, Password or ID 448 */ 449 public static final boolean isDuplexGroupMessage(LocoNetMessage m) { 450 if ((m.getOpCode() == LnConstants.OPC_PEER_XFER) 451 && (m.getElement(1) == LnConstants.RE_DPLX_OP_LEN)) { 452 // Message is a peer-to-peer message of appropriate length for 453 // Duplex Group operations. Check the individual message type 454 Integer byte2 = m.getElement(2); 455 if ((byte2 == LnConstants.RE_DPLX_GP_CHAN_TYPE) 456 || (byte2 == LnConstants.RE_DPLX_GP_NAME_TYPE) 457 || (byte2 == LnConstants.RE_DPLX_GP_ID_TYPE) 458 || (byte2 == LnConstants.RE_DPLX_GP_PW_TYPE)) { 459 // To be sure the message is a duplex operation, check the 460 // operation type. 461 Integer byte3 = m.getElement(3); 462 if ((byte3 == LnConstants.RE_DPLX_OP_TYPE_QUERY) 463 || (byte3 == LnConstants.RE_DPLX_OP_TYPE_REPORT) 464 || (byte3 == LnConstants.RE_DPLX_OP_TYPE_WRITE)) { 465 return true; 466 } 467 } 468 } 469 return false; 470 } 471 472 /** 473 * Classifies a LocoNet Message to see if it is a Duplex Group Identity 474 * message 475 * 476 * @param m a LocoNetMessage 477 * @return DuplexGroupMessageType, encoded as one of the following 478 * NOT_A_DUPLEX_GROUP_MESSAGE DUPLEX_GROUP_CHANNEL_QUERY_MESSAGE 479 * DUPLEX_GROUP_CHANNEL_REPORT_MESSAGE 480 * DUPLEX_GROUP_CHANNEL_WRITE_MESSAGE 481 * DUPLEX_GROUP_NAME_QUERY_MESSAGE 482 * DUPLEX_GROUP_NAME_ETC_REPORT_MESSAGE 483 * DUPLEX_GROUP_NAME_WRITE_MESSAGE 484 * DUPLEX_GROUP_PASSWORD_QUERY_MESSAGE 485 * DUPLEX_GROUP_PASSWORD_REPORT_MESSAGE 486 * DUPLEX_GROUP_PASSWORD_WRITE_MESSAGE DUPLEX_GROUP_ID_QUERY_MESSAGE 487 * DUPLEX_GROUP_ID_REPORT_MESSAGE DUPLEX_GROUP_ID_WRITE_MESSAGE 488 */ 489 public static final DuplexGroupMessageType getDuplexGroupIdentityMessageType(LocoNetMessage m) { 490 if ((m.getOpCode() == LnConstants.OPC_PEER_XFER) 491 && (m.getElement(1) == LnConstants.RE_DPLX_OP_LEN)) { 492 // Message is a peer-to-peer message of appropriate length for 493 // Duplex Group operations. Check the individual message type 494 Integer byte3 = m.getElement(3); 495 switch (m.getElement(2)) { 496 case LnConstants.RE_DPLX_GP_CHAN_TYPE: 497 return (byte3 == LnConstants.RE_DPLX_OP_TYPE_QUERY) ? DuplexGroupMessageType.DUPLEX_GROUP_CHANNEL_QUERY_MESSAGE 498 : (byte3 == LnConstants.RE_DPLX_OP_TYPE_REPORT) ? DuplexGroupMessageType.DUPLEX_GROUP_CHANNEL_REPORT_MESSAGE 499 : (byte3 == LnConstants.RE_DPLX_OP_TYPE_WRITE) ? DuplexGroupMessageType.DUPLEX_GROUP_CHANNEL_WRITE_MESSAGE 500 : DuplexGroupMessageType.NOT_A_DUPLEX_GROUP_MESSAGE; 501 case LnConstants.RE_DPLX_GP_NAME_TYPE: 502 return (byte3 == LnConstants.RE_DPLX_OP_TYPE_QUERY) ? DuplexGroupMessageType.DUPLEX_GROUP_NAME_QUERY_MESSAGE 503 : (byte3 == LnConstants.RE_DPLX_OP_TYPE_REPORT) ? DuplexGroupMessageType.DUPLEX_GROUP_NAME_ETC_REPORT_MESSAGE 504 : (byte3 == LnConstants.RE_DPLX_OP_TYPE_WRITE) ? DuplexGroupMessageType.DUPLEX_GROUP_NAME_WRITE_MESSAGE 505 : DuplexGroupMessageType.NOT_A_DUPLEX_GROUP_MESSAGE; 506 case LnConstants.RE_DPLX_GP_PW_TYPE: 507 return (byte3 == LnConstants.RE_DPLX_OP_TYPE_QUERY) ? DuplexGroupMessageType.DUPLEX_GROUP_PASSWORD_QUERY_MESSAGE 508 : (byte3 == LnConstants.RE_DPLX_OP_TYPE_REPORT) ? DuplexGroupMessageType.DUPLEX_GROUP_PASSWORD_REPORT_MESSAGE 509 : (byte3 == LnConstants.RE_DPLX_OP_TYPE_WRITE) ? DuplexGroupMessageType.DUPLEX_GROUP_PASSWORD_WRITE_MESSAGE 510 : DuplexGroupMessageType.NOT_A_DUPLEX_GROUP_MESSAGE; 511 case LnConstants.RE_DPLX_GP_ID_TYPE: 512 return (byte3 == LnConstants.RE_DPLX_OP_TYPE_QUERY) ? DuplexGroupMessageType.DUPLEX_GROUP_ID_QUERY_MESSAGE 513 : (byte3 == LnConstants.RE_DPLX_OP_TYPE_REPORT) ? DuplexGroupMessageType.DUPLEX_GROUP_ID_REPORT_MESSAGE 514 : (byte3 == LnConstants.RE_DPLX_OP_TYPE_WRITE) ? DuplexGroupMessageType.DUPLEX_GROUP_ID_WRITE_MESSAGE 515 : DuplexGroupMessageType.NOT_A_DUPLEX_GROUP_MESSAGE; 516 default: 517 return DuplexGroupMessageType.NOT_A_DUPLEX_GROUP_MESSAGE; 518 } 519 } 520 return DuplexGroupMessageType.NOT_A_DUPLEX_GROUP_MESSAGE; 521 } 522 523 /** 524 * Checks that m is a message with a Duplex Group Name encoded inside, then 525 * extracts the Duplex Group Name. Note that the returned string is always 8 526 * characters long. 527 * <p> 528 * If m does not contain a Duplex Group Name, returns null. 529 * 530 * @param m LocoNet message from which a Duplex Group Name is to be extracted. 531 * @return String containing Duplex Group Name as extracted from m 532 */ 533 public static String extractDuplexGroupName(LocoNetMessage m) { 534 switch (getDuplexGroupIdentityMessageType(m)) { 535 case DUPLEX_GROUP_NAME_ETC_REPORT_MESSAGE: 536 case DUPLEX_GROUP_NAME_WRITE_MESSAGE: 537 return extractGroupName(m); 538 default: 539 return null; 540 } 541 } 542 543 /** 544 * Assumes that m is a message with a Duplex Group Name encoded inside. 545 * Extracts the Duplex Group Name and returns it as an 8 character String. 546 * 547 * @return String containing Duplex Group Name as extracted from m 548 */ 549 private static String extractGroupName(LocoNetMessage m) { 550 StringBuilder gr_name = new StringBuilder(); 551 int gr_msb1; 552 int gr_msb2; 553 554 gr_msb1 = m.getElement(4) & LnConstants.RE_DPLX_MAX_NOT_OPC; 555 gr_msb2 = m.getElement(9) & LnConstants.RE_DPLX_MAX_NOT_OPC; 556 gr_name.append(Character.toString((char) ((m.getElement(5) & LnConstants.RE_DPLX_MAX_NOT_OPC) 557 + ((gr_msb1 & LnConstants.RE_DPLX_MSB1_BIT) << LnConstants.RE_DPLX_BUMP_MSB1_BIT)))); 558 gr_name.append(Character.toString((char) ((m.getElement(6) & LnConstants.RE_DPLX_MAX_NOT_OPC) 559 + ((gr_msb1 & LnConstants.RE_DPLX_MSB2_BIT) << LnConstants.RE_DPLX_BUMP_MSB2_BIT)))); 560 gr_name.append(Character.toString((char) ((m.getElement(7) & LnConstants.RE_DPLX_MAX_NOT_OPC) 561 + ((gr_msb1 & LnConstants.RE_DPLX_MSB3_BIT) << LnConstants.RE_DPLX_BUMP_MSB3_BIT)))); 562 gr_name.append(Character.toString((char) ((m.getElement(8) & LnConstants.RE_DPLX_MAX_NOT_OPC) 563 + ((gr_msb1 & LnConstants.RE_DPLX_MSB4_BIT) << LnConstants.RE_DPLX_BUMP_MSB4_BIT)))); 564 gr_name.append(Character.toString((char) ((m.getElement(10) & LnConstants.RE_DPLX_MAX_NOT_OPC) 565 + ((gr_msb2 & LnConstants.RE_DPLX_MSB1_BIT) << LnConstants.RE_DPLX_BUMP_MSB1_BIT)))); 566 gr_name.append(Character.toString((char) ((m.getElement(11) & LnConstants.RE_DPLX_MAX_NOT_OPC) 567 + ((gr_msb2 & LnConstants.RE_DPLX_MSB2_BIT) << LnConstants.RE_DPLX_BUMP_MSB2_BIT)))); 568 gr_name.append(Character.toString((char) ((m.getElement(12) & LnConstants.RE_DPLX_MAX_NOT_OPC) 569 + ((gr_msb2 & LnConstants.RE_DPLX_MSB3_BIT) << LnConstants.RE_DPLX_BUMP_MSB3_BIT)))); 570 gr_name.append(Character.toString((char) ((m.getElement(13) & LnConstants.RE_DPLX_MAX_NOT_OPC) 571 + ((gr_msb2 & LnConstants.RE_DPLX_MSB4_BIT) << LnConstants.RE_DPLX_BUMP_MSB4_BIT)))); 572 return gr_name.toString(); 573 } 574 575 /** 576 * Checks that m is a message with a Duplex Group Channel encoded inside, 577 * then extracts and returns the Duplex Group Channel. 578 * <p> 579 * Returns -1 if the m does not contain a Duplex Group Channel. 580 * 581 * @param m LocoNet message from which a Duplex Group Channel number will 582 * be extracted 583 * @return Integer containing Duplex Group Name as extracted from m 584 */ 585 public static int extractDuplexGroupChannel(LocoNetMessage m) { 586 switch (getDuplexGroupIdentityMessageType(m)) { 587 case DUPLEX_GROUP_NAME_ETC_REPORT_MESSAGE: 588 return m.getElement(17) 589 + (((m.getElement(14) & 0x4) == 0x4) ? 128 : 0); 590 case DUPLEX_GROUP_CHANNEL_REPORT_MESSAGE: 591 case DUPLEX_GROUP_CHANNEL_WRITE_MESSAGE: 592 return m.getElement(5) 593 + (((m.getElement(4) & 0x1) == 0x1) ? 128 : 0); 594 default: 595 return -1; 596 } 597 } 598 599 /** 600 * Checks that m is a message with a Duplex Group ID encoded inside, then 601 * extracts and returns the Duplex Group ID. 602 * <p> 603 * Returns -1 if the m does not contain a Duplex Group ID. 604 * 605 * @param m LocoNet message from which a Duplex Group ID will be extracted 606 * @return Integer containing Duplex Group Name as extracted from m 607 */ 608 public static int extractDuplexGroupID(LocoNetMessage m) { 609 switch (getDuplexGroupIdentityMessageType(m)) { 610 case DUPLEX_GROUP_NAME_ETC_REPORT_MESSAGE: 611 return m.getElement(18) 612 + (((m.getElement(14) & 0x8) == 0x8) ? 128 : 0); 613 case DUPLEX_GROUP_ID_REPORT_MESSAGE: 614 case DUPLEX_GROUP_ID_WRITE_MESSAGE: 615 return m.getElement(5) 616 + (((m.getElement(4) & 0x1) == 0x1) ? 128 : 0); 617 default: 618 return -1; 619 } 620 } 621 622 /** 623 * Checks that m is a message with a Duplex Group Password encoded inside, 624 * then extracts and returns the Duplex Group Password. 625 * <p> 626 * Returns null if the m does not contain a Duplex Group Password. 627 * 628 * @param m LocoNet message to be checked for a duplex group password message 629 * @return String containing the Duplex Group Password as extracted from m 630 */ 631 public static String extractDuplexGroupPassword(LocoNetMessage m) { 632 switch (getDuplexGroupIdentityMessageType(m)) { 633 case DUPLEX_GROUP_NAME_ETC_REPORT_MESSAGE: 634 return extractDuplexGroupPasswordSimplified( 635 (m.getElement(15) & 0x70) >> 4, 636 (m.getElement(15) & 0x0f), 637 (m.getElement(16) & 0x70) >> 4, 638 (m.getElement(16) & 0x0f), 639 (((m.getElement(14) & 0x1) == 0x1) ? true : false), 640 false, 641 (((m.getElement(14) & 0x2) == 0x2) ? true : false), 642 false); 643 case DUPLEX_GROUP_PASSWORD_REPORT_MESSAGE: 644 case DUPLEX_GROUP_PASSWORD_WRITE_MESSAGE: 645 return extractDuplexGroupPasswordSimplified( 646 (m.getElement(5) & 0x0f), 647 (m.getElement(6) & 0x0f), 648 (m.getElement(7) & 0x0f), 649 (m.getElement(8) & 0x0f), 650 false, 651 false, 652 false, 653 false 654 ); 655 default: 656 return null; 657 } 658 } 659 660 private static String extractDuplexGroupPasswordSimplified(Integer byte1, Integer byte2, Integer byte3, Integer byte4, 661 boolean x1, boolean x2, boolean x3, boolean x4) { 662 Integer b1; 663 Integer b2; 664 Integer b3; 665 Integer b4; 666 char gr_p1; 667 char gr_p2; 668 char gr_p3; 669 char gr_p4; 670 671 b1 = byte1 + (x1 ? 8 : 0); 672 b2 = byte2 + (x2 ? 8 : 0); 673 b3 = byte3 + (x3 ? 8 : 0); 674 b4 = byte4 + (x4 ? 8 : 0); 675 676 // extract reported password characters and convert to displayable character 677 gr_p1 = (char) ('0' + b1); 678 gr_p1 += (gr_p1 > '9') ? ('A' - '9' - 1) : 0; 679 680 gr_p2 = (char) ('0' + b2); 681 gr_p2 += (gr_p2 > '9') ? ('A' - '9' - 1) : 0; 682 683 gr_p3 = (char) ('0' + b3); 684 gr_p3 += (gr_p3 > '9') ? ('A' - '9' - 1) : 0; 685 686 gr_p4 = (char) ('0' + b4); 687 gr_p4 += (gr_p4 > '9') ? ('A' - '9' - 1) : 0; 688 689 return "" + gr_p1 + gr_p2 + gr_p3 + gr_p4; 690 691 } 692 693 /** 694 * Process all incoming LocoNet messages to look for Duplex Group 695 * information operations. Only pays attention to LocoNet report of Duplex 696 * Group Name/password/channel/groupID, and ignores all other LocoNet 697 * messages. 698 * <p> 699 * If tool has sent a query for Duplex group information and has not yet 700 * received a Duplex group report, the method updates the GUI with the 701 * received information. 702 * <p> 703 * If the tool is not currently waiting for a response to a query, then the 704 * method compares the received information against the information 705 * currently displayed in the GUI. If the received information does not 706 * match, a message is displayed on the status line in the GUI, else nothing 707 * is displayed in the GUI status line. 708 */ 709 @Override 710 public void message(LocoNetMessage m) { 711 messagesHandled++; 712 713 if (handleMessageIplResult(m)) { 714 return; 715 } 716 717 if (handleMessageDuplexInfoQuery(m)) { 718 gotQueryReply = true; 719 thisone.firePropertyChange(DPLX_PC_RCD_DPLX_IDENTITY_QUERY, false, true); 720 return; 721 } 722 723 if (handleMessageDuplexInfoReport(m)) { 724 gotQueryReply = true; 725 thisone.firePropertyChange(DPLX_PC_RCD_DPLX_IDENTITY_REPORT, false, true); 726 return; 727 } 728 729 return; 730 } 731 732 private boolean awaitingGroupReadReport; 733 private String acceptedGroupName; 734 private String acceptedGroupChannel; 735 private String acceptedGroupPassword; 736 private String acceptedGroupId; 737 738 /** 739 * 740 * @return String containing reported Duplex Group Name 741 */ 742 public String getFetchedDuplexGroupName() { 743 return acceptedGroupName; 744 } 745 746 /** 747 * 748 * @return String containing reported Duplex Group Name 749 */ 750 public String getFetchedDuplexGroupChannel() { 751 return acceptedGroupChannel; 752 } 753 754 /** 755 * 756 * @return String containing reported Duplex Group Name 757 */ 758 public String getFetchedDuplexGroupPassword() { 759 return acceptedGroupPassword; 760 } 761 762 /** 763 * 764 * @return String containing reported Duplex Group Name 765 */ 766 public String getFetchedDuplexGroupId() { 767 return acceptedGroupId; 768 } 769 770 /** 771 * Interprets a received LocoNet message. If message is an IPL report of 772 * attached IPL-capable equipment, check to see if it reports a UR92/UR93/LNWI device 773 * as attached. If so, increment count of devices. Else ignore. 774 * 775 * @return true if message is an IPL device report indicating a UR92 776 * present, else return false. 777 */ 778 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "DLS_DEAD_LOCAL_STORE", 779 justification = "False positive on the implied local variable in numUr92++") 780 private boolean handleMessageIplResult(LocoNetMessage m) { 781 if (LnIPLImplementation.isIplUr92IdentityReportMessage(m) || 782 LnIPLImplementation.isIplUr93IdentityReportMessage(m) || 783 LnIPLImplementation.isIplLnwiIdentityReportMessage(m)) { 784 numUr92CompatibleType++; 785 thisone.firePropertyChange(DPLX_PC_STAT_LN_UPDATE, " ", " "); 786 thisone.firePropertyChange(DPLX_IPL_DEVICE_DETAILS, "", new BasicIPLDeviceInfo(m)); 787 waitingForIplReply = false; 788 789 return true; 790 } else { 791 return false; 792 } 793 } 794 795 private boolean handleMessageDuplexInfoQuery(LocoNetMessage m) { 796 return (getDuplexGroupIdentityMessageType(m) == DuplexGroupMessageType.DUPLEX_GROUP_NAME_QUERY_MESSAGE); 797 } 798 799 /** 800 * Interprets a received LocoNet message. If message is a report of Duplex 801 * Group Identity information, extract it to local variables. Else ignore. 802 * 803 * @return true if message is a Duplex Group Identity Report, else return 804 * false. 805 */ 806 private boolean handleMessageDuplexInfoReport(LocoNetMessage m) { 807 808 String gr_name = ""; 809 String gr_password = ""; 810 int gr_ch; 811 int gr_id; 812 int i; 813 if (getDuplexGroupIdentityMessageType(m) == DuplexGroupMessageType.DUPLEX_GROUP_NAME_ETC_REPORT_MESSAGE) { 814 gr_name = extractDuplexGroupName(m); 815 // remove trailing spaces from name 816 i = (gr_name.length() - 1); 817 while ((gr_name.charAt(i) == ' ') && (i > 0)) { 818 gr_name = gr_name.substring(0, i); 819 i--; 820 } 821 gr_password = extractDuplexGroupPassword(m); 822 gr_ch = extractDuplexGroupChannel(m); 823 gr_id = extractDuplexGroupID(m); 824 // always report details to table 825 thisone.firePropertyChange(DPLX_IPL_DEVICE_RESPONSE_DETAILS, "", 826 new BasicIPLDeviceResponseInfo(gr_name,Integer.toString(gr_ch) + " (WiFi " + Integer.toString(gr_ch - 10) + ")",gr_password,Integer.toString(gr_id))); 827 828 if (awaitingGroupReadReport) { 829 awaitingGroupReadReport = false; 830 acceptedGroupName = gr_name; 831 acceptedGroupChannel = Integer.toString(gr_ch, LnDplxGrpInfoImplConstants.GENERAL_DECIMAL_RADIX); 832 acceptedGroupPassword = gr_password; 833 acceptedGroupId = Integer.toString(gr_id, LnDplxGrpInfoImplConstants.GENERAL_DECIMAL_RADIX); 834 835 thisone.firePropertyChange(DPLX_PC_NAME_UPDATE, false, true); 836 thisone.firePropertyChange(DPLX_PC_CHANNEL_UPDATE, false, true); 837 thisone.firePropertyChange(DPLX_PC_PASSWORD_UPDATE, false, true); 838 thisone.firePropertyChange(DPLX_PC_ID_UPDATE, false, true); 839 840 thisone.firePropertyChange(DPLX_PC_NAME_VALIDITY, false, true); 841 thisone.firePropertyChange(DPLX_PC_CHANNEL_VALIDITY, false, true); 842 thisone.firePropertyChange(DPLX_PC_PASSWORD_VALIDITY, false, true); 843 thisone.firePropertyChange(DPLX_PC_ID_VALIDITY, false, true); 844 845 thisone.firePropertyChange(DPLX_PC_STAT_LN_UPDATE, "", " "); 846 847 } else { 848 // if not expecting a group read, compare new read data 849 // versus first returned data 850 String c = "" + Integer.toString(gr_ch, LnDplxGrpInfoImplConstants.GENERAL_DECIMAL_RADIX); 851 String p = "" + gr_password; 852 String d = "" + Integer.toString(gr_id, LnDplxGrpInfoImplConstants.GENERAL_DECIMAL_RADIX); 853 854 if ((!acceptedGroupName.equals(gr_name)) 855 || (!acceptedGroupChannel.equals(c)) 856 || (!acceptedGroupPassword.equals(p)) 857 || (!acceptedGroupId.equals(d))) { 858 // show that a Duplex Group Identification information hapened. 859 // Show message as as red text on status line 860 thisone.firePropertyChange(DPLX_PC_STAT_LN_UPDATE, " ", "ErrorGroupMismatch"); 861 } 862 } 863 return true; 864 } 865 return false; 866 } 867 868 /** 869 * Sends a LocoNet Message to query the Duplex Group Identity. Starts a 870 * timer to monitor completion. 871 * 872 */ 873 public void queryDuplexGroupIdentity() { 874 875 awaitingGroupReadReport = true; 876 gotQueryReply = false; 877 878 memo.getLnTrafficController().sendLocoNetMessage(createUr92GroupIdentityQueryPacket()); 879 invalidateDuplexGroupIdentityInfo(); 880 881 if (swingTmrDuplexInfoQuery != null) { 882 if (swingTmrDuplexInfoQuery.isRunning()) { 883 swingTmrDuplexInfoQuery.restart(); 884 } else { 885 swingTmrDuplexInfoQuery.start(); 886 } 887 } 888 } 889 890 /** 891 * Creates and sends a LocoNet message which sets the Duplex Group Name. 892 * 893 * @param dgn String containing the new Duplex Group Name 894 * @throws jmri.jmrix.loconet.LocoNetException if dgn is not a valid Duplex 895 * Group Name. 896 */ 897 public void setDuplexGroupName(String dgn) throws jmri.jmrix.loconet.LocoNetException { 898 memo.getLnTrafficController().sendLocoNetMessage( 899 createSetUr92GroupNamePacket( 900 dgn)); 901 } 902 903 /** 904 * Creates and sends a LocoNet message which sets the Duplex Group Channel. 905 * 906 * @param dgc Integer containing the new Duplex Group Channel 907 * @throws jmri.jmrix.loconet.LocoNetException if dgc is not a valid Duplex 908 * Group Channel number. 909 */ 910 public void setDuplexGroupChannel(Integer dgc) throws jmri.jmrix.loconet.LocoNetException { 911 memo.getLnTrafficController().sendLocoNetMessage(createSetUr92GroupChannelPacket( 912 dgc)); 913 } 914 915 /** 916 * Creates and sends a LocoNet message which sets the Duplex Group Password. 917 * 918 * @param dgp String containing the new Duplex Group Password 919 * @throws jmri.jmrix.loconet.LocoNetException if dgp is not a valid Duplex 920 * Group Password. 921 */ 922 public void setDuplexGroupPassword(String dgp) throws jmri.jmrix.loconet.LocoNetException { 923 memo.getLnTrafficController().sendLocoNetMessage(createSetUr92GroupPasswordPacket( 924 dgp)); 925 } 926 927 /** 928 * Creates and sends a LocoNet message which sets the Duplex Group Id. 929 * 930 * @param dgi String containing the new Duplex Group Id 931 * @throws jmri.jmrix.loconet.LocoNetException if dgi is not a valid Duplex 932 * Group Id. 933 */ 934 public void setDuplexGroupId(String dgi) throws jmri.jmrix.loconet.LocoNetException { 935 memo.getLnTrafficController().sendLocoNetMessage(createSetUr92GroupIDPacket( 936 dgi)); 937 } 938 939 private void invalidateDataAndQueryDuplexInfo() { 940 // and clear query details from display table 941 if (numUr92CompatibleType > 0) { 942 thisone.firePropertyChange(DPLX_PC_STAT_LN_UPDATE, " ", "ProcessingReadingInfo"); 943 queryDuplexGroupIdentity(); 944 } 945 } 946 947 private void sendUr92IplQuery() { 948 waitingForIplReply = true; 949 thisone.firePropertyChange(DPLX_IPL_DEVICE_DETAILS,"", new BasicIPLDeviceInfo("","","")); 950 memo.getLnTrafficController().sendLocoNetMessage( 951 LnIPLImplementation.createIplSpecificHostQueryPacket( 952 LnConstants.RE_IPL_DIGITRAX_HOST_ALL, 953 LnConstants.RE_IPL_DIGITRAX_HOST_UR92)); 954 int oldvalue = 9999; 955 int newvalue = 0; 956 thisone.firePropertyChange("NumberOfUr92sUpdate", oldvalue, newvalue); // NOI18N 957 invalidateDuplexGroupIdentityInfo(); 958 959 if (swingTmrIplQuery != null) { 960 if (swingTmrIplQuery.isRunning()) { 961 swingTmrIplQuery.restart(); 962 } else { 963 swingTmrIplQuery.start(); 964 } 965 } 966 } 967 968 private void invalidateDuplexGroupIdentityInfo() { 969 acceptedGroupName = ""; 970 acceptedGroupChannel = ""; 971 acceptedGroupPassword = ""; 972 acceptedGroupId = ""; 973 thisone.firePropertyChange(DPLX_PC_NAME_UPDATE, true, false); 974 thisone.firePropertyChange(DPLX_PC_CHANNEL_UPDATE, true, false); 975 thisone.firePropertyChange(DPLX_PC_PASSWORD_UPDATE, true, false); 976 thisone.firePropertyChange(DPLX_PC_ID_UPDATE, true, false); 977 thisone.firePropertyChange(DPLX_PC_NAME_VALIDITY, true, false); 978 thisone.firePropertyChange(DPLX_PC_CHANNEL_VALIDITY, true, false); 979 thisone.firePropertyChange(DPLX_PC_PASSWORD_VALIDITY, true, false); 980 thisone.firePropertyChange(DPLX_PC_ID_VALIDITY, true, false); 981 thisone.firePropertyChange(DPLX_IPL_DEVICE_RESPONSE_DETAILS,"", new BasicIPLDeviceResponseInfo("","","","")); 982 } 983 984 /** 985 * Begins a sequence which includes counting available UR92s and similar and, if at 986 * least one UR92/UR93/LNWI is present, reads the Duplex Group Identity Info. 987 */ 988 public void countUr92sAndQueryDuplexIdentityInfo() { 989 if (thisone == null) { 990 log.error("called countUR92sAndQueryDuplexInfo before thisone is initialized"); 991 return; 992 } 993 if ((waitingForIplReply == true) 994 || (swingTmrIplQuery == null) 995 || (swingTmrDuplexInfoQuery == null) 996 || (swingTmrIplQuery.isRunning()) 997 || (swingTmrDuplexInfoQuery.isRunning())) { 998 thisone.firePropertyChange(DPLX_PC_STAT_LN_UPDATE, " ", "ErrorReadingTooSoon"); 999 return; 1000 } 1001 invalidateDuplexGroupIdentityInfo(); 1002 1003 numUr92CompatibleType = 0; 1004 1005 // configure timer for delay between UR92 query request and begin of duplex info query 1006 sendUr92IplQuery(); 1007 thisone.firePropertyChange(DPLX_PC_STAT_LN_UPDATE, " ", "ProcessingInitialStatusMessage"); 1008 swingTmrIplQuery.stop(); 1009 swingTmrIplQuery.setInitialDelay(LnDplxGrpInfoImplConstants.IPL_QUERY_DELAY); 1010 swingTmrIplQuery.setRepeats(false); 1011 swingTmrIplQuery.restart(); 1012 } 1013 1014 // the following code may be used to create a LocoNet message that follows the 1015 // form of the message sent by a UR92 in response to a Duplex Group Name query 1016 // LocoNet message. 1017 public static final LocoNetMessage createUr92GroupNameReportPacket( 1018 String dupName, 1019 String dupPass, 1020 int dupChan, 1021 int dupId) { 1022 // format packet 1023 LocoNetMessage m = new LocoNetMessage(LnConstants.RE_DPLX_OP_LEN); 1024 int i = 0; 1025 dupName += " "; 1026 dupName = dupName.substring(0, 8); // get first 8 chars of space-padded name 1027 m.setElement(i++, LnConstants.OPC_PEER_XFER); 1028 m.setElement(i++, LnConstants.RE_DPLX_OP_LEN); // 20-byte message 1029 m.setElement(i++, LnConstants.RE_DPLX_GP_NAME_TYPE); // Group Name Operation 1030 m.setElement(i++, LnConstants.RE_DPLX_OP_TYPE_REPORT); // Report Operation 1031 1032 m.setElement(i++, 1033 (((dupName.charAt(0) & 0x80) == 0x80) ? 1 : 0) 1034 + (((dupName.charAt(1) & 0x80) == 0x80) ? 2 : 0) 1035 + (((dupName.charAt(2) & 0x80) == 0x80) ? 4 : 0) 1036 + (((dupName.charAt(3) & 0x80) == 0x80) ? 8 : 0)); 1037 m.setElement(i++, dupName.charAt(0) & 0x7f); 1038 m.setElement(i++, dupName.charAt(1) & 0x7f); 1039 m.setElement(i++, dupName.charAt(2) & 0x7f); 1040 m.setElement(i++, dupName.charAt(3) & 0x7f); 1041 1042 m.setElement(i++, 1043 (((dupName.charAt(4) & 0x80) == 0x80) ? 1 : 0) 1044 + (((dupName.charAt(5) & 0x80) == 0x80) ? 2 : 0) 1045 + (((dupName.charAt(6) & 0x80) == 0x80) ? 4 : 0) 1046 + (((dupName.charAt(7) & 0x80) == 0x80) ? 8 : 0)); 1047 m.setElement(i++, dupName.charAt(4) & 0x7f); 1048 m.setElement(i++, dupName.charAt(5) & 0x7f); 1049 m.setElement(i++, dupName.charAt(6) & 0x7f); 1050 m.setElement(i++, dupName.charAt(7) & 0x7f); 1051 dupPass += "0000"; // NOI18N 1052 dupPass = dupPass.substring(0, 4); 1053 int gr_p1 = dupPass.charAt(0); 1054 int gr_p2 = dupPass.charAt(1); 1055 int gr_p3 = dupPass.charAt(2); 1056 int gr_p4 = dupPass.charAt(3); 1057 1058 // re-code individual characters when an alphabetic character is used 1059 gr_p1 -= (gr_p1 > '9') ? ('A' - '9' - 1) : 0; 1060 gr_p2 -= (gr_p2 > '9') ? ('A' - '9' - 1) : 0; 1061 gr_p3 -= (gr_p3 > '9') ? ('A' - '9' - 1) : 0; 1062 gr_p4 -= (gr_p4 > '9') ? ('A' - '9' - 1) : 0; 1063 int passLo = ((gr_p1 & 0x0f) << 4) + (gr_p2 & 0x0f); 1064 int passHi = ((gr_p3 & 0x0f) << 4) + (gr_p4 & 0x0f); 1065 m.setElement(i++, 1066 (((passLo & 0x80) == 0x80) ? 1 : 0) 1067 + (((passHi & 0x80) == 0x80) ? 2 : 0) 1068 + (((dupChan & 0x80) == 0x80) ? 4 : 0) 1069 + (((dupId & 0x80) == 0x80) ? 8 : 0)); 1070 m.setElement(i++, passLo & 0x7f); 1071 m.setElement(i++, passHi & 0x7f); 1072 m.setElement(i++, dupChan & 0x7f); 1073 m.setElement(i++, dupId & 0x7f); 1074 1075 return m; 1076 } 1077 1078 // the following code may be used to create a LocoNet message that follows the 1079 // form of the message sent by a UR92 in response to a Duplex Group Channel query 1080 // LocoNet message. 1081 public static final LocoNetMessage createUr92GroupChannelReportPacket( 1082 int dupChan) { 1083 // format packet 1084 LocoNetMessage m = new LocoNetMessage(LnConstants.RE_DPLX_OP_LEN); 1085 int i = 0; 1086 m.setElement(i++, LnConstants.OPC_PEER_XFER); 1087 m.setElement(i++, LnConstants.RE_DPLX_OP_LEN); // 20-byte message 1088 m.setElement(i++, LnConstants.RE_DPLX_GP_CHAN_TYPE); // Group Channel Operation 1089 m.setElement(i++, LnConstants.RE_DPLX_OP_TYPE_REPORT); // Report Operation 1090 1091 m.setElement(i++, (dupChan & 0x80) >> 7); 1092 m.setElement(i++, dupChan & 0x7f); 1093 1094 for (; i < LnConstants.RE_DPLX_OP_LEN; i++) { 1095 m.setElement(i, 0); 1096 } 1097 1098 return m; 1099 } 1100 1101 // the following code may be used to create a LocoNet message that follows the 1102 // form of the message sent by a UR92 in response to a Duplex Group Password query 1103 // LocoNet message. 1104 // No attempt is made to check the validity of the dupPass argument. 1105 public static final LocoNetMessage createUr92GroupPasswordReportPacket( 1106 String dupPass) { 1107 // format packet 1108 LocoNetMessage m = new LocoNetMessage(LnConstants.RE_DPLX_OP_LEN); 1109 int i = 0; 1110 m.setElement(i++, LnConstants.OPC_PEER_XFER); 1111 m.setElement(i++, LnConstants.RE_DPLX_OP_LEN); // 20-byte message 1112 m.setElement(i++, LnConstants.RE_DPLX_GP_PW_TYPE); // Group Password Operation 1113 m.setElement(i++, LnConstants.RE_DPLX_OP_TYPE_REPORT); // Report Operation 1114 1115 dupPass += "0000"; // NOI18N 1116 1117 m.setElement(i++, 1118 ((dupPass.charAt(0) & 0x80) == 0x80 ? 8 : 0) 1119 + ((dupPass.charAt(1) & 0x80) == 0x80 ? 4 : 0) 1120 + ((dupPass.charAt(2) & 0x80) == 0x80 ? 2 : 0) 1121 + ((dupPass.charAt(3) & 0x80) == 0x80 ? 1 : 0) 1122 ); 1123 m.setElement(i++, dupPass.charAt(0) & 0x7f); 1124 m.setElement(i++, dupPass.charAt(1) & 0x7f); 1125 m.setElement(i++, dupPass.charAt(2) & 0x7f); 1126 m.setElement(i++, dupPass.charAt(3) & 0x7f); 1127 1128 for (; i < LnConstants.RE_DPLX_OP_LEN; i++) { 1129 m.setElement(i, 0); 1130 } 1131 1132 return m; 1133 } 1134 1135 // the following code may be used to create a LocoNet message that follows the 1136 // form of the message sent by a UR92 in response to a Duplex Group Id query 1137 // LocoNet message. 1138 public static final LocoNetMessage createUr92GroupIdReportPacket( 1139 int dupId) { 1140 // format packet 1141 LocoNetMessage m = new LocoNetMessage(LnConstants.RE_DPLX_OP_LEN); 1142 int i = 0; 1143 m.setElement(i++, LnConstants.OPC_PEER_XFER); 1144 m.setElement(i++, LnConstants.RE_DPLX_OP_LEN); // 20-byte message 1145 m.setElement(i++, LnConstants.RE_DPLX_GP_ID_TYPE); // Group Id Operation 1146 m.setElement(i++, LnConstants.RE_DPLX_OP_TYPE_REPORT); // Report Operation 1147 1148 m.setElement(i++, (dupId & 0x80) >> 7); 1149 m.setElement(i++, dupId & 0x7f); 1150 1151 for (; i < LnConstants.RE_DPLX_OP_LEN; i++) { 1152 m.setElement(i, 0); 1153 } 1154 1155 return m; 1156 } 1157 1158 /** 1159 * Reports the number of UR92 devices which responded to the most-recent 1160 * LocoNet IPL query of UR92 devices. 1161 * <p> 1162 * Note that code should ignore the value returned by this method 1163 * if isWaitingForUr92DeviceReports() is true; 1164 * 1165 * @return the number of UR92 devices which reported in response to the 1166 * LocoNet IPL device query which is sent by this class. 1167 */ 1168 public int getNumUr92s() { 1169 return numUr92CompatibleType; 1170 } 1171 1172 /** 1173 * Reports whether this class is currently waiting for the first UR92 LocoNet 1174 * IPL Device Report messages in response to a LocoNet IPL Device Query for 1175 * UR92s sent by this class. 1176 * 1177 * @return true if the class is waiting for LocoNet IPL reply messages, else 1178 * false. 1179 */ 1180 public boolean isWaitingForFirstUr92IPLReport() { 1181 return waitingForIplReply; 1182 } 1183 1184 1185 /** 1186 * Reports the number of LocoNet messages handled since object construction. 1187 * 1188 * @return the number of LocoNet messages since this object was constructed. 1189 */ 1190 public int getMessagesHandled() { 1191 return messagesHandled; 1192 } 1193 1194 /** 1195 * Reports whether the IPL query timer is running. 1196 * 1197 * @return true if the timer is running, else false. 1198 */ 1199 public boolean isIplQueryTimerRunning() { 1200 return swingTmrIplQuery.isRunning(); 1201 } 1202 1203 /** 1204 * Reports whether the Duplex Group Info query timer is running. 1205 * 1206 * @return true if the timer is running, else false. 1207 */ 1208 public boolean isDuplexGroupQueryRunning() { 1209 return swingTmrDuplexInfoQuery.isRunning(); 1210 } 1211 1212 /** 1213 * Reports whether this object is currently waiting for 1214 * Duplex Group Name, etc. Report message. 1215 * 1216 * @return true if currently waiting, else false 1217 */ 1218 public boolean isAwaitingDuplexGroupReportMessage() { 1219 return awaitingGroupReadReport; 1220 } 1221 1222 // Property Change keys relating to GUI status line 1223 public final static String DPLX_PC_STAT_LN_UPDATE = "DPLXPCK_STAT_LN_UPDATE"; // NOI18N 1224 public final static String DPLX_PC_STAT_LN_UPDATE_IF_NOT_CURRENTLY_ERROR = "DPLXPCK_STAT_LN_ON_OVER_UPDATE"; // NOI18N 1225 1226 // Property Change keys relating to validity of identity info 1227 public final static String DPLX_PC_NAME_VALIDITY = "DPLXPCK_NAME_VALID"; // NOI18N 1228 public final static String DPLX_PC_CHANNEL_VALIDITY = "DPLXPCK_CH_VALID"; // NOI18N 1229 public final static String DPLX_PC_PASSWORD_VALIDITY = "DPLXPCK_PW_VALID"; // NOI18N 1230 public final static String DPLX_PC_ID_VALIDITY = "DPLXPCK_ID_VALID"; // NOI18N 1231 1232 // Property Change keys relating to identity info value changes 1233 public final static String DPLX_PC_NAME_UPDATE = "DPLXPCK_NAME_UPDATE"; // NOI18N 1234 public final static String DPLX_PC_CHANNEL_UPDATE = "DPLXPCK_CH_UPDATE"; // NOI18N 1235 public final static String DPLX_PC_PASSWORD_UPDATE = "DPLXPCK_PW_UPDATE"; // NOI18N 1236 public final static String DPLX_PC_ID_UPDATE = "DPLXPCK_ID_UPDATE"; // NOI18N 1237 1238 // Property Change keys relating to Duplex Group Identity LocoNet messages 1239 public final static String DPLX_PC_RCD_DPLX_IDENTITY_QUERY = "DPLXPCK_IDENTITY_QUERY"; // NOI18N 1240 public final static String DPLX_PC_RCD_DPLX_IDENTITY_REPORT = "DPLXPCK_IDENTITY_REPORT"; // NOI18N 1241 1242 public final static String DPLX_IPL_DEVICE_DETAILS = "DPLEXID"; // NOI18N 1243 public final static String DPLX_IPL_DEVICE_RESPONSE_DETAILS = "DPLEXDETAILS"; // NOI18N 1244 1245 /** 1246 * Connect this instance's LocoNetListener to the LocoNet Traffic Controller 1247 * 1248 * @param t LocoNet traffic controller 1249 */ 1250 public void connect(jmri.jmrix.loconet.LnTrafficController t) { 1251 if (t != null) { 1252 // connect to the LnTrafficController 1253 t.addLocoNetListener(~0, this); 1254 } 1255 } 1256 1257 /** 1258 * Break connection with the LnTrafficController and stop timers. 1259 */ 1260 public void dispose() { 1261 if (swingTmrIplQuery != null) { 1262 swingTmrIplQuery.stop(); 1263 } 1264 if (swingTmrDuplexInfoQuery != null) { 1265 swingTmrDuplexInfoQuery.stop(); 1266 } 1267 if (memo.getLnTrafficController() != null) { 1268 memo.getLnTrafficController().removeLocoNetListener(~0, this); 1269 } 1270 } 1271 1272 /* 1273 * This class is used for populating the IPL devices Table 1274 */ 1275 static protected class BasicIPLDeviceInfo { 1276 1277 protected BasicIPLDeviceInfo(String type, String serialNumber, String swVersion) { 1278 this.type = type; 1279 this.serialNumber = serialNumber; 1280 this.swVersion = swVersion; 1281 } 1282 1283 protected BasicIPLDeviceInfo(LocoNetMessage l) { 1284 switch (l.getElement(5)) { 1285 case LnConstants.RE_IPL_DIGITRAX_HOST_UR92: 1286 type = "UR92"; 1287 break; 1288 case LnConstants.RE_IPL_DIGITRAX_HOST_UR93: 1289 type = "UR93"; 1290 break; 1291 case LnConstants.RE_IPL_DIGITRAX_HOST_LNWI: 1292 type = "LNWI"; 1293 break; 1294 default: 1295 // should never get here 1296 type="NA"; 1297 } 1298 serialNumber = StringUtil.twoHexFromInt(l.getElement(12)) + StringUtil.twoHexFromInt ( l.getElement(11) ); 1299 swVersion = ((l.getElement(8) & 0x78) >> 3) + "." + ((l.getElement(8) & 0x7)); 1300 } 1301 1302 private String type; 1303 private String serialNumber; 1304 private String swVersion; 1305 1306 protected String getType() { 1307 return type; 1308 } 1309 protected String getSerialNumber() { 1310 return serialNumber; 1311 } 1312 protected String getSwVersion() { 1313 return swVersion; 1314 } 1315 1316 } 1317 1318 /* 1319 * This class is used for populating the Device Response Table 1320 */ 1321 static protected class BasicIPLDeviceResponseInfo { 1322 1323 protected BasicIPLDeviceResponseInfo(String groupName, String channel, String password, String groupId) { 1324 this.groupName = groupName; 1325 this.channel = channel; 1326 this.password = password; 1327 this.groupId = groupId; 1328 } 1329 1330 private String groupName; 1331 private String channel; 1332 private String password; 1333 private String groupId; 1334 1335 protected String getGroupName() { 1336 return groupName; 1337 } 1338 protected String getChannel() { 1339 return channel; 1340 } 1341 protected String getPassword() { 1342 return password; 1343 } 1344 protected String getGroupId() { 1345 return groupId; 1346 } 1347 1348 } 1349 1350 private final static Logger log = LoggerFactory.getLogger(LnDplxGrpInfoImpl.class); 1351 1352}