001package jmri.jmrix.loconet.uhlenbrock;
002
003import jmri.jmrix.loconet.LnConstants;
004import jmri.jmrix.loconet.LocoNetMessage;
005import jmri.jmrix.loconet.lnsvf2.Lnsv2MessageContents;
006
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010import java.util.Locale;
011import java.util.Objects;
012
013/**
014 * Supporting class for Uhlenbrock LocoNet LNCV Programming and Direct Format messaging.
015 * Structure adapted from {@link Lnsv2MessageContents}
016 * 
017 * Some of the message formats used in this class are Copyright Uhlenbrock.de
018 * and used with permission as part of the JMRI project. That permission does
019 * not extend to uses in other software products. If you wish to use this code,
020 * algorithm or these message formats outside of JMRI, please contact Uhlenbrock.
021 *
022 * @author Egbert Broerse Copyright (C) 2020, 2021
023 */
024public class LncvMessageContents {
025    private final int opc;
026    private final int src;
027    private final int dst_l;
028    private final int dst_h;
029    private final int dst;
030    private final int cmd;
031    private final int art_l; // D1
032    private final int art_h; // D2
033    private final int art;
034    private final String sArt;
035    private final int cvn_l; // D3
036    private final int cvn_h; // D4
037    private final int cvn;
038    private final String sCvn;
039    private final int mod_l; // D5
040    private final int mod_h; // D6
041    private final int mod;
042    private final String sMod;
043    private final int cmd_data; // D7
044    private final LncvCommand command;
045
046    // LocoNet "LNCV format" helper definitions: length byte value for LNCV message
047    public final static int LNCV_LENGTH_ELEMENT_VALUE = 0x0f;
048    public final static int LNCV_LNMODULE_VALUE = 0x05;
049    public final static int LNCV_CS_SRC_VALUE = 0x01;
050    public final static int LNCV_PC_SRC_VALUE = 0x08;
051    public final static int LNCV_CSDEST_VALUE = 0x4b49;
052    public final static int LNCV_ALL = 0xffff; // decimal 65535
053    public final static int LNCV_ALL_MASK = 0xff00; // decimal 65535
054    // the valid range for module addresses (CV0) as per the LNCV spec.
055    public final static int LNCV_MIN_MODULEADDR = 0;
056    public final static int LNCV_MAX_MODULEADDR = 65534;
057
058    // LocoNet "LNCV format" helper definitions: indexes into the LocoNet message
059    public final static int LNCV_LENGTH_ELEMENT_INDEX = 1;
060    public final static int LNCV_SRC_ELEMENT_INDEX = 2;
061    public final static int LNCV_DST_L_ELEMENT_INDEX = 3;
062    public final static int LNCV_DST_H_ELEMENT_INDEX = 4;
063    public final static int LNCV_CMD_ELEMENT_INDEX = 5;
064    public final static int PXCT1_ELEMENT_INDEX = 6;
065    public final static int LNCV_ART_L_ELEMENT_INDEX = 7;
066    public final static int LNCV_ART_H_ELEMENT_INDEX = 8;
067    public final static int LNCV_CVN_L_ELEMENT_INDEX = 9;
068    public final static int LNCV_CVN_H_ELEMENT_INDEX = 10;
069    public final static int LNCV_MOD_L_ELEMENT_INDEX = 11; // val_l reply is in same positions as mod_l read
070    public final static int LNCV_MOD_H_ELEMENT_INDEX = 12; // val_h reply is in same positions as mod_h read
071    public final static int LNCV_CMDDATA_ELEMENT_INDEX = 13;
072    // Checksum = index 14
073
074    //  helpers for decoding CV format 2 messages (no other OCP_PEER_XFER messages with length 0x0f)
075    public final static int LNCV_SRC_ELEMENT_MASK = 0x7f;
076    public final static int PXCT1_ELEMENT_VALIDITY_CHECK_MASK = 0x70;
077    public final static int LNCV_ART_L_ARTL7_CHECK_MASK = 0x01;
078    public final static int LNCV_ART_H_ARTH7_CHECK_MASK = 0x02;
079    public final static int LNCV_CVN_L_CVNL7_CHECK_MASK = 0x04;
080    public final static int LNCV_CVN_H_CVNH7_CHECK_MASK = 0x08;
081    public final static int LNCV_MOD_L_MODL7_CHECK_MASK = 0x10;
082    public final static int LNCV_MOD_H_MODH7_CHECK_MASK = 0x20;
083    public final static int LNCV_CMDDATA_DAT7_CHECK_MASK = 0x40;
084
085    // LocoNet "LNCV format" helper definitions for data
086    //    public final static int LNCV_DATA_START = 0x00;
087    //    public final static int LNCV_DATA_END = 0x40;
088    public final static int LNCV_DATA_PROFF_MASK = 0x40;
089    public final static int LNCV_DATA_PRON_MASK = 0x80;
090    public final static int LNCV_DATA_LED1_MASK = 0xff;
091    public final static int LNCV_DATA_LED2_MASK = 0xfe;
092    public final static int LNCV_DATA_RO_MASK = 0x01;
093
094    // helpers for decoding LNCV_CMD
095    public final static int LNCV_CMD_WRITE = 0x20;
096    public final static int LNCV_CMD_READ = 0x21;
097    public final static int LNCV_CMD_READ_REPLY = 0x1f; // reply to both LNCV_CMD_READ and ENTER_PROG_MOD (in which case CV0 VAL = MOD)
098    // reply to LNCV_CMD_WRITE = LACK, already defined as general LocoNet message type
099
100
101    /**
102     * Create a new LncvMessageContents object from a LocoNet message.
103     *
104     * @param m LocoNet message containing an LNCV Programming Format message
105     * @throws IllegalArgumentException if the LocoNet message is not a valid, supported LNCV Programming Format
106     *                                  message
107     */
108    public LncvMessageContents(LocoNetMessage m) throws IllegalArgumentException {
109
110        //log.debug("interpreting a LocoNet message - may be an LNCV message");  // NOI18N
111        if (!isSupportedLncvMessage(m)) {
112            //log.debug("interpreting a LocoNet message - is NOT an LNCV message");   // NOI18N
113            throw new IllegalArgumentException("LocoNet message is not an LNCV message"); // NOI18N
114        }
115        this.command = extractMessageType(m);
116
117        opc = m.getOpCode();
118        src = m.getElement(LNCV_SRC_ELEMENT_INDEX);
119
120        dst_l = m.getElement(LNCV_DST_L_ELEMENT_INDEX);
121        dst_h = m.getElement(LNCV_DST_H_ELEMENT_INDEX);
122        dst = dst_l + (256 * dst_h);
123        log.debug("src={}, dst={}{}", src, dst, (dst == 19273 ? "=IK" : "")); // must use vars for CI
124
125        cmd = m.getElement(LNCV_CMD_ELEMENT_INDEX);
126
127        int pxct1 = m.getElement(PXCT1_ELEMENT_INDEX);
128        String svx1bin = String.format("%8s", Integer.toBinaryString(pxct1)).replace(' ', '0');
129        log.debug("PXCT1 HIBITS = {}", svx1bin);
130
131        art_l = m.getElement(LNCV_ART_L_ELEMENT_INDEX) + (((pxct1 & LNCV_ART_L_ARTL7_CHECK_MASK) == LNCV_ART_L_ARTL7_CHECK_MASK) ? 0x80 : 0);
132        art_h = m.getElement(LNCV_ART_H_ELEMENT_INDEX) + (((pxct1 & LNCV_ART_H_ARTH7_CHECK_MASK) == LNCV_ART_H_ARTH7_CHECK_MASK) ? 0x80 : 0);
133        art = art_l + (256 * art_h);
134        sArt = art + "";
135
136        cvn_l = m.getElement(LNCV_CVN_L_ELEMENT_INDEX) + (((pxct1 & LNCV_CVN_L_CVNL7_CHECK_MASK) == LNCV_CVN_L_CVNL7_CHECK_MASK) ? 0x80 : 0);
137        cvn_h = m.getElement(LNCV_CVN_H_ELEMENT_INDEX) + (((pxct1 & LNCV_CVN_H_CVNH7_CHECK_MASK) == LNCV_CVN_H_CVNH7_CHECK_MASK) ? 0x80 : 0);
138        cvn = cvn_l + (256 * cvn_h);
139        sCvn = cvn + "";
140
141        mod_l = m.getElement(LNCV_MOD_L_ELEMENT_INDEX) + (((pxct1 & LNCV_MOD_L_MODL7_CHECK_MASK) == LNCV_MOD_L_MODL7_CHECK_MASK) ? 0x80 : 0);
142        mod_h = m.getElement(LNCV_MOD_H_ELEMENT_INDEX) + (((pxct1 & LNCV_MOD_H_MODH7_CHECK_MASK) == LNCV_MOD_H_MODH7_CHECK_MASK) ? 0x80 : 0);
143        mod = mod_l + (256 * mod_h);
144        sMod = mod + "";
145
146        cmd_data = m.getElement(LNCV_CMDDATA_ELEMENT_INDEX) + (((pxct1 & LNCV_CMDDATA_DAT7_CHECK_MASK) == LNCV_CMDDATA_DAT7_CHECK_MASK) ? 0x80 : 0);
147    }
148
149    /**
150     * Check a LocoNet message to determine if it is a valid LNCV Programming Format message.
151     *
152     * @param m LocoNet message to check
153     * @return true if LocoNet message m is a supported LNCV Programming Format message, else false.
154     */
155    public static boolean isSupportedLncvMessage(LocoNetMessage m) {
156        // must be OPC_PEER_XFER or OPC_IMM_PACKET opcode
157        if ((m.getOpCode() != LnConstants.OPC_PEER_XFER) && (m.getOpCode() != LnConstants.OPC_IMM_PACKET)) {
158            //log.debug("cannot be LNCV message because not OPC_PEER_XFER (0xe5) or OPC_IMM_PACKET (0xed)");  // NOI18N
159            return false;
160        }
161
162        // length must be 0x0f
163        if (m.getElement(1) != LNCV_LENGTH_ELEMENT_VALUE) {
164            //log.debug("cannot be LNCV message because not length 0x0f");  // NOI18N
165            return false;
166        }
167
168        // <SRC_ELEMENT> must be correct
169        if ((m.getElement(LNCV_SRC_ELEMENT_INDEX) != LNCV_CS_SRC_VALUE) && (m.getElement(LNCV_SRC_ELEMENT_INDEX) != LNCV_LNMODULE_VALUE) && (m.getElement(LNCV_SRC_ELEMENT_INDEX) != LNCV_PC_SRC_VALUE)) {
170            //log.debug("cannot be LNCV message because Source not correct");  // NOI18N
171            return false;
172        }
173
174        // "command_data" identifier must be correct. handled via Enum
175        // check the (compound) command element
176        int msgData = m.getElement(LNCV_CMDDATA_ELEMENT_INDEX) | (((m.getElement(PXCT1_ELEMENT_INDEX) & LNCV_CMDDATA_DAT7_CHECK_MASK) == LNCV_CMDDATA_DAT7_CHECK_MASK) ? 0x80 : 0);
177        return isSupportedLncvCommand(m.getElement(LNCV_CMD_ELEMENT_INDEX), m.getOpCode(), msgData);
178    }
179
180    /**
181     * Compare reply message against a specific LNCV Programming Format message type.
182     *
183     * @param m  LocoNet message to be verified as an LNCV Programming Format message with the specified
184     *           &lt;LNCV_CMD&gt; value
185     * @param lncvCmd LNCV Programming Format command to check against
186     * @return true if message is an LNCV Programming Format message of the specified &lt;LNCV_CMD&gt;, else false.
187     */
188    public static boolean isLnMessageASpecificLncvCommand(LocoNetMessage m, LncvCommand lncvCmd) {
189        if (!isSupportedLncvMessage(m)) {
190            log.debug("rejected in isLnMessageASpecificLncvCommand");
191            return false;
192        }
193        // compare the <LNCV_CMD> value against cvCmd
194        return Objects.equals(extractMessageType(m), lncvCmd);
195    }
196
197    /**
198     * Interpret a LocoNet message to determine its LNCV compound Programming Format.
199     * If the message is not an LNCV Programming/Direct Format message, returns null.
200     *
201     * @param m LocoNet message containing LNCV Programming Format message
202     * @return LncvCommand found in the LNCV Programming Format message or null if not found
203     */
204    public static LncvCommand extractMessageType(LocoNetMessage m) {
205        if (isSupportedLncvMessage(m)) {
206            int msgCmd = m.getElement(LNCV_CMD_ELEMENT_INDEX);
207            int msgData = m.getElement(LNCV_CMDDATA_ELEMENT_INDEX) | (((m.getElement(PXCT1_ELEMENT_INDEX) & LNCV_CMDDATA_DAT7_CHECK_MASK) == LNCV_CMDDATA_DAT7_CHECK_MASK) ? 0x80 : 0);
208            //log.debug("msgData = {}", msgData);
209            for (LncvCommand c : LncvCommand.values()) {
210                if (c.matches(msgCmd, m.getOpCode(), msgData)) {
211                    //log.debug("LncvCommand match found");  // NOI18N
212                    return c;
213                }
214            }
215        }
216        return null;
217    }
218
219    /**
220     * Interpret the LNCV Programming Format message into a human-readable string.
221     *
222     * @return String containing a human-readable version of the LNCV Programming Format message
223     */
224    @Override
225    public String toString() {
226        Locale l = Locale.getDefault();
227        return LncvMessageContents.this.toString(l);
228    }
229
230    /**
231     * Interpret the LNCV Programming Format message into a human-readable string.
232     *
233     * @param locale locale to use for the human-readable string
234     * @return String containing a human-readable version of the LNCV Programming Format message, in the language
235     * specified by the Locale, if the properties have been translated to that Locale, else in the default English
236     * language.
237     */
238    public String toString(Locale locale) {
239        String returnString;
240        //log.debug("interpreting an LNCV message - simple cmd is {}", cmd);  // NOI18N
241
242        switch (this.command) {
243            case LNCV_PROG_START:
244                if ((art & LNCV_ALL_MASK) == LNCV_ALL_MASK) {
245                    returnString = Bundle.getMessage(locale, "LNCV_ALL_PROG_START_INTERPRETED");
246                } else if ((mod & LNCV_ALL_MASK) == LNCV_ALL_MASK) {
247                    returnString = Bundle.getMessage(locale, "LNCV_ART_PROG_START_INTERPRETED", sArt);
248                } else {
249                    returnString = Bundle.getMessage(locale, "LNCV_MOD_PROG_START_INTERPRETED", sArt, sMod);
250                }
251                break;
252            case LNCV_PROG_END:
253                if ((art & LNCV_ALL_MASK) == LNCV_ALL_MASK) {
254                    returnString = Bundle.getMessage(locale, "LNCV_ALL_PROG_END_INTERPRETED");
255                } else if ((mod & LNCV_ALL_MASK) == LNCV_ALL_MASK) {
256                    returnString = Bundle.getMessage(locale, "LNCV_ART_PROG_END_INTERPRETED", sArt);
257                } else {
258                    returnString = Bundle.getMessage(locale, "LNCV_MOD_PROG_END_INTERPRETED", sArt, sMod);
259                }
260                break;
261            case LNCV_WRITE: // mod positions store CV value in ReadReply
262                returnString = Bundle.getMessage(locale, "LNCV_WRITE_INTERPRETED", sArt, sCvn, sMod);
263                break;
264            case LNCV_READ:
265                // read = module prog start
266                returnString = Bundle.getMessage(locale, "LNCV_READ_INTERPRETED", sArt, sMod, sCvn);
267                break;
268            case LNCV_READ_REPLY: // mod positions store CV value in ReadReply
269            case LNCV_READ_REPLY2: // for Digikeijs DK5088RC not following specs
270                returnString = Bundle.getMessage(locale, "LNCV_READ_REPLY_INTERPRETED", sArt, sCvn, sMod);
271                break;
272            case LNCV_DIRECT_LED1: // CV position contains module address, Value position contains LED 0-15 on/off
273                returnString = Bundle.getMessage(locale, "LNCV_DIRECT_INTERPRETED", "1", toBinString(mod), sCvn);
274                break;
275            case LNCV_DIRECT_LED2: // CV position contains module address, Value position contains LED 16-31 on/off
276                returnString = Bundle.getMessage(locale, "LNCV_DIRECT_INTERPRETED", "2", toBinString(mod), sCvn);
277                //to16Bits(cvn, true));
278                break;
279            case LNCV_DIRECT_REPLY: // CV position contains module address, value position = Button on/off message
280                returnString = Bundle.getMessage(locale, "LNCV_DIRECT_REPLY_INTERPRETED", sCvn, sMod);
281                break;
282            default:
283                return Bundle.getMessage(locale, "LNCV_UNDEFINED_MESSAGE") + "\n";
284        }
285
286        return returnString + "\n"; // NOI18N
287    }
288
289    /**
290     * Convert binary integer to "1010" representation string.
291     *
292     * @param bin integer to convert to binary display string
293     */
294    private String toBinString(int bin) {
295        return String.format("%8s", Integer.toBinaryString(bin)).replace(' ', '0');
296    }
297
298
299    /**
300     * Check set of parameters against compound {@link LncvCommand} enum set.
301     *
302     * @param command LNCV CMD value
303     * @param opc     OPC value
304     * @param cmdData LNCV cmdData value
305     * @return true if the possibleCmd value is one of the supported (simple) LNCV Programming Format commands
306     */
307    public static boolean isSupportedLncvCommand(int command, int opc, int cmdData) {
308        //log.debug("CMD = {}-{}-{}", command, opc, cmdData);
309        for (LncvCommand commandToCheck : LncvCommand.values()) {
310            if (commandToCheck.matches(command, opc, cmdData)) {
311                return true;
312            }
313        }
314        return false;
315    }
316
317    /**
318     * Confirm a message corresponds with a valid (known) LNCV Programming Format command.
319     *
320     * @return true if the LNCV message specifies a valid (known) LNCV Programming Format command.
321     */
322    public boolean isSupportedLncvCommand() {
323        return isSupportedLncvCommand(cmd, opc, cmd_data);
324    }
325
326    /**
327     * @return true if the LNCV message is an LNCV ReadReply message
328     */
329    public boolean isSupportedLncvReadReply() {
330        return (cmd == LNCV_CMD_READ_REPLY);
331    }
332
333    /**
334     * Create a LocoNet message containing an LNCV Programming Format message.
335     *
336     * @param opc         Opcode (&lt;OPC&gt;), see LnConstants
337     * @param source      source device (&lt;SRC&gt;)
338     * @param destination destination address (for &lt;DST_L&gt; and &lt;DST_H&gt;)
339     * @param command     LNCV Programming simple command (for &lt;LNCV_CMD&gt;), part of
340     *                    complex command {@link LncvCommand}
341     * @param articleNum  manufacturer's hardware class/article code as per specs (4 decimal digits)
342     * @param cvNum       CV number (for &lt;LNCV_CVN_L&gt; and &lt;LNCV_CVN_H&gt;)
343     * @param moduleNum   module address (for &lt;LNCV_MOD_L&gt; and &lt;LNCV_MOD_H&gt;),
344     *                    same field is used for CV Value in WRITE to and READ_REPLY from Module
345     * @param cmdData     signals programming start/stop: LNCV_DATA_PRON/LNCV_DATA_PROFF
346     * @return LocoNet message for the requested instruction
347     * @throws IllegalArgumentException of command is not a valid LNCV Programming Format &lt;LNCV_CMD&gt; value
348     */
349    public static LocoNetMessage createLncvMessage(int opc,
350                                                   int source,
351                                                   int destination,
352                                                   int command,
353                                                   int articleNum,
354                                                   int cvNum,
355                                                   int moduleNum,
356                                                   int cmdData) throws IllegalArgumentException {
357
358        if (!isSupportedLncvCommand(command, opc, cmdData)) {
359            throw new IllegalArgumentException("Command is not a supported LNCV command"); // NOI18N
360        }
361        LocoNetMessage m = new LocoNetMessage(LNCV_LENGTH_ELEMENT_VALUE);
362
363        m.setOpCode(opc);
364        m.setElement(LNCV_LENGTH_ELEMENT_INDEX, LNCV_LENGTH_ELEMENT_VALUE);
365        m.setElement(LNCV_SRC_ELEMENT_INDEX, source);
366        m.setElement(LNCV_DST_L_ELEMENT_INDEX, (destination & 0xff));
367        m.setElement(LNCV_DST_H_ELEMENT_INDEX, (destination >> 8));
368        //log.debug("element {} = command = {}", LNCV_CMD_ELEMENT_INDEX, command);
369        m.setElement(LNCV_CMD_ELEMENT_INDEX, command);
370
371        int svx1 = 0x0;
372        svx1 = svx1 + (((articleNum & 0x80) == 0x80) ? LNCV_ART_L_ARTL7_CHECK_MASK : 0);
373        svx1 = svx1 + (((articleNum & 0x8000) == 0x8000) ? LNCV_ART_H_ARTH7_CHECK_MASK : 0);
374        svx1 = svx1 + (((cvNum & 0x80) == 0x80) ? LNCV_CVN_L_CVNL7_CHECK_MASK : 0);
375        svx1 = svx1 + (((cvNum & 0x8000) == 0x8000) ? LNCV_CVN_H_CVNH7_CHECK_MASK : 0);
376        svx1 = svx1 + (((moduleNum & 0x80) == 0x80) ? LNCV_MOD_L_MODL7_CHECK_MASK : 0);
377        svx1 = svx1 + (((moduleNum & 0x8000) == 0x8000) ? LNCV_MOD_H_MODH7_CHECK_MASK : 0);
378        //("Fetching hi bit {} of cmdData, value = {}", ((cmdData & 0x80) == 0x80), cmdData);
379        svx1 = svx1 + (((cmdData & 0x80) == 0x80) ? LNCV_CMDDATA_DAT7_CHECK_MASK : 0);
380        // bit 7 always 0
381        m.setElement(PXCT1_ELEMENT_INDEX, svx1);
382
383        m.setElement(LNCV_ART_L_ELEMENT_INDEX, (articleNum & 0x7f));
384        m.setElement(LNCV_ART_H_ELEMENT_INDEX, ((articleNum >> 8) & 0x7f));
385        m.setElement(LNCV_CVN_L_ELEMENT_INDEX, (cvNum & 0x7f));
386        m.setElement(LNCV_CVN_H_ELEMENT_INDEX, ((cvNum >> 8) & 0x7f));
387        m.setElement(LNCV_MOD_L_ELEMENT_INDEX, (moduleNum & 0x7f));
388        //log.debug("LNCV MOD_L = {}", m.getElement(LNCV_MOD_L_ELEMENT_INDEX));
389        m.setElement(LNCV_MOD_H_ELEMENT_INDEX, ((moduleNum >> 8) & 0x7f));
390        //log.debug("LNCV MOD_H = {}", m.getElement(LNCV_MOD_H_ELEMENT_INDEX));
391        m.setElement(LNCV_CMDDATA_ELEMENT_INDEX, (cmdData & 0x7f));
392
393        //log.debug("LocoNet Message ready, cmd = {}", m.getElement(LNCV_CMD_ELEMENT_INDEX));
394        return m;
395    }
396
397    /**
398     * Create LNCV message from {@link LncvCommand} enum plus specific parameter values.
399     *
400     * @param source source device (&lt;SRC&gt;)
401     * @param destination destination address (for &lt;DST_L&gt; and &lt;DST_H&gt;)
402     * @param command one of the composite LncvCommand's
403     * @param articleNum manufacturer's hardware class/article code as per specs
404     * @param cvNum 16-bit CV number (for &lt;LNCV_CVN_L&gt; and &lt;LNCV_CVN_H&gt;)
405     * @param moduleNum module address (for &lt;LNCV_MOD_L&gt; and &lt;LNCV_MOD_H&gt;),
406     *                    same field is used for CV Value in WRITE to and READ_REPLY from Module
407     * @return LocoNet message for the requested instruction
408     */
409    public static LocoNetMessage createLncvMessage(int source, int destination, LncvCommand command, int articleNum, int cvNum, int moduleNum) {
410        return createLncvMessage(command.getOpc(), source, destination, command.getCmd(), articleNum, cvNum, moduleNum, command.getCmdData());
411    }
412
413    public int getCmd() {
414        return cmd;
415    }
416
417    public int getCvNum() {
418        if ((cmd == LncvCommand.LNCV_READ.cmd) ||
419                (cmd == LncvCommand.LNCV_WRITE.cmd) ||
420                (cmd == LncvCommand.LNCV_READ_REPLY.cmd) ||
421                (cmd == LncvCommand.LNCV_READ_REPLY2.cmd)) {
422            return cvn;
423        }
424        return -1;
425    }
426
427    public int getCvValue() {
428        if ((cmd == LncvCommand.LNCV_READ_REPLY.cmd) ||
429                (cmd == LncvCommand.LNCV_READ_REPLY2.cmd) ||
430                (cmd == LncvCommand.LNCV_WRITE.cmd)) {
431            return mod;
432        }
433        return -1;
434    }
435
436    public int getLncvArticleNum() {
437        if ((cmd == LncvCommand.LNCV_READ.cmd) ||
438                (cmd == LncvCommand.LNCV_WRITE.cmd) ||
439                (cmd == LncvCommand.LNCV_READ_REPLY.cmd) ||
440                (cmd == LncvCommand.LNCV_READ_REPLY2.cmd) ||
441                (cmd == LncvCommand.LNCV_PROG_START.cmd && art != LNCV_ALL) ||
442                (cmd == LncvCommand.LNCV_PROG_END.cmd && art != LNCV_ALL)) {
443            return art;
444        }
445        return -1;
446    }
447
448    public int getLncvModuleNum() {
449        if (cmd == LncvCommand.LNCV_READ.cmd ||
450                (cmd == LncvCommand.LNCV_PROG_START.cmd && art != LNCV_ALL)||
451                (cmd == LncvCommand.LNCV_PROG_END.cmd && art != LNCV_ALL)) {
452            return mod;
453        }
454        return -1;
455    }
456    
457    /**
458     * Create LocoNet broadcast message to start LNCV programming.
459     *
460     * @param articleNum LNCV device type number used as filter to respond. Leave this out to 'broadcast' to
461     *                   all connected devices (which works for discovery purpose only)
462     * @return LocoNet message
463     */
464    public static LocoNetMessage createAllProgStartRequest(int articleNum) {
465        return createLncvMessage(
466                0x1,
467                0x5,
468                LncvCommand.LNCV_PROG_START,
469                (articleNum > -1 ? articleNum : LNCV_ALL),
470                0x0,
471                LNCV_ALL);
472    }
473
474    /**
475     * Create LocoNet broadcast message to end LNCV programming.
476     * (expect no reply from module)
477     *
478     * @param articleNum LNCV device type number used as filter to respond. Leave out to 'broadcast' to
479     *                   all connected devices (which works for discovery purpose only). Best to use same
480     *                   value as used while opening the session.
481     * @return LocoNet message
482     */
483    public static LocoNetMessage createAllProgEndRequest(int articleNum) {
484        return createLncvMessage(
485                0x1,
486                0x5,
487                LncvCommand.LNCV_PROG_END,
488                (articleNum > -1 ? articleNum : LNCV_ALL),
489                0x0,
490                LNCV_ALL); // replaces 0x1 from KD notes
491    }
492
493    /**
494     * Create LocoNet message for first query of a CV of this module.
495     *
496     * @param articleNum  address of the module
497     * @param moduleAddress  address of the module
498     * @return LocoNet message
499     */
500    public static LocoNetMessage createModProgStartRequest(int articleNum, int moduleAddress) {
501        return createLncvMessage(
502                0x1,
503                0x5,
504                LncvCommand.LNCV_PROG_START,
505                articleNum,
506                0x0,
507                moduleAddress); // effectively reads first CV0 = module address
508    }
509
510    /**
511     * Create LocoNet message to leave programming of this module.
512     * (expect no reply from module)
513     *
514     * @param articleNum  address of the module
515     * @param moduleAddress  address of the module
516     * @return LocoNet message
517     */
518    public static LocoNetMessage createModProgEndRequest(int articleNum, int moduleAddress) {
519        //log.debug("MODPROG_END {} message created", moduleAddress);
520        return createLncvMessage(
521                0x1,
522                0x5,
523                LncvCommand.LNCV_PROG_END,
524                articleNum,
525                0x0,
526                moduleAddress);
527    }
528
529    /**
530     * Create LocoNet message for a write to a CV of this object.
531     *
532     * @param articleNum  address of the module
533     * @param cvNum  CV number to query
534     * @param newValue new value to store in CV
535     * @return LocoNet message
536     */
537    public static LocoNetMessage createCvWriteRequest(int articleNum, int cvNum, int newValue) {
538        return createLncvMessage(
539                0x1,
540                0x5,
541                LncvCommand.LNCV_WRITE,
542                articleNum,
543                cvNum,
544                newValue);
545    }
546
547    /**
548     * Create LocoNet message for a query of a CV of this object.
549     *
550     * @param articleNum  address of the module
551     * @param cvNum  CV number to query
552     * @param moduleAddress  address of the module
553     * @return LocoNet message
554     */
555    public static LocoNetMessage createCvReadRequest(int articleNum, int moduleAddress, int cvNum) {
556        return createLncvMessage(
557                0x1,
558                0x5,
559                LncvCommand.LNCV_READ,
560                articleNum,
561                cvNum,
562                moduleAddress);
563    }
564
565    /* These 2 static methods are used to mock replies to requests from JMRI */
566
567    /**
568     * In Hexfile simulation mode, mock a ReadReply message back to the CS (when simulate replies is ON).
569     *
570     * @param m  the LocoNet message to respond to
571     * @return  LocoNet message containing the reply, or null if preceding
572     *          message isn't a query
573     */
574    public static LocoNetMessage createLncvReadReply(LocoNetMessage m) {
575        if (!isLnMessageASpecificLncvCommand(m, LncvCommand.LNCV_READ)) {
576            return null;
577        }
578        LocoNetMessage reply = new LocoNetMessage(m);
579        reply.setOpCode(LnConstants.OPC_PEER_XFER);
580        reply.setElement(LNCV_LENGTH_ELEMENT_INDEX, LNCV_LENGTH_ELEMENT_VALUE);
581
582        reply.setElement(LNCV_DST_L_ELEMENT_INDEX, (reply.getElement(LNCV_SRC_ELEMENT_INDEX) == LNCV_CS_SRC_VALUE ? 0x49 : reply.getElement(LNCV_SRC_ELEMENT_INDEX)));
583        reply.setElement(LNCV_DST_H_ELEMENT_INDEX, (reply.getElement(LNCV_SRC_ELEMENT_INDEX) == LNCV_CS_SRC_VALUE ? 0x4b : 0x00));
584
585        // set SRC after reading old value to determine DST above
586        reply.setElement(LNCV_SRC_ELEMENT_INDEX, LNCV_LNMODULE_VALUE);
587        reply.setElement(5, LNCV_CMD_READ_REPLY);
588        // HIBITS handled last
589        reply.setElement(7, reply.getElement(7));
590        reply.setElement(8, reply.getElement(8));
591        reply.setElement(9, reply.getElement(9));
592        reply.setElement(10, reply.getElement(10));
593        if (reply.getElement(9) != 0 || reply.getElement(10) != 0) { // if CV=0, keep cv value as is, it was passed in as the module address
594            reply.setElement(LNCV_MOD_L_ELEMENT_INDEX, 0x8); // random cv value_low
595            reply.setElement(LNCV_MOD_H_ELEMENT_INDEX, 0x1); // random cv value_hi
596            reply.setElement(PXCT1_ELEMENT_INDEX, reply.getElement(PXCT1_ELEMENT_INDEX)^0x60); // HIBITS recalculate (only elements 11-12 have changed = HIBITS bits 5 & 6)
597        }
598        reply.setElement(13, 0x0);
599
600        return reply;
601    }
602
603    /**
604     * In Hexfile simulation mode, mock a ProgStart reply message back to the CS.
605     *
606     * @param m the LocoNet message to respond to
607     * @return  LocoNet message containing the reply, or null if preceding
608     *          message isn't a query
609     */
610    public static LocoNetMessage createLncvProgStartReply(LocoNetMessage m) {
611        if (!isLnMessageASpecificLncvCommand(m, LncvCommand.LNCV_PROG_START)) {
612            return null;
613        }
614        LncvMessageContents lmc = new LncvMessageContents(m);
615        log.debug("request to article {}", lmc.getLncvArticleNum());
616        LocoNetMessage forward = new LocoNetMessage(m);
617        forward.setElement(LncvMessageContents.LNCV_CMDDATA_ELEMENT_INDEX, 0x00); // correct CMDDATA for ReadRequest (0x80 also observed)
618        forward.setElement(LncvMessageContents.PXCT1_ELEMENT_INDEX, m.getElement(PXCT1_ELEMENT_INDEX)^0x40); // together with this HIBIT
619        if (lmc.getLncvArticleNum() == LNCV_ALL) { // mock a certain device
620            log.debug("art ALL");
621            forward.setElement(LncvMessageContents.LNCV_ART_L_ELEMENT_INDEX, 0x29); // article number 5033
622            forward.setElement(LncvMessageContents.LNCV_ART_H_ELEMENT_INDEX, 0x13);
623            forward.setElement(LncvMessageContents.PXCT1_ELEMENT_INDEX, 0x01); // hibits to go with 5033
624        }
625        if (lmc.getLncvModuleNum() == LNCV_ALL) { // mock a certain address
626            log.debug("mod ALL");
627            forward.setElement(LncvMessageContents.LNCV_MOD_L_ELEMENT_INDEX, 0x3); // address value 3
628            forward.setElement(LncvMessageContents.LNCV_MOD_H_ELEMENT_INDEX, 0x0);
629        }
630        return LncvMessageContents.createLncvReadReply(forward);
631    }
632
633    /**
634     * Create LocoNet message to set aseries of Track-Control module display LEDs.
635     *
636     * @param moduleAddress  address of the module
637     * @param ledValue  CV number to query
638     * @param range2 true if intended for LED2 Command (leds 16-31), fasle for LED1 (0-15)
639     * @return LocoNet message
640     */
641    public static LocoNetMessage createDirectWriteRequest(int moduleAddress, int ledValue, boolean range2) {
642        return createLncvMessage(
643                LNCV_PC_SRC_VALUE,
644                0x5,
645                (range2 ? LncvCommand.LNCV_DIRECT_LED2 : LncvCommand.LNCV_DIRECT_LED1),
646                6900,
647                moduleAddress, // special: CV position [D3-D4] contains the module address
648                ledValue);
649    }
650
651    /**
652     * LNCV Commands mapped to unique sets of 3 parts in message. LNCV knows only 3 simple &lt;CMD&gt; values.
653     */
654    public enum LncvCommand { // full commands mapped to 3 values in message, LNCV knows only 3 simple CMD commands
655        LNCV_WRITE (LNCV_CMD_WRITE, LnConstants.OPC_IMM_PACKET, 0x00), // CMD=0x20, CmdData=0x0
656        // LNCV_WRITE_REPLY = LACK
657        LNCV_READ (LNCV_CMD_READ, LnConstants.OPC_IMM_PACKET, 0x00), // CMD=0x21, CmdData=0x0
658        LNCV_READ_REPLY (LNCV_CMD_READ_REPLY, LnConstants.OPC_PEER_XFER, 0x00), // CMD=0x1f, CmdData=0x0
659        LNCV_READ_REPLY2 (LNCV_CMD_READ_REPLY, LnConstants.OPC_PEER_XFER, 0x80), // CMD=0x1f, CmdData=0x0
660        LNCV_PROG_START (LNCV_CMD_READ, LnConstants.OPC_IMM_PACKET, LNCV_DATA_PRON_MASK), // CMD=0x21, CmdData=0x80
661        LNCV_PROG_END (LNCV_CMD_READ, LnConstants.OPC_PEER_XFER, LNCV_DATA_PROFF_MASK), // CMD=0x21, CmdData=0x40
662        LNCV_DIRECT_LED1 (LNCV_CMD_WRITE, LnConstants.OPC_IMM_PACKET, LNCV_DATA_LED1_MASK), // CMD=0x20, CmdData=0xff
663        LNCV_DIRECT_LED2 (LNCV_CMD_WRITE, LnConstants.OPC_IMM_PACKET, LNCV_DATA_LED2_MASK), // CMD=0x20, CmdData=0xfe
664        LNCV_DIRECT_REPLY (LNCV_CMD_READ_REPLY, LnConstants.OPC_PEER_XFER, LNCV_DATA_LED1_MASK); // CMD=0x1f, CmdData=0xff
665
666        private final int cmd;
667        private final int opc;
668        private final int cmddata;
669        
670        LncvCommand(int cmd, int opc, int cmddata) {
671            this.cmd = cmd;
672            this.opc = opc;
673            this.cmddata = cmddata;
674        }
675
676        int getCmd() {return cmd;}
677        int getOpc() {return opc;}
678        int getCmdData() {return cmddata;}
679        
680        public static int getCmd(LncvCommand mt) {
681            return mt.getCmd();
682        }
683
684        public Boolean matches(int matchCommand, int matchOpc, int matchData) {
685            //log.debug("CMD ENUM command {}={}? {}", matchCommand, cmd, (matchCommand == cmd));
686            //log.debug("CMD ENUM opc {}={}? {}", matchOpc, opc, (matchOpc == opc));
687            //log.debug("CMD ENUM commanddata {}={}? {}", matchData, cmddata, (matchData == cmddata));
688            return ((matchCommand == cmd) && (matchOpc == opc) && (matchData == cmddata));
689        }
690    }
691
692    // initialize logging
693    private final static Logger log = LoggerFactory.getLogger(LncvMessageContents.class);
694    
695}