001package jmri.jmrix.mrc;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import java.text.DecimalFormat;
005import org.slf4j.Logger;
006import org.slf4j.LoggerFactory;
007
008/**
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 Kevin Dickerson 2014
016 * @author Ken Cameron 2014
017 */
018public class MrcPackets {
019
020    private static final Logger log = LoggerFactory.getLogger(MrcPackets.class);
021    public static final int THROTTLEPACKETCMD = 37;
022    static final int[] THROTTLEPACKETHEADER = new int[]{THROTTLEPACKETCMD, 0, THROTTLEPACKETCMD, 0};
023    static final int THROTTLEPACKETLENGTH = 10; //length of packet less the header
024
025    public static final int FUNCTIONGROUP1PACKETCMD = 52;
026    static final int[] FUNCTIONGROUP1PACKETHEADER = new int[]{FUNCTIONGROUP1PACKETCMD, 0, FUNCTIONGROUP1PACKETCMD, 0};
027
028    public static final int FUNCTIONGROUP2PACKETCMD = 68;
029    static final int[] FUNCTIONGROUP2PACKETHEADER = new int[]{FUNCTIONGROUP2PACKETCMD, 0, FUNCTIONGROUP2PACKETCMD, 0};
030
031    public static final int FUNCTIONGROUP3PACKETCMD = 84;
032    static final int[] FUNCTIONGROUP3PACKETHEADER = new int[]{FUNCTIONGROUP3PACKETCMD, 0, FUNCTIONGROUP3PACKETCMD, 0};
033
034    public static final int FUNCTIONGROUP4PACKETCMD = 116;
035    static final int[] FUNCTIONGROUP4PACKETHEADER = new int[]{FUNCTIONGROUP4PACKETCMD, 0, FUNCTIONGROUP4PACKETCMD, 0};
036
037    public static final int FUNCTIONGROUP5PACKETCMD = 132;
038    static final int[] FUNCTIONGROUP5PACKETHEADER = new int[]{FUNCTIONGROUP5PACKETCMD, 0, FUNCTIONGROUP5PACKETCMD, 0};
039
040    public static final int FUNCTIONGROUP6PACKETCMD = 164;
041    static final int[] FUNCTIONGROUP6PACKETHEADER = new int[]{FUNCTIONGROUP6PACKETCMD, 0, FUNCTIONGROUP6PACKETCMD, 0};
042
043    static final int FUNCTIONGROUPLENGTH = 8;
044
045    public static final int ADDTOCONSISTPACKETCMD = 100;
046    static final int[] ADDTOCONSISTPACKETHEADER = new int[]{ADDTOCONSISTPACKETCMD, 0, ADDTOCONSISTPACKETCMD, 0};
047    static final int ADDTOCONSISTPACKETLENGTH = 4;
048
049    public static final int CLEARCONSISTPACKETCMD = 98;
050    static final int[] CLEARCONSISTPACKETHEADER = new int[]{CLEARCONSISTPACKETCMD, 0, CLEARCONSISTPACKETCMD, 0};
051    static final int CLEARCONSISTPACKETLENGTH = 4;
052
053    public static final int ROUTECONTROLPACKETCMD = 195;
054    static final int[] ROUTECONTROLPACKETHEADER = new int[]{ROUTECONTROLPACKETCMD, 0, ROUTECONTROLPACKETCMD, 0};
055    static final int ROUTECONTROLPACKETLENGTH = 6; //Need to check.
056
057    public static final int CLEARROUTEPACKETCMD = 210;
058    static final int[] CLEARROUTEPACKETHEADER = new int[]{CLEARROUTEPACKETCMD, 0, CLEARROUTEPACKETCMD, 0};
059    static final int CLEARROUTEPACKETLENGTH = 4;
060
061    public static final int ADDTOROUTEPACKETCMD = 211;
062    static final int[] ADDTOROUTEPACKETHEADER = new int[]{ADDTOROUTEPACKETCMD, 0, ADDTOROUTEPACKETCMD, 0};
063    static final int ADDTOROUTEPACKETLENGTH = 6;
064
065    public static final int ACCESSORYPACKETCMD = 115;
066    static final int[] ACCESSORYPACKETHEADER = new int[]{ACCESSORYPACKETCMD, 0, ACCESSORYPACKETCMD, 0};
067    static final int ACCESSORYPACKETLENGTH = 6;
068
069    public static final int WRITECVPOMCMD = 86;
070    static final int[] WRITECVPOMHEADER = new int[]{WRITECVPOMCMD, 0, WRITECVPOMCMD, 0};
071    private static final int WRITECVPOMLENGTH = 12;
072
073    public static final int WRITECVPROGCMD = 36;
074    static final int[] WRITECVPROGHEADER = new int[]{WRITECVPROGCMD, 0, WRITECVPROGCMD, 0};
075    private static final int WRITECVPROGLENGTH = 8;
076
077    public static final int READDECODERADDRESSCMD = 66;
078    static final int[] READDECODERADDRESS = new int[]{READDECODERADDRESSCMD, 0, READDECODERADDRESSCMD, 0, READDECODERADDRESSCMD, 0};
079
080    public static final int READCVCMD = 67;
081    static final int[] READCVHEADER = new int[]{READCVCMD, 0, READCVCMD, 0};
082    private static final int READCVLENGTH = 6;
083
084    public static final int PROGCMDSENTCODE = 51;
085    static final int[] PROGCMDSENT = new int[]{PROGCMDSENTCODE, 0, PROGCMDSENTCODE, 0};
086
087    public static final int READCVHEADERREPLYCODE = 102;
088    static final int[] READCVHEADERREPLY = new int[]{READCVHEADERREPLYCODE, 0, READCVHEADERREPLYCODE, 0};
089    static final int READCVPACKETLENGTH = 4; //need to double check the length of this packet
090
091    public static final int SETCLOCKRATIOCMD = 18;
092    static final int[] SETCLOCKRATIOHEADER = new int[]{SETCLOCKRATIOCMD, 0, SETCLOCKRATIOCMD, 0};
093    private static final int SETCLOCKRATIOLENGTH = 4;
094
095    public static final int SETCLOCKTIMECMD = 19;
096    static final int[] SETCLOCKTIMEHEADER = new int[]{SETCLOCKTIMECMD, 0, SETCLOCKTIMECMD, 0};
097    private static final int SETCLOCKTIMELENGTH = 6;
098
099    public static final int SETCLOCKAMPMCMD = 50;
100    static final int[] SETCLOCKAMPMHEADER = new int[]{SETCLOCKAMPMCMD, 0, SETCLOCKAMPMCMD, 0};
101    private static final int SETCLOCKAMPMLENGTH = 4;
102
103    public static final int LOCOSOLECONTROLCODE = 34;
104    static final int[] LOCOSOLECONTROL = new int[]{LOCOSOLECONTROLCODE, 0, LOCOSOLECONTROLCODE, 0}; //Reply indicates that we are the sole controller of the loco
105
106    public static final int LOCODBLCONTROLCODE = 221;
107    static final int[] LOCODBLCONTROL = new int[]{LOCODBLCONTROLCODE, 0, LOCODBLCONTROLCODE, 0}; //Reply indicates that another throttle also has controll of the loco
108
109    public static final int GOODCMDRECEIVEDCODE = 85;
110    static final int[] GOODCMDRECEIVED = new int[]{GOODCMDRECEIVEDCODE, 0, GOODCMDRECEIVEDCODE, 0};
111
112    public static final int BADCMDRECEIVEDCODE = 238; //Or unable to read from decoder
113    static final int[] BADCMDRECEIVED = new int[]{BADCMDRECEIVEDCODE, 0, BADCMDRECEIVEDCODE, 0};
114
115    public static final int POWERONCMD = 130;
116    static final int[] POWERON = new int[]{POWERONCMD, 0, POWERONCMD, 0, POWERONCMD, 0, POWERONCMD, 0};
117
118    public static final int POWEROFFCMD = 146;
119    static final int[] POWEROFF = new int[]{POWEROFFCMD, 0, POWEROFFCMD, 0, POWEROFFCMD, 0, POWEROFFCMD, 0};
120
121    public static int getAddToConsistPacketLength() {
122        return ADDTOCONSISTPACKETHEADER.length + ADDTOCONSISTPACKETLENGTH;
123    }
124
125    public static int getClearConsistPacketLength() {
126        return CLEARCONSISTPACKETHEADER.length + CLEARCONSISTPACKETLENGTH;
127    }
128
129    public static int getRouteControlPacketLength() {
130        return ROUTECONTROLPACKETHEADER.length + ROUTECONTROLPACKETLENGTH;
131    }
132
133    public static int getClearRoutePacketLength() {
134        return CLEARROUTEPACKETHEADER.length + CLEARROUTEPACKETLENGTH;
135    }
136
137    public static int getAddToRoutePacketLength() {
138        return ADDTOROUTEPACKETHEADER.length + ADDTOROUTEPACKETLENGTH;
139    }
140
141    public static int getAccessoryPacketLength() {
142        return ACCESSORYPACKETHEADER.length + ACCESSORYPACKETLENGTH;
143    }
144
145    public static int getWriteCVPROGPacketLength() {
146        return WRITECVPROGHEADER.length + WRITECVPROGLENGTH;
147    }
148
149    public static int getWriteCVPOMPacketLength() {
150        return WRITECVPOMHEADER.length + WRITECVPOMLENGTH;
151    }
152
153    public static int getSetClockRatioPacketLength() {
154        return SETCLOCKRATIOHEADER.length + SETCLOCKRATIOLENGTH;
155    }
156
157    public static int getSetClockAmPmPacketLength() {
158        return SETCLOCKAMPMHEADER.length + SETCLOCKAMPMLENGTH;
159    }
160
161    public static int getFunctionPacketLength() {
162        return FUNCTIONGROUP1PACKETHEADER.length + FUNCTIONGROUPLENGTH;
163    }
164
165    public static int getReadDecoderAddressLength() {
166        return READDECODERADDRESS.length;
167    }
168
169    public static int getSetClockTimePacketLength() {
170        return SETCLOCKTIMEHEADER.length + SETCLOCKTIMELENGTH;
171    }
172
173    public static int getThrottlePacketLength() {
174        return THROTTLEPACKETHEADER.length + THROTTLEPACKETLENGTH;
175    }
176
177    public static int getReadCVPacketLength() {
178        return READCVHEADER.length + READCVLENGTH;
179    }
180
181    public static int getReadCVPacketReplyLength() {
182        return READCVHEADERREPLY.length + READCVPACKETLENGTH;
183    }
184
185    public static int getPowerOnPacketLength() {
186        return POWERON.length;
187    }
188
189    public static int getPowerOffPacketLength() {
190        return POWERON.length;
191    }
192
193    public static boolean startsWith(MrcMessage source, int[] match) {
194        if (match.length > (source.getNumDataElements())) {
195            return false;
196        }
197        for (int i = 0; i < match.length; i++) {
198            if ((source.getElement(i) & 255) != (match[i] & 255)) {
199                return false;
200            }
201        }
202        return true;
203    }
204
205    private static final DecimalFormat TWO_DIGITS = new DecimalFormat("00");
206    private static final String TXT_ON = Bundle.getMessage("MrcPacketsFunctionOn"); // NOI18N
207    private static final String TXT_OFF = Bundle.getMessage("MrcPacketsFunctionOff"); // NOI18N
208    //Need to test toString() for POM
209
210    static public String toString(MrcMessage m) {
211        StringBuilder txt = new StringBuilder();
212        if ((m.getNumDataElements() < 4)
213                || (m.getNumDataElements() >= 4 && m.getElement(0) != m.getElement(2) && m.getElement(1) != 0x01)) {  // is && right there?
214            // byte 0 and byte 2 should always be the same except for a clock update packet.
215            if (m.getNumDataElements() < 4) {
216                txt.append(Bundle.getMessage("MrcPacketsShort")); // NOI18N
217            } else {
218                txt.append(Bundle.getMessage("MrcPacketsError")); // NOI18N
219            }
220            for (int i = 0; i < m.getNumDataElements(); i++) {
221                txt.append(" ");
222                txt.append(jmri.util.StringUtil.twoHexFromInt(m.getElement(i) & 0xFF));
223            }
224        } else {
225            switch (m.getElement(0) & 0xFF) {
226                case SETCLOCKRATIOCMD:
227                    txt.append(Bundle.getMessage("MrcPacketsSetClockRatio")).append(m.getElement(4)); // NOI18N
228                    break;
229                case SETCLOCKTIMECMD:
230                    txt.append(Bundle.getMessage("MrcPacketsSetClockTime")).append(m.getElement(4) // NOI18N
231                    ).append(   Bundle.getMessage("MrcPacketsClockTimeSep")).append(m.getElement(6)); // NOI18N
232                    break;
233                case SETCLOCKAMPMCMD:
234                    txt.append(Bundle.getMessage("MrcPacketsSetClockMode")); // NOI18N
235                    break;
236                case MrcPackets.THROTTLEPACKETCMD:
237                    if (m.getElement(4) != 0) {
238                        txt.append(Bundle.getMessage("MrcPacketsLoco")).append(" ").append(Bundle.getMessage("MrcPacketsLocoLong")).append(" "); // NOI18N
239                    } else {
240                        txt.append(Bundle.getMessage("MrcPacketsLoco")).append(" ").append(Bundle.getMessage("MrcPacketsLocoShort")).append(" "); // NOI18N
241                    }
242                    txt.append(Integer.toString(m.getLocoAddress()));
243                    if (m.getElement(10) == 0x02) {
244                        txt.append(" ").append(Bundle.getMessage("MrcPackets128ss")); // NOI18N
245                        //128 speed step
246                        if ((m.getElement(8) & 0x80) == 0x80) {
247                            txt.append(" ").append(Bundle.getMessage("MrcPacketsForward")); // NOI18N
248                        } else {
249                            txt.append(" ").append(Bundle.getMessage("MrcPacketsReverse")); // NOI18N
250                        }
251                        txt.append(" ").append(Bundle.getMessage("MrcPacketsSpeed")); // NOI18N
252                        int speed = (m.getElement(8) & 0x7F) - 1;
253                        switch (speed) {
254                            case 0:
255                                txt.append(" ").append(Bundle.getMessage("MrcPacketsEStop")); // NOI18N
256                                break;
257                            case -1:
258                                txt.append(" ").append(Bundle.getMessage("MrcPacketsStop")); // NOI18N
259                                break;
260                            default:
261                                txt.append(" ").append(Integer.toString(speed));
262                                break;
263                        }
264                    } else if (m.getElement(10) == 0x00) {
265                        int value = m.getElement(8);
266                        txt.append(" ").append(Bundle.getMessage("MrcPackets28ss")); // NOI18N
267                        //28 Speed Steps
268                        if ((m.getElement(8) & 0x60) == 0x60) {
269                            //Forward
270                            value = value - 0x60;
271                            txt.append(" ").append(Bundle.getMessage("MrcPacketsForward")); // NOI18N
272                        } else {
273                            value = value - 0x40;
274                            txt.append(" ").append(Bundle.getMessage("MrcPacketsReverse")); // NOI18N
275                        }
276                        if (((value >> 4) & 0x01) == 0x01) {
277                            value = value - 0x10;
278                            value = (value << 1) + 1;
279                        } else {
280                            value = value << 1;
281                        }
282                        value = value - 3; //Turn into user expected 0-28
283                        if (value == -1) {
284                            txt.append(" ").append(Bundle.getMessage("MrcPacketsEStop")); // NOI18N
285                        } else {
286                            if (value < 0) {
287                                value = 0;
288                            }
289                            txt.append(" ").append(Bundle.getMessage("MrcPacketsSpeed")).append(" ").append(Integer.toString(value)); // NOI18N
290                        }
291                    }
292
293                    break;
294                case FUNCTIONGROUP1PACKETCMD:
295                    txt.append(Bundle.getMessage("MrcPacketsLoco")).append(" ").append(Integer.toString(m.getLocoAddress()))
296                            .append(" ").append(Bundle.getMessage("MrcPacketsGroup1")); // NOI18N
297                    txt.append(" F0 ").append(((m.getElement(8) & 0x10) != 0 ? TXT_ON : TXT_OFF));
298                    txt.append(" F1 ").append(((m.getElement(8) & 0x01) != 0 ? TXT_ON : TXT_OFF));
299                    txt.append(" F2 ").append(((m.getElement(8) & 0x02) != 0 ? TXT_ON : TXT_OFF));
300                    txt.append(" F3 ").append(((m.getElement(8) & 0x04) != 0 ? TXT_ON : TXT_OFF));
301                    txt.append(" F4 ").append(((m.getElement(8) & 0x08) != 0 ? TXT_ON : TXT_OFF));
302                    break;
303                case FUNCTIONGROUP2PACKETCMD:
304                    txt.append(Bundle.getMessage("MrcPacketsLoco")).append(" ").append(Integer.toString(m.getLocoAddress()))
305                            .append(" ").append(Bundle.getMessage("MrcPacketsGroup2")); // NOI18N
306                    txt.append(" F5 ").append(((m.getElement(8) & 0x01) != 0 ? TXT_ON : TXT_OFF));
307                    txt.append(" F6 ").append(((m.getElement(8) & 0x02) != 0 ? TXT_ON : TXT_OFF));
308                    txt.append(" F7 ").append(((m.getElement(8) & 0x04) != 0 ? TXT_ON : TXT_OFF));
309                    txt.append(" F8 ").append(((m.getElement(8) & 0x08) != 0 ? TXT_ON : TXT_OFF));
310                    break;
311                case FUNCTIONGROUP3PACKETCMD:
312                    txt.append(Bundle.getMessage("MrcPacketsLoco")).append(" ").append(Integer.toString(m.getLocoAddress()))
313                            .append(" ").append(Bundle.getMessage("MrcPacketsGroup3")); // NOI18N
314                    txt.append(" F9 ").append(((m.getElement(8) & 0x01) != 0 ? TXT_ON : TXT_OFF));
315                    txt.append(" F10 ").append(((m.getElement(8) & 0x02) != 0 ? TXT_ON : TXT_OFF));
316                    txt.append(" F11 ").append(((m.getElement(8) & 0x04) != 0 ? TXT_ON : TXT_OFF));
317                    txt.append(" F12 ").append(((m.getElement(8) & 0x08) != 0 ? TXT_ON : TXT_OFF));
318                    break;
319                case FUNCTIONGROUP4PACKETCMD:
320                    txt.append(Bundle.getMessage("MrcPacketsLoco")).append(" ").append(Integer.toString(m.getLocoAddress()))
321                            .append(" ").append(Bundle.getMessage("MrcPacketsGroup4")); // NOI18N
322                    txt.append(" F13 ").append(((m.getElement(8) & 0x01) != 0 ? TXT_ON : TXT_OFF));
323                    txt.append(" F14 ").append(((m.getElement(8) & 0x02) != 0 ? TXT_ON : TXT_OFF));
324                    txt.append(" F15 ").append(((m.getElement(8) & 0x04) != 0 ? TXT_ON : TXT_OFF));
325                    txt.append(" F16 ").append(((m.getElement(8) & 0x08) != 0 ? TXT_ON : TXT_OFF));
326                    break;
327                case FUNCTIONGROUP5PACKETCMD:
328                    txt.append(Bundle.getMessage("MrcPacketsLoco")).append(" ").append(Integer.toString(m.getLocoAddress()))
329                            .append(" ").append(Bundle.getMessage("MrcPacketsGroup5")); // NOI18N
330                    txt.append(" F17 ").append(((m.getElement(8) & 0x01) != 0 ? TXT_ON : TXT_OFF));
331                    txt.append(" F18 ").append(((m.getElement(8) & 0x02) != 0 ? TXT_ON : TXT_OFF));
332                    txt.append(" F19 ").append(((m.getElement(8) & 0x04) != 0 ? TXT_ON : TXT_OFF));
333                    txt.append(" F20 ").append(((m.getElement(8) & 0x08) != 0 ? TXT_ON : TXT_OFF));
334                    break;
335                case FUNCTIONGROUP6PACKETCMD:
336                    txt.append(Bundle.getMessage("MrcPacketsLoco")).append(" ").append(Integer.toString(m.getLocoAddress()))
337                            .append(" ").append(Bundle.getMessage("MrcPacketsGroup6")); // NOI18N
338                    txt.append(" F21 ").append(((m.getElement(8) & 0x01) != 0 ? TXT_ON : TXT_OFF));
339                    txt.append(" F22 ").append(((m.getElement(8) & 0x02) != 0 ? TXT_ON : TXT_OFF));
340                    txt.append(" F23 ").append(((m.getElement(8) & 0x04) != 0 ? TXT_ON : TXT_OFF));
341                    txt.append(" F24 ").append(((m.getElement(8) & 0x08) != 0 ? TXT_ON : TXT_OFF));
342                    txt.append(" F25 ").append(((m.getElement(8) & 0x10) != 0 ? TXT_ON : TXT_OFF));
343                    txt.append(" F26 ").append(((m.getElement(8) & 0x20) != 0 ? TXT_ON : TXT_OFF));
344                    txt.append(" F27 ").append(((m.getElement(8) & 0x40) != 0 ? TXT_ON : TXT_OFF));
345                    txt.append(" F28 ").append(((m.getElement(8) & 0x80) != 0 ? TXT_ON : TXT_OFF));
346                    break;
347                case READCVCMD:
348                    int cv = ((m.getElement(4) & 0xff) << 8) + (m.getElement(6) & 0xff);
349                    txt.append(Bundle.getMessage("MrcPacketsReadCv")).append(Integer.toString(cv)); // NOI18N
350                    break;
351                case READDECODERADDRESSCMD:
352                    txt.append(Bundle.getMessage("MrcPacketsReadLocoAddr")); // NOI18N
353                    break;
354                case WRITECVPOMCMD:
355                    txt.append(Bundle.getMessage("MrcPacketsWriteOpsCvLoco")).append(" ").append(Integer.toString(m.getLocoAddress())); // NOI18N
356                    txt.append(" ").append(Integer.toString((m.getElement(10) & 0xff) + 1));
357                    txt.append("=");
358                    txt.append(Integer.toString(m.getElement(12) & 0xff));
359                    break;
360                case WRITECVPROGCMD:
361                    txt.append(Bundle.getMessage("MrcPacketsWriteSvrCv")); // NOI18N
362                    txt.append(" ").append(Integer.toString(m.getElement(4) << 8)).append(m.getElement(6));
363                    txt.append("=");
364                    txt.append(Integer.toString(m.getElement(8) & 0xff));
365                    break;
366                case READCVHEADERREPLYCODE:
367                    txt.append(Bundle.getMessage("MrcPacketsReadCvValue")); // NOI18N
368                    txt.append(Integer.toString(m.value()));
369                    break;
370                case BADCMDRECEIVEDCODE:
371                    txt.append(Bundle.getMessage("MrcPacketBadCmdAck")); // NOI18N
372                    break;
373                case GOODCMDRECEIVEDCODE:
374                    txt.append(Bundle.getMessage("MrcPacketGoodCmdAck")); // NOI18N
375                    break;
376                case PROGCMDSENTCODE:
377                    txt.append(Bundle.getMessage("MrcPacketsPgmCmdSent")); // NOI18N
378                    break;
379                case LOCOSOLECONTROLCODE:
380                    txt.append(Bundle.getMessage("MrcPacketsSingleThrottle")); // NOI18N
381                    break;
382                case LOCODBLCONTROLCODE:
383                    txt.append(Bundle.getMessage("MrcPacketsMultipleThrottle")); // NOI18N
384                    break;
385                case POWERONCMD:
386                    txt.append(Bundle.getMessage("MrcPacketsTrkPwrOn")); // NOI18N
387                    break;
388                case POWEROFFCMD:
389                    txt.append(Bundle.getMessage("MrcPacketsTrkPwrOff")); // NOI18N
390                    break;
391                case ADDTOCONSISTPACKETCMD:
392                    txt.append(Bundle.getMessage("MrcPacketsLocoAddConsist")); // NOI18N
393                    break;
394                case CLEARCONSISTPACKETCMD:
395                    txt.append(Bundle.getMessage("MrcPacketsClearedConsist")); // NOI18N
396                    break;
397                case ROUTECONTROLPACKETCMD:
398                    txt.append(Bundle.getMessage("MrcPacketsRoute")); // NOI18N
399                    txt.append(" ").append(Integer.toString(m.getElement(4)));
400                    txt.append(" ").append(Bundle.getMessage("MrcPacketsRouteSet")); // NOI18N
401                    break;
402                case CLEARROUTEPACKETCMD:
403                    txt.append(Bundle.getMessage("MrcPacketsClearedRoute")); // NOI18N
404                    break;
405                case ADDTOROUTEPACKETCMD:
406                    txt.append(Bundle.getMessage("MrcPacketsAddedRoute")); // NOI18N
407                    break;
408                case ACCESSORYPACKETCMD:
409                    txt.append(Bundle.getMessage("MrcPacketsAccy")); // NOI18N
410                    txt.append(" ").append(Integer.toString(m.getAccAddress())).append(" ");
411                    switch (m.getAccState()) {
412                        case jmri.Turnout.CLOSED:
413                            txt.append(Bundle.getMessage("MrcPacketsAccyClosed")); // NOI18N
414                            break;
415                        case jmri.Turnout.THROWN:
416                            txt.append(Bundle.getMessage("MrcPacketsAccyThrown")); // NOI18N
417                            break;
418                        default:
419                            txt.append(Bundle.getMessage("MrcPacketsAccyUnk")); // NOI18N
420                    }
421                    break;
422                default:
423                    if (m.getNumDataElements() == 6) {
424                        if (m.getElement(0) == m.getElement(2) && m.getElement(0) == m.getElement(4)) {
425                            txt.append(Bundle.getMessage("MrcPacketsPollToCab")).append(" "); // NOI18N
426                            txt.append(m.getElement(0));
427                        } else if (m.getElement(0) == 0 && m.getElement(1) == 0x01) {
428                            txt.append(Bundle.getMessage("MrcPacketsClockUpdate")).append(" "); // NOI18N
429
430                            appendClockMessage(m, txt);
431
432                            txt.append(" ");
433                        }
434                    } else if (m.getNumDataElements() == 4 && m.getElement(0) == 0x00 && m.getElement(1) == 0x00) {
435                        txt.append("No Data From Last Cab"); // NOI18N
436                    } else {
437                        txt.append("Unk Cmd Code:"); // NOI18N
438                        for (int i = 0; i < m.getNumDataElements(); i++) {
439                            txt.append(" ");
440                            txt.append(jmri.util.StringUtil.twoHexFromInt(m.getElement(i) & 0xFF));
441                        }
442                    }
443                    break;
444            }
445        }
446        return txt.toString();
447    }
448
449    /**
450     * Adds the description of the clock's mode to a message being built
451     *
452     * @param m   clock info message
453     * @param txt build description of clock info onto this
454     */
455    @SuppressFBWarnings(value = "SF_SWITCH_NO_DEFAULT", justification = "covers all possible values")
456    static void appendClockMessage(MrcMessage m, StringBuilder txt) {
457        int clockModeBits = m.getElement(2) & 0xC0;
458        switch (clockModeBits) {
459            case 0x00: // AM format
460                txt.append((m.getElement(2) & 0x1F))
461                        .append(Bundle.getMessage("MrcPacketsClockTimeSep"))
462                        .append(TWO_DIGITS.format(m.getElement(4)))
463                        .append(Bundle.getMessage("MrcPacketsClockModeAm")); // NOI18N
464                break;
465            case 0x40: // PM format
466                txt.append((m.getElement(2) & 0x1F))
467                        .append(Bundle.getMessage("MrcPacketsClockTimeSep"))
468                        .append(TWO_DIGITS.format(m.getElement(4)))
469                        .append(Bundle.getMessage("MrcPacketsClockModePm")); // NOI18N
470                break;
471            case 0x80: // 24 hour format
472                txt.append(TWO_DIGITS.format(m.getElement(2) & 0x1F))
473                        .append(Bundle.getMessage("MrcPacketsClockTimeSep"))
474                        .append(TWO_DIGITS.format(m.getElement(4)))
475                        .append(Bundle.getMessage("MrcPacketsClockMode24"));// NOI18N
476                break;
477            case 0xC0: // Unk format
478                txt.append(TWO_DIGITS.format(m.getElement(2) & 0x1F))
479                        .append(Bundle.getMessage("MrcPacketsClockTimeSep"))
480                        .append(TWO_DIGITS.format(m.getElement(4)))
481                        .append(Bundle.getMessage("MrcPacketsClockModeUnk")); // NOI18N
482                break;
483            default:
484                log.warn("Unhandled clock mode code: {}", clockModeBits);
485                break;
486        }
487    }
488
489    //In principle last two are the checksum, the first four indicate the packet type and ignored.
490    //the rest should be XOR'd.
491    static public boolean validCheckSum(MrcMessage m) {
492        if (m.getNumDataElements() > 6) {
493            int result = 0;
494            for (int i = 4; i < m.getNumDataElements() - 2; i++) {
495                result = (m.getElement(i) & 255) ^ result;
496            }
497            if (result == (m.getElement(m.getNumDataElements() - 2) & 255)) {
498                return true;
499            }
500        }
501        return false;
502    }
503
504}