001package jmri.jmrix.nce;
002
003import java.util.Arrays;
004
005import javax.annotation.CheckForNull;
006import javax.annotation.Nonnull;
007
008/**
009 * Encodes a message to an NCE command station.
010 * <p>
011 * The {@link NceReply} class handles the response from the command station.
012 * <p>
013 * The NCE protocol has "binary" and "ASCII" command sets. Depending on the
014 * version of the EPROM it contains, NCE command stations have different support
015 * for command sets:
016 * <ul>
017 * <li>1999 - All ASCII works. Binary works except for programming.
018 * <li>2004 - ASCII needed for programming, binary for everything else.
019 * <li>2006 - binary needed for everything
020 * </ul>
021 * See the {@link NceTrafficController#setCommandOptions(int)} method for more
022 * information.
023 * <p>
024 * Apparently the binary "exitProgrammingMode" command can crash the command
025 * station if the EPROM was built before 2006. This method uses a state flag
026 * ({@link NceTrafficController#getNceProgMode}) to detect whether a command to
027 * enter program mode has been generated, and presumably sent, when using the
028 * later EPROMS.
029 *
030 * @author Bob Jacobsen Copyright (C) 2001
031 * @author Daniel Boudreau Copyright (C) 2007
032 * @author kcameron Copyright (C) 2014
033 */
034public class NceMessage extends jmri.jmrix.AbstractMRMessage {
035 
036    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NceMessage.class); // called in static block
037    private static final jmri.jmrix.nce.ncemon.NceMonBinary nceMon = new jmri.jmrix.nce.ncemon.NceMonBinary();
038
039    public static final int NOP_CMD = 0x80; //NCE NOP command
040    public static final int ASSIGN_CAB_CMD = 0x81; // NCE Assign loco to cab command, NCE-USB no
041    public static final int READ_CLOCK_CMD = 0x82; // NCE read clock command, NCE-USB no
042    public static final int STOP_CLOCK_CMD = 0x83; // NCE stop clock command, NCE-USB no
043    public static final int START_CLOCK_CMD = 0x84; // NCE start clock command, NCE-USB no
044    public static final int SET_CLOCK_CMD = 0x85; // NCE set clock command, NCE-USB no
045    public static final int CLOCK_1224_CMD = 0x86; // NCE change clock 12/24 command, NCE-USB no
046    public static final int CLOCK_RATIO_CMD = 0x87; // NCE set clock ratio command, NCE-USB no
047    public static final int DEQUEUE_CMD = 0x88; // NCE dequeue packets based on loco addr, NCE-USB no
048
049    public static final int READ_AUI4_CMD = 0x8A; // NCE read status of AUI yy, returns four bytes, NCE-USB no
050
051    public static final int DUMMY_CMD = 0x8C; // NCE Dummy instruction, NCE-USB yes
052    public static final int SPEED_MODE_CMD = 0x8D; // NCE set speed mode, NCE-USB no
053    public static final int WRITE_N_CMD = 0x8E; // NCE write up to 16 bytes of memory command, NCE-USB no
054    public static final int READ16_CMD = 0x8F; // NCE read 16 bytes of memory command, NCE-USB no
055    public static final int DISPLAY3_CMD = 0x90; // NCE write 16 char to cab display line 3, NCE-USB no
056    public static final int DISPLAY4_CMD = 0x91; // NCE write 16 char to cab display line 4, NCE-USB no
057    public static final int DISPLAY2_CMD = 0x92; // NCE write 8 char to cab display line 2 right, NCE-USB no
058    public static final int QUEUE3_TMP_CMD = 0x93; // NCE queue 3 bytes to temp queue, NCE-USB no
059    public static final int QUEUE4_TMP_CMD = 0x94; // NCE queue 4 bytes to temp queue, NCE-USB no
060    public static final int QUEUE5_TMP_CMD = 0x95; // NCE queue 5 bytes to temp queue, NCE-USB no
061    public static final int QUEUE6_TMP_CMD = 0x96; // NCE queue 6 bytes to temp queue, NCE-USB no
062    public static final int WRITE1_CMD = 0x97; // NCE write 1 bytes of memory command, NCE-USB no
063    public static final int WRITE2_CMD = 0x98; // NCE write 2 bytes of memory command, NCE-USB no
064    public static final int WRITE4_CMD = 0x99; // NCE write 4 bytes of memory command, NCE-USB no
065    public static final int WRITE8_CMD = 0x9A; // NCE write 8 bytes of memory command, NCE-USB no
066    public static final int READ_AUI2_CMD = 0x9B; // NCE read status of AUI yy, returns two bytes, NCE-USB >= 1.65
067    public static final int MACRO_CMD = 0x9C; // NCE execute macro n, NCE-USB yes
068    public static final int READ1_CMD = 0x9D; // NCE read 1 byte of memory command, NCE-USB no
069    public static final int ENTER_PROG_CMD = 0x9E; //NCE enter programming track mode command
070    public static final int EXIT_PROG_CMD = 0x9F; //NCE exit programming track mode command
071    public static final int WRITE_PAGED_CV_CMD = 0xA0; //NCE write CV paged command
072    public static final int READ_PAGED_CV_CMD = 0xA1; //NCE read CV paged command
073    public static final int LOCO_CMD = 0xA2; // NCE loco control command, NCE-USB yes
074    public static final int QUEUE3_TRK_CMD = 0xA3; // NCE queue 3 bytes to track queue, NCE-USB no
075    public static final int QUEUE4_TRK_CMD = 0xA4; // NCE queue 4 bytes to track queue, NCE-USB no
076    public static final int QUEUE5_TRK_CMD = 0xA5; // NCE queue 5 bytes to track queue, NCE-USB no
077    public static final int WRITE_REG_CMD = 0xA6; //NCE write register command
078    public static final int READ_REG_CMD = 0xA7; //NCE read register command
079    public static final int WRITE_DIR_CV_CMD = 0xA8; //NCE write CV direct command
080    public static final int READ_DIR_CV_CMD = 0xA9; //NCE read CV direct command
081    public static final int SW_REV_CMD = 0xAA; // NCE get EPROM revision cmd, Reply Format: VV.MM.mm, NCE-USB yes
082    public static final int RESET_SOFT_CMD = 0xAB; // NCE soft reset command, NCE-USB no
083    public static final int RESET_HARD_CMD = 0xAC; // NCE hard reset command, NCE-USB no
084    public static final int SEND_ACC_SIG_MACRO_CMD = 0xAD; // NCE send NMRA aspect command
085    public static final int OPS_PROG_LOCO_CMD = 0xAE;   // NCE ops mode program loco, NCE-USB yes
086    public static final int OPS_PROG_ACCY_CMD = 0xAF;   // NCE ops mode program accessories, NCE-USB yes
087    public static final int FACTORY_TEST_CMD = 0xB0;    // NCE factory test, NCE-USB yes
088    public static final int USB_SET_CAB_CMD = 0xB1;     // NCE set cab address in USB, NCE-USB yes
089    public static final int USB_MEM_POINTER_CMD = 0xB3; // NCE set memory context pointer, NCE-USB >= 1.65
090    public static final int USB_MEM_WRITE_CMD = 0xB4;   // NCE write memory, NCE-USB >= 1.65
091    public static final int USB_MEM_READ_CMD = 0xB5;    // NCE read memory, NCE-USB >= 1.65
092
093    // NCE Command 0xA2 sends speed or function packets to a locomotive
094    // 0xA2 sub commands speed and functions
095    public static final byte LOCO_CMD_SELECT_LOCO = 0x00;  // select loco
096    public static final byte LOCO_CMD_REV_28SPEED = 0x01;  // set loco speed 28 steps reverse
097    public static final byte LOCO_CMD_FWD_28SPEED = 0x02;  // set loco speed 28 steps forward
098    public static final byte LOCO_CMD_REV_128SPEED = 0x03; // set loco speed 128 steps reverse
099    public static final byte LOCO_CMD_FWD_128SPEED = 0x04; // set loco speed 128 steps forward
100    public static final byte LOCO_CMD_REV_ESTOP = 0x05;    // emergency stop reverse
101    public static final byte LOCO_CMD_FWD_ESTOP = 0x06;    // emergency stop forward
102    public static final byte LOCO_CMD_FG1 = 0x07;          // function group 1
103    public static final byte LOCO_CMD_FG2 = 0x08;          // function group 2
104    public static final byte LOCO_CMD_FG3 = 0x09;          // function group 3
105    public static final byte LOCO_CMD_FG4 = 0x15;          // function group 4
106    public static final byte LOCO_CMD_FG5 = 0x16;          // function group 5
107
108    // OxA2 sub commands consist
109    public static final byte LOCO_CMD_REV_CONSIST_LEAD = 0x0A;    // reverse consist address for lead loco
110    public static final byte LOCO_CMD_FWD_CONSIST_LEAD = 0x0B;    // forward consist address for lead loco 
111    public static final byte LOCO_CMD_REV_CONSIST_REAR = 0x0C;    // reverse consist address for rear loco 
112    public static final byte LOCO_CMD_FWD_CONSIST_REAR = 0x0D;    // forward consist address for rear loco
113    public static final byte LOCO_CMD_REV_CONSIST_MID = 0x0E;     // reverse consist address for additional loco 
114    public static final byte LOCO_CMD_FWD_CONSIST_MID = 0x0F;     // forward consist address for additional loco 
115    public static final byte LOCO_CMD_DELETE_LOCO_CONSIST = 0x10; // Delete loco from consist
116    public static final byte LOCO_CMD_KILL_CONSIST = 0x11;        // Kill consist
117    
118    // The following commands are not supported by the NCE USB  
119    public static final int ENABLE_MAIN_CMD = 0x89; //NCE enable main track, kill programming command
120    public static final int KILL_MAIN_CMD = 0x8B; //NCE kill main track, enable programming command
121    public static final int SENDn_BYTES_CMD = 0x90; //NCE send 3 to 6 bytes (0x9n, n = 3-6) command
122    public static final int QUEUEn_BYTES_CMD = 0xA0; //NCE queue 3 to 6 bytes (0xAn, n = 3-6) command
123
124    // some constants
125    protected static final int NCE_PAGED_CV_TIMEOUT = 20000;
126    protected static final int NCE_DIRECT_CV_TIMEOUT = 10000;
127    protected static final int SHORT_TIMEOUT = 10000; // worst case is when loading the first panel
128
129    public static final int REPLY_1 = 1; // reply length of 1 byte
130    public static final int REPLY_2 = 2; // reply length of 2 bytes
131    public static final int REPLY_3 = 3; // reply length of 3 bytes
132    public static final int REPLY_4 = 4; // reply length of 4 bytes
133    public static final int REPLY_16 = 16; // reply length of 16 bytes 
134    
135    public static char NCE_OKAY = '!';
136
137    public NceMessage() {
138        super();
139    }
140
141    // create a new one
142    public NceMessage(int i) {
143        super(i);
144    }
145
146    // copy one
147    public NceMessage(@Nonnull NceMessage m) {
148        super(m);
149        replyLen = m.replyLen;
150    }
151
152    // from String
153    public NceMessage(@Nonnull String m) {
154        super(m);
155    }
156
157    // default to expecting one reply character
158    int replyLen = 1;
159
160    /**
161     * Set the number of characters expected back from the command station. Used
162     * in binary mode, where there's no end-of-reply string to look for.
163     * 
164     * @param len length of expected reply
165     */
166    public void setReplyLen(int len) {
167        replyLen = len;
168    }
169
170    public int getReplyLen() {
171        return replyLen;
172    }
173
174    // diagnose format
175    public boolean isKillMain() {
176        if (isBinary()) {
177            return getOpCode() == KILL_MAIN_CMD;
178        } else {
179            return getOpCode() == 'K';
180        }
181    }
182
183    public boolean isEnableMain() {
184        if (isBinary()) {
185            return getOpCode() == ENABLE_MAIN_CMD;
186        } else {
187            return getOpCode() == 'E';
188        }
189    }
190
191    // static methods to return a formatted message
192    public static NceMessage getEnableMain(NceTrafficController tc) {
193        // this command isn't supported by the NCE USB
194        if (tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE) {
195            log.error("attempt to send unsupported binary command ENABLE_MAIN_CMD to NCE USB");
196            return null;
197        }
198        NceMessage m = new NceMessage(1);
199        if (tc.getCommandOptions() >= NceTrafficController.OPTION_1999) {
200            m.setBinary(true);
201            m.setReplyLen(1);
202            m.setOpCode(ENABLE_MAIN_CMD);
203        } else {
204            m.setBinary(false);
205            m.setOpCode('E');
206        }
207        return m;
208    }
209
210    public static NceMessage getKillMain(NceTrafficController tc) {
211        // this command isn't supported by the NCE USB
212        if (tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE) {
213            log.error("attempt to send unsupported binary command KILL_MAIN_CMD to NCE USB");
214            return null;
215        }
216        NceMessage m = new NceMessage(1);
217        if (tc.getCommandOptions() >= NceTrafficController.OPTION_1999) {
218            m.setBinary(true);
219            m.setReplyLen(REPLY_1);
220            m.setOpCode(KILL_MAIN_CMD);
221        } else {
222            m.setBinary(false);
223            m.setOpCode('K');
224        }
225        return m;
226    }
227
228    /**
229     * enter programming track mode
230     *
231     * @param tc controller for the associated connection
232     * @return a new message to enter programming track mode
233     */
234    @Nonnull
235    public static NceMessage getProgMode(@Nonnull NceTrafficController tc) {
236        // test if supported on current connection
237        if (tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE &&
238                (tc.getCmdGroups() & NceTrafficController.CMDS_PROGTRACK) != NceTrafficController.CMDS_PROGTRACK) {
239            log.error("attempt to send unsupported binary command ENTER_PROG_CMD to NCE USB");
240            //   return null;
241        }
242        NceMessage m = new NceMessage(1);
243        if (tc.getCommandOptions() >= NceTrafficController.OPTION_2006) {
244            tc.setNceProgMode(true);
245            m.setBinary(true);
246            m.setReplyLen(REPLY_1);
247            m.setOpCode(ENTER_PROG_CMD);
248            m.setTimeout(SHORT_TIMEOUT);
249        } else {
250            m.setBinary(false);
251            m.setOpCode('M');
252            m.setTimeout(SHORT_TIMEOUT);
253        }
254        return m;
255    }
256
257    /**
258     * Apparently the binary "exitProgrammingMode" command can crash the command
259     * station if the EPROM was built before 2006. This method uses a state flag
260     * ({@link NceTrafficController#getNceProgMode}) to detect whether a command
261     * to enter program mode has been generated, and presumably sent, when using
262     * the later EPROMS.
263     *
264     * @param tc controller for the associated connection
265     * @return a new message to exit programming track mode
266     */
267    @CheckForNull
268    public static NceMessage getExitProgMode(@Nonnull NceTrafficController tc) {
269        NceMessage m = new NceMessage(1);
270        if (tc.getCommandOptions() >= NceTrafficController.OPTION_2006) {
271            // Sending exit programming mode binary can crash pre 2006 EPROMs
272            // assumption is that program mode hasn't been entered, so exit without 
273            // sending command
274            if (tc.getNceProgMode() == false) {
275                return null;
276            }
277            // not supported by USB connected to SB3 or PH
278            if (tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_SB3 ||
279                    tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_SB5 ||
280                    tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_POWERPRO) {
281                log.error("attempt to send unsupported binary command EXIT_PROG_CMD to NCE USB");
282                //       return null;
283            }
284            tc.setNceProgMode(false);
285            m.setBinary(true);
286            m.setReplyLen(REPLY_1);
287            m.setOpCode(EXIT_PROG_CMD);
288            m.setTimeout(SHORT_TIMEOUT);
289        } else {
290            m.setBinary(false);
291            m.setOpCode('X');
292            m.setTimeout(SHORT_TIMEOUT);
293        }
294        return m;
295    }
296
297    /**
298     * Read Paged mode CV on programming track.
299     *
300     * @param tc controller for the associated connection
301     * @param cv the CV to read
302     * @return a new message to read a CV
303     */
304    @Nonnull
305    public static NceMessage getReadPagedCV(@Nonnull NceTrafficController tc, int cv) {
306        // test if supported on current connection
307        if (tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE &&
308                (tc.getCmdGroups() & NceTrafficController.CMDS_PROGTRACK) != NceTrafficController.CMDS_PROGTRACK) {
309            log.error("attempt to send unsupported binary command READ_PAGED_CV_CMD to NCE USB");
310            //   return null;
311        }
312        if (tc.getCommandOptions() >= NceTrafficController.OPTION_2006) {
313            NceMessage m = new NceMessage(3);
314            m.setBinary(true);
315            m.setReplyLen(REPLY_2);
316            m.setOpCode(READ_PAGED_CV_CMD);
317            m.setElement(1, (cv >> 8));
318            m.setElement(2, (cv & 0x0FF));
319            m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
320            m.setTimeout(NCE_PAGED_CV_TIMEOUT);
321            return m;
322        } else {
323            NceMessage m = new NceMessage(4);
324            m.setBinary(false);
325            m.setOpCode('R');
326            m.addIntAsThree(cv, 1);
327            m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
328            m.setTimeout(NCE_PAGED_CV_TIMEOUT);
329            return m;
330        }
331    }
332
333    /**
334     * Write paged mode CV to programming track.
335     *
336     * @param tc controller for the associated connection
337     * @param cv CV to write
338     * @param val value to write to cv
339     * @return a new message to write a CV
340     */
341    @Nonnull
342    public static NceMessage getWritePagedCV(@Nonnull NceTrafficController tc, int cv, int val) {
343        // test if supported on current connection
344        if (tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE &&
345                (tc.getCmdGroups() & NceTrafficController.CMDS_PROGTRACK) != NceTrafficController.CMDS_PROGTRACK) {
346            log.error("attempt to send unsupported binary command WRITE_PAGED_CV_CMD to NCE USB");
347            //   return null;
348        }
349        if (tc.getCommandOptions() >= NceTrafficController.OPTION_2006) {
350            NceMessage m = new NceMessage(4);
351            m.setBinary(true);
352            m.setReplyLen(REPLY_1);
353            m.setOpCode(WRITE_PAGED_CV_CMD);
354            m.setElement(1, cv >> 8);
355            m.setElement(2, cv & 0xFF);
356            m.setElement(3, val);
357            m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
358            m.setTimeout(NCE_PAGED_CV_TIMEOUT);
359            return m;
360        } else {
361            NceMessage m = new NceMessage(8);
362            m.setBinary(false);
363            m.setOpCode('P');
364            m.addIntAsThree(cv, 1);
365            m.setElement(4, ' ');
366            m.addIntAsThree(val, 5);
367            m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
368            m.setTimeout(NCE_PAGED_CV_TIMEOUT);
369            return m;
370        }
371    }
372
373    @CheckForNull
374    public static NceMessage getReadRegister(@Nonnull NceTrafficController tc, int reg) {
375        // not supported by USB connected to SB3 or PH
376        if (tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_SB3 ||
377                tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_SB5 ||
378                tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_POWERPRO) {
379            log.error("attempt to send unsupported binary command READ_REG_CMD to NCE USB");
380            return null;
381        }
382        if (reg > 8) {
383            log.error("register number too large: {}", reg);
384        }
385        if (tc.getCommandOptions() >= NceTrafficController.OPTION_2006) {
386            NceMessage m = new NceMessage(2);
387            m.setBinary(true);
388            m.setReplyLen(REPLY_2);
389            m.setOpCode(READ_REG_CMD);
390            m.setElement(1, reg);
391            m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
392            m.setTimeout(NCE_PAGED_CV_TIMEOUT);
393            return m;
394        } else {
395            NceMessage m = new NceMessage(2);
396            m.setBinary(false);
397            m.setOpCode('V');
398            String s = "" + reg;
399            m.setElement(1, s.charAt(s.length() - 1));
400            m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
401            m.setTimeout(NCE_PAGED_CV_TIMEOUT);
402            return m;
403        }
404    }
405
406    public static NceMessage getWriteRegister(NceTrafficController tc, int reg, int val) {
407        // not supported by USB connected to SB3 or PH
408        if (tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_SB3 ||
409                tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_SB5 ||
410                tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_POWERPRO) {
411            log.error("attempt to send unsupported binary command WRITE_REG_CMD to NCE USB");
412            return null;
413        }
414        if (reg > 8) {
415            log.error("register number too large: {}", reg);
416        }
417        if (tc.getCommandOptions() >= NceTrafficController.OPTION_2006) {
418            NceMessage m = new NceMessage(3);
419            m.setBinary(true);
420            m.setReplyLen(REPLY_1);
421            m.setOpCode(WRITE_REG_CMD);
422            m.setElement(1, reg);
423            m.setElement(2, val);
424            m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
425            m.setTimeout(NCE_PAGED_CV_TIMEOUT);
426            return m;
427        } else {
428            NceMessage m = new NceMessage(6);
429            m.setBinary(false);
430            m.setOpCode('S');
431            String s = "" + reg;
432            m.setElement(1, s.charAt(s.length() - 1));
433            m.setElement(2, ' ');
434            m.addIntAsThree(val, 3);
435            m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
436            m.setTimeout(NCE_PAGED_CV_TIMEOUT);
437            return m;
438        }
439    }
440
441    public static NceMessage getReadDirectCV(NceTrafficController tc, int cv) {
442        // not supported by USB connected to SB3 or PH
443        if (tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_SB3 ||
444                tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_SB5 ||
445                tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_POWERPRO) {
446            log.error("attempt to send unsupported binary command READ_DIR_CV_CMD to NCE USB");
447            return null;
448        }
449        if (tc.getCommandOptions() < NceTrafficController.OPTION_2006) {
450            log.error("getReadDirectCV with option {}", tc.getCommandOptions());
451            return null;
452        }
453        NceMessage m = new NceMessage(3);
454        m.setBinary(true);
455        m.setReplyLen(REPLY_2);
456        m.setOpCode(READ_DIR_CV_CMD);
457        m.setElement(1, (cv >> 8));
458        m.setElement(2, (cv & 0x0FF));
459        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
460        m.setTimeout(NCE_DIRECT_CV_TIMEOUT);
461        return m;
462    }
463
464    public static NceMessage getWriteDirectCV(NceTrafficController tc, int cv, int val) {
465        // not supported by USB connected to SB3 or PH
466        if (tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_SB3 ||
467                tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_SB5 ||
468                tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_POWERPRO) {
469            log.error("attempt to send unsupported binary command WRITE_DIR_CV_CMD to NCE USB");
470            return null;
471        }
472        if (tc.getCommandOptions() < NceTrafficController.OPTION_2006) {
473            log.error("getWriteDirectCV with option {}", tc.getCommandOptions());
474        }
475        NceMessage m = new NceMessage(4);
476        m.setBinary(true);
477        m.setReplyLen(REPLY_1);
478        m.setOpCode(WRITE_DIR_CV_CMD);
479        m.setElement(1, cv >> 8);
480        m.setElement(2, cv & 0xFF);
481        m.setElement(3, val);
482        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
483        m.setTimeout(NCE_DIRECT_CV_TIMEOUT);
484        return m;
485    }
486    
487    public static NceMessage getEpromVersion(NceTrafficController tc) {
488        byte[] bl = NceBinaryCommand.getNceEpromRev();
489        NceMessage m = NceMessage.createBinaryMessage(tc, bl, REPLY_3);
490        return m;
491    }
492    
493    public static NceMessage sendLocoCmd(NceTrafficController tc, int locoAddr, byte locoSubCmd, byte locoData) {
494        byte[] bl = NceBinaryCommand.nceLocoCmd(locoAddr, locoSubCmd, locoData);
495        NceMessage m = NceMessage.createBinaryMessage(tc, bl, REPLY_1);
496        return m;
497    }
498
499    public static NceMessage sendPacketMessage(NceTrafficController tc, byte[] bytes) {
500        NceMessage m = sendPacketMessage(tc, bytes, 2);
501        return m;
502    }
503
504    public static NceMessage sendPacketMessage(NceTrafficController tc, byte[] bytes, int retries) {
505        // this command isn't supported by the NCE USB
506        if (tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE) {
507            log.error("attempt to send unsupported sendPacketMessage to NCE USB cmd: 0x{}", Integer.toHexString(SENDn_BYTES_CMD + bytes.length));
508            return null;
509        }
510        if (tc.getCommandOptions() >= NceTrafficController.OPTION_1999) {
511            if (bytes.length < 3 || bytes.length > 6) {
512                log.error("Send of NCE track packet too short or long:{} packet:{}", Integer.toString(bytes.length), Arrays.toString(bytes));
513            }
514            NceMessage m = new NceMessage(2 + bytes.length);
515            m.setBinary(true);
516            m.setTimeout(SHORT_TIMEOUT);
517            m.setReplyLen(1);
518            int i = 0; // counter to make it easier to format the message
519
520            m.setElement(i++, SENDn_BYTES_CMD + bytes.length);
521            m.setElement(i++, retries); // send this many retries. 
522            for (int j = 0; j < bytes.length; j++) {
523                m.setElement(i++, bytes[j] & 0xFF);
524            }
525            return m;
526        } else {
527            NceMessage m = new NceMessage(5 + 3 * bytes.length);
528            m.setBinary(false);
529            int i = 0; // counter to make it easier to format the message
530
531            m.setElement(i++, 'S'); // "S C02 " means sent it twice
532            m.setElement(i++, ' ');
533            m.setElement(i++, 'C');
534            m.setElement(i++, '0');
535            m.setElement(i++, '2');
536
537            for (int j = 0; j < bytes.length; j++) {
538                m.setElement(i++, ' ');
539                m.addIntAsTwoHex(bytes[j] & 0xFF, i);
540                i = i + 2;
541            }
542            m.setTimeout(SHORT_TIMEOUT);
543            return m;
544        }
545    }
546
547    public static NceMessage createBinaryMessage(NceTrafficController tc, byte[] bytes) {
548        return createBinaryMessage(tc, bytes, REPLY_1);
549    }
550
551    public static NceMessage createBinaryMessage(NceTrafficController tc, byte[] bytes, int replyLen) {
552        if (tc.getCommandOptions() < NceTrafficController.OPTION_2004) {
553            log.error("Attempt to send NCE command to EPROM built before 2004");
554        }
555        if (bytes.length < 1 || bytes.length > 20) {
556            log.error("NCE command message length error:{}", bytes.length);
557        }
558
559        NceMessage m = new NceMessage(bytes.length);
560        m.setBinary(true);
561        m.setReplyLen(replyLen);
562        m.setTimeout(SHORT_TIMEOUT);
563
564        for (int j = 0; j < bytes.length; j++) {
565            m.setElement(j, bytes[j] & 0xFF);
566        }
567        return m;
568    }
569
570    public static NceMessage queuePacketMessage(NceTrafficController tc, byte[] bytes) {
571        // this command isn't supported by the NCE USB
572        if (tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE) {
573            log.error("attempt to send unsupported queuePacketMessage to NCE USB");
574            return null;
575        }
576        if (tc.getCommandOptions() >= NceTrafficController.OPTION_1999) {
577            if (bytes.length < 3 || bytes.length > 6) {
578                log.error("Queue of NCE track packet too long:{} packet :{}", Integer.toString(bytes.length), Arrays.toString(bytes));
579            }
580            NceMessage m = new NceMessage(1 + bytes.length);
581            m.setBinary(true);
582            m.setReplyLen(REPLY_1);
583            int i = 0; // counter to make it easier to format the message
584
585            m.setElement(i++, QUEUEn_BYTES_CMD + bytes.length);
586            for (int j = 0; j < bytes.length; j++) {
587                m.setElement(i++, bytes[j] & 0xFF);
588            }
589            return m;
590        } else {
591            NceMessage m = new NceMessage(1 + 3 * bytes.length);
592            m.setBinary(false);
593            int i = 0; // counter to make it easier to format the message
594
595            m.setElement(i++, 'Q'); // "S C02 " means sent it twice
596
597            for (int j = 0; j < bytes.length; j++) {
598                m.setElement(i++, ' ');
599                m.addIntAsTwoHex(bytes[j] & 0xFF, i);
600                i = i + 2;
601            }
602            return m;
603        }
604    }
605
606    public static NceMessage createAccySignalMacroMessage(NceTrafficController tc, int op, int addr, int data) {
607        if (tc.getCommandOptions() < NceTrafficController.OPTION_2004) {
608            log.error("Attempt to send NCE command to EPROM built before 2004");
609        }
610        NceMessage m = new NceMessage(5);
611        m.setBinary(true);
612        m.setReplyLen(REPLY_1);
613        m.setTimeout(SHORT_TIMEOUT);
614        m.setOpCode(SEND_ACC_SIG_MACRO_CMD);
615        m.setElement(1, (addr >> 8) & 0xFF);
616        m.setElement(2, addr & 0xFF);
617        m.setElement(3, op);
618        m.setElement(4, data);
619        return m;
620    }
621
622    public static NceMessage createAccDecoderPktOpsMode(NceTrafficController tc, int accyAddr, int cvAddr, int cvData) {
623        NceMessage m = new NceMessage(6);
624        m.setBinary(true);
625        m.setReplyLen(REPLY_1);
626        m.setTimeout(SHORT_TIMEOUT);
627        byte[] mess = NceBinaryCommand.usbOpsModeAccy(accyAddr, cvAddr, cvData);
628        m.setOpCode(mess[0]);
629        m.setElement(1, mess[1]);
630        m.setElement(2, mess[2]);
631        m.setElement(3, mess[3]);
632        m.setElement(4, mess[4]);
633        m.setElement(5, mess[5]);
634        return m;
635    }
636
637    /**
638     * {@inheritDoc}
639     */
640    @Override
641    public String toMonitorString() {
642        return nceMon.displayMessage(this);
643    }
644}