001package jmri.jmrix.qsi;
002
003import jmri.ProgrammingMode;
004import jmri.util.StringUtil;
005
006/**
007 * Encodes a message to an QSI command station.
008 * <p>
009 * The {@link QsiReply} class handles the response from the command station.
010 *
011 * @author Bob Jacobsen Copyright (C) 2007, 2008
012 */
013public class QsiMessage extends jmri.jmrix.AbstractMessage {
014
015    // Special characters (NOTE: microchip bootloader does not use standard ASCII)
016    public static final int STX = 15;
017    public static final int DLE = 5;
018    public static final int ETX = 4;
019    public static final int CR = 0x0d;
020    public static final int LF = 0x0a;
021
022    // bootloader commands
023    public static final int RD_VER = 0;
024    public static final int WT_FLASH = 2;
025    public static final int ER_FLASH = 3;
026    public static final int WT_EEDATA = 5;
027
028    // Longest boot message is 256bytes each preceded by DLE + 2xSTX + ETX
029    static final int MAXSIZE = 515;
030
031    // create a new one
032    public QsiMessage(int i) {
033        super(i);
034    }
035
036    // from String
037    public QsiMessage(String s) {
038        super(s);
039    }
040
041    // copy one
042    public QsiMessage(QsiMessage m) {
043        super(m);
044    }
045
046    public void setOpCode(int i) {
047        _dataChars[0] = i;
048    }
049
050    public int getOpCode() {
051        return _dataChars[0];
052    }
053
054    public String getOpCodeHex() {
055        return "0x" + Integer.toHexString(getOpCode());
056    }
057
058    public void setLength(int i) {
059        _dataChars[1] = i;
060    }
061
062    public void setV4Length(int i) {
063        _dataChars[0] = hexDigit((i & 0xf0) >> 4);
064        _dataChars[1] = hexDigit(i & 0xf);
065    }
066
067    public void setAddress(int i) {
068        _dataChars[2] = i & 0xff;
069        _dataChars[3] = (i >> 8) & 0xff;
070        _dataChars[4] = i >> 16;
071    }
072
073    public void setV4Address(int i) {
074        _dataChars[2] = hexDigit((i & 0xf000) >> 12);
075        _dataChars[3] = hexDigit((i & 0xf00) >> 8);
076        _dataChars[4] = hexDigit((i & 0xf0) >> 4);
077        _dataChars[5] = hexDigit(i & 0xf);
078    }
079
080    public void setV4RecType(int i) {
081        _dataChars[6] = hexDigit((i & 0xf0) >> 4);
082        _dataChars[7] = hexDigit(i & 0xf);
083    }
084
085    public void setData(int[] d) {
086        System.arraycopy(d, 0, _dataChars, 5, d.length);
087    }
088
089    public void setV4Data(int[] d) {
090        int j = 8;
091        for (int i = 0; i < d.length; i++) {
092            _dataChars[j++] = hexDigit((d[i] & 0xf0) >> 4);
093            _dataChars[j++] = hexDigit(d[i] & 0xf);
094        }
095    }
096
097    public void setChecksum() {
098        int checksum = 0;
099        for (int i = 0; i < _nDataChars - 1; i++) {
100            checksum += _dataChars[i];
101        }
102        checksum = checksum & 0xff;
103        if (checksum > 0) {
104            checksum = 256 - checksum;
105        }
106        _dataChars[_nDataChars - 1] = checksum;
107    }
108
109    public void setV4Checksum(int length, int addr, int type, int[] data) {
110        int checksum = length + ((addr & 0xff00) >> 8) + (addr & 0xff) + type;
111        for (int i = 0; i < data.length; i++) {
112            checksum += data[i];
113        }
114        checksum = checksum & 0xff;
115        if (checksum > 0) {
116            checksum = 256 - checksum;
117        }
118        _dataChars[_nDataChars - 2] = hexDigit((checksum & 0xf0) >> 4);
119        _dataChars[_nDataChars - 1] = hexDigit(checksum & 0x0f);
120    }
121
122    private int hexDigit(int b) {
123        if (b > 9) {
124            return (b - 9 + 0x40);
125        } else {
126            return (b + 0x30);
127        }
128    }
129
130    public QsiMessage frame() {
131        int j = 2;
132        // Create new message to hold the framed one
133        QsiMessage f = new QsiMessage(MAXSIZE);
134        f.setElement(0, STX);
135        f.setElement(1, STX);
136        // copy existing message adding DLE
137        for (int i = 0; i < _nDataChars; i++) {
138            if (_dataChars[i] == STX
139                    || _dataChars[i] == ETX
140                    || _dataChars[i] == DLE) {
141                f.setElement(j++, DLE);
142            }
143            f.setElement(j++, _dataChars[i]);
144        }
145        f.setElement(j++, ETX);
146        f._nDataChars = j;
147        // return new message
148        return f;
149    }
150
151    public QsiMessage v4frame() {
152        int i;
153        // Create new message to hold the framed one
154        QsiMessage f = new QsiMessage(MAXSIZE);
155        f.setElement(0, ':');
156        // copy existing message adding CRLF
157        for (i = 1; i <= _nDataChars; i++) {
158            f.setElement(i, _dataChars[i - 1]);
159        }
160        f.setElement(i++, CR);
161        f.setElement(i++, LF);
162        f._nDataChars = i;
163        // return new message
164        return f;
165    }
166
167    @Override
168    public String toString() {
169        QsiSystemConnectionMemo memo = jmri.InstanceManager.getDefault(jmri.jmrix.qsi.QsiSystemConnectionMemo.class);
170        return toString(memo.getQsiTrafficController());
171    }
172
173    public String toString(QsiTrafficController controller) {
174        if (_dataChars == null) {
175            return "<none>";
176        }
177        StringBuilder s = new StringBuilder("");
178        if (controller == null || controller.isSIIBootMode()) {
179            for (int i = 0; i < _nDataChars; i++) {
180                s.append(StringUtil.twoHexFromInt(_dataChars[i])).append(" ");
181            }
182        } else {
183            for (int i = 0; i < _nDataChars; i++) {
184                s.append("<").append(_dataChars[i]).append(">");
185            }
186        }
187        return s.toString();
188    }
189
190    // diagnose format
191    public boolean isKillMain() {
192        return getOpCode() == '-';
193    }
194
195    public boolean isEnableMain() {
196        return getOpCode() == '+';
197    }
198
199    // static methods to return a formatted message
200    static public QsiMessage getEnableMain() {
201        QsiMessage m = new QsiMessage(1);
202        m.setOpCode('+');
203        return m;
204    }
205
206    static public QsiMessage getKillMain() {
207        QsiMessage m = new QsiMessage(1);
208        m.setOpCode('-');
209        return m;
210    }
211
212    static public QsiMessage getProgMode() {
213        QsiMessage m = new QsiMessage(1);
214        m.setOpCode('P');
215        return m;
216    }
217
218    // [AC] 11/09/2002 Leave QSI in programmer mode. Don't want to go
219    // to booster mode as this would power up the track.
220    static public QsiMessage getExitProgMode() {
221        QsiMessage m = new QsiMessage(1);
222        m.setOpCode(' ');
223        return m;
224    }
225
226    static public QsiMessage getClearStatus() {
227        // OP_REQ_CLEAR_ERROR_STATUS
228        QsiMessage m = new QsiMessage(3);
229        m.setElement(0, 17);
230        m.setElement(1, 0);
231        m.setElement(2, 0);
232        return m;
233    }
234
235    static public QsiMessage getReadCV(int cv, ProgrammingMode mode) {
236        // OP_REQ_READ_CV
237        QsiMessage m = new QsiMessage(4);
238        m.setElement(0, 9);
239        m.setElement(1, 1);
240        m.setElement(2, 0);
241        m.setElement(3, cv);
242        return m;
243    }
244
245    static public QsiMessage getWriteCV(int cv, int val, ProgrammingMode mode) {
246        // OP_REQ_WRITE_CV
247        QsiMessage m = new QsiMessage(5);
248        m.setElement(0, 30);
249        m.setElement(1, 2);
250        m.setElement(2, 0);
251        m.setElement(3, cv);
252        m.setElement(4, val);
253        return m;
254    }
255
256    // [AC] 11/09/2002 QSI doesn't currently support registered mode
257    static public QsiMessage getReadRegister(int reg) { //Vx
258        //        if (reg>8) log.error("register number too large: "+reg);
259        //        QsiMessage m = new QsiMessage(2);
260        //        m.setOpCode('V');
261        //        String s = ""+reg;
262        //        m.setElement(1, s.charAt(s.length()-1));
263        //        return m;
264        QsiMessage m = new QsiMessage(1);
265        m.setOpCode(' ');
266        return m;
267    }
268
269    static public QsiMessage getWriteRegister(int reg, int val) { //Sx xx
270        //        if (reg>8) log.error("register number too large: "+reg);
271        //        QsiMessage m = new QsiMessage(4);
272        //        m.setOpCode('S');
273        //        String s = ""+reg;
274        //        m.setElement(1, s.charAt(s.length()-1));
275        //        addIntAsTwoHex(val, m, 2);
276        //        return m;
277        QsiMessage m = new QsiMessage(1);
278        m.setOpCode(' ');
279        return m;
280    }
281
282    // Bootloader messages are initially created long enough for
283    // the message and checksum. The message is then framed with control
284    // characters before being returned
285    static public QsiMessage getReadBootVersion() {
286        QsiMessage m = new QsiMessage(3);
287        m.setOpCode(RD_VER);
288        m.setLength(2);
289        m.setChecksum();
290        return m.frame();
291    }
292
293    static public QsiMessage getWriteFlash(int addr, int[] data) {
294        int l = data.length;
295        // Writes are rounded up to multiples of 8 bytes
296        if (l % 8 != 0) {
297            l = l + (8 - l % 8);
298        }
299        // and data padded with erased condition
300        int padded[] = new int[l];
301        for (int i = 0; i < l; i++) {
302            if (i < data.length) {
303                padded[i] = data[i];
304            } else {
305                padded[i] = 0xff;
306            }
307        }
308        QsiMessage m = new QsiMessage(6 + l);
309        m.setOpCode(WT_FLASH);
310        // length is number of 8 byte blocks
311        m.setLength(l / 8);
312        m.setAddress(addr);
313        m.setData(padded);
314        m.setChecksum();
315        return m.frame();
316    }
317
318    static public QsiMessage getV4WriteFlash(int addr, int[] data, int type) {
319        // Create a v4 bootloader message which is same format as a record
320        // in the hex file
321        int l = (data.length + 5) * 2;
322        QsiMessage m = new QsiMessage(l);
323        m.setV4Length(data.length);
324        m.setV4Address(addr);
325        m.setV4RecType(type);
326        m.setV4Data(data);
327        m.setV4Checksum(data.length, addr, type, data);
328        return m.v4frame();
329    }
330
331    static public QsiMessage getV4EndOfFile() {
332        // Create a v4 bootloader end of file message
333        int l = 10;
334        QsiMessage m = new QsiMessage(l);
335        m.setV4Length(0);
336        m.setV4Address(0);
337        m.setV4RecType(1);
338        m.setV4Checksum(0, 0, 1, new int[0]);
339        return m.v4frame();
340    }
341
342    static public QsiMessage getv4ExtAddr() {
343        // Create a v4 bootloader extended address message
344        int l = 14;
345        int[] data = {0, 0};
346        QsiMessage m = new QsiMessage(l);
347        m.setV4Length(2);
348        m.setV4Address(0);
349        m.setV4RecType(4);
350        m.setV4Data(data);
351        m.setV4Checksum(0, 0, 4, data);
352        return m.v4frame();
353    }
354
355    static public QsiMessage getEraseFlash(int addr, int rows) {
356        QsiMessage m = new QsiMessage(6);
357        m.setOpCode(ER_FLASH);
358        // Erase a number of 64 byte rows
359        m.setLength(rows);
360        m.setAddress(addr);
361        m.setChecksum();
362        return m.frame();
363    }
364
365    static public QsiMessage getWriteEE(int addr, int[] data) {
366        QsiMessage m = new QsiMessage(6 + data.length);
367        m.setOpCode(WT_EEDATA);
368        m.setLength(data.length);
369        m.setAddress(addr & 0xff);
370        m.setData(data);
371        m.setChecksum();
372        return m.frame();
373    }
374
375    static public QsiMessage getReset() {
376        QsiMessage m = new QsiMessage(3);
377        m.setOpCode(0);
378        m.setLength(0);
379        m.setChecksum();
380        return m.frame();
381    }
382
383}