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}