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    
128    @SuppressWarnings("hiding")  // redefines value from super class
129    protected static final int SHORT_TIMEOUT = 10000; // worst case is when loading the first panel
130
131    public static final int REPLY_1 = 1; // reply length of 1 byte
132    public static final int REPLY_2 = 2; // reply length of 2 bytes
133    public static final int REPLY_3 = 3; // reply length of 3 bytes
134    public static final int REPLY_4 = 4; // reply length of 4 bytes
135    public static final int REPLY_16 = 16; // reply length of 16 bytes 
136    
137    public static char NCE_OKAY = '!';
138
139    public NceMessage() {
140        super();
141    }
142
143    // create a new one
144    public NceMessage(int i) {
145        super(i);
146    }
147
148    // copy one
149    public NceMessage(@Nonnull NceMessage m) {
150        super(m);
151        replyLen = m.replyLen;
152    }
153
154    // from String
155    public NceMessage(@Nonnull String m) {
156        super(m);
157    }
158
159    // default to expecting one reply character
160    int replyLen = 1;
161
162    /**
163     * Set the number of characters expected back from the command station. Used
164     * in binary mode, where there's no end-of-reply string to look for.
165     * 
166     * @param len length of expected reply
167     */
168    public void setReplyLen(int len) {
169        replyLen = len;
170    }
171
172    public int getReplyLen() {
173        return replyLen;
174    }
175
176    // diagnose format
177    public boolean isKillMain() {
178        if (isBinary()) {
179            return getOpCode() == KILL_MAIN_CMD;
180        } else {
181            return getOpCode() == 'K';
182        }
183    }
184
185    public boolean isEnableMain() {
186        if (isBinary()) {
187            return getOpCode() == ENABLE_MAIN_CMD;
188        } else {
189            return getOpCode() == 'E';
190        }
191    }
192
193    // static methods to return a formatted message
194    public static NceMessage getEnableMain(NceTrafficController tc) {
195        // this command isn't supported by the NCE USB
196        if (tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE) {
197            log.error("attempt to send unsupported binary command ENABLE_MAIN_CMD to NCE USB");
198            return null;
199        }
200        NceMessage m = new NceMessage(1);
201        if (tc.getCommandOptions() >= NceTrafficController.OPTION_1999) {
202            m.setBinary(true);
203            m.setReplyLen(1);
204            m.setOpCode(ENABLE_MAIN_CMD);
205        } else {
206            m.setBinary(false);
207            m.setOpCode('E');
208        }
209        return m;
210    }
211
212    public static NceMessage getKillMain(NceTrafficController tc) {
213        // this command isn't supported by the NCE USB
214        if (tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE) {
215            log.error("attempt to send unsupported binary command KILL_MAIN_CMD to NCE USB");
216            return null;
217        }
218        NceMessage m = new NceMessage(1);
219        if (tc.getCommandOptions() >= NceTrafficController.OPTION_1999) {
220            m.setBinary(true);
221            m.setReplyLen(REPLY_1);
222            m.setOpCode(KILL_MAIN_CMD);
223        } else {
224            m.setBinary(false);
225            m.setOpCode('K');
226        }
227        return m;
228    }
229
230    /**
231     * enter programming track mode
232     *
233     * @param tc controller for the associated connection
234     * @return a new message to enter programming track mode
235     */
236    @Nonnull
237    public static NceMessage getProgMode(@Nonnull NceTrafficController tc) {
238        // test if supported on current connection
239        if (tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE &&
240                (tc.getCmdGroups() & NceTrafficController.CMDS_PROGTRACK) != NceTrafficController.CMDS_PROGTRACK) {
241            log.error("attempt to send unsupported binary command ENTER_PROG_CMD to NCE USB");
242            //   return null;
243        }
244        NceMessage m = new NceMessage(1);
245        if (tc.getCommandOptions() >= NceTrafficController.OPTION_2006) {
246            tc.setNceProgMode(true);
247            m.setBinary(true);
248            m.setReplyLen(REPLY_1);
249            m.setOpCode(ENTER_PROG_CMD);
250            m.setTimeout(SHORT_TIMEOUT);
251        } else {
252            m.setBinary(false);
253            m.setOpCode('M');
254            m.setTimeout(SHORT_TIMEOUT);
255        }
256        return m;
257    }
258
259    /**
260     * Apparently the binary "exitProgrammingMode" command can crash the command
261     * station if the EPROM was built before 2006. This method uses a state flag
262     * ({@link NceTrafficController#getNceProgMode}) to detect whether a command
263     * to enter program mode has been generated, and presumably sent, when using
264     * the later EPROMS.
265     *
266     * @param tc controller for the associated connection
267     * @return a new message to exit programming track mode
268     */
269    @CheckForNull
270    public static NceMessage getExitProgMode(@Nonnull NceTrafficController tc) {
271        NceMessage m = new NceMessage(1);
272        if (tc.getCommandOptions() >= NceTrafficController.OPTION_2006) {
273            // Sending exit programming mode binary can crash pre 2006 EPROMs
274            // assumption is that program mode hasn't been entered, so exit without 
275            // sending command
276            if (tc.getNceProgMode() == false) {
277                return null;
278            }
279            // not supported by USB connected to SB3 or PH
280            if (tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_SB3 ||
281                    tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_SB5 ||
282                    tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_POWERPRO) {
283                log.error("attempt to send unsupported binary command EXIT_PROG_CMD to NCE USB");
284                //       return null;
285            }
286            tc.setNceProgMode(false);
287            m.setBinary(true);
288            m.setReplyLen(REPLY_1);
289            m.setOpCode(EXIT_PROG_CMD);
290            m.setTimeout(SHORT_TIMEOUT);
291        } else {
292            m.setBinary(false);
293            m.setOpCode('X');
294            m.setTimeout(SHORT_TIMEOUT);
295        }
296        return m;
297    }
298
299    /**
300     * Read Paged mode CV on programming track.
301     *
302     * @param tc controller for the associated connection
303     * @param cv the CV to read
304     * @return a new message to read a CV
305     */
306    @Nonnull
307    public static NceMessage getReadPagedCV(@Nonnull NceTrafficController tc, int cv) {
308        // test if supported on current connection
309        if (tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE &&
310                (tc.getCmdGroups() & NceTrafficController.CMDS_PROGTRACK) != NceTrafficController.CMDS_PROGTRACK) {
311            log.error("attempt to send unsupported binary command READ_PAGED_CV_CMD to NCE USB");
312            //   return null;
313        }
314        if (tc.getCommandOptions() >= NceTrafficController.OPTION_2006) {
315            NceMessage m = new NceMessage(3);
316            m.setBinary(true);
317            m.setReplyLen(REPLY_2);
318            m.setOpCode(READ_PAGED_CV_CMD);
319            m.setElement(1, (cv >> 8));
320            m.setElement(2, (cv & 0x0FF));
321            m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
322            m.setTimeout(NCE_PAGED_CV_TIMEOUT);
323            return m;
324        } else {
325            NceMessage m = new NceMessage(4);
326            m.setBinary(false);
327            m.setOpCode('R');
328            m.addIntAsThree(cv, 1);
329            m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
330            m.setTimeout(NCE_PAGED_CV_TIMEOUT);
331            return m;
332        }
333    }
334
335    /**
336     * Write paged mode CV to programming track.
337     *
338     * @param tc controller for the associated connection
339     * @param cv CV to write
340     * @param val value to write to cv
341     * @return a new message to write a CV
342     */
343    @Nonnull
344    public static NceMessage getWritePagedCV(@Nonnull NceTrafficController tc, int cv, int val) {
345        // test if supported on current connection
346        if (tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE &&
347                (tc.getCmdGroups() & NceTrafficController.CMDS_PROGTRACK) != NceTrafficController.CMDS_PROGTRACK) {
348            log.error("attempt to send unsupported binary command WRITE_PAGED_CV_CMD to NCE USB");
349            //   return null;
350        }
351        if (tc.getCommandOptions() >= NceTrafficController.OPTION_2006) {
352            NceMessage m = new NceMessage(4);
353            m.setBinary(true);
354            m.setReplyLen(REPLY_1);
355            m.setOpCode(WRITE_PAGED_CV_CMD);
356            m.setElement(1, cv >> 8);
357            m.setElement(2, cv & 0xFF);
358            m.setElement(3, val);
359            m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
360            m.setTimeout(NCE_PAGED_CV_TIMEOUT);
361            return m;
362        } else {
363            NceMessage m = new NceMessage(8);
364            m.setBinary(false);
365            m.setOpCode('P');
366            m.addIntAsThree(cv, 1);
367            m.setElement(4, ' ');
368            m.addIntAsThree(val, 5);
369            m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
370            m.setTimeout(NCE_PAGED_CV_TIMEOUT);
371            return m;
372        }
373    }
374
375    @CheckForNull
376    public static NceMessage getReadRegister(@Nonnull NceTrafficController tc, int reg) {
377        // not supported by USB connected to SB3 or PH
378        if (tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_SB3 ||
379                tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_SB5 ||
380                tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_POWERPRO) {
381            log.error("attempt to send unsupported binary command READ_REG_CMD to NCE USB");
382            return null;
383        }
384        if (reg > 8) {
385            log.error("register number too large: {}", reg);
386        }
387        if (tc.getCommandOptions() >= NceTrafficController.OPTION_2006) {
388            NceMessage m = new NceMessage(2);
389            m.setBinary(true);
390            m.setReplyLen(REPLY_2);
391            m.setOpCode(READ_REG_CMD);
392            m.setElement(1, reg);
393            m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
394            m.setTimeout(NCE_PAGED_CV_TIMEOUT);
395            return m;
396        } else {
397            NceMessage m = new NceMessage(2);
398            m.setBinary(false);
399            m.setOpCode('V');
400            String s = "" + reg;
401            m.setElement(1, s.charAt(s.length() - 1));
402            m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
403            m.setTimeout(NCE_PAGED_CV_TIMEOUT);
404            return m;
405        }
406    }
407
408    public static NceMessage getWriteRegister(NceTrafficController tc, int reg, int val) {
409        // not supported by USB connected to SB3 or PH
410        if (tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_SB3 ||
411                tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_SB5 ||
412                tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_POWERPRO) {
413            log.error("attempt to send unsupported binary command WRITE_REG_CMD to NCE USB");
414            return null;
415        }
416        if (reg > 8) {
417            log.error("register number too large: {}", reg);
418        }
419        if (tc.getCommandOptions() >= NceTrafficController.OPTION_2006) {
420            NceMessage m = new NceMessage(3);
421            m.setBinary(true);
422            m.setReplyLen(REPLY_1);
423            m.setOpCode(WRITE_REG_CMD);
424            m.setElement(1, reg);
425            m.setElement(2, val);
426            m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
427            m.setTimeout(NCE_PAGED_CV_TIMEOUT);
428            return m;
429        } else {
430            NceMessage m = new NceMessage(6);
431            m.setBinary(false);
432            m.setOpCode('S');
433            String s = "" + reg;
434            m.setElement(1, s.charAt(s.length() - 1));
435            m.setElement(2, ' ');
436            m.addIntAsThree(val, 3);
437            m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
438            m.setTimeout(NCE_PAGED_CV_TIMEOUT);
439            return m;
440        }
441    }
442
443    public static NceMessage getReadDirectCV(NceTrafficController tc, int cv) {
444        // not supported by USB connected to SB3 or PH
445        if (tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_SB3 ||
446                tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_SB5 ||
447                tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_POWERPRO) {
448            log.error("attempt to send unsupported binary command READ_DIR_CV_CMD to NCE USB");
449            return null;
450        }
451        if (tc.getCommandOptions() < NceTrafficController.OPTION_2006) {
452            log.error("getReadDirectCV with option {}", tc.getCommandOptions());
453            return null;
454        }
455        NceMessage m = new NceMessage(3);
456        m.setBinary(true);
457        m.setReplyLen(REPLY_2);
458        m.setOpCode(READ_DIR_CV_CMD);
459        m.setElement(1, (cv >> 8));
460        m.setElement(2, (cv & 0x0FF));
461        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
462        m.setTimeout(NCE_DIRECT_CV_TIMEOUT);
463        return m;
464    }
465
466    public static NceMessage getWriteDirectCV(NceTrafficController tc, int cv, int val) {
467        // not supported by USB connected to SB3 or PH
468        if (tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_SB3 ||
469                tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_SB5 ||
470                tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_POWERPRO) {
471            log.error("attempt to send unsupported binary command WRITE_DIR_CV_CMD to NCE USB");
472            return null;
473        }
474        if (tc.getCommandOptions() < NceTrafficController.OPTION_2006) {
475            log.error("getWriteDirectCV with option {}", tc.getCommandOptions());
476        }
477        NceMessage m = new NceMessage(4);
478        m.setBinary(true);
479        m.setReplyLen(REPLY_1);
480        m.setOpCode(WRITE_DIR_CV_CMD);
481        m.setElement(1, cv >> 8);
482        m.setElement(2, cv & 0xFF);
483        m.setElement(3, val);
484        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
485        m.setTimeout(NCE_DIRECT_CV_TIMEOUT);
486        return m;
487    }
488    
489    public static NceMessage getEpromVersion(NceTrafficController tc) {
490        byte[] bl = NceBinaryCommand.getNceEpromRev();
491        NceMessage m = NceMessage.createBinaryMessage(tc, bl, REPLY_3);
492        return m;
493    }
494    
495    public static NceMessage sendLocoCmd(NceTrafficController tc, int locoAddr, byte locoSubCmd, byte locoData) {
496        byte[] bl = NceBinaryCommand.nceLocoCmd(locoAddr, locoSubCmd, locoData);
497        NceMessage m = NceMessage.createBinaryMessage(tc, bl, REPLY_1);
498        return m;
499    }
500
501    public static NceMessage sendPacketMessage(NceTrafficController tc, byte[] bytes) {
502        NceMessage m = sendPacketMessage(tc, bytes, 2);
503        return m;
504    }
505
506    public static NceMessage sendPacketMessage(NceTrafficController tc, byte[] bytes, int retries) {
507        // this command isn't supported by the NCE USB
508        if (tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE) {
509            log.error("attempt to send unsupported sendPacketMessage to NCE USB cmd: 0x{}", Integer.toHexString(SENDn_BYTES_CMD + bytes.length));
510            return null;
511        }
512        if (tc.getCommandOptions() >= NceTrafficController.OPTION_1999) {
513            if (bytes.length < 3 || bytes.length > 6) {
514                log.error("Send of NCE track packet too short or long:{} packet:{}", Integer.toString(bytes.length), Arrays.toString(bytes));
515            }
516            NceMessage m = new NceMessage(2 + bytes.length);
517            m.setBinary(true);
518            m.setTimeout(SHORT_TIMEOUT);
519            m.setReplyLen(1);
520            int i = 0; // counter to make it easier to format the message
521
522            m.setElement(i++, SENDn_BYTES_CMD + bytes.length);
523            m.setElement(i++, retries); // send this many retries. 
524            for (int j = 0; j < bytes.length; j++) {
525                m.setElement(i++, bytes[j] & 0xFF);
526            }
527            return m;
528        } else {
529            NceMessage m = new NceMessage(5 + 3 * bytes.length);
530            m.setBinary(false);
531            int i = 0; // counter to make it easier to format the message
532
533            m.setElement(i++, 'S'); // "S C02 " means sent it twice
534            m.setElement(i++, ' ');
535            m.setElement(i++, 'C');
536            m.setElement(i++, '0');
537            m.setElement(i++, '2');
538
539            for (int j = 0; j < bytes.length; j++) {
540                m.setElement(i++, ' ');
541                m.addIntAsTwoHex(bytes[j] & 0xFF, i);
542                i = i + 2;
543            }
544            m.setTimeout(SHORT_TIMEOUT);
545            return m;
546        }
547    }
548
549    public static NceMessage createBinaryMessage(NceTrafficController tc, byte[] bytes) {
550        return createBinaryMessage(tc, bytes, REPLY_1);
551    }
552
553    public static NceMessage createBinaryMessage(NceTrafficController tc, byte[] bytes, int replyLen) {
554        if (tc.getCommandOptions() < NceTrafficController.OPTION_2004) {
555            log.error("Attempt to send NCE command to EPROM built before 2004");
556        }
557        if (bytes.length < 1 || bytes.length > 20) {
558            log.error("NCE command message length error:{}", bytes.length);
559        }
560
561        NceMessage m = new NceMessage(bytes.length);
562        m.setBinary(true);
563        m.setReplyLen(replyLen);
564        m.setTimeout(SHORT_TIMEOUT);
565
566        for (int j = 0; j < bytes.length; j++) {
567            m.setElement(j, bytes[j] & 0xFF);
568        }
569        return m;
570    }
571
572    public static NceMessage queuePacketMessage(NceTrafficController tc, byte[] bytes) {
573        // this command isn't supported by the NCE USB
574        if (tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE) {
575            log.error("attempt to send unsupported queuePacketMessage to NCE USB");
576            return null;
577        }
578        if (tc.getCommandOptions() >= NceTrafficController.OPTION_1999) {
579            if (bytes.length < 3 || bytes.length > 6) {
580                log.error("Queue of NCE track packet too long:{} packet :{}", Integer.toString(bytes.length), Arrays.toString(bytes));
581            }
582            NceMessage m = new NceMessage(1 + bytes.length);
583            m.setBinary(true);
584            m.setReplyLen(REPLY_1);
585            int i = 0; // counter to make it easier to format the message
586
587            m.setElement(i++, QUEUEn_BYTES_CMD + bytes.length);
588            for (int j = 0; j < bytes.length; j++) {
589                m.setElement(i++, bytes[j] & 0xFF);
590            }
591            return m;
592        } else {
593            NceMessage m = new NceMessage(1 + 3 * bytes.length);
594            m.setBinary(false);
595            int i = 0; // counter to make it easier to format the message
596
597            m.setElement(i++, 'Q'); // "S C02 " means sent it twice
598
599            for (int j = 0; j < bytes.length; j++) {
600                m.setElement(i++, ' ');
601                m.addIntAsTwoHex(bytes[j] & 0xFF, i);
602                i = i + 2;
603            }
604            return m;
605        }
606    }
607
608    public static NceMessage createAccySignalMacroMessage(NceTrafficController tc, int op, int addr, int data) {
609        if (tc.getCommandOptions() < NceTrafficController.OPTION_2004) {
610            log.error("Attempt to send NCE command to EPROM built before 2004");
611        }
612        NceMessage m = new NceMessage(5);
613        m.setBinary(true);
614        m.setReplyLen(REPLY_1);
615        m.setTimeout(SHORT_TIMEOUT);
616        m.setOpCode(SEND_ACC_SIG_MACRO_CMD);
617        m.setElement(1, (addr >> 8) & 0xFF);
618        m.setElement(2, addr & 0xFF);
619        m.setElement(3, op);
620        m.setElement(4, data);
621        return m;
622    }
623
624    public static NceMessage createAccDecoderPktOpsMode(NceTrafficController tc, int accyAddr, int cvAddr, int cvData) {
625        NceMessage m = new NceMessage(6);
626        m.setBinary(true);
627        m.setReplyLen(REPLY_1);
628        m.setTimeout(SHORT_TIMEOUT);
629        byte[] mess = NceBinaryCommand.usbOpsModeAccy(accyAddr, cvAddr, cvData);
630        m.setOpCode(mess[0]);
631        m.setElement(1, mess[1]);
632        m.setElement(2, mess[2]);
633        m.setElement(3, mess[3]);
634        m.setElement(4, mess[4]);
635        m.setElement(5, mess[5]);
636        return m;
637    }
638
639    /**
640     * {@inheritDoc}
641     */
642    @Override
643    public String toMonitorString() {
644        return nceMon.displayMessage(this);
645    }
646}