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