001package jmri.jmrix.nce.ncemon;
002
003import java.text.MessageFormat;
004
005import org.slf4j.Logger;
006import org.slf4j.LoggerFactory;
007
008import jmri.jmrix.nce.NceMessage;
009import jmri.jmrix.nce.NceReply;
010import jmri.util.StringUtil;
011
012/**
013 * A utility class for formatting NCE binary command and replies into
014 * human-readable text. The text for the display comes from NCE's Bincmds.txt
015 * published November 2007 and is used with NCE's permission.
016 *
017 * @author Daniel Boudreau Copyright (C) 2012
018 * @author Ken Cameron (C) 2023
019 */
020public class NceMonBinary {
021
022    public static final NceMonBinary INSTANCE = new NceMonBinary();
023
024    private static final Logger log = LoggerFactory.getLogger(NceMonBinary.class);
025
026    private static final int REPLY_UNKNOWN = 0;
027    private static final int REPLY_STANDARD = 1;
028    private static final int REPLY_DATA = 2;
029    private static final int REPLY_ENTER_PROGRAMMING_MODE = 3;
030
031    // The standard replies
032    private static final int REPLY_ZERO = '0'; // command not supported
033    private static final int REPLY_ONE = '1'; // loco/accy/signal address out of range
034    private static final int REPLY_TWO = '2'; // cab address or op code out of range
035    private static final int REPLY_THREE = '3';// CV address or data out of range
036    private static final int REPLY_FOUR = '4'; // byte count out of range
037    private static final int REPLY_OK = '!'; // command completed successfully
038
039    private int _replyType = REPLY_UNKNOWN;
040    
041    // private constructor since this class is a singleton
042    private NceMonBinary() {
043    }
044
045    /**
046     * Creates a command message for the log, in a human-friendly form if
047     * possible.
048     *
049     * @param m the raw command message
050     * @return the displayable message string
051     */
052    public String displayMessage(NceMessage m) {
053        return parseMessage(m);
054    }
055
056    private String parseMessage(NceMessage m) {
057        // first check for messages that have a standard reply
058        setReplyType(REPLY_STANDARD);
059        switch (m.getOpCode() & 0xFF) {
060            case (NceMessage.NOP_CMD):
061                return Bundle.getMessage("NOP_CMD");
062            case (NceMessage.STOP_CLOCK_CMD):
063                return Bundle.getMessage("STOP_CLOCK_CMD");
064            case (NceMessage.START_CLOCK_CMD):
065                return Bundle.getMessage("START_CLOCK_CMD");
066            case (NceMessage.SET_CLOCK_CMD):
067                if (m.getNumDataElements() == 3) {
068                    return MessageFormat.format(Bundle.getMessage("SET_CLOCK_CMD"),
069                            new Object[]{m.getElement(1), m.getElement(2)});
070                }
071                break;
072            case (NceMessage.CLOCK_1224_CMD):
073                if (m.getNumDataElements() == 2) {
074                    String hr = "12";
075                    if (m.getElement(1) == 1) {
076                        hr = "24";
077                    }
078                    return MessageFormat.format(Bundle.getMessage("CLOCK_1224_CMD"),
079                            new Object[]{hr});
080                }
081                break;
082            case (NceMessage.CLOCK_RATIO_CMD):
083                if (m.getNumDataElements() == 2) {
084                    return MessageFormat.format(Bundle.getMessage("CLOCK_RATIO_CMD"),
085                            new Object[]{m.getElement(1)});
086                }
087                break;
088            case (NceMessage.ENABLE_MAIN_CMD):
089                return Bundle.getMessage("ENABLE_MAIN_CMD");
090            case (NceMessage.KILL_MAIN_CMD):
091                return Bundle.getMessage("KILL_MAIN_CMD");
092            case (NceMessage.WRITE_N_CMD):
093                if (m.getNumDataElements() == 20) {
094                    return MessageFormat.format(Bundle.getMessage("WRITEn_CMD"),
095                            new Object[]{m.getElement(3), getAddress(m), getDataBytes(m, 4, 16)});
096                }
097                break;
098            // Send n bytes commands 0x93 - 0x96
099            case (NceMessage.SENDn_BYTES_CMD + 3):
100                if (m.getNumDataElements() == 5) {
101                    return MessageFormat.format(Bundle.getMessage("SENDn_BYTES_CMD"),
102                            new Object[]{"3", m.getElement(1), getDataBytes(m, 2, 3)});
103                }
104                break;
105            case (NceMessage.SENDn_BYTES_CMD + 4):
106                if (m.getNumDataElements() == 6) {
107                    return MessageFormat.format(Bundle.getMessage("SENDn_BYTES_CMD"),
108                            new Object[]{"4", m.getElement(1), getDataBytes(m, 2, 4)});
109                }
110                break;
111            case (NceMessage.SENDn_BYTES_CMD + 5):
112                if (m.getNumDataElements() == 7) {
113                    return MessageFormat.format(Bundle.getMessage("SENDn_BYTES_CMD"),
114                            new Object[]{"5", m.getElement(1), getDataBytes(m, 2, 5)});
115                }
116                break;
117            case (NceMessage.SENDn_BYTES_CMD + 6):
118                if (m.getNumDataElements() == 8) {
119                    return MessageFormat.format(Bundle.getMessage("SENDn_BYTES_CMD"),
120                            new Object[]{"6", m.getElement(1), getDataBytes(m, 2, 6)});
121                }
122                break;
123            case (NceMessage.WRITE1_CMD):
124                if (m.getNumDataElements() == 4) {
125                    return MessageFormat.format(Bundle.getMessage("WRITE1_CMD"),
126                            new Object[]{getAddress(m), getDataBytes(m, 3, 1)});
127                }
128                break;
129            case (NceMessage.WRITE2_CMD):
130                if (m.getNumDataElements() == 5) {
131                    return MessageFormat.format(Bundle.getMessage("WRITE2_CMD"),
132                            new Object[]{getAddress(m), getDataBytes(m, 3, 2)});
133                }
134                break;
135            case (NceMessage.WRITE4_CMD):
136                if (m.getNumDataElements() == 7) {
137                    return MessageFormat.format(Bundle.getMessage("WRITE4_CMD"),
138                            new Object[]{getAddress(m), getDataBytes(m, 3, 4)});
139                }
140                break;
141            case (NceMessage.WRITE8_CMD):
142                if (m.getNumDataElements() == 11) {
143                    return MessageFormat.format(Bundle.getMessage("WRITE8_CMD"),
144                            new Object[]{getAddress(m), getDataBytes(m, 3, 8)});
145                }
146                break;
147            case (NceMessage.MACRO_CMD):
148                if (m.getNumDataElements() == 2) {
149                    return MessageFormat.format(Bundle.getMessage("MACRO_CMD"),
150                            new Object[]{m.getElement(1)});
151                }
152                break;
153            case (NceMessage.ENTER_PROG_CMD): {
154                setReplyType(REPLY_ENTER_PROGRAMMING_MODE);
155                return Bundle.getMessage("ENTER_PROG_CMD");
156            }
157            case (NceMessage.EXIT_PROG_CMD):
158                return Bundle.getMessage("EXIT_PROG_CMD");
159            case (NceMessage.WRITE_PAGED_CV_CMD):
160                if (m.getNumDataElements() == 4) {
161                    return MessageFormat.format(Bundle.getMessage("WRITE_PAGED_CV_CMD"),
162                            new Object[]{getNumber(m), getDataBytes(m, 3, 1)});
163                }
164                break;
165            case (NceMessage.LOCO_CMD):
166                if (m.getNumDataElements() == 5) {
167                    // byte three is the Op_1
168                    switch (m.getElement(3)) {
169                        case (0):
170                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_00"),
171                                    new Object[]{getLocoAddress(m), m.getElement(4)});
172                        case (1):
173                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_01"),
174                                    new Object[]{getLocoAddress(m), m.getElement(4)});
175                        case (2):
176                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_02"),
177                                    new Object[]{getLocoAddress(m), m.getElement(4)});
178                        case (3):
179                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_03"),
180                                    new Object[]{getLocoAddress(m), m.getElement(4)});
181                        case (4):
182                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_04"),
183                                    new Object[]{getLocoAddress(m), m.getElement(4)});
184                        case (5):
185                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_05"),
186                                    new Object[]{getLocoAddress(m), m.getElement(4)});
187                        case (6):
188                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_06"),
189                                    new Object[]{getLocoAddress(m), m.getElement(4)});
190                        case (7):
191                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_07"),
192                                    new Object[]{getLocoAddress(m), m.getElement(4), getFunctionNumber(m)});
193                        case (8):
194                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_08"),
195                                    new Object[]{getLocoAddress(m), m.getElement(4), getFunctionNumber(m)});
196                        case (9):
197                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_09"),
198                                    new Object[]{getLocoAddress(m), m.getElement(4), getFunctionNumber(m)});
199                        case (0x0A):
200                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_0A"),
201                                    new Object[]{getLocoAddress(m), m.getElement(4)});
202                        case (0x0b):
203                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_0B"),
204                                    new Object[]{getLocoAddress(m), m.getElement(4)});
205                        case (0x0C):
206                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_0C"),
207                                    new Object[]{getLocoAddress(m), m.getElement(4)});
208                        case (0x0D):
209                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_0D"),
210                                    new Object[]{getLocoAddress(m), m.getElement(4)});
211                        case (0x0E):
212                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_0E"),
213                                    new Object[]{getLocoAddress(m), m.getElement(4)});
214                        case (0x0F):
215                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_0F"),
216                                    new Object[]{getLocoAddress(m), m.getElement(4)});
217                        case (0x10):
218                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_10"),
219                                    new Object[]{getLocoAddress(m), m.getElement(4)});
220                        case (0x11):
221                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_11"),
222                                    new Object[]{getLocoAddress(m), m.getElement(4)});
223                        case (0x12):
224                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_12"),
225                                    new Object[]{getLocoAddress(m), m.getElement(4)});
226                        case (0x15):
227                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_15"),
228                                    new Object[]{getLocoAddress(m), m.getElement(4)});
229                        case (0x16):
230                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_16"),
231                                    new Object[]{getLocoAddress(m), m.getElement(4)});
232                        case (0x17):
233                            return MessageFormat.format(Bundle.getMessage("LOCO_CMD_Op1_17"),
234                                    new Object[]{getLocoAddress(m), m.getElement(4)});
235                        default:
236                            log.error("Unhandled loco cmd op1 code: {}", m.getElement(3));
237                            break;
238                    }
239                }
240                break;
241            // Queue commands 0xA3 - 0xA5
242            case (NceMessage.QUEUEn_BYTES_CMD + 3):
243                if (m.getNumDataElements() == 5) {
244                    return MessageFormat.format(Bundle.getMessage("QUEUEn_BYTES_CMD"),
245                            new Object[]{"3", m.getElement(1), getDataBytes(m, 2, 3)});
246                }
247                break;
248            case (NceMessage.QUEUEn_BYTES_CMD + 4):
249                if (m.getNumDataElements() == 6) {
250                    return MessageFormat.format(Bundle.getMessage("QUEUEn_BYTES_CMD"),
251                            new Object[]{"4", m.getElement(1), getDataBytes(m, 2, 4)});
252                }
253                break;
254            case (NceMessage.QUEUEn_BYTES_CMD + 5):
255                if (m.getNumDataElements() == 7) {
256                    return MessageFormat.format(Bundle.getMessage("QUEUEn_BYTES_CMD"),
257                            new Object[]{"5", m.getElement(1), getDataBytes(m, 2, 5)});
258                }
259                break;
260            case (NceMessage.WRITE_REG_CMD):
261                if (m.getNumDataElements() == 3) {
262                    return MessageFormat.format(Bundle.getMessage("WRITE_REG_CMD"),
263                            new Object[]{m.getElement(1), getDataBytes(m, 2, 1)});
264                }
265                break;
266            case (NceMessage.WRITE_DIR_CV_CMD):
267                if (m.getNumDataElements() == 4) {
268                    return MessageFormat.format(Bundle.getMessage("WRITE_DIR_CV_CMD"),
269                            new Object[]{getNumber(m), getDataBytes(m, 3, 1)});
270                }
271                break;
272            case (NceMessage.SEND_ACC_SIG_MACRO_CMD):
273                if (m.getNumDataElements() == 5) {
274                    // byte three is the Op_1
275                    switch (m.getElement(3)) {
276                        case (1):
277                            return MessageFormat.format(Bundle.getMessage("ACC_CMD_Op1_01"),
278                                    new Object[]{m.getElement(4)});
279                        case (3):
280                            return MessageFormat.format(Bundle.getMessage("ACC_CMD_Op1_03"),
281                                    new Object[]{getNumber(m)});
282                        case (4):
283                            return MessageFormat.format(Bundle.getMessage("ACC_CMD_Op1_04"),
284                                    new Object[]{getNumber(m)});
285                        case (5):
286                            return MessageFormat.format(Bundle.getMessage("ACC_CMD_Op1_05"),
287                                    new Object[]{getNumber(m), m.getElement(4)});
288                        default:
289                            log.error("Unhandled acc cmd op1 code: {}", m.getElement(3));
290                            break;
291                    }
292                }
293                break;
294            case (NceMessage.USB_SET_CAB_CMD):
295                if (m.getNumDataElements() == 2) {
296                    return MessageFormat.format(Bundle.getMessage("Usb_Set_Cab_Op1"),
297                            new Object[]{m.getElement(1)});
298                }
299                break;
300            case (NceMessage.USB_MEM_POINTER_CMD):
301                if (m.getNumDataElements() == 3) {
302                    return MessageFormat.format(Bundle.getMessage("Usb_Set_Mem_Ptr_Cmd"),
303                            new Object[]{m.getElement(1), m.getElement(2)});
304                }
305                break;
306            case (NceMessage.USB_MEM_WRITE_CMD):
307                if (m.getNumDataElements() == 2) {
308                    return MessageFormat.format(Bundle.getMessage("Usb_Mem_Write_Cmd"),
309                            new Object[]{m.getElement(1)});
310                }
311                break;
312            default:
313                //                log.debug("Unhandled command code: {} after 1st pass", Integer.toHexString(m.getOpCode() & 0xFF));
314                break;
315        }
316        // 2nd pass, check for messages that have a data reply
317        setReplyType(REPLY_DATA);
318        switch (m.getOpCode() & 0xFF) {
319            case (NceMessage.READ_CLOCK_CMD):
320                return Bundle.getMessage("READ_CLOCK_CMD");
321            case (NceMessage.READ_AUI4_CMD):
322                if (m.getNumDataElements() == 2) {
323                    return MessageFormat.format(Bundle.getMessage("READ_AUI4_CMD"),
324                            new Object[]{m.getElement(1)});
325                }
326                break;
327            case (NceMessage.DUMMY_CMD):
328                return Bundle.getMessage("DUMMY_CMD");
329            case (NceMessage.READ16_CMD):
330                if (m.getNumDataElements() == 3) {
331                    return MessageFormat.format(Bundle.getMessage("READ16_CMD"),
332                            new Object[]{getAddress(m)});
333                }
334                break;
335            case (NceMessage.USB_MEM_READ_CMD):
336                if (m.getNumDataElements() == 2) {
337                    return MessageFormat.format(Bundle.getMessage("Usb_Mem_Read_Cmd"),
338                            new Object[]{m.getElement(1)});
339                }
340                break;
341            case (NceMessage.READ_AUI2_CMD):
342                if (m.getNumDataElements() == 2) {
343                    return MessageFormat.format(Bundle.getMessage("READ_AUI2_CMD"),
344                            new Object[]{m.getElement(1)});
345                }
346                break;
347            case (NceMessage.READ1_CMD):
348                if (m.getNumDataElements() == 3) {
349                    return MessageFormat.format(Bundle.getMessage("READ1_CMD"),
350                            new Object[]{getAddress(m)});
351                }
352                break;
353            case (NceMessage.READ_PAGED_CV_CMD):
354                if (m.getNumDataElements() == 3) {
355                    return MessageFormat.format(Bundle.getMessage("READ_PAGED_CV_CMD"),
356                            new Object[]{getNumber(m)});
357                }
358                break;
359            case (NceMessage.READ_REG_CMD):
360                if (m.getNumDataElements() == 2) {
361                    return MessageFormat.format(Bundle.getMessage("READ_REG_CMD"),
362                            new Object[]{m.getElement(1)});
363                }
364                break;
365            case (NceMessage.READ_DIR_CV_CMD):
366                if (m.getNumDataElements() == 3) {
367                    return MessageFormat.format(Bundle.getMessage("READ_DIR_CV_CMD"),
368                            new Object[]{getNumber(m)});
369                }
370                break;
371            case (NceMessage.SW_REV_CMD):
372                return Bundle.getMessage("SW_REV_CMD");
373            default:
374                log.debug("Unhandled command code: {} after 2nd pass", Integer.toHexString(m.getOpCode() & 0xFF));
375                break;
376        }
377        // this is one we don't know about or haven't coded it up
378        setReplyType(REPLY_UNKNOWN);
379        log.debug("Unhandled command code: {}, display as raw", Integer.toHexString(m.getOpCode() & 0xFF));
380        return MessageFormat.format(Bundle.getMessage("BIN_CMD"), new Object[]{m.toString()});
381    }
382
383    /*
384     * Static access needed since there are two instances of this class, one for
385     * transmitting and one for receiving.
386     */
387    private void setReplyType(int replyType) {
388        _replyType = replyType;
389    }
390
391    private int getReplyType() {
392        return _replyType;
393    }
394
395    private String getAddress(NceMessage m) {
396        return StringUtil.twoHexFromInt(m.getElement(1)) + StringUtil.twoHexFromInt(m.getElement(2));
397    }
398
399    private String getDataBytes(NceMessage m, int start, int number) {
400        StringBuilder sb = new StringBuilder(" ");
401        for (int i = start; i < start + number; i++) {
402            sb.append(StringUtil.twoHexFromInt(m.getElement(i))).append(" ");
403        }
404        return sb.toString();
405    }
406
407    private String getNumber(NceMessage m) {
408        return Integer.toString(((m.getElement(1) & 0xFF) << 8) | (m.getElement(2) & 0xFF));
409    }
410
411    private String getLocoAddress(NceMessage m) {
412        // show address type
413        String appendix = " (short)";
414        if ((m.getElement(1) & 0xE0) != 0) {
415            appendix = " (long)";
416        }
417        return Integer.toString(((m.getElement(1) & 0x3F) << 8) | (m.getElement(2) & 0xFF)) + appendix;
418    }
419
420    private String getFunctionNumber(NceMessage m) {
421        // byte three is the Op_1
422        switch (m.getElement(3)) {
423            case (7): {
424                StringBuilder buf = new StringBuilder();
425                if ((m.getElement(4) & 0x10) != 0) {
426                    buf.append(Bundle.getMessage("F0_ON")).append(", ");
427                } else {
428                    buf.append(Bundle.getMessage("F0_OFF")).append(", ");
429                }
430                if ((m.getElement(4) & 0x01) != 0) {
431                    buf.append(Bundle.getMessage("F1_ON")).append(", ");
432                } else {
433                    buf.append(Bundle.getMessage("F1_OFF")).append(", ");
434                }
435                if ((m.getElement(4) & 0x02) != 0) {
436                    buf.append(Bundle.getMessage("F2_ON")).append(", ");
437                } else {
438                    buf.append(Bundle.getMessage("F2_OFF")).append(", ");
439                }
440                if ((m.getElement(4) & 0x04) != 0) {
441                    buf.append(Bundle.getMessage("F3_ON")).append(", ");
442                } else {
443                    buf.append(Bundle.getMessage("F3_OFF")).append(", ");
444                }
445                if ((m.getElement(4) & 0x08) != 0) {
446                    buf.append(Bundle.getMessage("F4_ON"));
447                } else {
448                    buf.append(Bundle.getMessage("F4_OFF"));
449                }
450                return buf.toString();
451            }
452            case (8): {
453                StringBuilder buf = new StringBuilder();
454                if ((m.getElement(4) & 0x01) != 0) {
455                    buf.append(Bundle.getMessage("F5_ON")).append(", ");
456                } else {
457                    buf.append(Bundle.getMessage("F5_OFF")).append(", ");
458                }
459                if ((m.getElement(4) & 0x02) != 0) {
460                    buf.append(Bundle.getMessage("F6_ON")).append(", ");
461                } else {
462                    buf.append(Bundle.getMessage("F6_OFF")).append(", ");
463                }
464                if ((m.getElement(4) & 0x04) != 0) {
465                    buf.append(Bundle.getMessage("F7_ON")).append(", ");
466                } else {
467                    buf.append(Bundle.getMessage("F7_OFF")).append(", ");
468                }
469                if ((m.getElement(4) & 0x08) != 0) {
470                    buf.append(Bundle.getMessage("F8_ON"));
471                } else {
472                    buf.append(Bundle.getMessage("F8_OFF"));
473                }
474                return buf.toString();
475            }
476            case (9): {
477                StringBuilder buf = new StringBuilder();
478                if ((m.getElement(4) & 0x01) != 0) {
479                    buf.append(Bundle.getMessage("F9_ON")).append(", ");
480                } else {
481                    buf.append(Bundle.getMessage("F9_OFF")).append(", ");
482                }
483                if ((m.getElement(4) & 0x02) != 0) {
484                    buf.append(Bundle.getMessage("F10_ON")).append(", ");
485                } else {
486                    buf.append(Bundle.getMessage("F10_OFF")).append(", ");
487                }
488                if ((m.getElement(4) & 0x04) != 0) {
489                    buf.append(Bundle.getMessage("F11_ON")).append(", ");
490                } else {
491                    buf.append(Bundle.getMessage("F11_OFF")).append(", ");
492                }
493                if ((m.getElement(4) & 0x08) != 0) {
494                    buf.append(Bundle.getMessage("F12_ON"));
495                } else {
496                    buf.append(Bundle.getMessage("F12_OFF"));
497                }
498                return buf.toString();
499            }
500            default:
501                return ("Error");
502        }
503    }
504
505    /**
506     * Creates a reply message for the log, in a human-friendly form if
507     * possible.
508     *
509     * @param r the raw reply message
510     * @return the displayable message string
511     */
512    public String displayReply(NceReply r) {
513        return parseReply(r);
514    }
515
516    private String parseReply(NceReply r) {
517        switch (getReplyType()) {
518            case (REPLY_STANDARD):
519                /*
520                 * standard reply is a single byte Errors returned: '0'= command
521                 * not supported '1'= loco/accy/signal address out of range '2'=
522                 * cab address or op code out of range '3'= CV address or data
523                 * out of range '4'= byte count out of range '!'= command
524                 * completed successfully
525                 */
526                if (r.getNumDataElements() == 1) {
527                    switch (r.getOpCode() & 0xFF) {
528                        case (REPLY_ZERO):
529                            return Bundle.getMessage("NceReplyZero");
530                        case (REPLY_ONE):
531                            return Bundle.getMessage("NceReplyOne");
532                        case (REPLY_TWO):
533                            return Bundle.getMessage("NceReplyTwo");
534                        case (REPLY_THREE):
535                            return Bundle.getMessage("NceReplyThree");
536                        case (REPLY_FOUR):
537                            return Bundle.getMessage("NceReplyFour");
538                        case (REPLY_OK):
539                            return Bundle.getMessage("NceReplyOK");
540                        default:
541                            log.error("Unhandled reply code: {}", Integer.toHexString(r.getOpCode() & 0xFF));
542                            break;
543                    }
544                }
545                break;
546            case (REPLY_ENTER_PROGRAMMING_MODE):
547                /*
548                 * enter programming mode reply is a single byte '3'= short
549                 * circuit '!'= command completed successfully
550                 */
551                if (r.getNumDataElements() == 1) {
552                    switch (r.getOpCode() & 0xFF) {
553                        case (REPLY_THREE):
554                            return Bundle.getMessage("NceReplyThreeProg");
555                        case (REPLY_OK):
556                            return Bundle.getMessage("NceReplyOK");
557                        default:
558                            log.error("Unhandled programming reply code: {}",
559                                    Integer.toHexString(r.getOpCode() & 0xFF));
560                            break;
561                    }
562                }
563                break;
564            case (REPLY_DATA):
565                break;
566            default:
567                log.debug("Unhandled reply type code1: {}, display as raw", getReplyType());
568                break;
569        }
570        return MessageFormat.format(Bundle.getMessage("NceReply"), new Object[]{r.toString()});
571    }
572}