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