001package jmri.jmrix.loconet.lnsvf1;
002
003import jmri.jmrix.loconet.LnConstants;
004import jmri.jmrix.loconet.LocoNetMessage;
005import jmri.jmrix.loconet.messageinterp.LocoNetMessageInterpret;
006import org.slf4j.Logger;
007import org.slf4j.LoggerFactory;
008
009import java.util.Locale;
010import java.util.Objects;
011
012/**
013 * Supporting class for LocoNet SV Programming Format 1 (LocoIO) messaging.
014 * <p>
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 * <p>
021 * Uses the LOCONETSV1MODE programming mode.
022 * <p>
023 * Uses LnProgrammer LOCOIO_PEER_CODE_SV_VER1 message format, comparable to DecoderPro LOCONETSV1MODE
024 * The DecoderPro decoder definition is recommended for all LocoIO versions. Requires JMRI 4.12 or later.
025 *
026 * @see jmri.jmrix.loconet.LnOpsModeProgrammer#message(LocoNetMessage)
027 *
028 * Programming SV's
029 * <p>
030 * The SV's in a LocoIO hardware module can be programmed using LocoNet OPC_PEER_XFER messages.
031 * <p>
032 * Commands for setting SV's:
033 * <p>
034 * PC to LocoIO LocoNet message (OPC_PEER_XFER)
035 * <pre><code>
036 * Code LNSV1_SV_READ       LNSV1_SV_WRITE
037 * 0xE5 OPC_PEER_XFER
038 * 0x10 2nd part of OpCode
039 * SRCL 0x50                0x50 // low address of LocoBuffer, high address assumed 1
040 * DSTL LocoIO address
041 * DSTH always 0x01
042 * PXCT1
043 * D1 LNSV1_SV_READ _or_    LNSV1_SV_WRITE // Read/Write command
044 * D2 SV number             SV number
045 * D3 0x00                  0x00
046 * D4 0x00                  New value byte to Write
047 * PXCT2
048 * D5 LocoIO sub-address    LocoIO sub-address
049 * D6 0x00                  0x00
050 * D7 0x00                  0x00
051 * D8 0x00                  0x00
052 * CHK Checksum             Checksum
053 * </code></pre>
054 *
055 * LocoIO to PC reply message (OPC_PEER_XFER)
056 * <pre><code>
057 * Code LNSV1__SV_READ      LNSV1__SV_WRITE
058 * 0xE5 OPC_PEER_XFER
059 * 0x10 2nd part of OpCode
060 * SRCL LocoIO low address  LocoIO low address
061 * DSTL 0x50                0x50 // address of LocoBuffer
062 * DSTH always 0x01         always 0x01
063 * PXCT1 MSB SV + version   // High order bits of SV and LocoIO version
064 * D1 LNSV1_READ _or_       LNSV1_WRITE // Copy of original Command
065 * D2 SV number requested   SV number requested
066 * D3 LSBs LocoIO version   // Lower 7 bits of LocoIO version
067 * D4 0x00                  0x00
068 * PXCT2 MSB Requested Data // High order bits of written cvValue
069 * D5 LocoIO Sub-address    LocoIO Sub-address
070 * D6 Requested Data        0x00
071 * D7 Requested Data + 1    0x00
072 * D8 Requested Data + 2    Written cvValue confirmed
073 * CHK Checksum             Checksum
074 * </code></pre>
075 *
076 * @author John Plocher 2006, 2007
077 * @author B. Milhaupt Copyright (C) 2015
078 * @author E. Broerse Copyright (C) 2025
079 */
080public class Lnsv1MessageContents {
081    public static final int LNSV1_BROADCAST_ADDRESS = 0x00; // LocoIO broadcast (addr_H = 1)
082    public static final int LNSV1_LOCOBUFFER_ADDRESS = 0x50; // LocoBuffer reserved address (addr_H = 1)
083    public static final int LNSV1_PEER_CODE_SV_VER0 = 0x00; // observed in read and write replies from LocoIO
084    public static final int LNSV1_PEER_CODE_SV_VER1 = 0x08; // for read and write requests, not for replies
085
086    private final int src_l;
087    private final int sv_cmd;
088    private final int dst_l;
089    private final int dst_h;
090    private final int sub_adr;
091    private final int sv_num;
092    private final int vrs;
093    private final int d4;
094    private final int d6;
095    private final int d7;
096    private final int d8;
097
098    // Helper to calculate LocoIO Sensor address from returned data is in LocoNetMessage
099
100    // LocoNet "SV 1 format" helper definitions: indexes into the LocoNet message
101    public final static int SV1_SV_SRC_L_ELEMENT_INDEX = 2;
102    public final static int SV1_SV_DST_L_ELEMENT_INDEX = 3;
103    public final static int SV1_SV_DST_H_ELEMENT_INDEX = 4;
104    public final static int SV1_SVX1_ELEMENT_INDEX = 5;
105    public final static int SV1_SV_CMD_ELEMENT_INDEX = 6;
106    public final static int SV1_SVX2_ELEMENT_INDEX = 10;
107
108    // decoding SV format 1 messages (versus other OCP_PEER_XFER messages with length 0x10) uses m.getPeerXfrData()
109    public final static int SV1_SVX1_ELEMENT_VALIDITY_CHECK_MASK = 0x70;
110    public final static int SV1_SVX1_ELEMENT_VALIDITY_CHECK_VALUE = 0x00;
111    public final static int SV1_SVX2_ELEMENT_VALIDITY_CHECK_MASK = 0x70;
112    public final static int SV0_SVX2_ELEMENT_VALIDITY_CHECK_VALUE = 0x10; // LNSV0 mask
113    public final static int SV1_SVX2_ELEMENT_VALIDITY_CHECK_VALUE = 0x00; // LNSV1 mask
114
115    // helpers for decoding SV_CMD, compare to ENUM Sv1Command below
116    public final static int SV_CMD_WRITE_ONE = 0x01;
117    public final static int SV_CMD_READ_ONE = 0x02;
118
119    /**
120     * Create a new Lnsv1MessageContents object from a LocoNet message.
121     *
122     * @param m LocoNet message containing an SV Programming Format 1 message
123     * @throws IllegalArgumentException if the LocoNet message is not a valid, supported
124     *      SV Programming Format 1 message
125     */
126    public Lnsv1MessageContents(LocoNetMessage m)
127            throws IllegalArgumentException {
128
129        log.debug("interpreting a LocoNet message - may be an SV1 message");  // NOI18N
130        if (!isSupportedSv1Message(m)) {
131            log.debug("interpreting a LocoNet message - is NOT an SV1 message");   // NOI18N
132            throw new IllegalArgumentException("LocoNet message is not an SV1 message"); // NOI18N
133        }
134        src_l = m.getElement(SV1_SV_SRC_L_ELEMENT_INDEX);
135        dst_l = m.getElement(SV1_SV_DST_L_ELEMENT_INDEX);
136        dst_h = m.getElement(SV1_SV_DST_H_ELEMENT_INDEX); // should always be 0x01
137
138        int[] d = m.getPeerXfrData();
139        sv_cmd = d[0];
140        sv_num = d[1];
141        vrs = d[2];
142        d4 = d[3];
143        sub_adr = d[4];
144        d6 = d[5];
145        d7 = d[6];
146        d8 = d[7];
147    }
148
149    /**
150     * Check a LocoNet message to determine if it is a valid SV Programming Format 1
151     *      message.
152     *
153     * @param m  LocoNet message to check
154     * @return true if LocoNet message m is a supported SV Programming Format 1
155     *      message, else false.
156     */
157    public static boolean isSupportedSv1Message(LocoNetMessage m) {
158        // must be OPC_PEER_XFER opcode
159        if (m.getOpCode() != LnConstants.OPC_PEER_XFER) {
160            log.debug ("cannot be SV1 message because not OPC_PEER_XFER");  // NOI18N
161            return false;
162        }
163
164        // Element 1 must be 0x10
165        if (m.getElement(1) != 0x10) {
166            log.debug ("cannot be SV1 message because elem. 1 not 0x10");  // NOI18N
167            return false;
168        }
169
170        if (m.getElement(SV1_SV_DST_H_ELEMENT_INDEX) != 1) {
171            log.debug ("cannot be SV1 message because elem. 4 not 0x01");  // NOI18N
172            return false;
173        }
174
175        // Check PXCT1
176        if ((m.getElement(SV1_SVX1_ELEMENT_INDEX)
177                & SV1_SVX1_ELEMENT_VALIDITY_CHECK_MASK)
178                != SV1_SVX1_ELEMENT_VALIDITY_CHECK_VALUE) {
179            log.debug ("cannot be SV1 message because SVX1 upper nibble wrong");  // NOI18N
180            return false;
181        }
182        // Check PXCT2
183        if ((m.getElement(SV1_SVX2_ELEMENT_INDEX) // SV0 Broadcast/Write from LocoBuffer
184                & SV1_SVX2_ELEMENT_VALIDITY_CHECK_MASK)
185                != SV0_SVX2_ELEMENT_VALIDITY_CHECK_VALUE) {
186            if ((m.getElement(SV1_SVX2_ELEMENT_INDEX) // SV1 Read/Write reply from LocoIO
187                    & SV1_SVX2_ELEMENT_VALIDITY_CHECK_MASK) != SV1_SVX2_ELEMENT_VALIDITY_CHECK_VALUE) {
188                log.debug("cannot be SV1 message because SVX2 upper nibble wrong: {}", // extra CHECK_VALUE for replies?
189                        m.getElement(SV1_SVX2_ELEMENT_INDEX) & SV1_SVX2_ELEMENT_VALIDITY_CHECK_MASK);  // NOI18N
190                return false;
191            }
192        }
193
194        // check the <SV_CMD> value
195        if (isSupportedSv1Command(m.getElement(SV1_SV_CMD_ELEMENT_INDEX))) {
196            log.debug("LocoNet message is a supported SV Format 1 message");
197            return true;
198        }
199        log.debug("LocoNet message is not a supported SV Format 1 message");  // NOI18N
200        return false;
201    }
202
203    /**
204     * Compare reply message against a specific SV Programming Format 1 message type.
205     *
206     * @param m  LocoNet message to be verified as an SV Programming Format 1 message
207     *      with the specified &lt;SV_CMD&gt; value
208     * @param svCmd  SV Programming Format 1 command to expect
209     * @return true if message is an SV Programming Format 1 message of the specified &lt;SV_CMD&gt;,
210     *      else false.
211     */
212    public static boolean isLnMessageASpecificSv1Command(LocoNetMessage m, Sv1Command svCmd) {
213        // must be OPC_PEER_XFER opcode
214        if (m.getOpCode() != LnConstants.OPC_PEER_XFER) {
215            log.debug ("cannot be SV1 message because not OPC_PEER_XFER");  // NOI18N
216            return false;
217        }
218
219        // length of OPC_PEER_XFER must be 0x10
220        if (m.getElement(1) != 0x10) {
221            log.debug ("cannot be SV1 message because not length 0x10");  // NOI18N
222            return false;
223        }
224
225        // The upper nibble of PXCT1 must be 0, and the upper nibble of PXCT2 must be 1 or 2.
226        // Check PCX1
227        if ((m.getElement(SV1_SVX1_ELEMENT_INDEX)
228                & SV1_SVX1_ELEMENT_VALIDITY_CHECK_MASK)
229                != SV1_SVX1_ELEMENT_VALIDITY_CHECK_VALUE) {
230            log.debug ("cannot be SV1 message because SVX1 upper nibble wrong");  // NOI18N
231            return false;
232        }
233        // Check PCX2
234        if ((m.getElement(SV1_SVX2_ELEMENT_INDEX) // SV0 Broadcast/Write from LocoBuffer
235                & SV1_SVX2_ELEMENT_VALIDITY_CHECK_MASK)
236                != SV0_SVX2_ELEMENT_VALIDITY_CHECK_VALUE) {
237            if ((m.getElement(SV1_SVX2_ELEMENT_INDEX) // SV1 Read/Write reply from LocoIO
238                    & SV1_SVX2_ELEMENT_VALIDITY_CHECK_MASK)
239                    != SV1_SVX2_ELEMENT_VALIDITY_CHECK_VALUE) {
240                log.debug ("cannot be SV1 message because SVX2 upper nibble wrong {}",
241                        m.getElement(SV1_SVX2_ELEMENT_INDEX) & SV1_SVX2_ELEMENT_VALIDITY_CHECK_MASK);  // NOI18N
242                return false;
243            }
244        }
245
246        // check the <SV_CMD> value
247        if (isSupportedSv1Command(m.getElement(SV1_SV_CMD_ELEMENT_INDEX))) {
248            log.debug("LocoNet message is a supported SV Format 1 message");  // NOI18N
249            if (Objects.equals(extractMessageType(m), svCmd)) {
250                log.debug("LocoNet message is the specified SV Format 1 message");  // NOI18N
251                return true;
252            }
253        }
254        log.debug("LocoNet message is not a supported SV Format 1 message");  // NOI18N
255        return false;
256    }
257
258    /**
259     * Interpret a LocoNet message to extract its SV Programming Format 1 &lt;SV_CMD&gt;.
260     * If the message is not an SV Programming Format 1 message, returns null.
261     *
262     * @param m  LocoNet message containing SV Programming Format 1 message
263     * @return Sv1Command found in the SV Programming Format 1 message or null if not found
264     */
265    public static Sv1Command extractMessageType(LocoNetMessage m) {
266        if (isSupportedSv1Message(m)) {
267            int msgCmd = m.getPeerXfrData()[0];
268            for (Sv1Command s: Sv1Command.values()) {
269                if (s.getCmd() == msgCmd) {
270                    log.debug("LocoNet message has SV1 message command {}", msgCmd);  // NOI18N
271                    return s;
272                }
273            }
274        }
275        return null;
276    }
277
278    /**
279     * Interpret a LocoNet message to extract its SV Programming Format 1 &lt;SV_CMD&gt;.
280     * If the message is not an SV Programming Format 1 message, return null.
281     *
282     * @param m  LocoNet message containing SV Programming Format 1 version field
283     * @return Version found in the SV Programming Format 1 message or -1 if not found
284     */
285    public static int extractMessageVersion(LocoNetMessage m) {
286        if (isSupportedSv1Message(m)) {
287            int v = m.getPeerXfrData()[2];
288            log.debug("LocoNet LNSV1 message contains version {}", v);  // NOI18N
289            return (v > 0 ? v : -1);
290        }
291        return -1;
292    }
293
294    /**
295     * Interpret the SV Programming Format 1 message into a human-readable string.
296     *
297     * @return String containing a human-readable version of the SV Programming
298     *      Format 1 message
299     */
300    @Override
301    public String toString() {
302        Locale l = Locale.getDefault();
303        return Lnsv1MessageContents.this.toString(l);
304    }
305
306    /**
307     * Interpret the SV Programming Format 1 message into a human-readable string.
308     *
309     * @param locale  locale to use for the human-readable string
310     * @return String containing a human-readable version of the SV Programming
311     *      Format 1 message, in the language specified by the Locale if the
312     *      properties have been translated to that Locale, else in the default
313     *      English language.
314     */
315    public String toString(Locale locale) {
316        String returnString;
317        log.debug("interpreting an SV1 message - sv_cmd is {}", sv_cmd);  // NOI18N
318
319        //  use Integer.toHexString(i) and/or String.format("0x%02X", i))
320        switch (sv_cmd) {
321            case (SV_CMD_WRITE_ONE):
322                if (src_l == 0x50) {
323                    if (dst_l == 0) {
324                        returnString = Bundle.getMessage(locale, "SV1_WRITE_ALL_INTERPRETED",
325                                (sv_num == 1 ? "" : "sub"), // makes sub+address // NOI18N
326                                toHexStr(sv_num),
327                                toHexStr(d4));
328                    } else if (dst_l == 0x50) {
329                        returnString = Bundle.getMessage(locale, "SV1_WRITE_LB_INTERPRETED",
330                                toHexStr(sv_num),
331                                toHexStr(d4),
332                                (vrs > 0) ? " Firmware rev " + LocoNetMessageInterpret.dotme(vrs) : ""); // in test, useful?
333                    } else {
334                        returnString = Bundle.getMessage(locale, "SV1_WRITE_INTERPRETED",
335                                toHexComposite(dst_l, sub_adr),
336                                toHexStr(sv_num),
337                                toHexStr(d4),
338                                (vrs > 0) ? " Firmware rev " + LocoNetMessageInterpret.dotme(vrs) : ""); // NOI18N
339                    }
340                } else {
341                    returnString = Bundle.getMessage(locale, "SV1_WRITE_REPLY_INTERPRETED",
342                            toHexComposite(src_l, sub_adr),
343                            toHexStr(sv_num),
344                            toHexStr(d8),
345                            (vrs > 0) ? " Firmware rev " + LocoNetMessageInterpret.dotme(vrs) : ""); // NOI18N
346                }
347                break;
348
349            case (SV_CMD_READ_ONE):
350                if (src_l == 0x50) {
351                    if (dst_l == 0) {
352                        returnString = Bundle.getMessage(locale, "SV1_PROBE_ALL_INTERPRETED");
353                    } else if (dst_l == 0x50) {
354                        returnString = Bundle.getMessage(locale, "SV1_READ_LB_INTERPRETED",
355                                toHexStr(sv_num),
356                                (vrs > 0) ? " Firmware rev " + LocoNetMessageInterpret.dotme(vrs) : ""); // in test, useful?
357                    } else {
358                        returnString = Bundle.getMessage(locale, "SV1_READ_INTERPRETED",
359                                toHexComposite(dst_l, sub_adr),
360                                toHexStr(sv_num),
361                                (vrs > 0) ? " Firmware rev " + LocoNetMessageInterpret.dotme(vrs) : "");
362                    }
363                } else {
364                    returnString = Bundle.getMessage(locale, "SV1_READ_REPLY_INTERPRETED",
365                            toHexComposite(src_l, sub_adr),
366                            toHexStr(sv_num),
367                            toHexStr(d6),
368                            (vrs > 0) ? " Firmware rev " + LocoNetMessageInterpret.dotme(vrs) : ""); // NOI18N
369                }
370                break;
371
372            default:
373                return Bundle.getMessage(locale, "SV1_UNDEFINED_MESSAGE") + "\n";
374        }
375
376        log.debug("interpreted: {}", returnString);  // NOI18N
377        return returnString + "\n"; // NOI18N
378    }
379
380    /**
381     * Format byte for decimal + (optional) hex display
382     *
383     */
384    public static String toHexStr(int value) {
385        if (value > 9) {
386            return value + " (" + String.format("0x%02X", value) + ")";
387        } else {
388            return String.valueOf(value);
389        }
390    }
391
392    /**
393     * Format byte for hex display
394     *
395     */
396    public static String toHexComposite(int low, int high) {
397        String res = String.valueOf(low);
398        if (high == 0) {
399            if (low > 9) {
400                res += " (" + String.format("0x%02X", low) + ")";
401            }
402            return res;
403        }
404        res += "/" + high;
405        if (low > 9 || high > 9) {
406            res += " (";
407            res += (low > 9 ? String.format("0x%02X", low) : low);
408            res += "/";
409            res += (high > 9 ? String.format("0x%02X", high) : high);
410            res += ")";
411        }
412        return res;
413    }
414
415    /**
416     *
417     * @param possibleCmd  integer to be compared to the command list
418     * @return  true if the possibleCmd value is one of the supported SV
419     *      Programming Format 1 commands
420     */
421    public static boolean isSupportedSv1Command(int possibleCmd) {
422        switch (possibleCmd) {
423            case (SV_CMD_WRITE_ONE):
424            case (SV_CMD_READ_ONE):
425                return true;
426            default:
427                return false;
428        }
429    }
430
431    /**
432     * Confirm a message specifies a valid (known) SV Programming Format 1 command.
433     *
434     * @return true if the SV1 message specifies a valid (known) SV Programming
435     *      Format 1 command.
436     */
437    public boolean isSupportedSv1Command() {
438        return isSupportedSv1Command(sv_cmd);
439    }
440
441    /**
442     *
443     * @return true if the SV1 message is a SV1 Read One Reply message
444     */
445    public boolean isSupportedSv1ReadOneReply() {
446        return (sv_cmd == SV_CMD_READ_ONE && src_l != 0x50 && vrs != 0);
447    }
448
449    /**
450     * Get the data from a SVs READ_ONE Reply message. May also be used to
451     * return the effective SV value reported in an SV1 WRITE_ONE Reply message (or is that returned in d8?).
452     *
453     * @return the {@code <D6>} value from the SV1 message
454     */
455    public int getSingleReadReportData() {
456        return d6;
457    }
458
459    public int getSrcL() {
460        return src_l;
461    }
462
463    public int getDstL() {
464        return dst_l;
465    }
466
467    /** Used to check message. LNSV1 messages do not use the DST_H field for high address */
468    public int getDstH() {
469        return dst_h;
470    }
471
472    /** Not returning a valid address because LNSV1 messages do not use the DST_H field for high address.
473     * and a composite address is not used.
474     * - LocoBuffer subaddress is always 1.
475     * - LocoIO subaddress is stored and fetched from PEER_XFER element SV1_SV_SUBADR_ELEMENT_INDEX (11).
476     * - JMRI LocoIO decoder address as stored in the Roster is calculated as a 14-bit number
477     * in jmri.jmrix.loconet.swing.lnsv1prog.Lnsv1ProgPane
478     */
479    public int getDestAddr() {
480        return -1;
481    }
482
483    public int getSubAddress() {
484        return sub_adr;
485    }
486
487    public int getCmd() {
488        return sv_cmd;
489    }
490
491    public int getSvNum() {
492        if ((sv_cmd == Sv1Command.SV1_READ.sv_cmd) ||
493                (sv_cmd == Sv1Command.SV1_WRITE.sv_cmd)) {
494            return sv_num;
495        }
496        return -1;
497    }
498
499    public int getSvValue() {
500        if (sv_cmd == Sv1Command.SV1_READ.sv_cmd) {
501            if (vrs > 0) { // Read reply
502                return d6;
503            } else {
504                return d4; // Read request
505            }
506        } else if (sv_cmd == Sv1Command.SV1_WRITE.sv_cmd) {
507            if (vrs > 0) {
508                return d8; // Write reply
509            } else {
510                return d4; // Write request
511            }
512        }
513        return -1;
514    }
515
516    public int getVersionNum() {
517        if (vrs > 0) {
518            return vrs;
519        }
520        return -1;
521    }
522
523    /**
524     * Get the d4 value
525     * @return d4 element contents
526     */
527    public int getSv1D4() {
528        return d4;
529    }
530
531    /**
532     * Get the d6 value
533     * @return d6 element contents
534     */
535    public int getSv1D6() {
536        return d6;
537    }
538
539    /**
540     * Get the d7 value
541     * @return d7 element contents
542     */
543    public int getSv1D7() {
544        return d7;
545    }
546
547    /**
548     * Get the d8 value
549     * @return d8 element contents
550     */
551    public int getSv1D8() {
552        return d8;
553    }
554
555    // ****** Create LNSV1 messages ***** //
556
557    /**
558     * Create a LocoNet message containing an SV Programming Format 0 message.
559     * Used only to simulate replies from LocoIO. Uses LNSV1_PEER_CODE_SV_VER0.
560     * <p>
561     * See Programmer message code in {@link jmri.jmrix.loconet.LnOpsModeProgrammer} loadSV1MessageFormat
562     *
563     * @param source  source device address (for &lt;SRC_L&gt;)
564     * @param destination = SV format 1 7-bit destination address (for &lt;DST_L&gt;)
565     * @param subAddress = SV format 1 7-bit destination subaddress (for &lt;DST_H&gt;)
566     * @param command  SV Programming Format 1 command number (for &lt;SV_CMD&gt;)
567     * @param svNum  SV Programming Format 1 8-bit SV number
568     * @param newVal (d4)  SV first 8-bit data value to write (for &lt;D4&gt;)
569     * @param version  Programming Format 1 8-bit firmware version number; 0 in request,{@literal >0} in replies
570     * @param d6  second 8-bit data value (for &lt;D6&gt;)
571     * @param d7  third 8-bit data value (for &lt;D7&gt;)
572     * @param d8  fourth 8-bit data value (for &lt;D8&gt;)
573     * @return LocoNet message for the requested message
574     * @throws IllegalArgumentException if command is not a valid SV Programming Format 1 &lt;SV_CMD&gt; value
575     */
576    public static LocoNetMessage createSv0Message (
577            int source,
578            int destination,
579            int subAddress,
580            int command,
581            int svNum,
582            int version,
583            int newVal,
584            int d6,
585            int d7,
586            int d8)
587            throws IllegalArgumentException {
588
589        if (! isSupportedSv1Command(command)) {
590            throw new IllegalArgumentException("Command is not a supported SV1 command"); // NOI18N
591        }
592        int[] contents = {command, svNum, version, newVal, subAddress, d6, d7, d8};
593        log.debug("createSv1Message src={} dst={} subAddr={} data[]={}", source, destination, subAddress, contents);
594        return LocoNetMessage.makePeerXfr(
595                source,
596                destination,
597                contents,
598                LNSV1_PEER_CODE_SV_VER0
599        );
600    }
601
602    /**
603     * Create a LocoNet message containing an SV Programming Format 1 message.
604     * <p>
605     * See Programmer message code in {@link jmri.jmrix.loconet.LnOpsModeProgrammer} loadSV1MessageFormat
606     *
607     * @param source  source device address (for &lt;SRC_L&gt;)
608     * @param destination = SV format 1 7-bit destination address (for &lt;DST_L&gt;)
609     * @param subAddress = SV format 1 7-bit destination subaddress (for &lt;DST_H&gt;)
610     * @param command  SV Programming Format 1 command number (for &lt;SV_CMD&gt;)
611     * @param svNum  SV Programming Format 1 8-bit SV number
612     * @param newVal (d4)  SV first 8-bit data value to write (for &lt;D4&gt;)
613     * @param version  Programming Format 1 8-bit firmware version number; 0 in request,{@literal >0} in replies
614     * @param d6  second 8-bit data value (for &lt;D6&gt;)
615     * @param d7  third 8-bit data value (for &lt;D7&gt;)
616     * @param d8  fourth 8-bit data value (for &lt;D8&gt;)
617     * @return LocoNet message for the requested message
618     * @throws IllegalArgumentException if command is not a valid SV Programming Format 1 &lt;SV_CMD&gt; value
619     */
620    public static LocoNetMessage createSv1Message (
621            int source,
622            int destination,
623            int subAddress,
624            int command,
625            int svNum,
626            int version,
627            int newVal,
628            int d6,
629            int d7,
630            int d8)
631            throws IllegalArgumentException {
632
633        if (! isSupportedSv1Command(command)) {
634            throw new IllegalArgumentException("Command is not a supported SV1 command"); // NOI18N
635        }
636        int[] contents = {command, svNum, version, newVal, subAddress, d6, d7, d8};
637        log.debug("createSv1Message src={} dst={} subAddr={} data[]={}", source, destination, subAddress, contents);
638        return LocoNetMessage.makePeerXfr(
639                source,
640                destination,
641                contents,
642                LNSV1_PEER_CODE_SV_VER1
643        );
644    }
645
646    /**
647     * Create LocoNet message for a query of an SV of this object.
648     *
649     * @param dst  address of the device to read from
650     * @param subAddress  subaddress of the device to read from
651     * @param svNum  SV number to read
652     * @return LocoNet message
653     */
654public static LocoNetMessage createSv1ReadRequest(int dst, int subAddress, int svNum) {
655    int dstExtr = dst | 0x0100; // force version 1 tag, cf. LnOpsModeProgrammer
656    log.debug("createSv1ReadRequest dst={} dstExtr={} subAddr={}", dst, dstExtr, subAddress);
657        return createSv1Message(LNSV1_LOCOBUFFER_ADDRESS, dstExtr, subAddress,
658                Sv1Command.SV1_READ.sv_cmd, svNum, 0,0, 0, 0, 0);
659    }
660
661    /**
662     * Simulate a read/probe reply for testing/developing.
663     *
664     * @param src board low address
665     * @param dst dest high address, usually 0x50 for LocoBuffer/PC
666     * @param subAddress board high address
667     * @param version fictional firmware version number to add
668     * @param svNum SV read
669     * @return LocoNet message containing the reply
670     */
671    public static LocoNetMessage createSv1ReadReply(int src, int dst, int subAddress, int version, int svNum, int returnValue) {
672        log.debug("createSv0ReadReply");
673        int dstExtr = dst | 0x0100; // force version 1 tag, cf. LnOpsModeProgrammer
674        return createSv0Message(src, dstExtr, subAddress,
675                Sv1Command.SV1_READ.sv_cmd,
676                svNum, version, 0, returnValue, 0, 0);
677    }
678
679    public static LocoNetMessage createSv1WriteRequest(int dst, int subAddress, int svNum, int newValue) {
680        log.debug("createSv1WriteRequest");
681        int dstExtr = dst | 0x0100; // force version 1 tag, cf. LnOpsModeProgrammer
682        return createSv1Message(LNSV1_LOCOBUFFER_ADDRESS, dstExtr, subAddress,
683                Sv1Command.SV1_WRITE.sv_cmd, svNum, 0, newValue, 0, 0, 0);
684    }
685
686    /**
687     * Simulate a read/probe reply for testing/developing.
688     *
689     * @param src board low address
690     * @param subAddress board high address
691     * @param dst dest high address, usually 0x1050 for LocoBuffer/PC
692     * @param version fictional firmware version number to add
693     * @param svNum SV read
694     * @return LocoNet message containing the reply
695     */
696    public static LocoNetMessage createSv1WriteReply(int src, int dst, int subAddress, int version, int svNum, int returnValue) {
697        log.debug("createSv0WriteReply");
698        int dstExtr = dst | 0x0100; // force version 1 tag, cf. LnOpsModeProgrammer
699        return createSv0Message(src, dstExtr, subAddress,
700                Sv1Command.SV1_WRITE.sv_cmd,
701                svNum, version, 0, 0, 0, returnValue);
702    }
703
704    /**
705     * Compose a message that changes the hardware board address of ALL connected
706     * LNSV1 (LocoIO) boards.
707     *
708     * @param address the new base address of the LocoIO board to change
709     * @param subAddress the new subAddress of the board
710     * @return an array containing one or two LocoNet messages
711     */
712    public static LocoNetMessage[] createBroadcastSetAddress(int address, int subAddress) {
713        LocoNetMessage[] messages = new LocoNetMessage[2];
714        messages[0] = createSv1WriteRequest(LNSV1_BROADCAST_ADDRESS, 0, 1, address & 0xFF);
715        if (subAddress != 0) {
716            messages[1] = createSv1WriteRequest(LNSV1_BROADCAST_ADDRESS, 0, 2, subAddress);
717        }
718        return messages;
719    }
720
721    /**
722     * Create a message to probe all connected LocoIO (LNSV1) units on a given LocoNet connection.
723     *
724     * @return the complete LocoNet message
725     */
726    public static LocoNetMessage createBroadcastProbeAll() {
727        return createSv1ReadRequest(LNSV1_BROADCAST_ADDRESS, 0, 2);
728    }
729
730    public enum Sv1Command {
731        SV1_WRITE (0x01),
732        SV1_READ (0x02);
733
734        private final int sv_cmd;
735
736        Sv1Command(int sv_cmd) {
737            this.sv_cmd = sv_cmd;
738        }
739
740        int getCmd() {return sv_cmd;}
741
742        public static int getCmd(Sv1Command mt) {
743            return mt.getCmd();
744        }
745    }
746
747    // initialize logging
748    private final static Logger log = LoggerFactory.getLogger(Lnsv1MessageContents.class);
749
750}