001package jmri.jmrix.mrc;
002
003import org.slf4j.Logger;
004import org.slf4j.LoggerFactory;
005
006/**
007 * Encodes and decoders messages to an MRC command station.
008 * <p>
009 * Some of the message formats used in this class are Copyright MRC, Inc. and
010 * used with permission as part of the JMRI project. That permission does not
011 * extend to uses in other software products. If you wish to use this code,
012 * algorithm or these message formats outside of JMRI, please contact MRC Inc
013 * for separate permission.
014 *
015 * @author Bob Jacobsen Copyright (C) 2001, 2004
016 * @author Kevin Dickerson Copyright (C) 2014
017 * @author kcameron Copyright (C) 2014
018 */
019public class MrcMessage {
020
021    // create a new one
022    public MrcMessage(int len) {
023        if (len < 1) {
024            log.error("invalid length in call to ctor: {}", len);  // NOI18N
025        }
026        _nDataChars = len;
027        _dataChars = new int[len];
028    }
029
030    // copy one
031    public MrcMessage(MrcMessage original) {
032        this(original._dataChars);
033    }
034
035    public MrcMessage(int[] contents) {
036        this(contents.length);
037        for (int i = 0; i < contents.length; i++) {
038            this.setElement(i, contents[i]);
039        }
040    }
041
042    public MrcMessage(byte[] contents) {
043        this(contents.length);
044        for (int i = 0; i < contents.length; i++) {
045            this.setElement(i, contents[i] & 0xFF);
046        }
047    }
048
049    MrcTrafficListener source = null;
050
051    public void setSource(MrcTrafficListener s) {
052        source = s;
053    }
054
055    public MrcTrafficListener getSource() {
056        return source;
057    }
058
059    int msgClass = ~0;
060
061    void setMessageClass(int i) {
062        msgClass = i;
063    }
064
065    public int getMessageClass() {
066        return msgClass;
067    }
068
069    public void replyNotExpected() {
070        replyExpected = false;
071    }
072
073    boolean replyExpected = true;
074
075    public boolean isReplyExpected() {
076        return replyExpected;
077    }
078
079    int SHORT_TIMEOUT = 150;
080    int SHORT_PROG_TIMEOUT = 4000;
081
082    int timeout = SHORT_TIMEOUT;
083
084    void setTimeout(int i) {
085        timeout = i;
086    }
087
088    public int getTimeout() {
089        return timeout;
090    }
091
092    int retries = 3;
093
094    public int getRetries() {
095        return retries;
096    }
097
098    public void setRetries(int i) {
099        retries = i;
100    }
101
102    boolean inError = false;
103
104    public void setMessageInError() {
105        inError = true;
106    }
107
108    public boolean isPacketInError() {
109        return inError;
110    }
111
112    int putHeader(int[] insert) {
113        int i = 0;
114        for (i = 0; i < insert.length; i++) {
115            this.setElement(i, insert[i]);
116        }
117        return i;
118    }
119
120    @Override
121    public String toString() {
122        return MrcPackets.toString(this);
123    }
124
125    static public MrcMessage getSendSpeed128(int addressLo, int addressHi, int speed) {
126        MrcMessage m = new MrcMessage(MrcPackets.getThrottlePacketLength());
127        m.setMessageClass(MrcInterface.THROTTLEINFO);
128        int i = m.putHeader(MrcPackets.THROTTLEPACKETHEADER);
129
130        m.setElement(i++, addressHi);
131        m.setElement(i++, 0x00);
132        m.setElement(i++, addressLo);
133        m.setElement(i++, 0x00);
134        m.setElement(i++, speed);
135        m.setElement(i++, 0x00);
136        m.setElement(i++, 0x02);
137        m.setElement(i++, 0x00);
138        m.setElement(i++, getCheckSum(addressHi, addressLo, speed, 0x02));
139        m.setElement(i++, 0x00);
140        //    m.setTimeout(100);
141        return m;
142    }
143
144    static public MrcMessage getSendSpeed28(int addressLo, int addressHi, int speed, boolean fwd) {
145        MrcMessage m = new MrcMessage(MrcPackets.getThrottlePacketLength());
146        m.setMessageClass(MrcInterface.THROTTLEINFO);
147        int i = m.putHeader(MrcPackets.THROTTLEPACKETHEADER);
148
149        int speedC = (speed & 0x1E) >> 1;
150        int c = (speed & 0x01) << 4;
151        speedC = speedC + c;
152        speedC = (fwd ? 0x60 : 0x40) | speedC;
153
154        m.setElement(i++, addressHi);
155        m.setElement(i++, 0x00);
156        m.setElement(i++, addressLo);
157        m.setElement(i++, 0x00);
158        m.setElement(i++, speedC);
159        m.setElement(i++, 0x00);
160        m.setElement(i++, 0x00);
161        m.setElement(i++, 0x00);
162        m.setElement(i++, getCheckSum(addressHi, addressLo, speedC, 0x00));
163        m.setElement(i++, 0x00);
164        //    m.setTimeout(100);
165        return m;
166    }
167
168    static public MrcMessage getSendFunction(int group, int addressLo, int addressHi, int data) {
169        MrcMessage m = new MrcMessage(MrcPackets.getFunctionPacketLength());
170        m.setMessageClass(MrcInterface.THROTTLEINFO);
171        m.replyNotExpected();
172        int i = 0;
173        switch (group) {
174            case 1:
175                i = m.putHeader(MrcPackets.FUNCTIONGROUP1PACKETHEADER);
176                break;
177            case 2:
178                i = m.putHeader(MrcPackets.FUNCTIONGROUP2PACKETHEADER);
179                break;
180            case 3:
181                i = m.putHeader(MrcPackets.FUNCTIONGROUP3PACKETHEADER);
182                break;
183            case 4:
184                i = m.putHeader(MrcPackets.FUNCTIONGROUP4PACKETHEADER);
185                break;
186            case 5:
187                i = m.putHeader(MrcPackets.FUNCTIONGROUP5PACKETHEADER);
188                break;
189            case 6:
190                i = m.putHeader(MrcPackets.FUNCTIONGROUP6PACKETHEADER);
191                break;
192            default:
193                log.error("Invalid function group: {}", group);  // NOI18N
194                return null;
195        }
196
197        m.setElement(i++, addressHi);
198        m.setElement(i++, 0x00);
199        m.setElement(i++, addressLo);
200        m.setElement(i++, 0x00);
201        m.setElement(i++, data);
202        m.setElement(i++, 0x00);
203        m.setElement(i++, getCheckSum(addressHi, addressLo, data, 0x00));
204        m.setElement(i++, 0x00);
205        //    m.setTimeout(100);
206        return m;
207    }
208
209    static int getCheckSum(int addressHi, int addressLo, int data1, int data2) {
210        int address = addressHi ^ addressLo;
211        int data = data1 ^ data2;
212        return (address ^ data);
213    }
214
215    static public MrcMessage getReadCV(int cv) { //R xxx
216        int cvLo = (cv);
217        int cvHi = (cv >> 8);
218
219        MrcMessage m = new MrcMessage(MrcPackets.getReadCVPacketLength());
220        m.setMessageClass(MrcInterface.PROGRAMMING);
221        m.setTimeout(LONG_TIMEOUT);
222        //m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
223        int i = m.putHeader(MrcPackets.READCVHEADER);
224
225        m.setElement(i++, cvHi);
226        m.setElement(i++, 0x00);
227        m.setElement(i++, cvLo);
228        m.setElement(i++, 0x00);
229        m.setElement(i++, getCheckSum(0x00, 0x00, cvHi, cvLo));
230        m.setElement(i++, 0x00);
231        return m;
232    }
233
234    static public MrcMessage getPOM(int addressLo, int addressHi, int cv, int val) {
235        MrcMessage m = new MrcMessage(MrcPackets.getWriteCVPOMPacketLength());
236        m.setMessageClass(MrcInterface.PROGRAMMING);
237        int i = m.putHeader(MrcPackets.WRITECVPOMHEADER);
238
239        cv--;
240        m.setElement(i++, addressHi);
241        m.setElement(i++, 0x00);
242        m.setElement(i++, addressLo);
243        m.setElement(i++, 0x00);
244        m.setElement(i++, 0xEC);
245        m.setElement(i++, 0x00);
246        m.setElement(i++, cv);
247        m.setElement(i++, 0x00);
248        m.setElement(i++, val);
249        m.setElement(i++, 0x00);
250        int checksum = getCheckSum(addressHi, addressLo, 0xEC, cv);
251        checksum = getCheckSum(checksum, val, 0x00, 0x00);
252        m.setElement(i++, checksum);
253        return m;
254    }
255
256    static public MrcMessage getWriteCV(int cv, int val) {
257        MrcMessage m = new MrcMessage(MrcPackets.getWriteCVPROGPacketLength());
258        m.setMessageClass(MrcInterface.PROGRAMMING);
259        int i = m.putHeader(MrcPackets.WRITECVPROGHEADER);
260
261        int cvLo = cv;
262        int cvHi = cv >> 8;
263
264        m.setElement(i++, cvHi);
265        m.setElement(i++, 0x00);
266        m.setElement(i++, cvLo);
267        m.setElement(i++, 0x00);
268        m.setElement(i++, val);
269        m.setElement(i++, 0x00);
270        m.setElement(i++, getCheckSum(cvHi, cvLo, val, 0x00));
271        return m;
272    }
273
274    static protected final int LONG_TIMEOUT = 65000;  // e.g. for programming options
275
276    public boolean validCheckSum() {
277        if (getNumDataElements() > 6) {
278            int result = 0;
279            for (int i = 4; i < getNumDataElements() - 2; i++) {
280                result = (getElement(i) & 255) ^ result;
281            }
282            if (result == (getElement(getNumDataElements() - 2) & 255)) {
283                return true;
284            }
285        }
286        return false;
287    }
288
289    public int value() {
290        int val = -1;
291        if (MrcPackets.startsWith(this, MrcPackets.READCVHEADERREPLY)) {
292            if (getElement(4) == getElement(6)) {
293                val = getElement(4) & 0xff;
294            } else {
295                log.error("Error in format of the returned CV value"); // NOI18N
296            }
297        } else {
298            log.error("Not a CV Read formated packet"); // NOI18N
299        }
300        return val;
301    }
302
303    public int getLocoAddress() {
304        if (getMessageClass() != MrcInterface.THROTTLEINFO && getMessageClass() != MrcInterface.PROGRAMMING) {
305            return -1;
306        }
307        int hi = getElement(4);
308        int lo = getElement(6);
309        if (hi == 0) {
310            return lo;
311        } else {
312            hi = (((hi & 255) - 192) << 8);
313            hi = hi + (lo & 255);
314            return hi;
315        }
316    }
317
318    public int getAccAddress() {
319        if (getMessageClass() != MrcInterface.TURNOUTS) {
320            return -1;
321        }
322        int lowbyte = (getElement(4) & 0xFF) & 0x3f;
323        int highbyte = ((getElement(6) & 0xFF) & 0x70) >> 4;
324        highbyte = ((~highbyte & 0x07) << 6);
325
326        int address = (((lowbyte + highbyte) - 1) << 2) + 1;
327
328        address += ((getElement(6) & 0xFF) & 0x07) >> 1;
329        return address;
330    }
331
332    public int getAccState() {
333        if (((getElement(6) & 0x07) & 0x01) == 0x01) {
334            return jmri.Turnout.CLOSED;
335        } else {
336            return jmri.Turnout.THROWN;
337        }
338    }
339
340    /**
341     * set the fast clock ratio ratio is integer and max of 60 and min of 1
342     * @param ratio value to set new clock speed
343     *
344     * @return new message to set the clock speed ratio
345     */
346    static public MrcMessage setClockRatio(int ratio) {
347        if (ratio < 0 || ratio > 60) {
348            log.error("ratio number too large: {}", ratio); // NOI18N
349        }
350        MrcMessage m = new MrcMessage(MrcPackets.getSetClockRatioPacketLength());
351        m.setMessageClass(MrcInterface.CLOCK);
352        int i = m.putHeader(MrcPackets.SETCLOCKRATIOHEADER);
353
354        m.setElement(i++, ratio);
355        m.setElement(i++, 0x00);
356        m.setElement(i++, getCheckSum(ratio, 0x00, 0x00, 0x00));
357        m.replyNotExpected();
358        return m;
359    }
360
361    /**
362     * set the fast time clock
363     * @param hour hour value for fast clock
364     * @param minute minute value for fast clock
365     *
366     * @return new message to set the hour/minutes of the fast clock
367     */
368    static public MrcMessage setClockTime(int hour, int minute) {
369        if (hour < 0 || hour > 23) {
370            log.error("hour number out of range : {}", hour); // NOI18N
371        }
372        if (minute < 0 || minute > 59) {
373            log.error("minute number out of range : {}", minute); // NOI18N
374        }
375        MrcMessage m = new MrcMessage(MrcPackets.getSetClockTimePacketLength());
376        m.setMessageClass(MrcInterface.CLOCK);
377        int i = m.putHeader(MrcPackets.SETCLOCKTIMEHEADER);
378
379        m.setElement(i++, hour);
380        m.setElement(i++, 0x00);
381        m.setElement(i++, minute);
382        m.setElement(i++, 0x00);
383        m.setElement(i++, getCheckSum(hour, 0x00, minute, 0x00));
384        m.replyNotExpected();
385        return m;
386    }
387
388    /**
389     * Toggle the AM/PM vs 24 hour mode
390     *
391     * @return MrcMessage
392     */
393    static public MrcMessage setClockAmPm() {
394        MrcMessage m = new MrcMessage(MrcPackets.getSetClockAmPmPacketLength());
395        m.setMessageClass(MrcInterface.CLOCK);
396        int i = m.putHeader(MrcPackets.SETCLOCKAMPMHEADER);
397
398        m.setElement(i++, 0x32);
399        m.setElement(i++, 0x00);
400        m.setElement(i++, getCheckSum(0x32, 0x00, 0x00, 0x00));
401        m.replyNotExpected();
402        return m;
403    }
404
405    /**
406     * Set Track Power Off/Emergency Stop
407     *
408     * @return MrcMessage
409     */
410    static public MrcMessage setPowerOff() {
411        MrcMessage m = new MrcMessage(MrcPackets.getPowerOffPacketLength());
412        m.setMessageClass(MrcInterface.POWER);
413        m.putHeader(MrcPackets.POWEROFF);
414        m.replyNotExpected();
415        return m;
416    }
417
418    static public MrcMessage setPowerOn() {
419        MrcMessage m = new MrcMessage(MrcPackets.getPowerOffPacketLength());
420        m.setMessageClass(MrcInterface.POWER);
421        m.putHeader(MrcPackets.POWERON);
422        m.replyNotExpected();
423        return m;
424    }
425
426    /**
427     * Get a message for a "Switch Position Normal" command to a specific
428     * accessory decoder on the layout.
429     * @param address address of turnout
430     * @param closed position for the turnout
431     * @return new message for getting switch posistion
432     */
433    static MrcMessage getSwitchMsg(int address, boolean closed) {
434        MrcMessage m = new MrcMessage(MrcPackets.getAccessoryPacketLength());
435        m.setMessageClass(MrcInterface.TURNOUTS);
436        m.putHeader(MrcPackets.ACCESSORYPACKETHEADER);
437        byte[] packet = jmri.NmraPacket.accDecoderPkt(address, closed);
438        if (packet == null) {
439            return null;
440        }
441        m.setElement(4, packet[0]);
442        m.setElement(5, 0x00);
443        m.setElement(6, packet[1]);
444        m.setElement(7, 0x00);
445        m.setElement(8, packet[2]);
446        m.setElement(9, 0x00);
447        m.setRetries(2);
448        m.replyNotExpected();
449        m.setByteStream();
450        return m;
451    }
452
453    static MrcMessage getRouteMsg(int address, boolean closed) {
454        MrcMessage m = new MrcMessage(MrcPackets.getRouteControlPacketLength());
455        m.setMessageClass(MrcInterface.TURNOUTS);
456        m.putHeader(MrcPackets.ROUTECONTROLPACKETHEADER);
457
458        int i = m.putHeader(MrcPackets.ROUTECONTROLPACKETHEADER);
459        m.setElement(i++, address);
460        m.setElement(i++, 0x00);
461        int state = closed ? 0x80 : 0x00;
462        m.setElement(i++, state);
463        m.setElement(i++, 0x00);
464        m.setElement(i++, getCheckSum(address, 0x00, state, 0x00));
465        m.setElement(i++, 0x00);
466        m.setRetries(2);
467        m.replyNotExpected();
468        m.setByteStream();
469        return m;
470    }
471
472    static public MrcMessage setNoData() {
473        MrcMessage m = new MrcMessage(4);
474        m.setMessageClass(MrcInterface.POLL);
475        m.setElement(0, 0x00);
476        m.setElement(1, 0x00);
477        m.setElement(2, 0x00);
478        m.setElement(3, 0x00);
479//Message is throw away, so if it doesn't get transmited correctly then forget about it, don't attempt retry.
480        m.setTimeout(0);
481        m.setRetries(0);
482        m.setByteStream();
483        return m;
484    }
485
486    byte[] byteStream;
487
488    void setByteStream() {
489        int len = getNumDataElements();
490        byteStream = new byte[len];
491        for (int i = 0; i < len; i++) {
492            byteStream[i] = (byte) getElement(i);
493        }
494    }
495
496    byte[] getByteStream() {
497        return byteStream;
498    }
499
500    public int getElement(int n) {
501        return _dataChars[n];
502    }
503
504    // accessors to the bulk data
505    public int getNumDataElements() {
506        return _nDataChars;
507    }
508
509    public void setElement(int n, int v) {
510        _dataChars[n] = v;
511    }
512
513    // contents (private)
514    private int _nDataChars = 0;
515    private int _dataChars[] = null;
516
517    private final static Logger log = LoggerFactory.getLogger(MrcMessage.class);
518
519}
520
521
522