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    private static final String HEX_FORMAT = "0x%02X";
381
382    /**
383     * Format byte for decimal + (optional) hex display
384     *
385     */
386    public static String toHexStr(int value) {
387        if (value > 9) {
388            return value + " (" + String.format(HEX_FORMAT, value) + ")";
389        } else {
390            return String.valueOf(value);
391        }
392    }
393
394    /**
395     * Format byte for hex display
396     *
397     */
398    public static String toHexComposite(int low, int high) {
399        String res = String.valueOf(low);
400        if (high == 0) {
401            if (low > 9) {
402                res += " (" + String.format(HEX_FORMAT, low) + ")";
403            }
404            return res;
405        }
406        res += "/" + high;
407        if (low > 9 || high > 9) {
408            res += " (";
409            res += (low > 9 ? String.format(HEX_FORMAT, low) : low);
410            res += "/";
411            res += (high > 9 ? String.format(HEX_FORMAT, high) : high);
412            res += ")";
413        }
414        return res;
415    }
416
417    /**
418     *
419     * @param possibleCmd  integer to be compared to the command list
420     * @return  true if the possibleCmd value is one of the supported SV
421     *      Programming Format 1 commands
422     */
423    public static boolean isSupportedSv1Command(int possibleCmd) {
424        switch (possibleCmd) {
425            case (SV_CMD_WRITE_ONE):
426            case (SV_CMD_READ_ONE):
427                return true;
428            default:
429                return false;
430        }
431    }
432
433    /**
434     * Confirm a message specifies a valid (known) SV Programming Format 1 command.
435     *
436     * @return true if the SV1 message specifies a valid (known) SV Programming
437     *      Format 1 command.
438     */
439    public boolean isSupportedSv1Command() {
440        return isSupportedSv1Command(sv_cmd);
441    }
442
443    /**
444     *
445     * @return true if the SV1 message is a SV1 Read One Reply message
446     */
447    public boolean isSupportedSv1ReadOneReply() {
448        return (sv_cmd == SV_CMD_READ_ONE && src_l != 0x50 && vrs != 0);
449    }
450
451    /**
452     * Get the data from a SVs READ_ONE Reply message. May also be used to
453     * return the effective SV value reported in an SV1 WRITE_ONE Reply message (or is that returned in d8?).
454     *
455     * @return the {@code <D6>} value from the SV1 message
456     */
457    public int getSingleReadReportData() {
458        return d6;
459    }
460
461    public int getSrcL() {
462        return src_l;
463    }
464
465    public int getDstL() {
466        return dst_l;
467    }
468
469    /** Used to check message. LNSV1 messages do not use the DST_H field for high address */
470    public int getDstH() {
471        return dst_h;
472    }
473
474    /** Not returning a valid address because LNSV1 messages do not use the DST_H field for high address.
475     * and a composite address is not used.
476     * - LocoBuffer subaddress is always 1.
477     * - LocoIO subaddress is stored and fetched from PEER_XFER element SV1_SV_SUBADR_ELEMENT_INDEX (11).
478     * - JMRI LocoIO decoder address as stored in the Roster is calculated as a 14-bit number
479     * in jmri.jmrix.loconet.swing.lnsv1prog.Lnsv1ProgPane
480     */
481    public int getDestAddr() {
482        return -1;
483    }
484
485    public int getSubAddress() {
486        return sub_adr;
487    }
488
489    public int getCmd() {
490        return sv_cmd;
491    }
492
493    public int getSvNum() {
494        if ((sv_cmd == Sv1Command.SV1_READ.sv_cmd) ||
495                (sv_cmd == Sv1Command.SV1_WRITE.sv_cmd)) {
496            return sv_num;
497        }
498        return -1;
499    }
500
501    public int getSvValue() {
502        if (sv_cmd == Sv1Command.SV1_READ.sv_cmd) {
503            if (vrs > 0) { // Read reply
504                return d6;
505            } else {
506                return d4; // Read request
507            }
508        } else if (sv_cmd == Sv1Command.SV1_WRITE.sv_cmd) {
509            if (vrs > 0) {
510                return d8; // Write reply
511            } else {
512                return d4; // Write request
513            }
514        }
515        return -1;
516    }
517
518    public int getVersionNum() {
519        if (vrs > 0) {
520            return vrs;
521        }
522        return -1;
523    }
524
525    /**
526     * Get the d4 value
527     * @return d4 element contents
528     */
529    public int getSv1D4() {
530        return d4;
531    }
532
533    /**
534     * Get the d6 value
535     * @return d6 element contents
536     */
537    public int getSv1D6() {
538        return d6;
539    }
540
541    /**
542     * Get the d7 value
543     * @return d7 element contents
544     */
545    public int getSv1D7() {
546        return d7;
547    }
548
549    /**
550     * Get the d8 value
551     * @return d8 element contents
552     */
553    public int getSv1D8() {
554        return d8;
555    }
556
557    // ****** Create LNSV1 messages ***** //
558
559    /**
560     * Create a LocoNet message containing an SV Programming Format 0 message.
561     * Used only to simulate replies from LocoIO. Uses LNSV1_PEER_CODE_SV_VER0.
562     * <p>
563     * See Programmer message code in {@link jmri.jmrix.loconet.LnOpsModeProgrammer} loadSV1MessageFormat
564     *
565     * @param source  source device address (for &lt;SRC_L&gt;)
566     * @param destination = SV format 1 7-bit destination address (for &lt;DST_L&gt;)
567     * @param subAddress = SV format 1 7-bit destination subaddress (for &lt;DST_H&gt;)
568     * @param command  SV Programming Format 1 command number (for &lt;SV_CMD&gt;)
569     * @param svNum  SV Programming Format 1 8-bit SV number
570     * @param newVal (d4)  SV first 8-bit data value to write (for &lt;D4&gt;)
571     * @param version  Programming Format 1 8-bit firmware version number; 0 in request,{@literal >0} in replies
572     * @param d6  second 8-bit data value (for &lt;D6&gt;)
573     * @param d7  third 8-bit data value (for &lt;D7&gt;)
574     * @param d8  fourth 8-bit data value (for &lt;D8&gt;)
575     * @return LocoNet message for the requested message
576     * @throws IllegalArgumentException if command is not a valid SV Programming Format 1 &lt;SV_CMD&gt; value
577     */
578    public static LocoNetMessage createSv0Message (
579            int source,
580            int destination,
581            int subAddress,
582            int command,
583            int svNum,
584            int version,
585            int newVal,
586            int d6,
587            int d7,
588            int d8)
589            throws IllegalArgumentException {
590
591        if (! isSupportedSv1Command(command)) {
592            throw new IllegalArgumentException("Command is not a supported SV1 command"); // NOI18N
593        }
594        int[] contents = {command, svNum, version, newVal, subAddress, d6, d7, d8};
595        log.debug("createSv1Message src={} dst={} subAddr={} data[]={}", source, destination, subAddress, contents);
596        return LocoNetMessage.makePeerXfr(
597                source,
598                destination,
599                contents,
600                LNSV1_PEER_CODE_SV_VER0
601        );
602    }
603
604    /**
605     * Create a LocoNet message containing an SV Programming Format 1 message.
606     * <p>
607     * See Programmer message code in {@link jmri.jmrix.loconet.LnOpsModeProgrammer} loadSV1MessageFormat
608     *
609     * @param source  source device address (for &lt;SRC_L&gt;)
610     * @param destination = SV format 1 7-bit destination address (for &lt;DST_L&gt;)
611     * @param subAddress = SV format 1 7-bit destination subaddress (for &lt;DST_H&gt;)
612     * @param command  SV Programming Format 1 command number (for &lt;SV_CMD&gt;)
613     * @param svNum  SV Programming Format 1 8-bit SV number
614     * @param newVal (d4)  SV first 8-bit data value to write (for &lt;D4&gt;)
615     * @param version  Programming Format 1 8-bit firmware version number; 0 in request,{@literal >0} in replies
616     * @param d6  second 8-bit data value (for &lt;D6&gt;)
617     * @param d7  third 8-bit data value (for &lt;D7&gt;)
618     * @param d8  fourth 8-bit data value (for &lt;D8&gt;)
619     * @return LocoNet message for the requested message
620     * @throws IllegalArgumentException if command is not a valid SV Programming Format 1 &lt;SV_CMD&gt; value
621     */
622    public static LocoNetMessage createSv1Message (
623            int source,
624            int destination,
625            int subAddress,
626            int command,
627            int svNum,
628            int version,
629            int newVal,
630            int d6,
631            int d7,
632            int d8)
633            throws IllegalArgumentException {
634
635        if (! isSupportedSv1Command(command)) {
636            throw new IllegalArgumentException("Command is not a supported SV1 command"); // NOI18N
637        }
638        int[] contents = {command, svNum, version, newVal, subAddress, d6, d7, d8};
639        log.debug("createSv1Message src={} dst={} subAddr={} data[]={}", source, destination, subAddress, contents);
640        return LocoNetMessage.makePeerXfr(
641                source,
642                destination,
643                contents,
644                LNSV1_PEER_CODE_SV_VER1
645        );
646    }
647
648    /**
649     * Create LocoNet message for a query of an SV of this object.
650     *
651     * @param dst  address of the device to read from
652     * @param subAddress  subaddress of the device to read from
653     * @param svNum  SV number to read
654     * @return LocoNet message
655     */
656public static LocoNetMessage createSv1ReadRequest(int dst, int subAddress, int svNum) {
657    int dstExtr = dst | 0x0100; // force version 1 tag, cf. LnOpsModeProgrammer
658    log.debug("createSv1ReadRequest dst={} dstExtr={} subAddr={}", dst, dstExtr, subAddress);
659        return createSv1Message(LNSV1_LOCOBUFFER_ADDRESS, dstExtr, subAddress,
660                Sv1Command.SV1_READ.sv_cmd, svNum, 0,0, 0, 0, 0);
661    }
662
663    /**
664     * Simulate a read/probe reply for testing/developing.
665     *
666     * @param src board low address
667     * @param dst dest high address, usually 0x50 for LocoBuffer/PC
668     * @param subAddress board high address
669     * @param version fictional firmware version number to add
670     * @param svNum SV read
671     * @return LocoNet message containing the reply
672     */
673    public static LocoNetMessage createSv1ReadReply(int src, int dst, int subAddress, int version, int svNum, int returnValue) {
674        log.debug("createSv0ReadReply");
675        int dstExtr = dst | 0x0100; // force version 1 tag, cf. LnOpsModeProgrammer
676        return createSv0Message(src, dstExtr, subAddress,
677                Sv1Command.SV1_READ.sv_cmd,
678                svNum, version, 0, returnValue, 0, 0);
679    }
680
681    public static LocoNetMessage createSv1WriteRequest(int dst, int subAddress, int svNum, int newValue) {
682        log.debug("createSv1WriteRequest");
683        int dstExtr = dst | 0x0100; // force version 1 tag, cf. LnOpsModeProgrammer
684        return createSv1Message(LNSV1_LOCOBUFFER_ADDRESS, dstExtr, subAddress,
685                Sv1Command.SV1_WRITE.sv_cmd, svNum, 0, newValue, 0, 0, 0);
686    }
687
688    /**
689     * Simulate a read/probe reply for testing/developing.
690     *
691     * @param src board low address
692     * @param subAddress board high address
693     * @param dst dest high address, usually 0x1050 for LocoBuffer/PC
694     * @param version fictional firmware version number to add
695     * @param svNum SV read
696     * @return LocoNet message containing the reply
697     */
698    public static LocoNetMessage createSv1WriteReply(int src, int dst, int subAddress, int version, int svNum, int returnValue) {
699        log.debug("createSv0WriteReply");
700        int dstExtr = dst | 0x0100; // force version 1 tag, cf. LnOpsModeProgrammer
701        return createSv0Message(src, dstExtr, subAddress,
702                Sv1Command.SV1_WRITE.sv_cmd,
703                svNum, version, 0, 0, 0, returnValue);
704    }
705
706    /**
707     * Compose a message that changes the hardware board address of ALL connected
708     * LNSV1 (LocoIO) boards.
709     *
710     * @param address the new base address of the LocoIO board to change
711     * @param subAddress the new subAddress of the board
712     * @return an array containing one or two LocoNet messages
713     */
714    public static LocoNetMessage[] createBroadcastSetAddress(int address, int subAddress) {
715        LocoNetMessage[] messages = new LocoNetMessage[2];
716        messages[0] = createSv1WriteRequest(LNSV1_BROADCAST_ADDRESS, 0, 1, address & 0xFF);
717        if (subAddress != 0) {
718            messages[1] = createSv1WriteRequest(LNSV1_BROADCAST_ADDRESS, 0, 2, subAddress);
719        }
720        return messages;
721    }
722
723    /**
724     * Create a message to probe all connected LocoIO (LNSV1) units on a given LocoNet connection.
725     *
726     * @return the complete LocoNet message
727     */
728    public static LocoNetMessage createBroadcastProbeAll() {
729        return createSv1ReadRequest(LNSV1_BROADCAST_ADDRESS, 0, 2);
730    }
731
732    public enum Sv1Command {
733        SV1_WRITE (0x01),
734        SV1_READ (0x02);
735
736        private final int sv_cmd;
737
738        Sv1Command(int sv_cmd) {
739            this.sv_cmd = sv_cmd;
740        }
741
742        int getCmd() {return sv_cmd;}
743
744        public static int getCmd(Sv1Command mt) {
745            return mt.getCmd();
746        }
747    }
748
749    // initialize logging
750    private final static Logger log = LoggerFactory.getLogger(Lnsv1MessageContents.class);
751
752}