001package jmri.jmrix.sprog;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import jmri.ProgrammingMode;
005import jmri.jmrix.sprog.SprogConstants.SprogState;
006import org.slf4j.Logger;
007import org.slf4j.LoggerFactory;
008
009/**
010 * Encode a message to an SPROG command station.
011 * <p>
012 * The {@link SprogReply} class handles the response from the command station.
013 *
014 * @author Bob Jacobsen Copyright (C) 2001
015 */
016public class SprogMessage extends jmri.jmrix.AbstractMRMessage {
017
018    // Special characters (NOTE: microchip bootloader does not use standard ASCII)
019    public static final int STX = 15;
020    public static final int DLE = 5;
021    public static final int ETX = 4;
022    public static final int CR = 0x0d;
023    public static final int LF = 0x0a;
024
025    // bootloader commands
026    public static final int RD_VER = 0;
027    public static final int WT_FLASH = 2;
028    public static final int ER_FLASH = 3;
029    public static final int WT_EEDATA = 5;
030
031    // Longest boot message is 256bytes each preceded by DLE + 2xSTX + ETX
032    public static final int MAXSIZE = 515;
033
034    private static int msgId = 0;
035    protected int _id = -1;
036    
037    /**
038     * Get next message id
039     * 
040     * For modules that need to match their own message/reply pairs in strict sequence, e.g., 
041     * SprogCommandStation, return a unique message id. The id wraps at a suitably large
042     * value.
043     * 
044     * @return the message id
045     */
046    protected synchronized int newMsgId() {
047        msgId = (msgId+1)%65536;
048        return msgId;
049    }
050    
051    public int getId() {
052        return _id;
053    }
054    
055    // create a new one
056    public SprogMessage(int i) {
057        if (i < 1) {
058            log.error("invalid length in call to ctor");
059        }
060        _nDataChars = i;
061        _dataChars = new int[i];
062        _id = newMsgId();
063    }
064
065    /**
066     * Create a new SprogMessage containing a byte array to represent a packet
067     * to output.
068     *
069     * @param packet The contents of the packet
070     */
071    public SprogMessage(byte[] packet) {
072        this(1 + (packet.length * 3));
073        int i; // counter of byte in output message
074        int j; // counter of byte in input packet
075
076        i = 0;
077        this.setElement(i++, 'O');  // "O " starts output packet
078
079        // add each byte of the input message
080        for (j = 0; j < packet.length; j++) {
081            this.setElement(i++, ' ');
082            String s = Integer.toHexString(packet[j] & 0xFF).toUpperCase();
083            if (s.length() == 1) {
084                this.setElement(i++, '0');
085                this.setElement(i++, s.charAt(0));
086            } else {
087                this.setElement(i++, s.charAt(0));
088                this.setElement(i++, s.charAt(1));
089            }
090        }
091        _id = newMsgId();
092    }
093
094    // from String
095    public SprogMessage(String s) {
096        _nDataChars = s.length();
097        _dataChars = new int[_nDataChars];
098        for (int i = 0; i < _nDataChars; i++) {
099            _dataChars[i] = s.charAt(i);
100        }
101        _id = newMsgId();
102    }
103
104    // copy one
105    public SprogMessage(SprogMessage m) {
106        if (m == null) {
107            log.error("copy ctor of null message");
108            return;
109        }
110        _nDataChars = m._nDataChars;
111        _dataChars = new int[_nDataChars];
112        for (int i = 0; i < _nDataChars; i++) {
113            _dataChars[i] = m._dataChars[i];
114        }
115        // Copy has a unique id
116        _id = newMsgId();
117    }
118
119    @Override
120    public void setElement(int n, int v) {
121        _dataChars[n] = v;
122    }
123
124    private void setLength(int i) {
125        _dataChars[1] = i;
126    }
127
128    private void setAddress(int i) {
129        _dataChars[2] = i & 0xff;
130        _dataChars[3] = (i >> 8) & 0xff;
131        _dataChars[4] = i >> 16;
132    }
133
134    private void setData(int[] d) {
135        for (int i = 0; i < d.length; i++) {
136            _dataChars[5 + i] = d[i];
137        }
138    }
139
140    private void setChecksum() {
141        int checksum = 0;
142        for (int i = 0; i < _nDataChars - 1; i++) {
143            checksum += _dataChars[i];
144        }
145        checksum = checksum & 0xff;
146        if (checksum > 0) {
147            checksum = 256 - checksum;
148        }
149        _dataChars[_nDataChars - 1] = checksum;
150    }
151
152    private SprogMessage frame() {
153        int j = 2;
154        // Create new message to hold the framed one
155        SprogMessage f = new SprogMessage(MAXSIZE);
156        f.setElement(0, STX);
157        f.setElement(1, STX);
158        // copy existing message adding DLE
159        for (int i = 0; i < _nDataChars; i++) {
160            if (_dataChars[i] == STX
161                    || _dataChars[i] == ETX
162                    || _dataChars[i] == DLE) {
163                f.setElement(j++, DLE);
164            }
165            f.setElement(j++, _dataChars[i]);
166        }
167        f.setElement(j++, ETX);
168        f._nDataChars = j;
169        // return new message
170        return f;
171    }
172
173    // display format
174    @Override
175    public String toString(){
176       // default to not SIIBootMode being false.
177       return this.toString(false);
178    }
179
180    public String toString(boolean isSIIBootMode) {
181        StringBuffer buf = new StringBuffer();
182        if (!isSIIBootMode) {
183            for (int i = 0; i < _nDataChars; i++) {
184                buf.append((char) _dataChars[i]);
185            }
186        } else {
187            for (int i = 0; i < _nDataChars; i++) {
188                //s+="<"+_dataChars[i]+">";
189                buf.append("<");
190                buf.append(_dataChars[i]);
191                buf.append(">");
192            }
193        }
194        return buf.toString();
195    }
196
197    /**
198     * Get formatted message for direct output to stream - this is the final
199     * format of the message as a byte array.
200     *
201     * @param sprogState a SprogState variable representing the current state of
202     *                   the Sprog
203     * @return the formatted message as a byte array
204     */
205    public byte[] getFormattedMessage(SprogState sprogState) {
206        int len = this.getNumDataElements();
207
208        // space for carriage return if required
209        int cr = 0;
210        if (sprogState != SprogState.SIIBOOTMODE) {
211            cr = 1;
212        }
213
214        byte msg[] = new byte[len + cr];
215
216        for (int i = 0; i < len; i++) {
217            if (sprogState != SprogState.SIIBOOTMODE) {
218               msg[i] = (byte) ( this.getElement(i) & 0x7f);
219            } else {
220               msg[i] = (byte) ( this.getElement(i));
221            }
222        }
223        if (sprogState != SprogState.SIIBOOTMODE) {
224            msg[len] = 0x0d;
225        }
226        return msg;
227    }
228
229    // diagnose format
230    public boolean isKillMain() {
231        return getOpCode() == '-';
232    }
233
234    public boolean isEnableMain() {
235        return getOpCode() == '+';
236    }
237
238    // static methods to return a formatted message
239    static public SprogMessage getEnableMain() {
240        SprogMessage m = new SprogMessage(1);
241        m.setOpCode('+');
242        return m;
243    }
244
245    static public SprogMessage getKillMain() {
246        SprogMessage m = new SprogMessage(1);
247        m.setOpCode('-');
248        return m;
249    }
250
251    static public SprogMessage getStatus() {
252        SprogMessage m = new SprogMessage(1);
253        m.setOpCode('S');
254        return m;
255    }
256
257    /*
258     * SPROG uses same commands for reading and writing, with the number of
259     * parameters determining the action. Currently supports page mode and
260     * bit direct modes. A single parameter is taken as the CV address to read.
261     * Two parametes are taken as the CV address and data to be written.
262     */
263    static public SprogMessage getReadCV(int cv, ProgrammingMode mode) {
264        SprogMessage m = new SprogMessage(6);
265        if (mode == ProgrammingMode.PAGEMODE) {
266            m.setOpCode('V');
267        } else { // Bit direct mode
268            m.setOpCode('C');
269        }
270        addSpace(m, 1);
271        addIntAsFour(cv, m, 2);
272        return m;
273    }
274
275    /*
276     * CV reads can pass a hint by using different commands. The hint will first
277     * be verified, potentially speeding up the read process.
278     * 
279     * @param cv        CV address
280     * @param mode      Programming mode
281     * @param startVal  Hint
282     * @return 
283     */
284    static public SprogMessage getReadCV(int cv, ProgrammingMode mode, int startVal) {
285        SprogMessage m = new SprogMessage(10);
286        if (mode == ProgrammingMode.PAGEMODE) {
287            m.setOpCode('U');
288        } else { // Bit direct mode
289            m.setOpCode('D');
290        }
291        addSpace(m, 1);
292        addIntAsFour(cv, m, 2);
293        addSpace(m, 6);
294        addIntAsThree(startVal, m, 7);
295        return m;
296    }
297
298    static public SprogMessage getWriteCV(int cv, int val, ProgrammingMode mode) {
299        SprogMessage m = new SprogMessage(10);
300        if (mode == ProgrammingMode.PAGEMODE) {
301            m.setOpCode('V');
302        } else { // Bit direct mode
303            m.setOpCode('C');
304        }
305        addSpace(m, 1);
306        addIntAsFour(cv, m, 2);
307        addSpace(m, 6);
308        addIntAsThree(val, m, 7);
309        return m;
310    }
311
312    // [AC] 11/09/2002 SPROG doesn't currently support registered mode
313    static public SprogMessage getReadRegister(int reg) { //Vx
314        SprogMessage m = new SprogMessage(1);
315        m.setOpCode(' ');
316        return m;
317    }
318
319    static public SprogMessage getWriteRegister(int reg, int val) { //Sx xx
320        SprogMessage m = new SprogMessage(1);
321        m.setOpCode(' ');
322        return m;
323    }
324
325    /**
326     * Get a message containing a DCC packet.
327     *
328     * @param bytes byte[]
329     * @return SprogMessage
330     */
331    static public SprogMessage getPacketMessage(byte[] bytes) {
332        SprogMessage m = new SprogMessage(1 + 3 * bytes.length);
333        int i = 0; // counter to make it easier to format the message
334
335        m.setElement(i++, 'O');  // "O" Output DCC packet command
336        for (int j = 0; j < bytes.length; j++) {
337            m.setElement(i++, ' ');
338            m.addIntAsTwoHex(bytes[j] & 0xFF, i);
339            i = i + 2;
340        }
341        return m;
342    }
343
344    // Bootloader messages are initially created long enough for
345    // the message and checksum. The message is then framed with control
346    // characters before being returned
347    static public SprogMessage getReadBootVersion() {
348        SprogMessage m = new SprogMessage(3);
349        m.setOpCode(RD_VER);
350        m.setLength(2);
351        m.setChecksum();
352        return m.frame();
353    }
354
355    static public SprogMessage getWriteFlash(int addr, int[] data, int blockLen) {
356        int l = data.length;
357        int offset;
358        // Writes are rounded up to multiples of blockLen
359        if (l % blockLen != 0) {
360            l = l + (blockLen - l % blockLen);
361        }
362        // and data padded with erased condition
363        int padded[] = new int[l];
364        for (int i = 0; i < l; i++) {
365            padded[i] = 0xff;
366        }
367        // Address is masked to start on blockLen boundary
368        if (blockLen == 16) {
369            offset = addr & 0xF;
370            addr = addr & 0xFFFFFFF0;
371        } else {
372            offset = addr & 0x7;
373            addr = addr & 0xFFFFFFF8;
374        }
375        // Copy data into padded array at address offset
376        for (int i = 0; i < data.length; i++) {
377            padded[i + offset] = data[i];
378        }
379        SprogMessage m = new SprogMessage(6 + l);
380        m.setOpCode(WT_FLASH);
381        // length is number of blockLen blocks
382        m.setLength(l / blockLen);
383        m.setAddress(addr);
384        m.setData(padded);
385        m.setChecksum();
386        return m.frame();
387    }
388
389    static public SprogMessage getEraseFlash(int addr, int rows) {
390        SprogMessage m = new SprogMessage(6);
391        m.setOpCode(ER_FLASH);
392        // Erase a number of 64 byte rows
393        m.setLength(rows);
394        m.setAddress(addr);
395        m.setChecksum();
396        return m.frame();
397    }
398
399    static public SprogMessage getWriteEE(int addr, int[] data) {
400        SprogMessage m = new SprogMessage(6 + data.length);
401        m.setOpCode(WT_EEDATA);
402        m.setLength(data.length);
403        m.setAddress(addr & 0xff);
404        m.setData(data);
405        m.setChecksum();
406        return m.frame();
407    }
408
409    static public SprogMessage getReset() {
410        SprogMessage m = new SprogMessage(3);
411        m.setOpCode(0);
412        m.setLength(0);
413        m.setChecksum();
414        return m.frame();
415    }
416
417    // [AC] 11/09/2002
418    private static String addSpace(SprogMessage m, int offset) {
419        String s = " ";
420        m.setElement(offset, ' ');
421        return s;
422    }
423
424    // [AC] 11/09/2002
425    @SuppressWarnings("unused")
426    @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", justification="was previously marked with @SuppressWarnings, reason unknown")
427    private static String addIntAsTwo(int val, SprogMessage m, int offset) {
428        String s = "" + val;
429        if (s.length() != 2) {
430            s = "0" + s;  // handle <10
431        }
432        m.setElement(offset, s.charAt(0));
433        m.setElement(offset + 1, s.charAt(1));
434        return s;
435    }
436
437    private static String addIntAsThree(int val, SprogMessage m, int offset) {
438        String s = "" + val;
439        if (s.length() != 3) {
440            s = "0" + s;  // handle <10
441        }
442        if (s.length() != 3) {
443            s = "0" + s;  // handle <100
444        }
445        m.setElement(offset, s.charAt(0));
446        m.setElement(offset + 1, s.charAt(1));
447        m.setElement(offset + 2, s.charAt(2));
448        return s;
449    }
450
451    private static String addIntAsFour(int val, SprogMessage m, int offset) {
452        String s = "" + val;
453        if (s.length() != 4) {
454            s = "0" + s;  // handle <10
455        }
456        if (s.length() != 4) {
457            s = "0" + s;  // handle <100
458        }
459        if (s.length() != 4) {
460            s = "0" + s;  // handle <1000
461        }
462        m.setElement(offset, s.charAt(0));
463        m.setElement(offset + 1, s.charAt(1));
464        m.setElement(offset + 2, s.charAt(2));
465        m.setElement(offset + 3, s.charAt(3));
466        return s;
467    }
468
469    private final static Logger log = LoggerFactory.getLogger(SprogMessage.class);
470
471}