001package jmri.jmrix.loconet.messageinterp;
002
003
004import java.time.LocalTime;
005import java.util.ArrayList;
006
007import jmri.InstanceManager;
008import jmri.NmraPacket;
009import jmri.Reporter;
010import jmri.ReporterManager;
011import jmri.Sensor;
012import jmri.SensorManager;
013import jmri.Turnout;
014import jmri.TurnoutManager;
015import jmri.jmrix.loconet.LnConstants;
016import jmri.jmrix.loconet.LocoNetMessage;
017import jmri.jmrix.loconet.lnsvf2.LnSv2MessageContents;
018import jmri.jmrix.loconet.uhlenbrock.LncvMessageContents;
019import jmri.util.StringUtil;
020
021import org.apache.commons.lang3.StringUtils;
022import org.slf4j.Logger;
023import org.slf4j.LoggerFactory;
024
025/**
026 * A utility class for formatting LocoNet packets into human-readable text.
027 * <p>
028 * Note that the formatted strings end in a \n, and may contain more than one
029 * line separated by \n. Someday this should be converted to proper Java line
030 * handling.
031 * <p>
032 * Much of this file is a Java-recoding of the display.c file from the llnmon
033 * package of John Jabour. Some of the conversions involve explicit decoding of
034 * structs defined in loconet.h in that same package. Those parts are (C)
035 * Copyright 2001 Ron W. Auld. Use of these parts is by direct permission of the
036 * author.
037 * <p>
038 * This class is derived from and replaces JMRI's
039 * jmri.jmrix.loconet.locomon.Llnmon.java .
040 * <p>
041 * Many major comment blocks here are quotes from the Digitrax LocoNet(r) OPCODE
042 * SUMMARY: found in the LocoNet(r) Personal Edition 1.
043 * <p>
044 * Some of the message formats used in this class are Copyright Digitrax, Inc.
045 * and used with permission as part of the JMRI project. That permission does
046 * not extend to uses in other software products. If you wish to use this code,
047 * algorithm or these message formats outside of JMRI, please contact Digitrax
048 * Inc for separate permission.
049 * <p>
050 * Reverse engineering of OPC_MULTI_SENSE was provided by Al Silverstein, used
051 * with permission.
052 * <p>
053 * Reverse engineering of the Duplex Group/Password/Channel management was
054 * provided by Leo Bicknell with help from B. Milhaupt, used with permission.
055 * <p>
056 * Reverse-engineering of device-specific OpSw messages, throttle text message,
057 * and throttle semaphore message was provided by B. Milhaupt, used with
058 * permission.
059 * <p>
060 * Reverse-engineering of device-specific LNSV messages was provided by K. Drenth,
061 * used with permission.
062 *
063 * @author Bob Jacobsen Copyright 2001, 2002, 2003
064 * @author B. Milhaupt Copyright 2015, 2016, 2018, 2022
065 * @author Randall Wood Copyright 2016
066 * @author Michael Richardson Copyright (C) 2021
067 */
068public class LocoNetMessageInterpret {
069    private static final String Ln_Off = Bundle.getMessage("LN_MSG_OFF");
070    private static final String Ln_On  = Bundle.getMessage("LN_MSG_ON");
071
072    /**
073     * Format the message into a text string.
074     * <p>
075     * Where the code is unable to determine a correct interpretation, the returned
076     * string contains a message indicating that the message is not decoded followed
077     * by the individual bytes of the message (in hexadecimal).
078     *
079     * Note that many message types are "poorly defined" here, with many 
080     * "reverse-engineered" messages, and many that do not define actual bits.  This 
081     * means that this code can give interpretations that may not actually "decode"
082     * an actual message. 
083     * 
084     * @param l Message to parse
085     * @param turnoutPrefix "System Name+ prefix which designates the connection's
086     *          Turnouts, such as "LT"
087     * @param sensorPrefix "System Name+ prefix which designates the connection's
088     *          Turnouts, such as "LS"
089     * @param reporterPrefix "System Name+ prefix which designates the connection's
090     *          Turnouts, such as "LR"
091     * @return String representation of the interpretation of the message
092     */
093    public static String interpretMessage(LocoNetMessage l, String turnoutPrefix, String sensorPrefix, String reporterPrefix) {
094
095        String result;
096
097        result = "";
098        /*
099         * 2 Byte MESSAGE OPCODES
100         * ; FORMAT = <OPC>,<CKSUM>
101         * ;
102         *
103         * 4 byte MESSAGE OPCODES
104         * ; FORMAT = <OPC>,<ARG1>,<ARG2>,<CKSUM>
105         * :
106         *  CODES 0xA8 to 0xAF have responses
107         *  CODES 0xB8 to 0xBF have responses
108         *
109         * 6 byte MESSAGE OPCODES
110         * ; FORMAT = <OPC>,<ARG1>,<ARG2>,<ARG3>,<ARG4>,<CKSUM>
111         * :
112         *  CODES 0xC8 to 0xCF have responses
113         *  CODES 0xD8 to 0xDF have responses
114         */
115        switch (l.getOpCode()) {
116
117            /*
118             * OPC_IDLE 0x85 ;FORCE IDLE state, Broadcast emergency STOP
119             *
120             * Page 8 of LocoNet Personal Edition v1.0.
121             */
122            case LnConstants.OPC_IDLE: {
123                return Bundle.getMessage("LN_MSG_IDLE");
124            }
125
126            /*
127             * OPC_GPON 0x83 ;GLOBAL power ON request
128             *
129             * Page 8 of LocoNet Personal Edition v1.0.
130             */
131            case LnConstants.OPC_GPON: {
132                return Bundle.getMessage("LN_MSG_GPON");
133
134            }
135
136            /*
137             * OPC_GPOFF 0x82 ;GLOBAL power OFF request
138             *
139             * Page 8 of LocoNet Personal Edition v1.0.
140             */
141            case LnConstants.OPC_GPOFF: {
142                return Bundle.getMessage("LN_MSG_GPOFF");
143            }
144
145            /*
146             * OPC_GPBUSY 0x81 ;MASTER busy code, NULL
147             *
148             * Page 8 of LocoNet Personal Edition v1.0.
149             */
150            case LnConstants.OPC_GPBUSY: {
151                return Bundle.getMessage("LN_MSG_MASTER_BUSY");
152            }
153
154            case LnConstants.OPC_RE_LOCORESET_BUTTON: {
155                return Bundle.getMessage("LN_MSG_RE_LOCO_RESET");
156
157            }
158
159            /*
160             * OPC_LOCO_ADR     0xBF   ; REQ loco ADR
161             *                         ; Follow on message: <E7>SLOT READ
162             *                         ; <0xBF>,<0>,<ADR>,<CHK> REQ loco ADR
163             *                         ; DATA return <E7>, is SLOT#, DATA that ADR was
164             *                         : found in.
165             *                         ; IF ADR not found, MASTER puts ADR in FREE slot
166             *                         ; and sends DATA/STATUS return <E7>......
167             *                         ; IF no FREE slot, Fail LACK,0 is returned
168             *                         ; [<B4>,<3F>,<0>,<CHK>]
169             *
170             * Page 8 of LocoNet Personal Edition v1.0.
171             */
172            case LnConstants.OPC_LOCO_ADR: {
173                String locoAddress = convertToMixed(l.getElement(2), l.getElement(1));
174                return Bundle.getMessage("LN_MSG_REQ_SLOT_FOR_ADDR",
175                        locoAddress);
176            }
177
178            case LnConstants.OPC_EXP_REQ_SLOT: {
179                String locoAddress = convertToMixed(l.getElement(2), l.getElement(1));
180                return Bundle.getMessage("LN_MSG_REQ_EXP_SLOT_FOR_ADDR",
181                        locoAddress);
182            }
183
184            /*
185             * OPC_SW_ACK       0xBD   ; REQ SWITCH WITH acknowledge function (not DT200)
186             *                         ; Follow on message: LACK
187             *                         ; <0xBD>,<SW1>,<SW2>,<CHK> REQ SWITCH function
188             *                         ;       <SW1> =<0,A6,A5,A4- A3,A2,A1,A0>
189             *                         ;               7 ls adr bits.
190             *                         ;               A1,A0 select 1 of 4 input pairs
191             *                         ;               in a DS54
192             *                         ;       <SW2> =<0,0,DIR,ON- A10,A9,A8,A7>
193             *                         ;               Control bits and 4 MS adr bits.
194             *                         ;               DIR=1 for Closed/GREEN
195             *                         ;                  =0 for Thrown/RED
196             *                         ;               ON=1 for Output ON
197             *                         ;                 =0 FOR output OFF
198             *                         ; response is:
199             *                         ; <0xB4><3D><00> if DCS100 FIFO is full, rejected.
200             *                         ; <0xB4><3D><7F> if DCS100 accepted
201             *
202             * Page 8 of LocoNet Personal Edition v1.0.
203             */
204            case LnConstants.OPC_SW_ACK: {
205                result = interpretOpcSwAck(l, turnoutPrefix);
206                if (result.length() > 0) {
207                    return result;
208                }
209                break;
210            }
211
212            /*
213             * OPC_SW_STATE     0xBC   ; REQ state of SWITCH
214             *                         ; Follow on message: LACK
215             *                         ; <0xBC>,<SW1>,<SW2>,<CHK> REQ state of SWITCH
216             *
217             * Page 8 of LocoNet Personal Edition v1.0.
218             */
219            case LnConstants.OPC_SW_STATE: {
220                result = interpretOpcSwState(l, turnoutPrefix);
221                if (result.length() > 0) {
222                    return result;
223                }
224                break;
225            }
226
227
228            /*
229             * OPC_RQ_SL_DATA   0xBB   ; Request SLOT DATA/status block
230             *                         ; Follow on message: <E7>SLOT READ
231             *                         ; <0xBB>,<SLOT>,<0>,<CHK> Request SLOT DATA/status block.
232             *
233             * Page 8 of LocoNet Personal Edition v1.0.
234             */
235            case LnConstants.OPC_RQ_SL_DATA: {
236                result = interpretOpcRqSlData(l);
237                if (result.length() > 0) {
238                    return result;
239                }
240                break;
241            }
242
243            /*
244             * OPC_MOVE_SLOTS   0xBA   ; MOVE slot SRC to DEST
245             *                         ; Follow on message: <E7>SLOT READ
246             *                         ; <0xBA>,<SRC>,<DEST>,<CHK> Move SRC to DEST if
247             *                         ; SRC or LACK etc is NOT IN_USE, clr SRC
248             *                         ; SPECIAL CASES:
249             *                         ; If SRC=0 ( DISPATCH GET) , DEST=dont care,
250             *                         ;    Return SLOT READ DATA of DISPATCH Slot
251             *                         ; IF SRC=DEST (NULL move) then SRC=DEST is set to
252             *                         ;    IN_USE , if legal move.
253             *                         ; If DEST=0, is DISPATCH Put, mark SLOT as DISPATCH
254             *                         ;    RETURN slot status <0xE7> of DESTINATION slot
255             *                         ;       DEST if move legal
256             *                         ;    RETURN Fail LACK code if illegal move
257             *                         ;       <B4>,<3A>,<0>,<chk>, illegal to move to/from
258             *                         ;       slots 120/127
259             *
260             * Page 8 of LocoNet Personal Edition v1.0.
261             */
262            case LnConstants.OPC_MOVE_SLOTS: {
263                result = interpretOpcMoveSlots(l);
264                if (result.length() > 0) {
265                    return result;
266                }
267                break;
268            }
269
270//            case LnConstants.OPC_EXP_SLOT_MOVE: {
271//                result = interpretOpcExpMoveSlots(l);
272//                if (result.length() > 0) {
273//                    return result;
274//                }
275//                break;
276//            }
277
278            /*
279             * OPC_LINK_SLOTS   0xB9   ; LINK slot ARG1 to slot ARG2=
280             *                         ; Follow on message: <E7>SLOT READ=
281             *                         ; <0xB9>,<SL1>,<SL2>,<CHK> SLAVE slot SL1 to slot SL2
282             *                         ; Master LINKER sets the SL_CONUP/DN flags
283             *                         ; appropriately. Reply is return of SLOT Status
284             *                         ; <0xE7>. Inspect to see result of Link, invalid
285             *                         ; Link will return Long Ack Fail <B4>,<39>,<0>,<CHK>
286             *
287             * Page 9 of LocoNet Personal Edition v1.0.
288             */
289            case LnConstants.OPC_LINK_SLOTS: {
290                int src = l.getElement(1);
291                int dest = l.getElement(2);
292                return Bundle.getMessage("LN_MSG_LINK_SLOTS", src, dest);
293            }
294
295            /*
296             * OPC_UNLINK_SLOTS 0xB8   ;UNLINK slot ARG1 from slot ARG2
297             *                         ; Follow on message: <E7>SLOT READ
298             *                         ; <0xB8>,<SL1>,<SL2>,<CHK> UNLINK slot SL1 from SL2
299             *                         ; UNLINKER executes unlink STRATEGY and returns new SLOT#
300             *                         ; DATA/STATUS of unlinked LOCO . Inspect data to evaluate UNLINK
301             *
302             * Page 9 of LocoNet Personal Edition v1.0.
303             */
304            case LnConstants.OPC_UNLINK_SLOTS: {
305                int src = l.getElement(1);
306                int dest = l.getElement(2);
307                return Bundle.getMessage("LN_MSG_UNLINK_SLOTS", src, dest);
308            } // case LnConstants.OPC_UNLINK_SLOTS
309
310            /*
311             * OPC_CONSIST_FUNC 0xB6   ; SET FUNC bits in a CONSIST uplink element
312             *                         ; <0xB6>,<SLOT>,<DIRF>,<CHK> UP consist FUNC bits
313             *                         ; NOTE this SLOT adr is considered in UPLINKED slot space.
314             *
315             * Page 9 of LocoNet Personal Edition v1.0.
316             */
317            case LnConstants.OPC_CONSIST_FUNC: {
318                result = interpretOpcConsistFunc(l);
319                if (result.length() > 0) {
320                    return result;
321                }
322                break;
323            }
324
325            /*
326             * OPC_SLOT_STAT1   0xB5   ; WRITE slot stat1
327             *                         ; <0xB5>,<SLOT>,<STAT1>,<CHK> WRITE stat1
328             *
329             * Page 9 of LocoNet Personal Edition v1.0.
330             */
331            case LnConstants.OPC_SLOT_STAT1: {
332                int slot = l.getElement(1);
333                int stat = l.getElement(2);
334                return Bundle.getMessage("LN_MSG_SLOT_STAT1", slot, stat,
335                        Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
336                                StringUtil.twoHexFromInt(stat)), LnConstants.CONSIST_STAT(stat),
337                        LnConstants.LOCO_STAT(stat), LnConstants.DEC_MODE(stat));
338            }
339
340            /*
341             * OPC_LONG_ACK     0xB4   ; Long acknowledge
342             *                         ; <0xB4>,<LOPC>,<ACK1>,<CHK> Long acknowledge
343             *                         ; <LOPC> is COPY of OPCODE responding to (msb=0).
344             *                         ; LOPC=0 (unused OPC) is also VALID fail code
345             *                         ; <ACK1> is appropriate response code for the OPCode
346             *
347             * Page 9 of LocoNet Personal Edition v1.0.
348             */
349            case LnConstants.OPC_LONG_ACK: {
350                result = interpretLongAck(l);
351                if (result.length() > 0) {
352                    return result;
353                }
354                break;
355            }
356
357            /*
358             * OPC_INPUT_REP    0xB2   ; General SENSOR Input codes
359             *                         ; <0xB2>, <IN1>, <IN2>, <CHK>
360             *                         ;   <IN1> =<0,A6,A5,A4- A3,A2,A1,A0>,
361             *                         ;           7 ls adr bits.
362             *                         ;           A1,A0 select 1 of 4 inputs pairs in a DS54.
363             *                         ;   <IN2> =<0,X,I,L- A10,A9,A8,A7>,
364             *                         ;           Report/status bits and 4 MS adr bits.
365             *                         ;           "I"=0 for DS54 "aux" inputs
366             *                         ;              =1 for "switch" inputs mapped to 4K SENSOR space.
367             *                         ;
368             *                         ;           (This is effectively a least significant adr bit when
369             *                         ;            using DS54 input configuration)
370             *                         ;
371             *                         ;           "L"=0 for input SENSOR now 0V (LO),
372             *                         ;              =1 for Input sensor >=+6V (HI)
373             *                         ;           "X"=1, control bit,
374             *                         ;              =0 is RESERVED for future!
375             *
376             * Page 9 of LocoNet Personal Edition v1.0.
377             */
378            case LnConstants.OPC_INPUT_REP: {
379                result = interpretOpcInputRep(l, sensorPrefix);
380                if (result.length() > 0) {
381                    return result;
382                }
383                break;
384            } // case LnConstants.OPC_INPUT_REP
385
386            /*
387             * OPC_SW_REP       0xB1   ; Turnout SENSOR state REPORT
388             *                         ; <0xB1>,<SN1>,<SN2>,<CHK> SENSOR state REPORT
389             *                         ;   <SN1> =<0,A6,A5,A4- A3,A2,A1,A0>,
390             *                         ;           7 ls adr bits.
391             *                         ;           A1,A0 select 1 of 4 input pairs in a DS54
392             *                         ;   <SN2> =<0,1,I,L- A10,A9,A8,A7>
393             *                         ;           Report/status bits and 4 MS adr bits.
394             *                         ;           this <B1> opcode encodes input levels
395             *                         ;           for turnout feedback
396             *                         ;           "I" =0 for "aux" inputs (normally not feedback),
397             *                         ;               =1 for "switch" input used for
398             *                         ;                  turnout feedback for DS54
399             *                         ;                  ouput/turnout # encoded by A0-A10
400             *                         ;           "L" =0 for this input 0V (LO),
401             *                         ;               =1 this input > +6V (HI)
402             *                         ;
403             *                         ;   alternately;
404             *                         ;
405             *                         ;   <SN2> =<0,0,C,T- A10,A9,A8,A7>
406             *                         ;           Report/status bits and 4 MS adr bits.
407             *                         ;           this <B1> opcode encodes current OUTPUT levels
408             *                         ;           "C" =0 if "Closed" ouput line is OFF,
409             *                         ;               =1 "closed" output line is ON
410             *                         ;                  (sink current)
411             *                         ;           "T" =0 if "Thrown" output line is OFF,
412             *                         ;               =1 "thrown" output line is ON
413             *                         ;                  (sink I)
414             *
415             * Page 9 of LocoNet Personal Edition v1.0.
416             */
417            case LnConstants.OPC_SW_REP: {
418                result = interpretOpcSwRep(l, turnoutPrefix);
419                if (result.length() > 0) {
420                    return result;
421                }
422                break;
423            }
424
425            /*
426             * OPC_SW_REQ       0xB0   ; REQ SWITCH function
427             *                         ; <0xB0>,<SW1>,<SW2>,<CHK> REQ SWITCH function
428             *                         ;   <SW1> =<0,A6,A5,A4- A3,A2,A1,A0>,
429             *                         ;           7 ls adr bits.
430             *                         ;           A1,A0 select 1 of 4 input pairs in a DS54
431             *                         ;   <SW2> =<0,0,DIR,ON- A10,A9,A8,A7>
432             *                         ;           Control bits and 4 MS adr bits.
433             *                         ;   DIR  =1 for Closed,/GREEN,
434             *                         ;        =0 for Thrown/RED
435             *                         ;   ON   =1 for Output ON,
436             *                         ;        =0 FOR output OFF
437             *                         ;
438             *                         ;   Note-Immediate response of <0xB4><30><00> if command failed,
439             *                         ;        otherwise no response "A" CLASS codes
440             *
441             * Page 9 of LocoNet Personal Edition v1.0.
442             * Page 12 special form Broadcast.
443             * Page 13 special form LocoNet interrogate.
444             */
445            case LnConstants.OPC_SW_REQ: {
446                result = interpretOpcSwReq(l, turnoutPrefix);
447                if (result.length() > 0) {
448                    return result;
449                }
450                break;
451            }
452
453            /*
454             * OPC_LOCO_SND     0xA2   ;SET SLOT sound functions
455             *
456             * Page 10 of LocoNet Personal Edition v1.0.
457             */
458            case LnConstants.OPC_LOCO_SND: {
459                result = interpretOpcLocoSnd(l);
460                if (result.length() > 0) {
461                    return result;
462                }
463                break;
464            } // case LnConstants.OPC_LOCO_SND
465
466            /*
467             * OPC_LOCO_DIRF 0xA1 ;SET SLOT dir, F0-4 state
468             *
469             * Page 10 of LocoNet Personal Edition v1.0.
470             */
471            case LnConstants.OPC_LOCO_DIRF: {
472                result = interpretOpcLocoDirf(l);
473                if (result.length() > 0) {
474                    return result;
475                }
476                break;
477            }
478
479            /*
480             * OPC_LOCO_SPD 0xA0 ;SET SLOT speed e.g. <0xA0><SLOT#><SPD><CHK>
481             *
482             * Page 10 of LocoNet Personal Edition v1.0.
483             */
484            case LnConstants.OPC_LOCO_SPD: {
485                result = interpretOpcLocoSpd(l);
486                if (result.length() > 0) {
487                    return result;
488                }
489                break;
490            }
491
492            case LnConstants.OPC_EXP_SEND_FUNCTION_OR_SPEED_AND_DIR: {
493                result = interpretPocExpLocoSpdDirFunction(l);
494                if (result.length() > 0) {
495                    return result;
496                }
497                break;
498            }
499
500            /*
501             * OPC_PANEL_QUERY 0xDF messages used by throttles to discover
502             * panels
503             *
504             * This op code is not documented by Digitrax. Some reverse engineering
505             * performed by Leo Bicknell.  The opcode "name" OPC_PANEL_QUERY
506             * is not necessarily the name used by Digitrax.
507             */
508            case LnConstants.OPC_PANEL_QUERY: {
509                result = interpretOpcPanelQuery(l);
510                if (result.length() > 0) {
511                    return result;
512                }
513                break;
514
515            }
516
517            /*
518             * OPC_PANEL_RESPONSE 0xD7 messages used by throttles to discover
519             * panels
520             *
521             * This op code is not documented by Digitrax. Reverse engineering
522             * performed by Leo Bicknell.  The opcode "name" OPC_PANEL_RESPONSE
523             * is not necessarily the name used by Digitrax.
524             */
525            case LnConstants.OPC_PANEL_RESPONSE: {
526                result = interpretOpcPanelResponse(l);
527                if (result.length() > 0) {
528                    return result;
529                }
530                break;
531
532            }
533
534            /*
535             * OPC_MULTI_SENSE 0xD0 messages about power management and
536             * transponding
537             *
538             * If byte 1 high nibble is 0x20 or 0x00 this is a transponding
539             * message
540             *
541             * This op code is not documented by Digitrax. Reverse engineering
542             * performed by Al Silverstein, and corrections added by B. Milhaupt.
543             */
544            case LnConstants.OPC_MULTI_SENSE: {
545                result = interpretOpcMultiSense(l, reporterPrefix);
546                if (result.length() > 0) {
547                    return result;
548                }
549                break;
550            }
551
552            /*
553             * ********************************************************************************************
554             * OPC_MULTI_SENSE_LONG 0xE0 messages about transponding.
555             *
556             * This op code is not documented by Digitrax. The use of this message was observed when using a
557             * Digikeijs 5088RC.  With a capable decoder, this message contains additional Railcom information
558             * (direction, speed, QoS) compared to the standard OPC_MULTI_SENSE message.
559             *
560             * Reverse engineering performed by Michael Richardson.
561             * ********************************************************************************************
562             */
563            case LnConstants.OPC_MULTI_SENSE_LONG: {
564                result = interpretOpcMultiSenseLong(l, reporterPrefix);
565                if (result.length() > 0) {
566                    return result;
567                }
568                break;
569            }
570
571            /*
572             * ********************************************************************************************
573             * OPC_WR_SL_DATA 0xEF ; WRITE SLOT DATA, 10 bytes * ; Follow on
574             * message: LACK * ; <0xEF>,<0E>,<SLOT#>,<STAT>,<ADR>,<SPD>,<DIRF>,
575             * * ;        <TRK>,<SS2>,<ADR2>,<SND>,<ID1>,<ID2>,<CHK> * ; SLOT DATA
576             * WRITE, 10 bytes data /14 byte MSG *
577             * **********************************************************************************************
578             * OPC_SL_RD_DATA 0xE7 ; SLOT DATA return, 10 bytes * ;
579             * <0xE7>,<0E>,<SLOT#>,<STAT>,<ADR>,<SPD>,<DIRF>, * ;
580             * <TRK>,<SS2>,<ADR2>,<SND>,<ID1>,<ID2>,<CHK> * ; SLOT DATA READ, 10
581             * bytes data /14 byte MSG * ; * ; NOTE; If STAT2.2=0 EX1/EX2
582             * encodes an ID#, * ; [if STAT2.2=1 the STAT.3=0 means EX1/EX2 * ;
583             * are ALIAS] * ; * ; ID1/ID2 are two 7 bit values encoding a 14 bit
584             * * ; unique DEVICE usage ID. * ; * ; 00/00 - means NO ID being
585             * used * ; * ; 01/00 - ID shows PC usage. * ; to Lo nibble is TYP
586             * PC# * ; 7F/01 (PC can use hi values) * ; * ; 00/02 -SYSTEM
587             * reserved * ; to * ; 7F/03 * ; * ; 00/04 -NORMAL throttle RANGE *
588             * ; to * ; 7F/7E *
589             * **********************************************************************************************
590             * Notes: * The SLOT DATA bytes are, in order of TRANSMISSION for
591             * <E7> READ or <EF> WRITE. * NOTE SLOT 0 <E7> read will return
592             * MASTER config information bytes. * * 0) SLOT NUMBER: * * ; 0-7FH,
593             * 0 is special SLOT, * ; 070H-07FH DIGITRAX reserved: * * 1) SLOT
594             * STATUS1: * * D7-SL_SPURGE ; 1=SLOT purge en, * ; ALSO adrSEL
595             * (INTERNAL use only) (not seen on NET!) * * D6-SL_CONUP ;
596             * CONDN/CONUP: bit encoding-Control double linked Consist List * ;
597             * 11=LOGICAL MID CONSIST , Linked up AND down * ; 10=LOGICAL
598             * CONSIST TOP, Only linked downwards * ; 01=LOGICAL CONSIST
599             * SUB-MEMBER, Only linked upwards * ; 00=FREE locomotive, no
600             * CONSIST indirection/linking * ; ALLOWS "CONSISTS of CONSISTS".
601             * Uplinked means that * ; Slot SPD number is now SLOT adr of
602             * SPD/DIR and STATUS * ; of consist. i.e. is ;an Indirect pointer.
603             * This Slot * ; has same BUSY/ACTIVE bits as TOP of Consist. TOP is
604             * * ; loco with SPD/DIR for whole consist. (top of list). * ;
605             * BUSY/ACTIVE: bit encoding for SLOT activity * * D5-SL_BUSY ;
606             * 11=IN_USE loco adr in SLOT -REFRESHED * * D4-SL_ACTIVE ; 10=IDLE
607             * loco adr in SLOT -NOT refreshed * ; 01=COMMON loco adr IN SLOT
608             * -refreshed * ; 00=FREE SLOT, no valid DATA -not refreshed * *
609             * D3-SL_CONDN ; shows other SLOT Consist linked INTO this slot, see
610             * SL_CONUP * * D2-SL_SPDEX ; 3 BITS for Decoder TYPE encoding for
611             * this SLOT * * D1-SL_SPD14 ; 011=send 128 speed mode packets * *
612             * D0-SL_SPD28 ; 010=14 step MODE * ; 001=28 step. Generate Trinary
613             * packets for this * ; Mobile ADR * ; 000=28 step. 3 BYTE PKT
614             * regular mode * ; 111=128 Step decoder, Allow Advanced DCC
615             * consisting * ; 100=28 Step decoder ,Allow Advanced DCC consisting
616             * * * 2) SLOT LOCO ADR: * * LOCO adr Low 7 bits (byte sent as ARG2
617             * in ADR req opcode <0xBF>) * * 3) SLOT SPEED: * 0x00=SPEED 0 ,STOP
618             * inertially * 0x01=SPEED 0 EMERGENCY stop * 0x02->0x7F increasing
619             * SPEED,0x7F=MAX speed * (byte also sent as ARG2 in SPD opcode
620             * <0xA0> ) * * 4) SLOT DIRF byte: (byte also sent as ARG2 in DIRF
621             * opcode <0xA1>) * * D7-0 ; always 0 * D6-SL_XCNT ; reserved , set
622             * 0 * D5-SL_DIR ; 1=loco direction FORWARD * D4-SL_F0 ;
623             * 1=Directional lighting ON * D3-SL_F4 ; 1=F4 ON * D2-SL_F3 ; 1=F3
624             * ON * D1-SL_F2 ; 1=F2 ON * D0-SL_F1 ; 1=F1 ON * * * * * 5) TRK
625             * byte: (GLOBAL system /track status) * * D7-D4 Reserved * D3
626             * GTRK_PROG_BUSY 1=Programming TRACK in this Master is BUSY. * D2
627             * GTRK_MLOK1 1=This Master IMPLEMENTS LocoNet 1.1 capability, *
628             * 0=Master is DT200 * D1 GTRK_IDLE 0=TRACK is PAUSED, B'cast EMERG
629             * STOP. * D0 GTRK_POWER 1=DCC packets are ON in MASTER, Global
630             * POWER up * * 6) SLOT STATUS: * * D3 1=expansion IN ID1/2,
631             * 0=ENCODED alias * D2 1=Expansion ID1/2 is NOT ID usage * D0
632             * 1=this slot has SUPPRESSED ADV consist-7) * * 7) SLOT LOCO ADR
633             * HIGH: * * Locomotive address high 7 bits. If this is 0 then Low
634             * address is normal 7 bit NMRA SHORT * address. If this is not zero
635             * then the most significant 6 bits of this address are used in *
636             * the first LONG address byte ( matching CV17). The second DCC LONG
637             * address byte matches CV18 * and includes the Adr Low 7 bit value
638             * with the LS bit of ADR high in the MS postion of this * track adr
639             * byte. * * Note a DT200 MASTER will always interpret this as 0. *
640             * * 8) SLOT SOUND: * * Slot sound/ Accesory Function mode II
641             * packets. F5-F8 * (byte also sent as ARG2 in SND opcode) * * D7-D4
642             * reserved * D3-SL_SND4/F8 * D2-SL_SND3/F7 * D1-SL_SND2/F6 *
643             * D0-SL_SND1/F5 1= SLOT Sound 1 function 1active (accessory 2) * *
644             * 9) EXPANSION RESERVED ID1: * * 7 bit ls ID code written by
645             * THROTTLE/PC when STAT2.4=1 * * 10) EXPANSION RESERVED ID2: * * 7
646             * bit ms ID code written by THROTTLE/PC when STAT2.4=1 *
647             * ********************************************************************************************
648             * page 10 of LocoNet PE
649             */
650            case LnConstants.OPC_WR_SL_DATA:
651            case LnConstants.OPC_SL_RD_DATA: {
652                result = interpretOpcWrSlDataOpcSlRdData(l);
653                if (result.length() > 0) {
654                    return result;
655                }
656                break;
657
658            }
659
660            case LnConstants.OPC_ALM_WRITE:
661            case LnConstants.OPC_ALM_READ: {
662            // case OPC_EXP_RD_SL_DATA: // NOTE: Duplicate of definition of OPC_ALM_WRITE!
663            // case OPC_EXP_WR_SL_DATA: // NOTE: Duplicate of definition of OPC_ALM_READ!
664
665                result = interpretAlm(l);
666                if (result.length() > 0) {
667                    return result;
668                }
669                break;
670            }
671
672            /*
673             * OPC_PEER_XFER   0xE5    ; move 8 bytes PEER to PEER, SRC->DST   NO resp
674             *                         ; <0xE5>,<10>,<SRC>,<DSTL><DSTH>,<PXCT1>,<D1>,<D2>,<D3>,<D4>,
675             *                         ; <PXCT2>,<D5>,<D6>,<D7>,<D8>,<CHK>
676             *                         ; SRC/DST are 7 bit args. DSTL/H=0 is BROADCAST msg
677             *                         ;     SRC=0 is MASTER
678             *                         ;     SRC=0x70-0x7E are reserved
679             *
680             * Page 10 of LocoNet Personal Edition v1.0.
681             *
682             * Duplex group management reverse engineered by Leo Bicknell, with input from
683             * B. Milhaupt.
684             */
685            case LnConstants.OPC_PEER_XFER: {
686                result = interpretOpcPeerXfer(l, reporterPrefix);
687                if (result.length() > 0) {
688                    return result;
689                }
690                break;
691            }
692
693            //                   0xE4
694            case LnConstants.OPC_LISSY_UPDATE: {
695                result = interpretOpcLissyUpdate(l);
696                if (result.length() > 0) {
697                    return result;
698                }
699                break;
700            }
701
702            //                    0xED
703            case LnConstants.OPC_IMM_PACKET: {
704                result = interpretOpcImmPacket(l);
705                if (result.length() > 0) {
706                    return result;
707                }
708                break;
709            }
710
711            //                     0xD3
712            case LnConstants.RE_OPC_PR3_MODE: {
713                result = interpretOpcPr3Mode(l);
714                if (result.length() > 0) {
715                    return result;
716                }
717                break;
718            }
719
720            //                      0xA3
721            case LnConstants.RE_OPC_IB2_F9_F12: {
722                result = interpretIb2F9_to_F12(l);
723                if (result.length() > 0) {
724                    return result;
725                }
726                break;
727            }
728
729//          TODO: put this back for intellibox cmd station.
730//            it conflicts with loconet speed/dir etc.
731            //                        0xD4
732            case LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL: {
733                result = interpretIb2Special(l);
734                if (result.length() > 0) {
735                    return result;
736                }
737                result = interpretOpcExpMoveSlots(l);
738                if (result.length() > 0) {
739                    return result;
740                }
741                break;
742            }//  case LnConstants.RE_OPC_IB2_SPECIAL: { //0xD4
743
744            //$FALL-THROUGH$
745            default:
746                break;
747        } // end switch over opcode type
748
749        return Bundle.getMessage("LN_MSG_UNKNOWN_MESSAGE") +
750                Bundle.getMessage("LN_MONITOR_MESSAGE_RAW_HEX_INFO", l.toString());
751    }
752
753
754    private static String interpretOpcPeerXfer20_1(LocoNetMessage l) {
755        switch (l.getElement(3)) {
756            case 0x08: {
757                return Bundle.getMessage("LN_MSG_DUPLEX_RECEIVER_QUERY");
758            }
759            case 0x10: {
760                return Bundle.getMessage("LN_MSG_DUPLEX_RECEIVER_RESPONSE");
761            }
762            default: {
763                break;
764            }
765        }
766        return "";
767    }
768
769    private static String interpretOpcPeerXfer20_2(LocoNetMessage l) {
770        switch (l.getElement(3)) {
771            case 0x00: {
772                int channel = l.getElement(5) | ((l.getElement(4) & 0x01) << 7);
773
774                return Bundle.getMessage("LN_MSG_DUPLEX_CHANNEL_SET",
775                        Integer.toString(channel));
776            }
777            case 0x08: {
778                return Bundle.getMessage("LN_MSG_DUPLEX_CHANNEL_QUERY");
779            }
780            case 0x10: {
781                int channel = l.getElement(5) | ((l.getElement(4) & 0x01) << 7);
782
783                return Bundle.getMessage("LN_MSG_DUPLEX_CHANNEL_REPORT",
784                        Integer.toString(channel));
785            }
786            default: {
787                break;
788            }
789        }
790        return "";
791    }
792
793    private static String interpretOpcPeerXfer20_3(LocoNetMessage l) {
794        // Characters appear to be 8 bit values, but transmitted over a 7 bit
795        // encoding, so high order bits are stashed in element 4 and 9.
796        char[] groupNameArray = {(char) (l.getElement(5) | ((l.getElement(4) & 0x01) << 7)),
797            (char) (l.getElement(6) | ((l.getElement(4) & 0x02) << 6)),
798            (char) (l.getElement(7) | ((l.getElement(4) & 0x04) << 5)),
799            (char) (l.getElement(8) | ((l.getElement(4) & 0x08) << 4)),
800            (char) (l.getElement(10) | ((l.getElement(9) & 0x01) << 7)),
801            (char) (l.getElement(11) | ((l.getElement(9) & 0x02) << 6)),
802            (char) (l.getElement(12) | ((l.getElement(9) & 0x04) << 5)),
803            (char) (l.getElement(13) | ((l.getElement(9) & 0x08) << 4))};
804        String groupName = new String(groupNameArray);
805
806        // The pass code is stuffed in here, each digit in 4 bits.  But again, it's a
807        // 7 bit encoding, so the MSB of the "upper" half is stuffed into byte 14.
808        int p1 = ((l.getElement(14) & 0x01) << 3) | ((l.getElement(15) & 0x70) >> 4);
809        int p2 = l.getElement(15) & 0x0F;
810        int p3 = ((l.getElement(14) & 0x02) << 2) | ((l.getElement(16) & 0x70) >> 4);
811        int p4 = l.getElement(16) & 0x0F;
812
813        // It's not clear you can set A-F from throttles or Digitrax's tools, but
814        // they do take and get returned if you send them on the wire...
815        String passcode = StringUtil.twoHexFromInt(p1) + StringUtil.twoHexFromInt(p2)
816                + StringUtil.twoHexFromInt(p3) + StringUtil.twoHexFromInt(p4);
817
818        // The MSB is stuffed elsewhere again...
819        int channel = l.getElement(17) | ((l.getElement(14) & 0x04) << 5);
820
821        // The MSB is stuffed elsewhere one last time.
822        int id = l.getElement(18) | ((l.getElement(14) & 0x08) << 4);
823
824        switch (l.getElement(3)) {
825            case 0x00: {
826                return Bundle.getMessage("LN_MSG_DUPLEX_NAME_WRITE",
827                        groupName);
828            }
829            case 0x08: {
830                return Bundle.getMessage("LN_MSG_DUPLEX_NAME_QUERY");
831            }
832            case 0x10: {
833                return Bundle.getMessage("LN_MSG_DUPLEX_NAME_REPORT",
834                        groupName, passcode, channel, id);
835            }
836            default: {
837                break;
838            }
839        }
840        return "";
841    }
842
843    private static String interpretOpcPeerXfer20_4(LocoNetMessage l) {
844        // The MSB is stuffed elsewhere again...
845        int id = l.getElement(5) | ((l.getElement(4) & 0x01) << 7);
846
847        switch (l.getElement(3)) {
848            case 0x00: {
849                return Bundle.getMessage("LN_MSG_DUPLEX_ID_SET", id);
850            }
851            case 0x08: {
852                return Bundle.getMessage("LN_MSG_DUPLEX_ID_QUERY");
853            }
854            case 0x10: {
855                return Bundle.getMessage("LN_MSG_DUPLEX_ID_REPORT", id);
856            }
857            default: {
858                break;
859            }
860        }
861        return "";
862    }
863
864    private static String interpretOpcPeerXfer20_7(LocoNetMessage l) {
865        if (l.getElement(3) == 0x08) {
866            return Bundle.getMessage("LN_MSG_DUPLEX_PASSWORD_QUERY");
867        }
868
869        if ((l.getElement(5) < 0x30) || (l.getElement(5) > 0x3c)
870                || (l.getElement(6) < 0x30) || (l.getElement(6) > 0x3c)
871                || (l.getElement(7) < 0x30) || (l.getElement(7) > 0x3c)
872                || (l.getElement(8) < 0x30) || (l.getElement(8) > 0x3c)) {
873            return "";
874        }
875        char[] groupPasswordArray = {(char) l.getElement(5),
876            (char) l.getElement(6),
877            (char) l.getElement(7),
878            (char) l.getElement(8)};
879        if ((groupPasswordArray[0] > 0x39) && (groupPasswordArray[0] < 0x3d)) {
880            groupPasswordArray[0] += ('A' - '9' - 1);
881        }
882        if ((groupPasswordArray[1] > 0x39) && (groupPasswordArray[1] < 0x3d)) {
883            groupPasswordArray[1] += ('A' - '9' - 1);
884        }
885        if ((groupPasswordArray[2] > 0x39) && (groupPasswordArray[2] < 0x3d)) {
886            groupPasswordArray[2] += ('A' - '9' - 1);
887        }
888        if ((groupPasswordArray[3] > 0x39) && (groupPasswordArray[3] < 0x3d)) {
889            groupPasswordArray[3] += ('A' - '9' - 1);
890        }
891        String groupPassword = new String(groupPasswordArray);
892
893        switch (l.getElement(3)) {
894            case 0x00: {
895                return Bundle.getMessage("LN_MSG_DUPLEX_PASSWORD_SET", groupPassword);
896            }
897            case 0x10: {
898                return Bundle.getMessage("LN_MSG_DUPLEX_PASSWORD_REPORT", groupPassword);
899            }
900            default: {
901                break;
902            }
903        }
904        return "";
905    }
906
907    private static String interpretOpcPeerXfer20_10(LocoNetMessage l) {
908        switch (l.getElement(3)) {
909            case 0x08: {
910                return Bundle.getMessage("LN_MSG_DUPLEX_CHANNEL_SCAN_QUERY", l.getElement(5));
911            }
912            case 0x10: {
913                // High order bit stashed in another element again.
914                int level = (l.getElement(6) & 0x7F) + ((l.getElement(4) & 0x02) << 6);
915
916                return Bundle.getMessage("LN_MSG_DUPLEX_CHANNEL_SCAN_REPORT", l.getElement(5),
917                        level);
918            }
919            default: {
920                break;
921            }
922        }
923        return "";
924    }
925
926    private static String interpretOpcPeerXfer20_8(LocoNetMessage l) {
927        /*
928         * **********************************************************************************
929         * IPL-capable device ping - OPC_RE_IPL (Device Ping Operations) * The
930         * message bytes as assigned as follows:
931         * <p>
932         * <E5> <14> <08> <GR_OP_T> <DI_F2> <DI_Ss0>
933         * <DI_Ss1> ...
934         * <p>
935         * <DI_Ss2> <DI_Ss3> <DI_U1> <00> <00> <DI_U2>
936         * <DI_U3> ...
937         * <p>
938         * <00> <00><00> <00><00> <CHK> * where:
939         * <p>
940         * <DI_F2> encodes additional bits for the Slave device serial number. *
941         * bits 7-4 always 0000b * bit 3 Bit 31 of Slave Device Serial Number *
942         * bit 2 Bit 23 of Slave Device Serial Number * bit 1 Bit 15 of Slave
943         * device Serial Number * bit 0 Bit 7 of Slave device Serial Number
944         * <p>
945         * <DI_Ss0> encodes 7 bits of the 32 bit Host device serial number: *
946         * bit 7 always 0 * bits 6-0 Bits 6:0 of Slave device serial number
947         * <p>
948         * <DI_Ss1> encodes 7 bits of the 32 bit Host device serial number: *
949         * bit 7 always 0 * bits 6-0 Bits 14:8 of Slave device serial number
950         * <p>
951         * <DI_Ss2> encodes 7 bits of the 32 bit Host device serial number: *
952         * bit 7 always 0 * bits 6-0 Bits 22:16 of Slave device serial number
953         * <p>
954         * <DI_Ss3> encodes 7 bits of the 32 bit Host device serial number: *
955         * bit 7 always 0 * bits 6-0 Bits 30:24 of Slave device serial number
956         * <p>
957         * <DI_U1> unknown data * when <GR_OP_T> = 0x08 * is always 0 * when
958         * <GR_OP_T> = 0x10 * is not reverse-engineered and may be non-zero.
959         * <p>
960         * <DI_U2> unknown data * when <GR_OP_T> = 0x08 * is always 0 * when
961         * <GR_OP_T> = 0x10 * is not reverse-engineered and may be non-zero.
962         * <p>
963         * <DI_U3> unknown data * when <GR_OP_T> = 0x08 * is always 0 * when
964         * <GR_OP_T> = 0x10 * is not reverse-engineered and may be non-zero. * *
965         * Information reverse-engineered by B. Milhaupt and used with
966         * permission *
967         * **********************************************************************************
968         */
969        /* OPC_RE_IPL (IPL Ping Operation) */
970        // Operations related to DigiIPL Device "Ping" operations
971        //
972        // "Ping" request issued from DigiIPL ver 1.09 issues this message on LocoNet.
973        // The LocoNet request message encodes a serial number but NOT a device type.
974        //
975        // Depending on which devices are selected in DigiIPL when the "ping"
976        // is selected, (and probably the S/Ns of the devices attached to the LocoNet,
977        // the response is as follows:
978        //     DT402D  LocoNet message includes the serial number from the DT402D's
979        //             Slave (RF24) serial number.  If a UR92 is attached to LocoNet,
980        //             it will send the message via its RF link to the addressed
981        //             DT402D.  (UR92 apparantly assumes that the long 802.15.4
982        //             address of the DT402D is based on the serial number embedded
983        //             in the LocoNet message, with the MS 32 bits based on the UR92
984        //             long address MS 32 bits).  If more than one UR92 is attached
985        //             to LocoNet, all will pass the message to the RF interface.
986        //     UR92    LocoNet message includes the Slave serial number from the UR92.
987        //             These messages are not passed to the RF link by the addressed
988        //             UR92.  If more than one UR92 is attached to LocoNet, and the
989        //             addressed UR92 hears the RF version of the LocoNet message, it
990        //             will respond via the RF interface with an acknowledge packet,
991        //             and a UR92 (not sure which one) responds on LocoNet with a
992        //             Ping report <e5><14><08><10>.
993        //     PR3     LocoNet message includes an effective serial number of all
994        //             zeros.  There is no LocoNet message reply generated to a
995        //             request to a PR3 S/N, but there will be a reply on the PR3's
996        //             computer interface if the ping request was sent via the PR3's
997        //             computer interface (i.e. not from some other LocoNet agent).
998        //     UT4D    While it has been suggested that the UT4D supports firmware
999        //             updates, the UT4D does not respond to the Ping message.
1000        //     LNRP    While it has been suggested that the LNRP supports firmware
1001        //             updates, the LNRP does not respond to the Ping message.
1002        //
1003        // Ping Report values:
1004        //     <unkn1> Seems always to be <0C>.  None of the bytes relate to
1005        //             Duplex Channel Number.
1006        //     <unkn2> Matches byte 15 of the MAC payload of the reply sent by the
1007        //             targeted UR92.
1008        //     <unkn3> Unclear what this byte means.
1009        //
1010        // Information reverse-engineered by B. Milhaupt and used with permission
1011        switch (l.getElement(3)) {
1012            case 0x08:
1013                /* OPC_RE_IPL (IPL Ping Query) */
1014                // Ping Request: <e5><14><08><08><msBits><Sn0><Sn1><Sn2><Sn3><0><0><0><0><0><0><0><0><0><0><0><Chk>
1015
1016                if ((((l.getElement(4) & 0xF) != 0) || (l.getElement(5) != 0)
1017                        || (l.getElement(6) != 0) || (l.getElement(7) != 0) || (l.getElement(8) != 0))
1018                        && (l.getElement(9) == 0) && (l.getElement(10) == 0)
1019                        && (l.getElement(11) == 0) && (l.getElement(12) == 0)
1020                        && (l.getElement(13) == 0) && (l.getElement(14) == 0)
1021                        && (l.getElement(15) == 0) && (l.getElement(16) == 0)
1022                        && (l.getElement(17) == 0) && (l.getElement(18) == 0)) {
1023
1024                    int hostSnInt;
1025                    hostSnInt = (l.getElement(5) + (((l.getElement(4) & 0x1) == 1) ? 128 : 0))
1026                            + ((l.getElement(6) + (((l.getElement(4) & 0x2) == 2) ? 128 : 0)) * 256)
1027                            + ((l.getElement(7) + (((l.getElement(4) & 0x4) == 4) ? 128 : 0)) * 256 * 256)
1028                            + ((l.getElement(8) + (((l.getElement(4) & 0x8) == 8) ? 128 : 0)) * 256 * 256 * 256);
1029                    return Bundle.getMessage("LN_MSG_DUPLEX_PING_REQUEST",
1030                            Integer.toHexString(hostSnInt).toUpperCase());
1031                }
1032                break;
1033            case 0x10:
1034                /* OPC_RE_IPL (IPL Ping Report) */
1035
1036                // Ping Report:  <e5><14><08><10><msbits><Sn0><Sn1><Sn2><Sn3><unkn1><0><0><Unkn2><Unkn3><0><0><0><0><0><Chk>
1037                if (((l.getElement(4) & 0xF) != 0) || (l.getElement(5) != 0) || (l.getElement(6) != 0)
1038                        || (l.getElement(7) != 0) || (l.getElement(8) != 0)) {   // if any serial number bit is non-zero //
1039                    int hostSnInt = (l.getElement(5) + (((l.getElement(4) & 0x1) == 1) ? 128 : 0))
1040                            + ((l.getElement(6) + (((l.getElement(4) & 0x2) == 2) ? 128 : 0)) * 256)
1041                            + ((l.getElement(7) + (((l.getElement(4) & 0x4) == 4) ? 128 : 0)) * 256 * 256)
1042                            + ((l.getElement(8) + (((l.getElement(4) & 0x8) == 8) ? 128 : 0)) * 256 * 256 * 256);
1043                    return Bundle.getMessage("LN_MSG_DUPLEX_PING_REPORT",
1044                            Integer.toHexString(hostSnInt).toUpperCase(),
1045                            StringUtil.twoHexFromInt(l.getElement(12) + (((l.getElement(9)) & 0x4) == 0x4 ? 128 : 0)).toUpperCase(),
1046                            StringUtil.twoHexFromInt(l.getElement(13) + (((l.getElement(9)) & 0x8) == 0x8 ? 128 : 0)).toUpperCase()
1047                    );
1048                }
1049                break;
1050            default:
1051                break;
1052        }
1053        return "";
1054    }
1055
1056    private static String interpretOpcPeerXfer20_0f(LocoNetMessage l) {
1057        String device;
1058
1059        switch (l.getElement(3)) {
1060            case 0x08: {
1061                if ((l.getElement(4) == 0)
1062                        && (l.getElement(5) == 0) && (l.getElement(6) == 0)
1063                        && (l.getElement(7) == 0) && (l.getElement(8) == 0)
1064                        && (l.getElement(9) == 0) && (l.getElement(10) == 0)
1065                        && (l.getElement(11) == 1) && (l.getElement(12) == 0)
1066                        && (l.getElement(13) == 0) && (l.getElement(14) == 0)
1067                        && (l.getElement(15) == 0) && (l.getElement(16) == 0)
1068                        && (l.getElement(17) == 0) && (l.getElement(18) == 0)) {
1069                    /*
1070                     * **********************************************************************************
1071                     * IPL capable device query - RE_IPL_IDENTITY_OPERATION
1072                     * (Device Query) * The message bytes are assigned as
1073                     * follows:
1074                     * <p>
1075                     * <E5> <14> <0F> <08> <00> <00>
1076                     * <00> <00> <00> <00> <00> <01>
1077                     * <00> <00> ...
1078                     * <p>
1079                     * <00> <00> <00> <00> <00> <CHK> * * Information
1080                     * reverse-engineered by B. Milhaupt and used with
1081                     * permission *
1082                     * **********************************************************************************
1083                     */
1084                    // Request for all IPL-queryable devices to report their presence
1085                    //
1086                    // Information reverse-engineered by B. Milhaupt and used with permission
1087
1088                    return Bundle.getMessage("LN_MSG_IPL_DISCOVER_ALL_DEVICES");
1089                } else if (((l.getElement(5) != 0) || (l.getElement(6) != 0))) {
1090                    /*
1091                     * **********************************************************************************
1092                     * IPL device query by type - RE_IPL_IDENTITY_OPERATION
1093                     * (Device Query) * The message bytes are assigned as
1094                     * follows:
1095                     * <p>
1096                     * <E5> <14> <0F> <08> <DI_Hmf>
1097                     * <DI_Hst> <DI_Slv> <00> <00> <00>
1098                     * <00> <01> ...
1099                     * <p>
1100                     * <00> <00> <00> <00> <00> <00>
1101                     * <00> <CHK> * where:
1102                     * <p>
1103                     * <DI_Hmf> DigiIPL-capable Host device manufacturer number.
1104                     * This is not * the same as an NMRA Manufacturer ID. * 0x00
1105                     * Digitrax * Others No other Host device manufacturer *
1106                     * numbers have been reverse- * engineered
1107                     * <p>
1108                     * <DI_Hst> encodes the DigiIPL-capable Host device type as
1109                     * follows: * When <DI_Hmf> = 0x00 * 0x00 (0 decimal) No
1110                     * Host device type reported * 0x04 (4 decimal) UT4D (Note
1111                     * that UT4D, UT4 and UT4R do * not respond to this DigiIPL
1112                     * * request) * 0x18 (24 decimal) RF24 - not typically a
1113                     * Host device * 0x23 (35 decimal) PR3 * 0x2A (42 decimal)
1114                     * DT402 (or DT402R or DT402D) * 0x33 (51 decimal) DCS51 *
1115                     * 0x5C (92 decimal) UR92 * Others No other Host device
1116                     * types have been * reverse-engineered * When
1117                     * <DI_Hmf> is not 0x00 * All values Not reverse-engineered
1118                     * <p>
1119                     * <DI_Slv> encodes the DigiIPL-capable Slave device type as
1120                     * follows: * When <DI_Smf> = 0x00 * 0x00 (0 decimal) Report
1121                     * for all Slave device types * 0x18 (24 decimal) RF24 *
1122                     * Others No other Slave device types have been *
1123                     * reverse-engineered * * Information reverse-engineered by
1124                     * B. Milhaupt and used with permission *
1125                     * **********************************************************************************
1126                     */
1127                    // Request for IPL-queryable devices of given manufacturer and type to report
1128                    // their presence
1129                    //
1130                    // Note that standard definitions are provided for UT4D and RF24, even though these
1131                    // devices do not respond to this query.  Note that UT4D will respond to IPL capable
1132                    // device query with DI_Hmf = 0, DI_Hst = 0, DI_Slv = 0, and DI_Smf = 0.
1133                    //
1134                    // Information reverse-engineered by B. Milhaupt and used with permission
1135
1136                    device = getDeviceNameFromIPLInfo(l.getElement(4), l.getElement(5));
1137                    String slave = getSlaveNameFromIPLInfo(l.getElement(4), l.getElement(6));
1138                    return Bundle.getMessage("LN_MSG_IPL_DISCOVER_SPECIFIC_DEVICES",
1139                            device, slave);
1140                }
1141                break;
1142            } // end case 0x08, which decodes 0xe5 0x14 0x0f 0x08
1143            case 0x10: {
1144                return interpretOpcPeerXfer20Sub10(l);
1145            } // end case 0x10, which decodes 0xe5 0x14 0x0f 0x10
1146            default: {
1147                break;
1148            }
1149
1150        } // end of switch (l.getElement(3)), which decodes 0xe5 0x14 0x0f 0x??
1151
1152        return "";
1153    }
1154
1155    private static String interpretOpcPeerXfer20(LocoNetMessage l) {
1156        // Duplex Radio Management
1157        // DigiIPL messages
1158        // LocoIO, LocoServo, LocoBuffer, LocoBooster configuration messages
1159
1160        switch (l.getElement(2)) {
1161            case 0x01: {
1162                // Seems to be a query for just duplex devices.
1163                String result = interpretOpcPeerXfer20_1(l);
1164                if (result.length() > 0) {
1165                    return result;
1166                }
1167                break;
1168            }
1169            case 0x02: {
1170                // Request Duplex Radio Channel
1171                String result = interpretOpcPeerXfer20_2(l);
1172                if (result.length() > 0) {
1173                    return result;
1174                }
1175                break;
1176            }
1177
1178            case 0x03: {
1179                // Duplex Group Name
1180                String result = interpretOpcPeerXfer20_3(l);
1181                if (result.length() > 0) {
1182                    return result;
1183                }
1184                break;
1185            }
1186            case 0x04: {
1187                // Duplex Group ID
1188                String result = interpretOpcPeerXfer20_4(l);
1189                if (result.length() > 0) {
1190                    return result;
1191                }
1192                break;
1193            }
1194            case 0x07: {
1195                // Duplex Group Password
1196                String result = interpretOpcPeerXfer20_7(l);
1197                if (result.length() > 0) {
1198                    return result;
1199                }
1200                break;
1201            }
1202            case 0x10: {
1203                // Radio Channel Noise/Activity
1204                String result = interpretOpcPeerXfer20_10(l);
1205                if (result.length() > 0) {
1206                    return result;
1207                }
1208                break;
1209            }
1210
1211            case LnConstants.RE_IPL_PING_OPERATION: { // case 0x08, which decodes 0xe5 0x14 0x08
1212                String result = interpretOpcPeerXfer20_8(l);
1213                if (result.length() > 0) {
1214                    return result;
1215                }
1216                break;
1217            }
1218
1219            case LnConstants.RE_IPL_IDENTITY_OPERATION: { // case 0x0f, which decodes 0xe5 0x14 0x0f
1220                // Operations related to DigiIPL "Ping", "Identify" and "Discover"
1221                String result = interpretOpcPeerXfer20_0f(l);
1222                if (result.length() > 0) {
1223                    return result;
1224                }
1225                break;
1226
1227            }
1228
1229            default: {
1230                break;
1231            }
1232        }
1233        return "";
1234    }
1235
1236    private static String interpretOpcPeerXfer20Sub10(LocoNetMessage l) {
1237        /**
1238         * **********************************************************************************
1239         * IPL device identity report - RE_IPL_IDENTITY_OPERATION (Device
1240         * Report) * The message bytes are assigned as follows:
1241         * <p>
1242         * <E5> <14> <0F> <08> <DI_Hmf> <DI_Hst>
1243         * <DI_Slv> <DI_Smf> <DI_Hsw> ...
1244         * <p>
1245         * <DI_F1> <DI_Ssw> <DI_Hs0> <DI_Hs1>
1246         * <DI_Hs2> <DI_F2> <DI_Ss0> ...
1247         * <p>
1248         * <DI_Ss1> <DI_Ss2> <DI_Ss3> <CHK> * where:
1249         * <p>
1250         * <DI_Hmf> DigiIPL-capable Host device manufacturer number. This is not
1251         * * the same as an NMRA Manufacturer ID. * 0x00 Digitrax * Others No
1252         * other Host device manufacturer * numbers have been reverse- *
1253         * engineered
1254         * <p>
1255         * <DI_Hst> encodes the DigiIPL-capable Host device type as follows: *
1256         * When
1257         * <DI_Hmf> = 0x00 * 0x00 (0 decimal) No Host device type reported *
1258         * 0x04 (4 decimal) UT4D * 0x23 (35 decimal) PR3 * 0x2A (42 decimal)
1259         * DT402 (or DT402R or DT402D) * 0x33 (51 decimal) DCS51 * 0x5C (92
1260         * decimal) UR92 * Others No other Host device types have been *
1261         * reverse-engineered * When <DI_Hmf> is not 0x00 * All values Not
1262         * reverse-engineered
1263         * <p>
1264         * <DI_Slv> encodes the DigiIPL-capable Slave device type as follows: *
1265         * When
1266         * <DI_Smf> = 0x00 * 0x00 (0 decimal) Report for all Slave device types
1267         * * 0x18 (24 decimal) RF24 * Others No other Slave device types have
1268         * been * reverse-engineered
1269         * <p>
1270         * <DI_Smf> DigiIPL-capable Slave device manufacturer number. This is
1271         * not * the same as an NMRA Manufacturer ID. * 0x00 Digitrax * Others
1272         * No other Slave device manufacturer * numbers have been reverse- *
1273         * engineered
1274         * <p>
1275         * <DI_Hsw> encodes the DigiIPL-capable Host device firmware revision *
1276         * number as follows: * bit 7 always 0 * bits 6-3 Host device firmware
1277         * major revision number * bits 2-0 Host device firmware minor revision
1278         * number
1279         * <p>
1280         * <DI_F1> encodes additional bits for the Slave device firmware major *
1281         * revision number and for the Host device serial number. * bits 7-4
1282         * always 0000b * bit 3 Bit 23 of Host Device Serial Number * bit 2 Bit
1283         * 15 of Host Device Serial Number * bit 1 Bit 7 of Host Device Serial
1284         * Number * bit 0 bit 4 of Slave device firmware Major number
1285         * <p>
1286         * <DI_Ssw> encodes the DigiIPL-capable Slave device firmware revision *
1287         * number as follows: * bit 7 always 0 * bits 6-3 Host device firmware
1288         * major revision number * bits 6-3 4 least-significant bits of Slave
1289         * device firmware major * revision number (see also <DI_F1>[0]) * bits
1290         * 2-0 Slave device firmware minor revision number
1291         * <p>
1292         * <DI_Hs0> encodes 7 bits of the 24 bit Host device serial number: *
1293         * bit 7 always 0 * bits 6-3 Bits 6-0 of Host device serial number
1294         * <p>
1295         * <DI_Hs1> encodes 7 bits of the 24 bit Host device serial number: *
1296         * bit 7 always 0 * bits 6-3 Bits 14-9 of Host device serial number
1297         * <p>
1298         * <DI_Hs2> encodes 7 bits of the 24 bit Host device serial number: *
1299         * bit 7 always 0 * bits 6-3 Bits 22-16 of Host device serial number
1300         * <p>
1301         * <DI_F2> encodes additional bits for the Slave device serial number. *
1302         * bits 7-4 always 0000b * bit 3 Bit 31 of Slave Device Serial Number *
1303         * bit 2 Bit 23 of Slave Device Serial Number * bit 1 Bit 15 of Slave
1304         * Device Serial Number * bit 0 Bit 7 of Slave Device Serial Number
1305         * <p>
1306         * <DI_Ss0> encodes 7 bits of the 32 bit Slave device serial number: *
1307         * bit 7 always 0 * bits 6-3 Bits 6-0 of Slave device serial number
1308         * <p>
1309         * <DI_Ss1> encodes 7 bits of the 32 bit Slave device serial number: *
1310         * bit 7 always 0 * bits 6-3 Bits 14-9 of Slave device serial number
1311         * <p>
1312         * <DI_Ss2> encodes 7 bits of the 32 bit Slave device serial number: *
1313         * bit 7 always 0 * bits 6-3 Bits 22-16 of Slave device serial number
1314         * <p>
1315         * <DI_Ss3> encodes 7 bits of the 32 bit Slave device serial number: *
1316         * bit 7 always 0 * bits 6-3 Bits 30-24 of Slave device serial number *
1317         * * Information reverse-engineered by B. Milhaupt and used with
1318         * permission *
1319         * **********************************************************************************
1320         */
1321        // Request for one specific IPL-queryable device to return its identity information.
1322        // Expected response is of type <E5><14><10>...
1323        //
1324        // Note that standard definitions are provided for RF24, even though these
1325        // devices do not generate this report.
1326        //
1327        // Information reverse-engineered by B. Milhaupt and used with permission
1328        String hostType = getDeviceNameFromIPLInfo(l.getElement(4), l.getElement(5));
1329
1330        String hostVer = ((l.getElement(8) & 0x78) >> 3) + "." + ((l.getElement(8) & 0x7));
1331
1332        int hostSnInt = ((l.getElement(13) + (((l.getElement(9) & 0x8) == 8) ? 128 : 0)) * 256 * 256)
1333                + ((l.getElement(12) + (((l.getElement(9) & 0x4) == 4) ? 128 : 0)) * 256)
1334                + (l.getElement(11) + (((l.getElement(9) & 0x2) == 2) ? 128 : 0));
1335        String hostSN = Integer.toHexString(hostSnInt).toUpperCase();
1336        String hostInfo = Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_HOST_DETAILS",
1337                hostType, hostSN, hostVer);
1338
1339        String slaveType = getSlaveNameFromIPLInfo(l.getElement(4), l.getElement(6));
1340        String slaveInfo;
1341        if (l.getElement(6) != 0) {
1342            String slaveVer = (((l.getElement(10) & 0x78) >> 3) + ((l.getElement(9) & 1) << 4)) + "." + ((l.getElement(10) & 0x7));
1343            int slaveSnInt
1344                    = ((l.getElement(15) + (((l.getElement(14) & 0x1) == 1) ? 128 : 0)))
1345                    + ((l.getElement(16) + (((l.getElement(14) & 0x2) == 2) ? 128 : 0)) * 256)
1346                    + ((l.getElement(17) + (((l.getElement(14) & 0x4) == 4) ? 128 : 0)) * 256 * 256)
1347                    + ((l.getElement(18) + (((l.getElement(14) & 0x8) == 8) ? 128 : 0)) * 256 * 256 * 256);
1348            slaveInfo = Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_SLAVE_DETAILS", slaveType,
1349                    Integer.toHexString(slaveSnInt).toUpperCase(),
1350                    slaveVer);
1351        } else {
1352            slaveInfo = Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_SLAVE_NO_SLAVE");
1353        }
1354        return Bundle.getMessage("LN_MSG_IPL_DEVICE_IDENTITY_REPORT",
1355                hostInfo,
1356                slaveInfo);
1357    }
1358
1359    private static String interpretOpcPeerXfer16(LocoNetMessage l) {
1360        /*
1361         * SRC=7F is THROTTLE msg xfer
1362         *  ; <DSTL><DSTH> encode ID#,
1363         *  ; <0><0> is THROT B'CAST
1364         *  ; <PXCT1>=<0,XC2,XC1,XC0 - D4.7,D3.7,D2.7,D1.7>
1365         *  ; XC0-XC2=ADR type CODE-0=7 bit Peer
1366         * TO Peer adrs *
1367         *  ; 1=<D1>is SRC HI,<D2>is DST HI
1368         *  ; <PXCT2>=<0,XC5,XC4,XC3 - D8.7,D7.7,D6.7,D5.7>
1369         *  ; XC3-XC5=data type CODE- 0=ANSI TEXT string,
1370         *  ; balance RESERVED *
1371         * ****************************************************
1372         * SV programming format 1
1373         *
1374         * This is the message format as implemented by the certain
1375         * existing devices. New designs should not use this format. The
1376         * message bytes are assigned as follows:
1377         *   ; <0xE5> <0x10> <SRC> <DST> <0x01> <PXCT1>
1378         *   ; <D1> <D2> <D3> <D4> <PXCT2>
1379         *   ; <D5> <D6> <D7> <D8> <CHK>
1380         *
1381         * The upper nibble of PXCT1 must be 0,
1382         * and the upper nibble of PXCT2 must be 1. The meanings of the
1383         * remaining bytes are as defined in the LocoNet Personal
1384         * Edition specification.
1385         * *********************************************
1386         * SV programming format 2
1387         *
1388         * This is the recommended format for new designs.
1389         * The message bytes as assigned as follows: *
1390         *  ; <0xE5> <0x10> <SRC> <SV_CMD> <SV_TYPE> <SVX1>
1391         *  ; <DST_L> <DST_H> <SV_ADRL> <SV_ADRH> <SVX2>
1392         *  ; <D1> <D2> <D3> <D4> <CHK>
1393         *
1394         * The upper nibble of both SVX1 (PXCT1) and SVX2 (PXCT2) must be 1.
1395         */
1396
1397        int src = l.getElement(2); // source of transfer
1398        int dst_l = l.getElement(3); // ls 7 bits of destination
1399        int dst_h = l.getElement(4); // ms 7 bits of destination
1400        int pxct1 = l.getElement(5);
1401        int pxct2 = l.getElement(10);
1402
1403        int d[] = l.getPeerXfrData();
1404
1405        if ((src == 0x7F) && (dst_l == 0x7F) && (dst_h == 0x7F)
1406                && ((pxct1 & 0x70) == 0x40)) {
1407            // Download (firmware?) messages.
1408            int sub = pxct2 & 0x70;
1409            switch (sub) {
1410                case 0x00: // setup
1411                    return Bundle.getMessage("LN_MSG_IPL_SETUP",
1412                            l.getElement(6),
1413                            l.getElement(8),
1414                            l.getElement(9),
1415                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
1416                                    StringUtil.twoHexFromInt(l.getElement(7))),
1417                            l.getElement(11));
1418                case 0x10: // set address
1419                    return Bundle.getMessage("LN_MSG_IPL_SET_ADDRESS",
1420                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
1421                                    StringUtil.twoHexFromInt(d[0])
1422                                    + StringUtil.twoHexFromInt(d[1])
1423                                    + StringUtil.twoHexFromInt(d[2])));
1424                case 0x20: // send data
1425                case 0x30: // verify
1426                    return Bundle.getMessage((sub == 0x20) ? "LN_MSG_IPL_SEND_DATA" : "LN_MSG_IPL_VERIFY_REQUEST",
1427                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION", StringUtil.twoHexFromInt(d[0])),
1428                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION", StringUtil.twoHexFromInt(d[1])),
1429                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION", StringUtil.twoHexFromInt(d[2])),
1430                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION", StringUtil.twoHexFromInt(d[3])),
1431                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION", StringUtil.twoHexFromInt(d[4])),
1432                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION", StringUtil.twoHexFromInt(d[5])),
1433                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION", StringUtil.twoHexFromInt(d[6])),
1434                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION", StringUtil.twoHexFromInt(d[7])));
1435                case 0x40: // end op
1436                    return Bundle.getMessage("LN_MSG_IPL_END");
1437                default: // everything else isn't understood, go to default
1438                    break;
1439            }
1440        }
1441
1442        if ((src == 0x7F) && (dst_l == 0x0) && (dst_h == 0x0)
1443                && ((pxct1 & 0x3) == 0x00) && ((pxct2 & 0x70) == 0x70)) {
1444            // throttle semaphore symbol message
1445            return Bundle.getMessage("LN_MSG_THROTTLE_SEMAPHORE",
1446                    ((d[0] * 128) + d[1]),
1447                    Bundle.getMessage(((d[2] & 0x10) == 0x10)
1448                            ? "LN_MSG_THROTTLE_SEMAPHORE_HELPER_LIT"
1449                            : "LN_MSG_THROTTLE_SEMAPHORE_HELPER_UNLIT"),
1450                    Bundle.getMessage(((d[2] & 0x08) == 0x08)
1451                            ? "LN_MSG_THROTTLE_SEMAPHORE_HELPER_LIT"
1452                            : "LN_MSG_THROTTLE_SEMAPHORE_HELPER_UNLIT"),
1453                    Bundle.getMessage(((d[2] & 0x04) == 0x04)
1454                            ? "LN_MSG_THROTTLE_SEMAPHORE_HELPER_LIT"
1455                            : "LN_MSG_THROTTLE_SEMAPHORE_HELPER_UNLIT"),
1456                    Bundle.getMessage(((d[2] & 0x02) == 0x02)
1457                            ? "LN_MSG_THROTTLE_SEMAPHORE_HELPER_LIT"
1458                            : "LN_MSG_THROTTLE_SEMAPHORE_HELPER_UNLIT"),
1459                    Bundle.getMessage(((d[2] & 0x01) == 0x01)
1460                            ? "LN_MSG_THROTTLE_SEMAPHORE_HELPER_BLINKING"
1461                            : "LN_MSG_THROTTLE_SEMAPHORE_HELPER_UNBLINKING")
1462            );
1463        }
1464
1465        if ((src == 0x7F) && ((pxct1 & 0x70) == 0x00)) {
1466
1467            if ((dst_l == 0x00) && (dst_h == 0x00)) {
1468                char c[] = new char[]{0, 0, 0, 0, 0, 0, 0, 0};
1469                c[0] = (char) d[0];
1470                c[1] = (char) d[1];
1471                c[2] = (char) d[2];
1472                c[3] = (char) d[3];
1473                c[4] = (char) d[4];
1474                c[5] = (char) d[5];
1475                c[6] = (char) d[6];
1476                c[7] = (char) d[7];
1477                return Bundle.getMessage("LN_MSG_THROTTLE_TEXT_MESSAGE_ALL_THROTTLES",
1478                        c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]);
1479            } else {
1480                return Bundle.getMessage("LN_MSG_THROTTLE_TEXT_MESSAGE_SPECIFIC_THROTTLE",
1481                        (char) d[0], (char) d[1],
1482                        (char) d[2], (char) d[3],
1483                        (char) d[4], (char) d[5],
1484                        (char) d[6], (char) d[7],
1485                        convertToMixed(dst_l, dst_h));
1486            }
1487        }
1488
1489        String result = interpretSV1Message(l);
1490        if (result.length() > 0) {
1491            return result;
1492        }
1493
1494        result = interpretSV0Message(l);
1495        if (result.length() > 0) {
1496            return result;
1497        }
1498
1499        // check for a specific type - SV Programming messages format 2
1500        result = interpretSV2Message(l);
1501        if (result.length() > 0) {
1502            return result;
1503        }
1504
1505        return "";
1506    }
1507
1508    private static String interpretOpcPeerXfer15(LocoNetMessage l) {
1509        /*
1510         * see interpretOpcImm15
1511         * and jmri.jmrix.loconet.uhlenbrock.LncvMessageContents.java
1512         */
1513
1514        // check for a specific type - Uhlenbrock LNSV Programming messages format
1515        String result = interpretLncvMessage(l);
1516        if (result.length() > 0) {
1517            return result;
1518        }
1519
1520        return "";
1521    }
1522
1523    private static String interpretSV1Message(LocoNetMessage l) {
1524        int d[] = l.getPeerXfrData();
1525        if ((l.getElement(4) != 1)
1526                || ((l.getElement(5) & 0x70) != 0)
1527                || ((l.getElement(10) & 0x70) != 0x10)) {
1528            // is not an SV1 message
1529            return "";
1530        }
1531        if (l.getElement(2) == 0x50) {
1532            // Packets from the LocoBuffer
1533            String dst_subaddrx = (l.getElement(4) != 0x01 ? "" : ((d[4] != 0) ? "/" + Integer.toHexString(d[4]) : ""));
1534            // LocoBuffer to LocoIO
1535            return "LocoBuffer => LocoIO@"
1536                    + ((l.getElement(3) == 0) ? "broadcast" : Integer.toHexString(l.getElement(3)) + dst_subaddrx)
1537                    + " "
1538                    + (d[0] == 2 ? "Query SV" + d[1] : "Write SV" + d[1] + "=0x" + Integer.toHexString(d[3]))
1539                    + ((d[2] != 0) ? " Firmware rev " + dotme(d[2]) : "") + ".\n";
1540        }
1541        return "";
1542    }
1543
1544    private static String interpretSV0Message(LocoNetMessage l) {
1545        int dst_h = l.getElement(4);
1546        int pxct1 = l.getElement(5);
1547        int pxct2 = l.getElement(10);
1548        if ((dst_h != 0x01) || ((pxct1 & 0xF0) != 0x00)
1549                || ((pxct2 & 0xF0) != 0x00)) {
1550            return "";
1551        }
1552
1553        // (Jabour/Deloof LocoIO), SV Programming messages format 1
1554        int dst_l = l.getElement(3);
1555        int d[] = l.getPeerXfrData();
1556        int src = l.getElement(2);
1557
1558        String src_subaddrx = ((d[4] != 0) ? "/" + Integer.toHexString(d[4]) : "");
1559        String dst_subaddrx = ((d[4] != 0) ? "/" + Integer.toHexString(d[4]) : "");
1560
1561        String src_dev = ((src == 0x50) ? "Locobuffer" : "LocoIO@" + "0x" + Integer.toHexString(src) + src_subaddrx);
1562        String dst_dev = ((dst_l == 0x50) ? "LocoBuffer "
1563                : ((dst_l == 0x0) ? "broadcast"
1564                        : "LocoIO@0x" + Integer.toHexString(dst_l) + dst_subaddrx));
1565        String operation = (src == 0x50)
1566                ? ((d[0] == 2) ? "Query" : "Write")
1567                : ((d[0] == 2) ? "Report" : "Write");
1568
1569        return src_dev + "=> " + dst_dev + " "
1570                + operation + " SV" + d[1]
1571                + ((src == 0x50) ? (d[0] != 2 ? ("=0x" + Integer.toHexString(d[3])) : "")
1572                        : " = " + ((d[0] == 2) ? ((d[2] != 0) ? (d[5] < 10) ? "" + d[5]
1573                                                : d[5] + " (0x" + Integer.toHexString(d[5]) + ")"
1574                                        : (d[7] < 10) ? "" + d[7]
1575                                                : d[7] + " (0x" + Integer.toHexString(d[7]) + ")")
1576                                : (d[7] < 10) ? "" + d[7]
1577                                        : d[7] + " (0x" + Integer.toHexString(d[7]) + ")"))
1578                + ((d[2] != 0) ? " Firmware rev " + dotme(d[2]) : "") + ".\n";
1579    }
1580
1581    private static String interpretSV2Message(LocoNetMessage l) {
1582        // (New Designs)
1583        String svReply = "";
1584        LnSv2MessageContents svmc = null;
1585        try {
1586            // assume the message is an SV2 message
1587            svmc = new LnSv2MessageContents(l);
1588        } catch (IllegalArgumentException e) {
1589            // message is not an SV2 message.  Ignore the exception.
1590        }
1591        if (svmc != null) {
1592            // the message was indeed an SV2 message
1593            try {
1594                // get string representation of the message from an
1595                // available translation which is best suited to
1596                // the currently-active "locale"
1597                svReply = svmc.toString();
1598            } catch (IllegalArgumentException e) {
1599                // message is not a properly-formatted SV2 message.  Ignore the exception.
1600            }
1601        }
1602        return svReply;
1603    }
1604
1605    private static String interpretLncvMessage(LocoNetMessage l) {
1606        String lncvReply = "";
1607        LncvMessageContents cvmc = null;
1608        try {
1609            // assume the message is an LNCV message
1610            cvmc = new LncvMessageContents(l);
1611        } catch (IllegalArgumentException e) {
1612            // message is not an LNCV message.  Ignore the exception.
1613        }
1614        if (cvmc != null) {
1615            // the message was indeed an LNCV message
1616            try {
1617                // get string representation of the message from an
1618                // available translation which is best suited to
1619                // the currently-active "locale"
1620                lncvReply = cvmc.toString();
1621            } catch (IllegalArgumentException e) {
1622                // message is not a properly-formatted LNCV message.  Ignore the exception.
1623            }
1624        }
1625        return lncvReply;
1626    }
1627
1628    private static String interpretOpcPeerXfer10(LocoNetMessage l) {
1629        // throttle status
1630        int tcntrl = l.getElement(2);
1631        String stat;
1632        switch (tcntrl) {
1633            case 0x40:
1634                stat = Bundle.getMessage("LN_MSG_THROTTLE_STATUS_HELPER_OK");
1635                break;
1636            case 0x7F:
1637                stat = Bundle.getMessage("LN_MSG_THROTTLE_STATUS_HELPER_NO_KEYPRESS");
1638                break;
1639            case 0x43:
1640                stat = Bundle.getMessage("LN_MSG_THROTTLE_STATUS_HELPER_PLUS_KEY");
1641                break;
1642            case 0x42:
1643                stat = Bundle.getMessage("LN_MSG_THROTTLE_STATUS_HELPER_MINUS_KEY");
1644                break;
1645            case 0x41:
1646                stat = Bundle.getMessage("LN_MSG_THROTTLE_STATUS_HELPER_RUNSTOP_KEY");
1647                break;
1648            case 0x4e:
1649                stat = Bundle.getMessage("LN_MSG_THROTTLE_STATUS_HELPER_RESP_SEM_DISP_CMD");
1650                break;
1651            default:
1652                stat = Bundle.getMessage("LN_MSG_THROTTLE_STATUS_HELPER_UNKONWN");
1653                break;
1654        }
1655
1656        return Bundle.getMessage("LN_MSG_THROTTLE_STATUS",
1657                StringUtil.twoHexFromInt(tcntrl),
1658                stat,
1659                idString(l.getElement(3), l.getElement(4)),
1660                Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
1661                        StringUtil.twoHexFromInt(l.getElement(7))),
1662                Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
1663                        StringUtil.twoHexFromInt(l.getElement(8))));
1664    }
1665
1666    private static String interpretOpcPeerXfer9(LocoNetMessage l, String reporterPrefix) {
1667        /*
1668         * Transponding "find" query and report messages.
1669         * Information reverse-engineered by B. Milhaupt and used with permission */
1670        switch (l.getElement(2)) {
1671            case 0x40: {
1672                /**
1673                 * **********************************************************************************
1674                 * Transponding "find" query message * The message bytes are
1675                 * assigned as follows:
1676                 * <p>
1677                 * <0xE5> <0x09> <0x40> <AD_H> <AD_L> <0x00>
1678                 * <0x00> <0x00> <CHK> * where:
1679                 * <p>
1680                 * <AD_H> is encoded as shown below: * When
1681                 * <AD_H> = 0x7D, * Address is a 7 bit value defined solely by
1682                 * <AD_L>. * When <AD_H> is not 0x7D, * Address is a 14 bit
1683                 * value; AD_H{6:0} represent the upper 7 bits * of the 14 bit
1684                 * address.
1685                 * <p>
1686                 * <AD_L> contains the least significant 7 bits of the 14 or 7
1687                 * bit address. * * Information reverse-engineered by B.
1688                 * Milhaupt and used with permission *
1689                 * **********************************************************************************
1690                 */
1691                String locoAddr = convertToMixed(l.getElement(4), l.getElement(3));
1692                return Bundle.getMessage("LN_MSG_TRANSP_FIND_QUERY",
1693                        locoAddr);
1694            }
1695            case 0x00: {
1696                /**
1697                 * **********************************************************************************
1698                 * Transponding "find" report message * The message bytes are
1699                 * assigned as follows:
1700                 * <p>
1701                 * <0xE5> <0x09> <0x00> <AD_H> <AD_L> <TR_ST>
1702                 * <TR_ZS> <0x00> <CHK> * where:
1703                 * <p>
1704                 * <AD_H> is encoded as shown below: * When
1705                 * <AD_H> = 0x7D, * Address is a 7 bit value defined solely by
1706                 * <AD_L>. * When <AD_H> is not 0x7D, * Address is a 14 bit
1707                 * value; AD_H{6:0} represent the upper 7 bits * of the 14 bit
1708                 * address.
1709                 * <p>
1710                 * <AD_L> contains the least significant 7 bits of the 14 or 7
1711                 * bit address.
1712                 * <p>
1713                 * <TR_ST> contains the transponding status for the addressed
1714                 * equipment, * encoded as: * bits 7-6 always 00b * bit 5
1715                 * encodes transponding presence * 0 = Addressed equipment is
1716                 * absent * 1 = Addressed equipment is present * bits 4-0 encode
1717                 * bits 7-3 of the Detection Section
1718                 * <p>
1719                 * <TR_ZS> contains the zone number and detection section,
1720                 * encoded as: * bit 7 always 0 * bits 6-4 encode bits 2-0 of
1721                 * the Detection Section * bits 3-1 encode the Transponding Zone
1722                 * as shown below * 000b Zone A * 001b Zone B * 010b Zone C *
1723                 * 011b Zone D * 100b Zone E * 101b Zone F * 110b Zone G * 111b
1724                 * Zone H * bit 0 always 0 * * Information reverse-engineered by
1725                 * B. Milhaupt and used with permission *
1726                 * **********************************************************************************
1727                 */
1728
1729                int section = ((l.getElement(5) & 0x1F) << 3) + ((l.getElement(6) & 0x70) >> 4) + 1;
1730                String zone;
1731                String locoAddr = convertToMixed(l.getElement(4), l.getElement(3));
1732
1733                switch (l.getElement(6) & 0x0F) {
1734                    case 0x00:
1735                        zone = "A";
1736                        break;
1737                    case 0x02:
1738                        zone = "B";
1739                        break;
1740                    case 0x04:
1741                        zone = "C";
1742                        break;
1743                    case 0x06:
1744                        zone = "D";
1745                        break;
1746                    case 0x08:
1747                        zone = "E";
1748                        break;
1749                    case 0x0A:
1750                        zone = "F";
1751                        break;
1752                    case 0x0C:
1753                        zone = "G";
1754                        break;
1755                    case 0x0E:
1756                        zone = "H";
1757                        break;
1758                    default:
1759                        zone = Bundle.getMessage("LN_MSG_TRANSP_HELPER_UNKNOWN_ZONE",
1760                                l.getElement(6) & 0x0F);
1761                        break;
1762                }
1763
1764                // get system and user names
1765                String reporterSystemName = reporterPrefix
1766                        + ((l.getElement(5) & 0x1F) * 128 + l.getElement(6) + 1);
1767
1768                Reporter reporter = InstanceManager.getDefault(ReporterManager.class).getReporter(reporterSystemName);
1769
1770                String uname = "";
1771                if (reporter != null) {
1772                    uname = reporter.getUserName();
1773                }
1774
1775                if ((uname != null) && (!uname.isEmpty())) {
1776                    return Bundle.getMessage("LN_MSG_TRANSP_REPORT_KNOWN_REPORTER_USERNAME",
1777                            locoAddr,
1778                            reporterSystemName,
1779                            uname,
1780                            section,
1781                            zone);
1782                }
1783                return Bundle.getMessage("LN_MSG_TRANSP_REPORT_KNOWN_REPORTER_UNKNOWN_USERNAME",
1784                        locoAddr,
1785                        reporterSystemName,
1786                        section,
1787                        zone);
1788            }
1789            default: {
1790                break;
1791            }
1792        }
1793        return "";
1794    }
1795
1796    private static String interpretOpcPeerXfer7(LocoNetMessage l) {
1797        // This might be Uhlenbrock IB-COM start/stop programming track
1798        if (l.getElement(2) == 0x01 && l.getElement(3) == 0x49 && l.getElement(4) == 0x42) {
1799            switch (l.getElement(5)) {
1800                case 0x40: {
1801                    return Bundle.getMessage("LN_MSG_UHLENBROCK_STOP_PROGRAMMING_TRACK");
1802                }
1803                case 0x41: {
1804                    return Bundle.getMessage("LN_MSG_UHLENBROCK_START_PROGRAMMING_TRACK");
1805                }
1806                default:
1807                    break;
1808            }
1809        }
1810        return "";
1811    }
1812
1813    private static String interpretOpcPeerXfer(LocoNetMessage l, String reporterPrefix) {
1814        String result = "";
1815        // The first byte seems to determine the type of message.
1816        switch (l.getElement(1)) {
1817            case 0x10: { //l.getZElement(1)
1818                result = interpretOpcPeerXfer16(l);
1819                if (result.length() > 0) {
1820                    return result;
1821                }
1822                break;
1823            }
1824            case 0x0F: {
1825                result = interpretOpcPeerXfer15(l);
1826                if (result.length() > 0) {
1827                    return result;
1828                }
1829                break;
1830            }
1831            case 0x0A: {
1832                result = interpretOpcPeerXfer10(l);
1833                if (result.length() > 0) {
1834                    return result;
1835                }
1836                break;
1837
1838            }
1839            case 0x14: {
1840                result = interpretOpcPeerXfer20(l);
1841                if (result.length() > 0) {
1842                    return result;
1843                }
1844                break;
1845            }
1846            case 0x09: { // l.getZElement(1)
1847                result = interpretOpcPeerXfer9(l, reporterPrefix);
1848                if (result.length() > 0) {
1849                    return result;
1850                }
1851                break;
1852            }
1853            case 0x07: {
1854                result = interpretOpcPeerXfer7(l);
1855                if (result.length() > 0) {
1856                    return result;
1857                }
1858                break;
1859            }
1860            default: {
1861                break;
1862            }
1863        }
1864        return "";
1865
1866    }
1867
1868    private static String interpretLongAck(LocoNetMessage l) {
1869        int opcode = l.getElement(1);
1870        int ack1 = l.getElement(2);
1871
1872        switch (opcode | 0x80) {
1873            case (LnConstants.OPC_LOCO_ADR):
1874                // response for OPC_LOCO_ADR
1875                return Bundle.getMessage("LN_MSG_LONG_ACK_LOCO_ADR");
1876
1877            case (LnConstants.OPC_LINK_SLOTS):
1878                // response for OPC_LINK_SLOTS
1879                return Bundle.getMessage("LN_MSG_LONG_ACK_LINK_SLOTS");
1880
1881            case (LnConstants.OPC_SW_ACK):
1882                // response for OPC_SW_ACK
1883                switch (ack1) {
1884                    case 0:
1885                        return Bundle.getMessage("LN_MSG_LONG_ACK_SW_ACK_FULL");
1886                    case 0x7f:
1887                        return Bundle.getMessage("LN_MSG_LONG_ACK_SW_ACK_ACCEPT");
1888                    default:
1889                        return Bundle.getMessage("LN_MSG_LONG_ACK_SW_ACK_UNKNOWN",
1890                                Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
1891                                        StringUtil.twoHexFromInt(ack1)))+
1892                                        Bundle.getMessage("LN_MONITOR_MESSAGE_RAW_HEX_INFO", l.toString());
1893                }
1894            case (LnConstants.OPC_SW_REQ):
1895                // response for OPC_SW_REQ
1896                return Bundle.getMessage("LN_MSG_LONG_ACK_SW_REQ_FAIL");
1897
1898            case (LnConstants.OPC_WR_SL_DATA):
1899                // response for OPC_WR_SL_DATA
1900                switch (ack1) {
1901                    case 0:
1902                        return Bundle.getMessage("LN_MSG_LONG_ACK_WR_SL_FAIL");
1903                    case 0x01:
1904                        return Bundle.getMessage("LN_MSG_LONG_ACK_WR_SL_OK");
1905                    case 0x23:
1906                    case 0x2b:
1907                    case 0x6B:
1908                        return Bundle.getMessage("LN_MSG_LONG_ACK_WR_SL_PROG_DCS51_OK");
1909                    case 0x40:
1910                        return Bundle.getMessage("LN_MSG_LONG_ACK_WR_SL_BLIND");
1911                    case 0x7f:
1912                        return Bundle.getMessage("LN_MSG_LONG_ACK_WR_SL_NOT_IMPL");
1913                    default:
1914                        return Bundle.getMessage("LN_MSG_LONG_ACK_WR_SL_UNKNOWN",
1915                                Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
1916                                        StringUtil.twoHexFromInt(ack1)))+
1917                                Bundle.getMessage("LN_MONITOR_MESSAGE_RAW_HEX_INFO", l.toString());
1918
1919                }
1920
1921            case (LnConstants.OPC_SW_STATE):
1922                // response for OPC_SW_STATE
1923                return Bundle.getMessage("LN_MSG_LONG_ACK_SW_STATE",
1924                        Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
1925                                StringUtil.twoHexFromInt(ack1)),
1926                        Bundle.getMessage((((ack1 & 0x20) != 0)
1927                                ? "LN_MSG_SWITCH_STATE_CLOSED"
1928                                : "LN_MSG_SWITCH_STATE_THROWN")));
1929
1930            case (LnConstants.OPC_MOVE_SLOTS):
1931                // response for OPC_MOVE_SLOTS
1932                switch (ack1) {
1933                    case 0:
1934                        return Bundle.getMessage("LN_MSG_LONG_ACK_MOVE_SL_REJECT");
1935                    case 0x7f:
1936                        return Bundle.getMessage("LN_MSG_LONG_ACK_MOVE_SL_ACCEPT");
1937                    default:
1938                        return Bundle.getMessage("LN_MSG_LONG_ACK_MOVE_SL_UNKNOWN",
1939                                Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
1940                                        StringUtil.twoHexFromInt(ack1)))+
1941                                Bundle.getMessage("LN_MONITOR_MESSAGE_RAW_HEX_INFO", l.toString());
1942
1943                }
1944
1945            case LnConstants.OPC_IMM_PACKET:
1946                // response for OPC_IMM_PACKET
1947                if (ack1 == 0) {
1948                    return Bundle.getMessage("LN_MSG_LONG_ACK_OPC_IMM_REJECT");
1949                } else if (ack1 == 0x7f) {
1950                    return Bundle.getMessage("LN_MSG_LONG_ACK_OPC_IMM_ACCEPT");
1951                } else if (ack1 == 0x01) { // (l.getElement(1) == 0x6D) is same as case, same code for LNCV "unsupported CV"
1952                    return Bundle.getMessage("LN_MSG_LONG_ACK_OPC_IMM_UHL_PROG");
1953                } else if (ack1 == 0x02) { // LNCV Uhlenbrock programming reply
1954                    return Bundle.getMessage("LN_MSG_LONG_ACK_OPC_IMM_LNCV_READONLY");
1955                } else if (ack1 == 0x03) { // LNCV Uhlenbrock programming reply
1956                    return Bundle.getMessage("LN_MSG_LONG_ACK_OPC_IMM_LNCV_ILLEGALVAL");
1957                } else {
1958                    return Bundle.getMessage("LN_MSG_LONG_ACK_OPC_IMM_UNKNOWN",
1959                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
1960                                    StringUtil.twoHexFromInt(ack1)),
1961                                    128+ack1,
1962                                    StringUtil.twoHexFromInt(128+ack1)
1963                    )+
1964                                Bundle.getMessage("LN_MONITOR_MESSAGE_RAW_HEX_INFO", l.toString());
1965                }
1966
1967            case LnConstants.OPC_IMM_PACKET_2:
1968                // response for OPC_IMM_PACKET_2
1969                return Bundle.getMessage("LN_MSG_LONG_ACK_OPC_IMM_LIM_MASTER",
1970                        ack1, Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
1971                                StringUtil.twoHexFromInt(ack1)));
1972
1973            case (LnConstants.RE_LACK_SPEC_CASE1 | 0x80): // 0x50 plus opcode bit so can match the switch'd value:
1974            case (LnConstants.RE_LACK_SPEC_CASE2 | 0x80): //0x00 plus opcode bit so can match the switch'd value:
1975                // OpSwitch read response reverse-engineered by B. Milhaupt and
1976                // used with permission
1977                int responseValue = l.getElement(2);
1978                if (responseValue == 0x7f) {
1979                    return Bundle.getMessage("LN_MSG_LONG_ACK_SPEC_CASE1_2_ACCEPTED");
1980                } else {
1981                    return Bundle.getMessage("LN_MSG_LONG_ACK_SPEC_CASE1_2_REPORT",
1982                            (((responseValue & 0x20) == 0x20) ? 1 : 0),
1983                            (((responseValue & 0x20) == 0x20)
1984                                    ? Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_OPSW_HELPER_CLOSED")
1985                                    : Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_OPSW_HELPER_THROWN")));
1986                }
1987            case LnConstants.OPC_EXP_SEND_FUNCTION_OR_SPEED_AND_DIR:
1988                return Bundle.getMessage("LN_MSG_LONG_ACK_WRONG_THROTTLE_ID",
1989                        Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
1990                                    StringUtil.twoHexFromInt(l.getElement(2))));
1991                
1992            case LnConstants.OPC_ALM_READ:
1993                if (l.getElement(2) == 0) {
1994                    return Bundle.getMessage("LN_MSG_LONG_ACK_SLOT_NOT_SUPPORTED",
1995                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
1996                                    StringUtil.twoHexFromInt(opcode)));
1997                }
1998                //$FALL-THROUGH$
1999            default:
2000                return Bundle.getMessage("LN_MSG_LONG_ACK_NOT_KNOWN",
2001                        Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
2002                                StringUtil.twoHexFromInt(opcode | 0x80)),
2003                        Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
2004                                StringUtil.twoHexFromInt(l.getElement(2))));
2005        }
2006    }
2007
2008    private static String interpretPm4xPowerEvent(LocoNetMessage l) {
2009        int pCMD = (l.getElement(3) & 0xF0);
2010
2011        if ((pCMD == 0x30) || (pCMD == 0x10)) {
2012            // autoreverse
2013            int cm1 = l.getElement(3);
2014            int cm2 = l.getElement(4);
2015            String sect1Mode, sect1State;
2016            String sect2Mode, sect2State;
2017            String sect3Mode, sect3State;
2018            String sect4Mode, sect4State;
2019
2020            if ((cm1 & 1) != 0) {
2021                sect1Mode = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_AUTOREV");
2022                sect1State = ((cm2 & 1) != 0)
2023                        ? Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_REV")
2024                        : Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_NORM");
2025            } else {
2026                sect1Mode = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_PROTECT");
2027                sect1State = ((cm2 & 1) != 0)
2028                        ? Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_SHORT")
2029                        : Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_NONSHORT");
2030            }
2031
2032            if ((cm1 & 2) != 0) {
2033                sect2Mode = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_AUTOREV");
2034                sect2State = ((cm2 & 2) != 0)
2035                        ? Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_REV")
2036                        : Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_NORM");
2037            } else {
2038                sect2Mode = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_PROTECT");
2039                sect2State = ((cm2 & 2) != 0)
2040                        ? Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_SHORT")
2041                        : Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_NONSHORT");
2042            }
2043
2044            if ((cm1 & 4) != 0) {
2045                sect3Mode = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_AUTOREV");
2046                sect3State = ((cm2 & 4) != 0)
2047                        ? Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_REV")
2048                        : Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_NORM");
2049            } else {
2050                sect3Mode = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_PROTECT");
2051                sect3State = ((cm2 & 4) != 0)
2052                        ? Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_SHORT")
2053                        : Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_NONSHORT");
2054            }
2055
2056            if ((cm1 & 8) != 0) {
2057                sect4Mode = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_AUTOREV");
2058                sect4State = ((cm2 & 8) != 0)
2059                        ? Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_REV")
2060                        : Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_NORM");
2061            } else {
2062                sect4Mode = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_PROTECT");
2063                sect4State = ((cm2 & 8) != 0)
2064                        ? Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_SHORT")
2065                        : Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X_HELPER_MODE_NONSHORT");
2066            }
2067            return Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_PM4X",
2068                    (l.getElement(2) + 1) + ((l.getElement(1) & 0x1) << 7),
2069                    sect1Mode, sect1State, sect2Mode, sect2State,
2070                    sect3Mode, sect3State, sect4Mode, sect4State);
2071        }
2072        if ((pCMD == 0x20) ) { //BXP88
2073            int cm1 = l.getElement(3);
2074            int cm2 = l.getElement(4);
2075            ArrayList<Integer> sectsShorted = new ArrayList<>();
2076            ArrayList<Integer> sectsUnshorted = new ArrayList<>();
2077            if ((cm2 & 0x01) != 0) {
2078                sectsShorted.add(1);
2079            } else {
2080                sectsUnshorted.add(1);
2081            }
2082            if ((cm2 & 0x02) != 0) {
2083                sectsShorted.add(2);
2084            } else {
2085                sectsUnshorted.add(2);
2086            }
2087            if ((cm2 & 0x04) != 0) {
2088                sectsShorted.add(3);
2089            } else {
2090                sectsUnshorted.add(3);
2091            }
2092            if ((cm2 & 0x08) != 0) {
2093                sectsShorted.add(4);
2094            } else {
2095                sectsUnshorted.add(4);
2096            }
2097            if ((cm1 & 0x01) != 0) {
2098                sectsShorted.add(5);
2099            } else {
2100                sectsUnshorted.add(5);
2101            }
2102            if ((cm1 & 0x02) != 0) {
2103                sectsShorted.add(6);
2104            } else {
2105                sectsUnshorted.add(6);
2106            }
2107            if ((cm1 & 0x04) != 0) {
2108                sectsShorted.add(7);
2109            } else {
2110                sectsUnshorted.add(7);
2111            }
2112            if ((cm1 & 0x08) != 0) {
2113                sectsShorted.add(8);
2114            } else {
2115                sectsUnshorted.add(8);
2116            }
2117            return Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_BXP88",
2118                    (l.getElement(2) + 1) + ((l.getElement(1) & 0x1) << 7),
2119                    StringUtils.join(sectsShorted, ','), StringUtils.join(sectsUnshorted, ','));
2120        }
2121        if ( (pCMD == 0x50) || (pCMD == 0x40)) { //BXPA1
2122            int cm1 = l.getElement(3);
2123            String RevState = "";
2124            String BreakState = "";
2125            if ((cm1 & 0x10) != 0) { // reversing state
2126                if ((cm1 & 0x08) != 0) {
2127                    RevState = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_BXPA1_HELPER_MODE_REV");
2128                } else {
2129                    RevState = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_BXPA1_HELPER_MODE_NORM");
2130                }
2131            } else {
2132                // breaker state
2133                if ((cm1 & 0x08) != 0) {
2134                    BreakState = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_BXPA1_HELPER_MODE_SHORT");
2135                } else {
2136                    BreakState = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_BXPA1_HELPER_MODE_NONSHORT");
2137                }
2138            }
2139            int bxpa1_Id = ((l.getElement(2) << 3 ) + (l.getElement(3) & 0x07 ) + 1);
2140            // Due to a problem with the firmware messages from x and x+4 are identical
2141            return Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_POWER_BXPA1",
2142                    bxpa1_Id, bxpa1_Id +4,
2143                    RevState, BreakState);
2144        }
2145        return "";
2146    }
2147
2148    private static String interpretOpSws(LocoNetMessage l) {
2149        int pCMD = (l.getElement(3) & 0xF0);
2150        if (pCMD == 0x70) {
2151            // programming
2152            int deviceType = l.getElement(3) & 0x7;
2153            String device;
2154            switch (deviceType) {
2155                case LnConstants.RE_MULTI_SENSE_DEV_TYPE_PM4X:
2156                    device = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_DEV_RPT_HELPER_PM4X");
2157                    break;
2158                case LnConstants.RE_MULTI_SENSE_DEV_TYPE_BDL16X:
2159                    device = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_DEV_RPT_HELPER_BDL16X");
2160                    break;
2161                case LnConstants.RE_MULTI_SENSE_DEV_TYPE_SE8:
2162                    device = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_DEV_RPT_HELPER_SE8C");
2163                    break;
2164                case LnConstants.RE_MULTI_SENSE_DEV_TYPE_DS64:
2165                    device = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_DEV_RPT_HELPER_DS64");
2166                    break;
2167                default:
2168                    return "";
2169            }
2170
2171            int val = (l.getElement(4) & 0x01);
2172            int opsw = (l.getElement(4) & 0x7E) / 2 + 1;
2173            int bdaddr = l.getElement(2) + 1;
2174            if ((l.getElement(1) & 0x1) != 0) {
2175                bdaddr += 128;
2176            }
2177
2178            if ((deviceType == 0) && (bdaddr == 1) && (l.getElement(4) == 0)) {
2179                return Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_OPSW_ACCESS_QUERY_ALL");
2180            }
2181
2182            if ((l.getElement(1) & 0x10) != 0) {
2183                // write
2184                String valType = (val == 1)
2185                        ? Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_OPSW_HELPER_CLOSED")
2186                        : Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_OPSW_HELPER_THROWN");
2187                return Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_OPSW_WRITE_ACCESS",
2188                        device, bdaddr, opsw, val, valType);
2189            } else {
2190                // query
2191                return Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_OPSW_QUERY_ACCESS",
2192                        device, bdaddr, opsw);
2193            }
2194        }
2195        return "";
2196    }
2197
2198    private static String interpretDeviceType(LocoNetMessage l) {
2199        int pCMD = (l.getElement(3) & 0xF0);
2200        if (pCMD == 0x00) {
2201            /**
2202             * **************************************************
2203             * Device type report * The message bytes as assigned as follows:
2204             * <p>
2205             * <0xD0> <DQT_REQ> <DQT_BRD> <DQT_B3> <DQT_B4>
2206             * <CHK> * * where:
2207             * <p>
2208             * <DQT_REQ> contains the device query request, * encoded as: * bits
2209             * 7-4 always 0110b * bits 3-1 always 001b * bit 0 (BoardID-1)<7>
2210             * <p>
2211             * <DQT_BRD> contains most the device board ID number, * encoded as:
2212             * * bit 7 always 0b * bits 6-0 (BoardID-1)<6:0>
2213             * <p>
2214             * <DQT_B3> contains the board type identification, * encoded as: *
2215             * bits 7-4 always 0000b * bits 3-0 contain the encoded device type,
2216             * * encoded as: * 0000b PM4x device * 0001b BDL16x device * 0010b
2217             * SE8C device * 0011b DS64 device * others Unknown device type
2218             * <p>
2219             * <DQT_B4> contains device version number: * bit 7 always 0b * bits
2220             * 6-0 VersionNumber(6:0) * * Information reverse-engineered by B.
2221             * Milhaupt and used with permission *
2222             * **************************************************
2223             */
2224            // This message is a report which is sent by a LocoNet device
2225            // in response to a query of attached devices
2226            // Note - this scheme is supported by only some Digitrax devices.
2227            //
2228            // A VersionNumber of 0 implies the hardware does not report
2229            // a valid version number.
2230            //
2231            // Device type report reverse-engineered by B. Milhaupt and
2232            // used with permission
2233            int deviceType = l.getElement(3) & 0x7;
2234            String device = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_DEV_RPT_HELPER_UNKNOWN");
2235            switch (deviceType) {
2236                case LnConstants.RE_MULTI_SENSE_DEV_TYPE_PM4X:
2237                    device = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_DEV_RPT_HELPER_PM4X");
2238                    break;
2239                case LnConstants.RE_MULTI_SENSE_DEV_TYPE_BDL16X:
2240                    device = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_DEV_RPT_HELPER_BDL16X");
2241                    break;
2242                case LnConstants.RE_MULTI_SENSE_DEV_TYPE_SE8:
2243                    device = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_DEV_RPT_HELPER_SE8C");
2244                    break;
2245                case LnConstants.RE_MULTI_SENSE_DEV_TYPE_DS64:
2246                    device = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_DEV_RPT_HELPER_DS64");
2247                    break;
2248                default:
2249                    log.warn("Unhandled device type: {}", deviceType);
2250                    break;
2251            }
2252
2253            int bdaddr = l.getElement(2) + 1;
2254            if ((l.getElement(1) & 0x1) != 0) {
2255                bdaddr += 128;
2256            }
2257            String versionNumber = Integer.toString(l.getElement(4));
2258            if (l.getElement(4) == 0) {
2259                versionNumber = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_DEV_RPT_HELPER_VER_UNKNOWN");
2260            }
2261            return Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_OPSW_DEV_TYPE_RPT",
2262                    device, bdaddr, versionNumber);
2263        }
2264        return "";
2265    }
2266
2267    private static String interpretOpcMultiSense(LocoNetMessage l, String reporterPrefix) {
2268        int type = l.getElement(1) & LnConstants.OPC_MULTI_SENSE_MSG;
2269        switch (type) {
2270            case LnConstants.OPC_MULTI_SENSE_POWER:
2271                // This is a PM42 power event.
2272                String result = interpretPm4xPowerEvent(l);
2273                if (result.length() > 0) {
2274                    return result;
2275                }
2276                result = interpretOpSws(l);
2277                if (result.length() > 0) {
2278                    return result;
2279                }
2280                result = interpretDeviceType(l);
2281                if (result.length() > 0) {
2282                    return result;
2283                } else {
2284                    break;
2285                }
2286
2287            case LnConstants.OPC_MULTI_SENSE_PRESENT:
2288            case LnConstants.OPC_MULTI_SENSE_ABSENT:
2289                result = interpretOpcMultiSenseTranspPresence(l, reporterPrefix);
2290                if (result.length() > 0) {
2291                    return result;
2292                }
2293                break;
2294            case LnConstants.OPC_MULTI_SENSE_RAILCOM_AD:
2295                result = interpretOpcMultiSenseRailcomAD(l, reporterPrefix);
2296                if (result.length() > 0) {
2297                    return result;
2298                }
2299                break;
2300            default:
2301                break;
2302        }
2303        return "";
2304    }
2305
2306    private static String interpretOpcMultiSenseTranspPresence(LocoNetMessage l, String reporterPrefix) {
2307        // Transponding Event
2308        // get system and user names
2309        String reporterSystemName;
2310        String reporterUserName;
2311        String zone;
2312        int bxp88Zone = 1 + (l.getElement(2) & 0x07);
2313        switch (l.getElement(2) & 0x0f) { // ignore bit 0 which seems to provide some unknown info from the BXP88
2314            case 0x00:
2315                zone = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_TRANSP_ZONEA");
2316                break;
2317            case 0x02:
2318                zone = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_TRANSP_ZONEB");
2319                break;
2320            case 0x04:
2321                zone = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_TRANSP_ZONEC");
2322                break;
2323            case 0x06:
2324                zone = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_TRANSP_ZONED");
2325                break;
2326            case 0x08:
2327                zone = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_TRANSP_ZONEE");
2328                break;
2329            case 0x0A:
2330                zone = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_TRANSP_ZONEF");
2331                break;
2332            case 0x0C:
2333                zone = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_TRANSP_ZONEG");
2334                break;
2335            case 0x0E:
2336                zone = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_TRANSP_ZONEH");
2337                break;
2338            default:
2339                zone = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_TRANSP_ZONE_UNKNOWN",
2340                        (l.getElement(2) & 0x0F));
2341                break;
2342        }
2343        int type = l.getElement(1) & LnConstants.OPC_MULTI_SENSE_MSG;
2344
2345        reporterSystemName = reporterPrefix
2346                + ((l.getElement(1) & 0x1F) * 128 + l.getElement(2) + 1);
2347
2348        Reporter reporter = InstanceManager.getDefault(ReporterManager.class).getReporter(reporterSystemName);
2349        reporterUserName = "";
2350        if (reporter != null) {
2351            String uname = reporter.getUserName();
2352            if ((uname != null) && (!uname.isEmpty())) {
2353                reporterUserName = uname;
2354            }
2355        }
2356        int bxpa1Number = 1 + l.getElement(2) + (l.getElement(1) & 0x1F) * 128;
2357        int bxp88Number = 1 + (l.getElement(2)/8) + (l.getElement(1) & 0x1F) * 16;
2358        int section = 1 + (l.getElement(2) / 16) + (l.getElement(1) & 0x1F) * 8;
2359
2360        String locoAddr = convertToMixed(l.getElement(4), l.getElement(3));
2361        String transpActivity = (type == LnConstants.OPC_MULTI_SENSE_PRESENT)
2362                ? Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_TRANSP_HELPER_IS_PRESENT")
2363                : Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_TRANSP_HELPER_IS_ABSENT");
2364
2365        if ((l.getElement(2) & 0x1) == 0) {
2366            return Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_TRANSP_REPORT_WITH_BXP88",
2367                    locoAddr, transpActivity, reporterSystemName,
2368                    reporterUserName, section, zone, bxp88Number, bxp88Zone, bxpa1Number);
2369        } else {
2370            return Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_TRANSP_REPORT_NOT_BDL16X",
2371                    locoAddr, transpActivity, reporterSystemName,
2372                    reporterUserName, bxp88Number, bxp88Zone, bxpa1Number);
2373        }
2374    }
2375
2376    private static String convertRailComAD(int indexValue, int dynamicValue) {
2377        /**
2378         ***************************************************
2379         * RailCom App DYN (ID 7) message
2380         * indexValue = 6 bit value value per standard
2381         * dynamicValue = 8 bit value per standard
2382         **/
2383
2384        String indexString = "";
2385        switch (indexValue) {
2386            case 0: //Speed
2387                indexString = Bundle.getMessage("LN_MSG_RAILCOM_HELPER_INDEX_VALUE_0");
2388                break;
2389            case 7: //QoS
2390                indexString = Bundle.getMessage("LN_MSG_RAILCOM_HELPER_INDEX_VALUE_7");
2391                break;
2392            default:
2393                indexString = Bundle.getMessage("LN_MSG_RAILCOM_HELPER_INDEX_VALUE_UNKNOWN");
2394                break;
2395        }
2396
2397        return Bundle.getMessage("LN_MSG_RAILCOM_REPORT",
2398                indexValue, indexString, dynamicValue);
2399    }
2400
2401    private static String interpretOpcMultiSenseRailcomAD(LocoNetMessage l, String reporterPrefix) {
2402        /**
2403        ***************************************************
2404        * Multi Sense Standard RailCom App DYN message (Loconet OpCode 0xD0)
2405        * The message bytes as assigned as follows:
2406        *
2407        * <0xD0> <RC_I> <RCDV_L> <AD_H> <AD_L> <CHK>
2408        *
2409        * <RC_I> is encoded as shown below
2410        * bit 7 always 0
2411        * bits 6-5 always 10 (0x40)
2412        * bits 4-1 RailCom App:Dyn Index Value (4 bit value, but expected 6 bits per standard)
2413        * bit 0 RailCom Dynamic Value high bit
2414        *
2415        * <RCDV_L> RCDV_L{6:0} represent the upper 7 bits * of the 8 bit RailCom Dynamic Value.  The 8th bit is bit 0 of <RC_I>
2416        *
2417        * <AD_H> is encoded as shown below: * When
2418        * <AD_H> = 0x7D, * Address is a 7 bit value defined solely by
2419        * <AD_L>. * When <AD_H> is not 0x7D, * Address is a 14 bit
2420        * value; AD_H{6:0} represent the upper 7 bits * of the 14 bit
2421        * address.
2422        *
2423        * Information reverse-engineered by Michael Ricahrdson
2424        **/
2425
2426        String locoAddr = convertToMixed(l.getElement(4), l.getElement(3));
2427        int indexValue = (l.getElement(1) & 0x1E)/2; //bits 4-1
2428        int dynamicValue = l.getElement(2) + (l.getElement(1) & 0x01) * 128;
2429
2430        String railcomAdString = convertRailComAD(indexValue, dynamicValue);
2431
2432        return Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_TRANSP_RAILCOM_REPORT",
2433                locoAddr, railcomAdString);
2434    }
2435
2436    private static String interpretOpcMultiSenseLong(LocoNetMessage l, String reporterPrefix) {
2437         /***************************************************
2438         * Multi Sense Long RailCom App DYN message (Loconet OpCode 0xE0)
2439         * The message bytes as assigned as follows:
2440         *
2441         * <0xE0> <0x09> <MSL_I> <BLK_L> <AD_H> <AD_L> <RCDV_H> <RCDV_L> <CHK>
2442         *
2443         * <0xEO> OpCode
2444         * <Ox09> Message Length
2445         * <MSL_I> is encoded as shown below
2446         *  bit 7 always 0
2447         *  bits 6-5 (00 = Absent, 01 = Present, 10 = Present with AppDyn Message, 11 = unknown)
2448         *  bit 4 ? - unverified - currently part of detection block number logic following Multi Sense Standard
2449         *  bits 0-3 block high
2450         *
2451         * <BLK_L> 11 bit number representing the detection block. Lower 7 bits plus 4 high bits from <MSL_I> {3:0}
2452         *
2453         * <AD_H> is encoded as shown below:
2454         *  When <AD_H> = 0x7D, Address is a 7 bit value defined solely by <AD_L>.
2455         *  When <AD_H> is not 0x7D, Address is a 14 bit value;
2456         *  AD_H{6:0} represent the upper 7 bits * of the 14 bit address.
2457         *
2458         * <RCDV_H> is encoded as shown below:
2459         *  bit 7 always 0
2460         *  bit 6 - Loco direction: 0 = East, 1 = West
2461         *  bits 5-1 RailCom App:Dyn Index Value (5 bit value, but expected 6 bits per standard)
2462         *  bit 0 RailCom Dynamic Value high bit
2463         *
2464         * <RCDV_L> {6:0} represent the lower 7 bits of the 8 bit RailCom Dynamic Value.  The 8th bit is bit 0 of <RCDV_H>
2465         *
2466         * <CHK>
2467         *
2468         * Information reverse-engineered by Michael Ricahrdson
2469         */
2470
2471        if (l.getElement(1) == 0x09){  // Only process 0xE0 0x09 messages
2472            // Transponding Event
2473            // get system and user names
2474
2475            int type = l.getElement(2) & LnConstants.OPC_MULTI_SENSE_MSG; //bits 5-4
2476            //0x00 = absent = 00
2477            //0x20 = present = 10
2478            //0x40 = present with App Dyn = 01
2479            //0x60 = unknown = 11
2480
2481            if (type == 0x60) { //unknown at this point
2482                return Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_LONG_UNKNOWN_MESSAGE");
2483            }
2484
2485            // Now Process 0x00, 0x20, and 0x40
2486            String reporterSystemName = reporterPrefix + ((l.getElement(2) & 0x1F) * 128 + l.getElement(3) + 1);
2487
2488            Reporter reporter = InstanceManager.getDefault(ReporterManager.class).getReporter(reporterSystemName);
2489            String reporterUserName = "";
2490            if (reporter != null) {
2491                String uname = reporter.getUserName();
2492                if ((uname != null) && (!uname.isEmpty())) {
2493                    reporterUserName = uname;
2494                }
2495            }
2496
2497            String locoAddr = convertToMixed(l.getElement(5), l.getElement(4));
2498
2499            String transpActivity = "";
2500
2501            String direction = ((l.getElement(6) & 0x40) == 0)
2502                    ? Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_LONG_LOCO_DIRECTION_HELPER_EAST")
2503                    : Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_LONG_LOCO_DIRECTION_HELPER_WEST");
2504
2505            switch (type) {
2506                case LnConstants.OPC_MULTI_SENSE_RAILCOM_AD:
2507                    int indexValue = (l.getElement(6) & 0x3E)/2; //bits 5-1
2508                    int dynamicValue = l.getElement(7) + (l.getElement(6) & 0x01) * 128;
2509                    transpActivity = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_TRANSP_HELPER_IS_PRESENT");
2510
2511                    String railcomAdString = convertRailComAD(indexValue, dynamicValue);
2512                    String multiSenseLongString = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_LONG_TRANSP_REPORT",
2513                        locoAddr, direction, transpActivity, reporterSystemName, reporterUserName);
2514
2515                    return Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_LONG_TRANSP_RAILCOM_REPORT",
2516                            multiSenseLongString, railcomAdString);
2517
2518                case LnConstants.OPC_MULTI_SENSE_PRESENT:
2519                    if ((l.getElement(6) & 0x3F) != 0 || l.getElement(7) != 0 ) {
2520                        // within current understanding values here are not expected
2521                        return Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_LONG_UNKNOWN_MESSAGE");
2522                    }
2523
2524                    transpActivity = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_TRANSP_HELPER_IS_PRESENT");
2525
2526                    return Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_LONG_TRANSP_REPORT",
2527                            locoAddr, direction, transpActivity, reporterSystemName, reporterUserName);
2528
2529                case LnConstants.OPC_MULTI_SENSE_ABSENT:
2530                    if ((l.getElement(6) & 0x3F) != 0 || l.getElement(7) != 0 )  {
2531                        // within current understanding values here are not expected
2532                        return Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_LONG_UNKNOWN_MESSAGE");
2533                    }
2534
2535                    transpActivity = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_TRANSP_HELPER_IS_ABSENT");
2536
2537                    return Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_LONG_TRANSP_REPORT",
2538                            locoAddr, direction, transpActivity, reporterSystemName, reporterUserName);
2539
2540                default:
2541                    return Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_LONG_UNKNOWN_MESSAGE");
2542            }
2543
2544        } else {
2545            return Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_LONG_UNKNOWN_MESSAGE");
2546        }
2547    }
2548
2549    private static String interpretOpcWrSlDataOpcSlRdData(LocoNetMessage l) {
2550        int slot = l.getElement(2); // slot number for this request
2551        String mode;
2552        int command = l.getOpCode();
2553        int id1 = l.getElement(11); // ls 7 bits of ID code
2554        int id2 = l.getElement(12); // ms 7 bits of ID code
2555        /*
2556         * These messages share a common data format with the only difference being
2557         * whether we are reading or writing the slot data.
2558         */
2559        if (command == LnConstants.OPC_WR_SL_DATA) {
2560            mode = Bundle.getMessage("LN_MSG_SLOT_HELPER_ACCESS_TYPE_REQUEST");
2561        } else {
2562            mode = Bundle.getMessage("LN_MSG_SLOT_HELPER_ACCESS_TYPE_RESPONSE");
2563        }
2564
2565        switch (slot) {
2566            case LnConstants.FC_SLOT:
2567                String result;
2568                result = interpretFastClockSlot(l, mode, id1, id2);
2569                if (result.length() > 0) {
2570                    return result;
2571                }
2572                break;
2573            case LnConstants.PRG_SLOT:
2574                result = interpretProgSlot(l, mode, id1, id2, command);
2575                if (result.length() > 0) {
2576                    return result;
2577                }
2578                break;
2579
2580            case 0x79:
2581            case 0x7a:
2582            case 0x7D:
2583                return "";
2584            case LnConstants.CFG_EXT_SLOT:
2585                result = interpretCmdStnExtCfgSlotRdWr(l, command);
2586                if (result.length() > 0) {
2587                    return result;
2588                }
2589                break;
2590
2591            // end programming track block
2592            case LnConstants.CFG_SLOT:
2593                result = interpretCmdStnCfgSlotRdWr(l, command);
2594                if (result.length() > 0) {
2595                    return result;
2596                }
2597                break;
2598
2599            default:
2600                result = interpretStandardSlotRdWr(l, id1, id2, command, slot);
2601                if (result.length() > 0) {
2602                    return result;
2603                }
2604                break;
2605        }
2606
2607        return "";
2608    }
2609
2610    private static String interpretOpcInputRep(LocoNetMessage l, String sensorPrefix) {
2611        int in1 = l.getElement(1);
2612        int in2 = l.getElement(2);
2613        int contactNum = ((SENSOR_ADR(in1, in2) - 1) * 2 + ((in2 & LnConstants.OPC_INPUT_REP_SW) != 0 ? 2 : 1));
2614        // get system and user names
2615        String sensorSystemName = sensorPrefix + contactNum;
2616        String sensorUserName = "";
2617        Sensor sensor = InstanceManager.getDefault(SensorManager.class).getSensor(sensorSystemName);
2618        sensorUserName = "";
2619        if (sensor != null) {
2620            String uname = sensor.getUserName();
2621            if ((uname != null) && (!uname.isEmpty())) {
2622                sensorUserName = " ("+uname+")";
2623            }
2624        }
2625
2626        int sensorid = (SENSOR_ADR(in1, in2) - 1) * 2
2627                + ((in2 & LnConstants.OPC_INPUT_REP_SW) != 0 ? 2 : 1);
2628
2629        int bdlid = ((sensorid - 1) / 16) + 1;
2630        int bdlin = ((sensorid - 1) % 16) + 1;
2631        String bdl = Bundle.getMessage("LN_MSG_OPC_INPUT_REP_BDL_INFO",
2632                bdlid, bdlin);
2633
2634        int boardid = ((sensorid - 1) / 8) + 1;
2635        int boardindex = ((sensorid - 1) % 8);
2636        String otherBoardsNames;
2637        String otherBoardsInputs;
2638        if (sensorid < 289) {
2639            otherBoardsNames = Bundle.getMessage("LN_MSG_OPC_INPUT_REP_ALL_EQUIV_BOARDS", boardid);
2640            otherBoardsInputs = Bundle.getMessage("LN_MSG_OPC_INPUT_REPORT_INPUT_NAMES_ALL_EQUIV_BOARDS",
2641                    ds54sensors[boardindex], ds64sensors[boardindex],
2642                    se8csensors[boardindex]);
2643        } else {
2644            otherBoardsNames = Bundle.getMessage("LN_MSG_OPC_INPUT_REP_NO_SE8C", boardid);
2645            otherBoardsInputs = Bundle.getMessage("LN_MSG_OPC_INPUT_REPORT_INPUT_NAMES_NO_SE8C",
2646                    ds54sensors[boardindex], ds64sensors[boardindex]);
2647        }
2648
2649        // There is no way to tell what kind of a board sent the message.
2650        // To be user friendly, we just print all the known combos.
2651        return Bundle.getMessage("LN_MSG_OPC_INPUT_REP",
2652                sensorSystemName, sensorUserName,
2653                Bundle.getMessage((in2 & LnConstants.OPC_INPUT_REP_HI) != 0
2654                        ? "LN_MSG_SENSOR_STATE_HIGH" : "LN_MSG_SENSOR_STATE_LOW"),
2655                bdl,
2656                otherBoardsNames, otherBoardsInputs);
2657    }
2658
2659    private static String interpretOpcSwRep(LocoNetMessage l, String turnoutPrefix) {
2660        int sn1 = l.getElement(1);
2661        int sn2 = l.getElement(2);
2662        // get system and user names
2663        String turnoutUserName = "";
2664
2665        String turnoutSystemName = turnoutPrefix
2666                + SENSOR_ADR(sn1, sn2);
2667        Turnout turnout = InstanceManager.getDefault(TurnoutManager.class).getTurnout(turnoutSystemName);
2668
2669        String uname = "";
2670        if (turnout != null) {
2671            uname = turnout.getUserName();
2672            if ((uname != null) && (!uname.isEmpty())) {
2673                turnoutUserName = uname;
2674            } else {
2675                turnoutUserName = "";
2676            }
2677        }
2678
2679        if ((sn2 & LnConstants.OPC_SW_REP_INPUTS) != 0) {
2680            return Bundle.getMessage("LN_MSG_OPC_SW_REP_INPUTS_STATE",
2681                    turnoutSystemName, turnoutUserName,
2682                    Bundle.getMessage(((sn2 & LnConstants.OPC_SW_REP_SW) != 0
2683                            ? "LN_MSG_SENSOR_SW_INPUT_TYPE_HI"
2684                            : "LN_MSG_SENSOR_SW_INPUT_TYPE_LO")),
2685                    Bundle.getMessage((((sn2 & LnConstants.OPC_SW_REP_HI) != 0)
2686                            ? "LN_MSG_SENSOR_SW_INPUT_STATE_HI"
2687                            : "LN_MSG_SENSOR_SW_INPUT_STATE_LO")));
2688        }
2689        return Bundle.getMessage("LN_MSG_OPC_SW_REP_OUTPUT_STATE",
2690                turnoutSystemName, turnoutUserName,
2691                Bundle.getMessage((((sn2 & LnConstants.OPC_SW_REP_CLOSED) != 0)
2692                        ? "LN_MSG_SENSOR_SW_OUTPUT_STATE_ON"
2693                        : "LN_MSG_SENSOR_SW_OUTPUT_STATE_OFF")),
2694                Bundle.getMessage((((sn2 & LnConstants.OPC_SW_REP_THROWN) != 0)
2695                        ? "LN_MSG_SENSOR_SW_OUTPUT_STATE_ON"
2696                        : "LN_MSG_SENSOR_SW_OUTPUT_STATE_OFF")));
2697    }
2698
2699    private static String interpretOpcSwAck(LocoNetMessage l, String turnoutPrefix) {
2700        int sw2 = l.getElement(2);
2701        if ((sw2 & 0x40) == 0x40) {
2702            return "";
2703        }
2704        // get system and user names
2705        String turnoutUserName = "";
2706
2707        String turnoutSystemName = turnoutPrefix
2708                + SENSOR_ADR(l.getElement(1), l.getElement(2));
2709        Turnout turnout = InstanceManager.getDefault(TurnoutManager.class).getTurnout(turnoutSystemName);
2710
2711        String uname = "";
2712        if (turnout != null) {
2713            uname = turnout.getUserName();
2714            if ((uname != null) && (!uname.isEmpty())) {
2715                turnoutUserName = uname;
2716            } else {
2717                turnoutUserName = "";
2718            }
2719        }
2720
2721        String pointsDirection = ((sw2 & LnConstants.OPC_SW_ACK_CLOSED) != 0
2722                ? Bundle.getMessage("LN_MSG_SW_POS_CLOSED")
2723                : Bundle.getMessage("LN_MSG_SW_POS_THROWN"));
2724        String outputState = (((sw2 & LnConstants.OPC_SW_ACK_OUTPUT) != 0)
2725                ? Bundle.getMessage("LN_MSG_SENSOR_SW_OUTPUT_STATE_ON")
2726                : Bundle.getMessage("LN_MSG_SENSOR_SW_OUTPUT_STATE_OFF"));
2727        return Bundle.getMessage("LN_MSG_REQ_SWITCH", turnoutSystemName,
2728                turnoutUserName, pointsDirection, outputState);
2729    }
2730
2731    private static String interpretOpcSwState(LocoNetMessage l, String turnoutPrefix) {
2732        // get system and user names
2733        if ((l.getElement(2) & 0x40) != 0x00) {
2734            return "";
2735        }
2736        String turnoutUserName = "";
2737        String turnoutSystemName = turnoutPrefix
2738                + SENSOR_ADR(l.getElement(1), l.getElement(2));
2739        Turnout turnout = InstanceManager.getDefault(TurnoutManager.class).getTurnout(turnoutSystemName);
2740
2741        String uname = "";
2742        if (turnout != null) {
2743            uname = turnout.getUserName();
2744            if ((uname != null) && (!uname.isEmpty())) {
2745                turnoutUserName = uname;
2746            } else {
2747                turnoutUserName = "";
2748            }
2749        }
2750
2751
2752        return Bundle.getMessage("LN_MSG_SW_STATE", turnoutSystemName,
2753                turnoutUserName);
2754    }
2755
2756    private static String interpretOpcRqSlData(LocoNetMessage l) {
2757        int slot = l.getElement(1) + 128 * (l.getElement(2) & 0x07);
2758        boolean expSlotRequ = (l.getElement(2) & 0x40) == 0X40 ? true : false;
2759        switch (slot) {
2760         // Slots > 120 & < 128 are all special, but these are the only ones we know to decode.
2761         // Extended System Slots 248 thru 251 dealt with separately, not here
2762            case LnConstants.FC_SLOT:
2763                return Bundle.getMessage("LN_MSG_SLOT_REQ_SLOT_FC_SLOT");
2764            case LnConstants.CFG_SLOT:
2765                return Bundle.getMessage("LN_MSG_SLOT_REQ_SLOT_CFG_SLOT");
2766            case LnConstants.CFG_EXT_SLOT:
2767                return Bundle.getMessage("LN_MSG_SLOT_REQ_SLOT_EXT_CFG_SLOT");
2768            case LnConstants.PRG_SLOT:
2769                return Bundle.getMessage("LN_MSG_SLOT_REQ_SLOT_PRG_SLOT");
2770            case 0x79:
2771            case 0x7a:
2772            case 0x7d:
2773                break;
2774            default:
2775                if (expSlotRequ) {
2776                    return Bundle.getMessage("LN_MSG_SLOT_REQ_SLOT_LOCO_EXP_SLOT", slot);
2777                } else {
2778                    return Bundle.getMessage("LN_MSG_SLOT_REQ_SLOT_LOCO_SLOT", slot);
2779                }
2780        }
2781        return "";
2782    }
2783
2784    private static String interpretOpcMoveSlots(LocoNetMessage l) {
2785        int src = l.getElement(1);
2786        int dest = l.getElement(2);
2787        if ((src >= 0x79) && (src <= 0x7f)) {
2788            return "";
2789        }
2790        if ((dest >= 0x79) && (dest <= 0x7f)) {
2791            return "";
2792        }
2793
2794        /* check special cases */
2795        if (src == 0) {
2796            /* DISPATCH GET */
2797
2798            return Bundle.getMessage("LN_MSG_MOVE_SL_GET_DISP");
2799        } else if (src == dest) {
2800            /* IN USE */
2801
2802            return Bundle.getMessage("LN_MSG_MOVE_SL_NULL_MOVE", src);
2803        } else if (dest == 0) {
2804            /* DISPATCH PUT */
2805
2806            return Bundle.getMessage("LN_MSG_MOVE_SL_DISPATCH_PUT", src);
2807        } else {
2808            /* general move */
2809
2810            return Bundle.getMessage("LN_MSG_MOVE_SL_MOVE", src, dest);
2811        }
2812    }
2813
2814    private static String interpretOpcConsistFunc(LocoNetMessage l) {
2815        int slot = l.getElement(1);
2816        int dirf = l.getElement(2);
2817        if ((dirf & 0x40) == 0x40) {
2818            return "";
2819        }
2820        return Bundle.getMessage("LN_MSG_CONSIST_FUNC",
2821                slot,
2822                interpretDIRF(dirf));
2823    }
2824
2825    private static String interpretOpcLocoSnd(LocoNetMessage l) {
2826        int slot = l.getElement(1);
2827        int snd = l.getElement(2);
2828        return Bundle.getMessage("LN_MSG_OPC_LOCO_SND",
2829                slot,
2830                Bundle.getMessage((snd & LnConstants.SND_F5) != 0
2831                        ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF"),
2832                Bundle.getMessage((snd & LnConstants.SND_F6) != 0
2833                        ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF"),
2834                Bundle.getMessage((snd & LnConstants.SND_F7) != 0
2835                        ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF"),
2836                Bundle.getMessage((snd & LnConstants.SND_F8) != 0
2837                        ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF"));
2838
2839    }
2840
2841    protected static String interpretDIRF(int dirf) {
2842        if ((dirf & 0x40) == 0x40) {
2843            return "";
2844        }
2845        String dirf0_4[] = interpretF0_F4toStrings(dirf);
2846        return Bundle.getMessage("LN_MSG_HELPER_DIRF",
2847                Bundle.getMessage((dirf & LnConstants.DIRF_DIR) != 0
2848                        ? "LN_MSG_DIRECTION_REV" : "LN_MSG_DIRECTION_FWD"),
2849                dirf0_4[0], dirf0_4[1], dirf0_4[2], dirf0_4[3], dirf0_4[4]);
2850
2851    }
2852
2853    private static String interpretOpcLocoDirf(LocoNetMessage l) {
2854        int slot = l.getElement(1);
2855        int dirf = l.getElement(2);
2856
2857        String dirFinfo = interpretDIRF(dirf);
2858        if (dirFinfo.length() == 0) {
2859            return "";
2860        }
2861
2862        return Bundle.getMessage("LN_MSG_OPC_LOCO_DIRF",
2863                slot, dirFinfo);
2864    }
2865
2866    private static String interpretOpcLocoSpd(LocoNetMessage l) {
2867        int slot = l.getElement(1);
2868        int spd = l.getElement(2);
2869
2870        if (spd == LnConstants.OPC_LOCO_SPD_ESTOP) {
2871            return Bundle.getMessage("LN_MSG_OPC_LOCO_SPD_ESTOP", slot);
2872        } else {
2873            return Bundle.getMessage("LN_MSG_OPC_LOCO_SPD_NORMAL", slot, spd);
2874        }
2875
2876    }
2877
2878    private static String interpretOpcPanelQuery(LocoNetMessage l) {
2879        switch (l.getElement(1)) {
2880            case 0x00: {
2881                return Bundle.getMessage("LN_MSG_OPC_DF_TETHERLESS_QUERY");
2882            }
2883            case 0x40: {
2884                if (l.getElement(2) == 0x1F) {
2885                    // Some UR devices treat this operation as a set plus query, others
2886                    // treat this only as a set.
2887                    return Bundle.getMessage("LN_MSG_OPC_DF_SET_LOCONETID", l.getElement(3));
2888                }
2889                break;
2890            }
2891            default: {
2892                break;
2893            }
2894        }
2895        return "";
2896    }
2897
2898    private static String interpretOpcSwReq(LocoNetMessage l, String turnoutPrefix) {
2899        int sw1 = l.getElement(1);
2900        int sw2 = l.getElement(2);
2901        if ((sw2 & 0x40) == 0x40) {
2902            return "";
2903        }
2904
2905        if ((!(((sw2 & 0xCF) == 0x0F) && ((sw1 & 0xFC) == 0x78)))
2906                && (!(((sw2 & 0xCF) == 0x07) && ((sw1 & 0xFC) == 0x78)))) {
2907            // ordinary form, LPU V1.0 page 9
2908            // handle cases which are not "stationary decoder interrogate" messages
2909            // get system and user names
2910            String turnoutUserName = "";
2911
2912            String turnoutSystemName = turnoutPrefix
2913                    + SENSOR_ADR(l.getElement(1), l.getElement(2));
2914            Turnout turnout = InstanceManager.getDefault(TurnoutManager.class).getTurnout(turnoutSystemName);
2915
2916            String uname = "";
2917            if (turnout != null) {
2918                uname = turnout.getUserName();
2919                if ((uname != null) && (!uname.isEmpty())) {
2920                    turnoutUserName = uname;
2921                } else {
2922                    turnoutUserName = "";
2923                }
2924            }
2925
2926            String pointsDirection = ((sw2 & LnConstants.OPC_SW_ACK_CLOSED) != 0
2927                    ? Bundle.getMessage("LN_MSG_SW_POS_CLOSED")
2928                    : Bundle.getMessage("LN_MSG_SW_POS_THROWN"));
2929            String outputState = ((sw2 & LnConstants.OPC_SW_ACK_OUTPUT) != 0
2930                    ? Bundle.getMessage("LN_MSG_SW_OUTPUT_STATE_ON")
2931                    : Bundle.getMessage("LN_MSG_SW_OUTPUT_STATE_OFF"));
2932            if (turnoutUserName.length() == 0) {
2933                return Bundle.getMessage("LN_MSG_OPC_SW_REQ_NORMAL_WITHOUT_USERNAME",
2934                        turnoutSystemName,
2935                        pointsDirection, outputState);
2936            } else {
2937                return Bundle.getMessage("LN_MSG_OPC_SW_REQ_NORMAL_WITH_USERNAME",
2938                        turnoutSystemName, turnoutUserName,
2939                        pointsDirection, outputState);
2940            }
2941        }
2942
2943        /*
2944        Handle cases which are "stationary decoder interrogate" messages.
2945         */
2946
2947        /*
2948         * Decodes a/c/b bits to allow proper creation of a list of addresses
2949         * which ought to reply to the "stationary decoder interrogate" message.
2950         */
2951        int a = (sw2 & 0x20) >> 5;
2952        int c = (sw1 & 0x02) >> 1;
2953        int b = (sw1 & 0x01);
2954
2955        /*
2956         * All this blob does is loop through the ranges indicated by the
2957         * a/c/b bits, they are mask bits in the midde of the range. The
2958         * idea is to get 8 sensors at a time, since that is generally what
2959         * units have, and to query units 1, 9, 17... then 2, 10, 18... and
2960         * so on such that if they are all in a row they don't get hit at
2961         * the same time.
2962         */
2963        int topbits = 0;
2964        int midbits = (a << 2) + (c << 1) + b;
2965        int count = 0;
2966        StringBuilder addrListB = new StringBuilder();
2967        for (topbits = 0; topbits < 32; topbits++) {
2968            // The extra "+1" adjusts for the fact that we show 1-2048,
2969            // rather than 0-2047 on the wire.
2970            int lval = (topbits << 6) + (midbits << 3) + 1;
2971            int hval = lval + 7;
2972
2973            if ((count % 8) != 0) {
2974                addrListB.append(", "); // NOI18N
2975            } else {
2976                if (count == 0) {
2977                    addrListB.append("\t"); // NOI18N
2978                } else {
2979                    addrListB.append(",\n\t");  // NOI18N
2980                }
2981            }
2982            addrListB.append("").append(lval);  // NOI18N
2983            addrListB.append("-").append(hval); // NOI18N
2984            count++;
2985        }
2986
2987        String addrList = addrListB.toString();
2988
2989        if (((sw2 & 0xCF) == 0x0F) && ((sw1 & 0xFC) == 0x78)) {
2990            // broadcast address LPU V1.0 page 12
2991            return Bundle.getMessage("LN_MSG_OPC_SW_REQ_INTERROGATE_TURNOUTS",
2992                    a, c, b, addrList);
2993        } else {
2994            // broadcast address LPU V1.0 page 13
2995            return Bundle.getMessage("LN_MSG_OPC_SW_REQ_INTERROGATE_SENSORS_TURNOUTS",
2996                    a, c, b, addrList);
2997        }
2998    }
2999
3000    private static String interpretFastClockSlot(LocoNetMessage l, String mode, int id1, int id2) {
3001        /*
3002         * FAST Clock: The system FAST clock and parameters are implemented in
3003         * Slot#123 <7B>. Use <EF> to write new clock information, Slot read of
3004         * 0x7B,<BB><7B>.., will return current System clock information, and
3005         * other throttles will update to this SYNC. Note that all attached
3006         * display devices keep a current clock calculation based on this SYNC
3007         * read value, i.e. devices MUST not continuously poll the clock SLOT to
3008         * generate time, but use this merely to restore SYNC and follow current
3009         * RATE etc. This clock slot is typically "pinged" * or read SYNC'd
3010         * every 70 to 100 seconds, by a single user, so all attached devices
3011         * can synchronise any phase drifts. Upon seeing a SYNC read, all
3012         * devices should reset their local sub-minute phase counter and
3013         * invalidate the SYNC update ping generator.
3014         * <p>
3015         * Clock Slot Format:
3016         * <p>
3017         * <0xEF>,<0E>,<7B>,<CLK_RATE>,<FRAC_MINSL>,<FRAC_MINSH>,<256-MINS_60>,
3018         * <TRK><256-HRS_24>,<DAYS>,<CLK_CNTRL>,<ID1>,<1D2>,<CHK>
3019         * <p>
3020         * where:
3021         * <p>
3022         * <CLK_RATE> 0=Freeze clock, * 1=normal 1:1 rate, 10=10:1 etc, max
3023         * VALUE is 7F/128 to 1
3024         * <p>
3025         * <FRAC_MINSL> FRAC mins hi/lo are a sub-minute counter, depending on
3026         * the CLOCK generator
3027         * <p>
3028         * <FRAC_MINSH> Not for ext. usage. This counter is reset when valid
3029         * <E6><7B>
3030         * SYNC message is seen
3031         * <p>
3032         * <256-MINS_60> This is FAST clock MINUTES subtracted from 256. Modulo
3033         * 0-59
3034         * <p>
3035         * <256-HRS_24> This is FAST clock HOURS subtracted from 256. Modulo
3036         * 0-23
3037         * <p>
3038         * <DAYS> number of 24 Hr clock rolls, positive count
3039         * <p>
3040         * <CLK_CNTRL> Clock Control Byte D6- 1=This is valid Clock information,
3041         * 0=ignore this <E6><7B>, SYNC reply
3042         * <p>
3043         * <ID1>,<1D2> This is device ID last setting the clock.
3044         * <p>
3045         * <00><00> shows no set has happened
3046         * <p>
3047         * <7F><7x> are reserved for PC access *
3048         */
3049
3050        int minutes; // temporary time values
3051        int hours;
3052        int clk_rate = l.getElement(3); // 0 = Freeze clock, 1 = normal,
3053        // 10 = 10:1 etc. Max is 0x7f
3054        int mins_60 = l.getElement(6); // 256 - minutes
3055        int track_stat = l.getElement(7); // track status
3056        int hours_24 = l.getElement(8); // 256 - hours
3057        int days = l.getElement(9); // clock rollovers
3058        int clk_cntrl = l.getElement(10); // bit 6 = 1; data is valid
3059        // clock info
3060        // "  " 0; ignore this reply
3061        // id1/id2 is device id of last device to set the clock
3062        // "   " = zero shows not set has happened
3063
3064        /* recover hours and minutes values */
3065        minutes = ((255 - mins_60) & 0x7f) % 60;
3066        hours = ((256 - hours_24) & 0x7f) % 24;
3067        hours = (24 - hours) % 24;
3068        minutes = (60 - minutes) % 60;
3069
3070        return Bundle.getMessage("LN_MSG_SLOT_ACCESS_FAST_CLOCK",
3071                mode,
3072                ((clk_cntrl & 0x20) != 0 ? "" : Bundle.getMessage("LN_MSG_SLOT_HELPER_FC_SYNC")),
3073                (clk_rate != 0 ? Bundle.getMessage("LN_MSG_SLOT_HELPER_FC_RUNNING")
3074                        : Bundle.getMessage("LN_MSG_SLOT_HELPER_FC_FROZEN")),
3075                clk_rate,
3076                days,
3077                fcTimeToString(hours, minutes),
3078                idString(id1, id2),
3079                trackStatusByteToString(track_stat));
3080    }
3081
3082    private static String interpretProgSlot(LocoNetMessage l, String mode, int id1, int id2, int command) {
3083        /*
3084         * ********************************************************************************************
3085         * Programmer track:
3086         * =================
3087         * The programmer track is
3088         * accessed as Special slot #124 ( $7C, 0x7C). It is a full
3089         * asynchronous shared system resource.
3090         *
3091         * To start Programmer task,
3092         * write to slot 124. There will be an immediate LACK acknowledge
3093         * that indicates what programming will be allowed. If a valid programming
3094         * task is started,
3095         * then at the final (asynchronous) programming
3096         * completion, a Slot read <E7> from slot 124 will be sent. This is
3097         * the final task status reply.
3098         *
3099         * Programmer Task Start:
3100         *
3101         * ----------------------
3102         * <p>
3103         * <0xEF>,<0E>,<7C>,<PCMD>,<0>,<HOPSA>,<LOPSA>,<TRK>;<CVH>,<CVL>,
3104         * <p>
3105         * <DATA7>,<0>,<0>,<CHK> * * This OPC leads to immediate LACK codes:
3106         * <p>
3107         * <B4>,<7F>,<7F>,<chk> Function NOT implemented, no reply.
3108         * <p>
3109         * <B4>,<7F>,<0>,<chk> Programmer BUSY , task aborted, no reply.
3110         * <p>
3111         * <B4>,<7F>,<1>,<chk> Task accepted , <E7> reply at completion.
3112         * <p>
3113         * <B4>,<7F>,<0x40>,<chk> Task accepted blind NO <E7>
3114         * reply at completion. * * Note that the <7F> code will occur in
3115         * Operations Mode Read requests if the System is not * configured for
3116         * and has no Advanced Acknowledgement detection installed.. Operations
3117         * Mode * requests can be made and executed whilst a current Service
3118         * Mode programming task is keeping * the Programming track BUSY. If a
3119         * Programming request is rejected, delay and resend the * complete
3120         * request later. Some readback operations can keep the Programming
3121         * track busy for up * to a minute. Multiple devices, throttles/PC's
3122         * etc, can share and sequentially use the * Programming track as long
3123         * as they correctly interpret the response messages. Any Slot RD * from
3124         * the master will also contain the Programmer Busy status in bit 3 of
3125         * the <TRK> byte. * * A <PCMD> value of
3126         * <00> will abort current SERVICE mode programming task and will echo
3127         * with an <E6> RD the command string that was aborted.
3128         *
3129         * <PCMD>
3130         * Programmer Command:
3131         * --------------------------
3132         * Defined as
3133         * D7 -0
3134         * D6 -Write/Read 1= Write, 0=Read
3135         * D5 -Byte Mode 1= Byte operation, 0=Bit operation (if possible)
3136         * D4 -TY1 Programming Type select bit
3137         * D3 -TY0 Prog type select bit
3138         * D2 -Ops Mode 1=Ops Mode on Mainlines, 0=Service Mode on Programming Track
3139         * D1 -0 reserved
3140         * D0 -0-reserved
3141         *
3142         * Type codes:
3143         * ----------- * Byte Mode Ops Mode
3144         * TY1 TY0 Meaning * 1 0 0 0 Paged mode byte Read/Write on Service Track
3145         * * 1 0 0 0 Paged mode byte Read/Write on Service Track * 1 0 0 1
3146         * Direct mode byteRead/Write on Service Track * 0 0 0 1 Direct mode bit
3147         * Read/Write on Service Track * x 0 1 0 Physical Register byte
3148         * Read/Write on Service Track * x 0 1 1 Service Track- reserved
3149         * function * 1 1 0 0 Ops mode Byte program, no feedback * 1 1 0 1 Ops
3150         * mode Byte program, feedback * 0 1 0 0 Ops mode Bit program, no
3151         * feedback * 0 1 0 1 Ops mode Bit program, feedback * *
3152         * <HOPSA>Operations Mode Programming * 7 High address bits of Loco to
3153         * program, 0 if Service Mode
3154         * <p>
3155         * <LOPSA>Operations Mode Programming * 7 Low address bits of Loco to
3156         * program, 0 if Service Mode
3157         * <p>
3158         * <TRK> Normal Global Track status for this Master, * Bit 3 also is 1
3159         * WHEN Service Mode track is BUSY
3160         * <p>
3161         * <CVH> High 3 BITS of CV#, and ms bit of DATA.7
3162         * <p>
3163         * <0,0,CV9,CV8 - 0,0, D7,CV7>
3164         * <p>
3165         * <CVL> Low 7 bits of 10 bit CV address.
3166         * <p>
3167         * <0,CV6,CV5,CV4-CV3,CV2,CV1,CV0>
3168         * <p>
3169         * <DATA7>Low 7 BITS OF data to WR or RD COMPARE
3170         * <p>
3171         * <0,D6,D5,D4 - D3,D2,D1,D0> * ms bit is at CVH bit 1 position. * *
3172         * Programmer Task Final Reply: * ---------------------------- * (if saw
3173         * LACK
3174         * <B4>,<7F>,<1>,<chk> code reply at task start)
3175         * <p>
3176         * <0xE7>,<0E>,<7C>,<PCMD>,<PSTAT>,<HOPSA>,<LOPSA>,<TRK>;<CVH>,<CVL>,
3177         * <p>
3178         * <DATA7>,<0>,<0>,<CHK> * * <PSTAT> Programmer Status error flags.
3179         * Reply codes resulting from * completed task in PCMD * D7-D4 -reserved
3180         * * D3 -1= User Aborted this command * D2 -1= Failed to detect READ
3181         * Compare acknowledge response * from decoder * D1 -1= No Write
3182         * acknowledge response from decoder * D0 -1= Service Mode programming
3183         * track empty- No decoder detected * * This <E7> response is issued
3184         * whenever a Programming task is completed. It echos most of the *
3185         * request information and returns the PSTAT status code to indicate how
3186         * the task completed. * If a READ was requested <DATA7> and <CVH>
3187         * contain the returned data, if the PSTAT indicates * a successful
3188         * readback (typically =0). Note that if a Paged Read fails to detect a
3189         * * successful Page write acknowledge when first setting the Page
3190         * register, the read will be * aborted, showing no Write acknowledge
3191         * flag D1=1. *
3192         * ********************************************************************************************
3193         */
3194        int cvData;
3195        int cvNumber;
3196
3197        // progTask = (progTaskMsg *) msgBuf;
3198        // slot - slot number for this request - slot 124 is programmer
3199        int pcmd = l.getElement(3); // programmer command
3200        int pstat = l.getElement(4); // programmer status error flags in
3201        // reply message
3202        int hopsa = l.getElement(5); // Ops mode - 7 high address bits
3203        // of loco to program
3204        int lopsa = l.getElement(6); // Ops mode - 7 low address bits of
3205        // loco to program
3206        /* trk - track status. Note: bit 3 shows if prog track is busy */
3207        int cvh = l.getElement(8); // hi 3 bits of CV# and msb of data7
3208        int cvl = l.getElement(9); // lo 7 bits of CV#
3209        int data7 = l.getElement(10); // 7 bits of data to program, msb
3210        // is in cvh above
3211
3212        cvData = (((cvh & LnConstants.CVH_D7) << 6) | (data7 & 0x7f)); // was
3213        // PROG_DATA
3214        cvNumber = (((((cvh & LnConstants.CVH_CV8_CV9) >> 3) | (cvh & LnConstants.CVH_CV7)) * 128) + (cvl & 0x7f)) + 1; // was
3215        // PROG_CV_NUM(progTask)
3216
3217        if (command == LnConstants.OPC_WR_SL_DATA) {
3218            /* interpret the programming mode request (to programmer) */
3219            switch ((pcmd & (LnConstants.PCMD_MODE_MASK | LnConstants.PCMD_RW))) {
3220                case LnConstants.PAGED_ON_SRVC_TRK:
3221                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_REQUEST_SRVC_TRK_PAGED_RD",
3222                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_READ_REQ",
3223                                    cvNumber));
3224                case LnConstants.PAGED_ON_SRVC_TRK | LnConstants.PCMD_RW:
3225                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_REQUEST_SRVC_TRK_PAGED_WR",
3226                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_WRITE_REQ",
3227                                    cvNumber, cvData, Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3228                                            StringUtil.twoHexFromInt(cvData)), StringUtil.to8Bits(cvData, true)));
3229                case LnConstants.DIR_BYTE_ON_SRVC_TRK:
3230                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_REQUEST_SRVC_TRK_DIR_BYTE_RD",
3231                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_READ_REQ",
3232                                    cvNumber));
3233                case LnConstants.DIR_BYTE_ON_SRVC_TRK | LnConstants.PCMD_RW:
3234                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_REQUEST_SRVC_TRK_DIR_BYTE_WR",
3235                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_WRITE_REQ",
3236                                    cvNumber, cvData, Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3237                                            StringUtil.twoHexFromInt(cvData)), StringUtil.to8Bits(cvData, true)));
3238                case LnConstants.DIR_BIT_ON_SRVC_TRK:
3239                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_REQUEST_SRVC_TRK_DIR_BIT_RD",
3240                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_READ_REQ",
3241                                    cvNumber), cvData, Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3242                                    StringUtil.twoHexFromInt(cvData)), StringUtil.to8Bits(cvData, true));
3243                case LnConstants.DIR_BIT_ON_SRVC_TRK | LnConstants.PCMD_RW:
3244                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_REQUEST_SRVC_TRK_DIR_BIT_WR",
3245                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_WRITE_REQ",
3246                                    cvNumber, cvData, Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3247                                            StringUtil.twoHexFromInt(cvData)), StringUtil.to8Bits(cvData, true)));
3248                case LnConstants.REG_BYTE_RW_ON_SRVC_TRK:
3249                case LnConstants.REG_BYTE_RW_ON_SRVC_TRK | LnConstants.PCMD_BYTE_MODE:
3250                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_REQUEST_SRVC_TRK_REG_BYTE_RD",
3251                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_READ_REQ",
3252                                    cvNumber));
3253                case LnConstants.REG_BYTE_RW_ON_SRVC_TRK | LnConstants.PCMD_RW:
3254                case LnConstants.REG_BYTE_RW_ON_SRVC_TRK | LnConstants.PCMD_BYTE_MODE | LnConstants.PCMD_RW:
3255                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_REQUEST_SRVC_TRK_REG_BYTE_WR",
3256                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_WRITE_REQ",
3257                                    cvNumber, cvData, Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3258                                            StringUtil.twoHexFromInt(cvData)), StringUtil.to8Bits(cvData, true)));
3259                case LnConstants.SRVC_TRK_RESERVED:
3260                case LnConstants.SRVC_TRK_RESERVED | LnConstants.PCMD_BYTE_MODE:
3261                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_REQUEST_SRVC_TRK_RD_RESERVED",
3262                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_READ_REQ",
3263                                    cvNumber));
3264                case LnConstants.SRVC_TRK_RESERVED | LnConstants.PCMD_RW:
3265                case LnConstants.SRVC_TRK_RESERVED | LnConstants.PCMD_BYTE_MODE | LnConstants.PCMD_RW:
3266                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_REQUEST_SRVC_TRK_WR_RESERVED",
3267                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_WRITE_REQ",
3268                                    cvNumber, cvData, Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3269                                            StringUtil.twoHexFromInt(cvData)), StringUtil.to8Bits(cvData, true)));
3270                case LnConstants.OPS_BYTE_NO_FEEDBACK:
3271                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_REQUEST_OPS_BYTE_RD_NO_FEEDBACK",
3272                            convertToMixed(lopsa, hopsa),
3273                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_READ_REQ",
3274                                    cvNumber));
3275                case LnConstants.OPS_BYTE_NO_FEEDBACK | LnConstants.PCMD_RW:
3276                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_REQUEST_OPS_BYTE_WR_NO_FEEDBACK",
3277                            convertToMixed(lopsa, hopsa),
3278                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_WRITE_REQ",
3279                                    cvNumber, cvData,
3280                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3281                                            StringUtil.twoHexFromInt(cvData)), StringUtil.to8Bits(cvData, true)));
3282                case LnConstants.OPS_BYTE_FEEDBACK:
3283                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_REQUEST_OPS_BYTE_RD_FEEDBACK",
3284                            convertToMixed(lopsa, hopsa),
3285                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_READ_REQ",
3286                                    cvNumber));
3287                case LnConstants.OPS_BYTE_FEEDBACK | LnConstants.PCMD_RW:
3288                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_REQUEST_OPS_BYTE_WR_FEEDBACK",
3289                            convertToMixed(lopsa, hopsa),
3290                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_WRITE_REQ",
3291                                    cvNumber, cvData, Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3292                                            StringUtil.twoHexFromInt(cvData)), StringUtil.to8Bits(cvData, true)));
3293                case LnConstants.OPS_BIT_NO_FEEDBACK:
3294                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_REQUEST_OPS_BIT_RD_NO_FEEDBACK",
3295                            convertToMixed(lopsa, hopsa),
3296                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_READ_REQ",
3297                                    cvNumber));
3298                case LnConstants.OPS_BIT_NO_FEEDBACK | LnConstants.PCMD_RW:
3299                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_REQUEST_OPS_BIT_WR_NO_FEEDBACK",
3300                            convertToMixed(lopsa, hopsa),
3301                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_WRITE_REQ",
3302                                    cvNumber, cvData, Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3303                                            StringUtil.twoHexFromInt(cvData)), StringUtil.to8Bits(cvData, true)));
3304                case LnConstants.OPS_BIT_FEEDBACK:
3305                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_REQUEST_OPS_BIT_RD_FEEDBACK",
3306                            convertToMixed(lopsa, hopsa),
3307                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_READ_REQ",
3308                                    cvNumber));
3309                case LnConstants.OPS_BIT_FEEDBACK | LnConstants.PCMD_RW:
3310                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_REQUEST_OPS_BIT_WR_FEEDBACK",
3311                            convertToMixed(lopsa, hopsa),
3312                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_WRITE_REQ",
3313                                    cvNumber, cvData, Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3314                                            StringUtil.twoHexFromInt(cvData)), StringUtil.to8Bits(cvData, true)));
3315                case 0:
3316                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_REQUEST_UHLENBROCK_RD",
3317                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_READ_REQ",
3318                                    cvNumber));
3319                case LnConstants.PCMD_RW:
3320                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_REQUEST_UHLENBROCK_WR",
3321                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_WRITE_REQ",
3322                                    cvNumber, cvData, Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3323                                            StringUtil.twoHexFromInt(cvData)), StringUtil.to8Bits(cvData, true)));
3324                default:
3325                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_REQUEST_UNKNOWN",
3326                            pcmd,
3327                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3328                                    StringUtil.twoHexFromInt(pcmd)),
3329                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_WRITE_REQ",
3330                                    cvNumber, cvData, Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3331                                            StringUtil.twoHexFromInt(cvData)), StringUtil.to8Bits(cvData, true)));
3332            }
3333        } else {
3334            /* interpret the  programming mode response (from programmer) */
3335            /* if we're reading the slot back, check the status
3336             * this is supposed to be the Programming task final reply
3337             * and will have the resulting status byte.
3338             */
3339            String responseMessage = "(ODD BEHAVIOR - Default value not overwritten - report to developers!"; // NOI18N
3340            String hexMessage = "";
3341            if (pstat != 0) {
3342                if ((pstat & LnConstants.PSTAT_USER_ABORTED) != 0) {
3343                    responseMessage = Bundle.getMessage("LN_MSG_SLOT_PROG_HELPER_RESPONSE_USER_ABORT");
3344                } else if ((pstat & LnConstants.PSTAT_READ_FAIL) != 0) {
3345                    responseMessage = Bundle.getMessage("LN_MSG_SLOT_PROG_HELPER_RESPONSE_NO_READ_COMPARE_ACK_DETECT");
3346                } else if ((pstat & LnConstants.PSTAT_WRITE_FAIL) != 0) {
3347                    responseMessage = Bundle.getMessage("LN_MSG_SLOT_PROG_HELPER_RESPONSE_NO_WRITE_ACK_DETECT");
3348                } else if ((pstat & LnConstants.PSTAT_NO_DECODER) != 0) {
3349                    responseMessage = Bundle.getMessage("LN_MSG_SLOT_PROG_HELPER_RESPONSE_NO_LOCO_ON_PROGRAMMING_TRACK");
3350                } else if ((pstat & 0xF0) != 0) {
3351                    if ((pstat & 0xF0) == 0x10) {
3352                        // response from transponding decoder
3353                        responseMessage = Bundle.getMessage("LN_MSG_SLOT_PROG_HELPER_RESPONSE_SUCCESS_VIA_RX4_BDL16X");
3354
3355                    } else {
3356                        responseMessage = Bundle.getMessage("LN_MSG_SLOT_PROG_HELPER_RESPONSE_UNDECODED",
3357                                Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3358                                        StringUtil.twoHexFromInt(pstat)));
3359                        hexMessage = Bundle.getMessage("LN_MONITOR_MESSAGE_RAW_HEX_INFO", l.toString());
3360                    }
3361                }
3362            } else {
3363                responseMessage = Bundle.getMessage("LN_MSG_SLOT_PROG_HELPER_RESPONSE_SUCCEEDED");
3364            }
3365
3366            switch ((pcmd & (LnConstants.PCMD_MODE_MASK | LnConstants.PCMD_RW))) {
3367                case LnConstants.PAGED_ON_SRVC_TRK:
3368                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_RESPONSE_SRVC_TRK_PAGED_RD",
3369                            responseMessage,
3370                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_REPLY",
3371                                    cvNumber, cvData,
3372                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3373                                            StringUtil.twoHexFromInt(cvData)),
3374                                    StringUtil.to8Bits(cvData, true)))+hexMessage;
3375                case LnConstants.PAGED_ON_SRVC_TRK | LnConstants.PCMD_RW:
3376                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_RESPONSE_SRVC_TRK_PAGED_WR",
3377                            responseMessage,
3378                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_REPLY",
3379                                    cvNumber, cvData,
3380                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3381                                            StringUtil.twoHexFromInt(cvData)),
3382                                    StringUtil.to8Bits(cvData, true)))+hexMessage;
3383                case LnConstants.DIR_BYTE_ON_SRVC_TRK:
3384                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_RESPONSE_SRVC_TRK_DIR_BYTE_RD",
3385                            responseMessage,
3386                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_REPLY",
3387                                    cvNumber, cvData,
3388                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3389                                            StringUtil.twoHexFromInt(cvData)),
3390                                    StringUtil.to8Bits(cvData, true)))+hexMessage;
3391                case LnConstants.DIR_BYTE_ON_SRVC_TRK | LnConstants.PCMD_RW:
3392                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_RESPONSE_SRVC_TRK_DIR_BYTE_WR",
3393                            responseMessage,
3394                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_REPLY",
3395                                    cvNumber,
3396                                    cvData,
3397                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3398                                            StringUtil.twoHexFromInt(cvData)),
3399                                    StringUtil.to8Bits(cvData, true)))+hexMessage;
3400                case LnConstants.DIR_BIT_ON_SRVC_TRK:
3401                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_RESPONSE_SRVC_TRK_DIR_BIT_RD",
3402                            responseMessage,
3403                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_REPLY",
3404                                    cvNumber, cvData,
3405                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3406                                            StringUtil.twoHexFromInt(cvData)),
3407                                    StringUtil.to8Bits(cvData, true)))+hexMessage;
3408                case LnConstants.DIR_BIT_ON_SRVC_TRK | LnConstants.PCMD_RW:
3409                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_RESPONSE_SRVC_TRK_DIR_BIT_WR",
3410                            responseMessage,
3411                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_REPLY",
3412                                    cvNumber, cvData,
3413                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3414                                            StringUtil.twoHexFromInt(cvData)),
3415                                    StringUtil.to8Bits(cvData, true)))+hexMessage;
3416                case LnConstants.REG_BYTE_RW_ON_SRVC_TRK:
3417                case LnConstants.REG_BYTE_RW_ON_SRVC_TRK | LnConstants.PCMD_BYTE_MODE:
3418                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_RESPONSE_SRVC_TRK_REG_BYTE_RD",
3419                            responseMessage,
3420                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_REPLY",
3421                                    cvNumber, cvData,
3422                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3423                                            StringUtil.twoHexFromInt(cvData)),
3424                                    StringUtil.to8Bits(cvData, true)))+hexMessage;
3425                case LnConstants.REG_BYTE_RW_ON_SRVC_TRK | LnConstants.PCMD_RW:
3426                case LnConstants.REG_BYTE_RW_ON_SRVC_TRK | LnConstants.PCMD_BYTE_MODE | LnConstants.PCMD_RW:
3427                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_RESPONSE_SRVC_TRK_REG_BYTE_WR",
3428                            responseMessage,
3429                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_REPLY",
3430                                    cvNumber, cvData,
3431                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3432                                            StringUtil.twoHexFromInt(cvData)),
3433                                    StringUtil.to8Bits(cvData, true)))+hexMessage;
3434                case LnConstants.SRVC_TRK_RESERVED:
3435                case LnConstants.SRVC_TRK_RESERVED | LnConstants.PCMD_BYTE_MODE:
3436                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_RESPONSE_SRVC_TRK_RD_RESERVED",
3437                            responseMessage,
3438                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_REPLY",
3439                                    cvNumber, cvData,
3440                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3441                                            StringUtil.twoHexFromInt(cvData)),
3442                                    StringUtil.to8Bits(cvData, true)))+hexMessage;
3443                case LnConstants.SRVC_TRK_RESERVED | LnConstants.PCMD_RW:
3444                case LnConstants.SRVC_TRK_RESERVED | LnConstants.PCMD_BYTE_MODE | LnConstants.PCMD_RW:
3445                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_RESPONSE_SRVC_TRK_WR_RESERVED",
3446                            responseMessage,
3447                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_REPLY",
3448                                    cvNumber, cvData,
3449                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3450                                            StringUtil.twoHexFromInt(cvData)),
3451                                    StringUtil.to8Bits(cvData, true)))+hexMessage;
3452                case LnConstants.OPS_BYTE_NO_FEEDBACK:
3453                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_RESPONSE_OPS_BYTE_RD_NO_FEEDBACK",
3454                            responseMessage,
3455                            convertToMixed(lopsa, hopsa),
3456                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_REPLY",
3457                                    cvNumber, cvData,
3458                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3459                                            StringUtil.twoHexFromInt(cvData)),
3460                                    StringUtil.to8Bits(cvData, true)))+hexMessage;
3461                case LnConstants.OPS_BYTE_NO_FEEDBACK | LnConstants.PCMD_RW:
3462                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_RESPONSE_OPS_BYTE_WR_NO_FEEDBACK",
3463                            responseMessage,
3464                            convertToMixed(lopsa, hopsa),
3465                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_REPLY",
3466                                    cvNumber, cvData,
3467                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3468                                            StringUtil.twoHexFromInt(cvData)),
3469                                    StringUtil.to8Bits(cvData, true)))+hexMessage;
3470                case LnConstants.OPS_BYTE_FEEDBACK:
3471                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_RESPONSE_OPS_BYTE_RD_FEEDBACK",
3472                            responseMessage,
3473                            convertToMixed(lopsa, hopsa),
3474                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_REPLY",
3475                                    cvNumber, cvData,
3476                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3477                                            StringUtil.twoHexFromInt(cvData)),
3478                                    StringUtil.to8Bits(cvData, true)))+hexMessage;
3479                case LnConstants.OPS_BYTE_FEEDBACK | LnConstants.PCMD_RW:
3480                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_RESPONSE_OPS_BYTE_WR_FEEDBACK",
3481                            responseMessage,
3482                            convertToMixed(lopsa, hopsa),
3483                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_REPLY",
3484                                    cvNumber, cvData,
3485                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3486                                            StringUtil.twoHexFromInt(cvData)),
3487                                    StringUtil.to8Bits(cvData, true)))+hexMessage;
3488                case LnConstants.OPS_BIT_NO_FEEDBACK:
3489                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_RESPONSE_OPS_BIT_RD_NO_FEEDBACK",
3490                            responseMessage,
3491                            convertToMixed(lopsa, hopsa),
3492                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_REPLY",
3493                                    cvNumber, cvData,
3494                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3495                                            StringUtil.twoHexFromInt(cvData)),
3496                                    StringUtil.to8Bits(cvData, true)))+hexMessage;
3497                case LnConstants.OPS_BIT_NO_FEEDBACK | LnConstants.PCMD_RW:
3498                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_RESPONSE_OPS_BIT_WR_NO_FEEDBACK",
3499                            responseMessage,
3500                            convertToMixed(lopsa, hopsa),
3501                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_REPLY",
3502                                    cvNumber, cvData,
3503                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3504                                            StringUtil.twoHexFromInt(cvData)),
3505                                    StringUtil.to8Bits(cvData, true)))+hexMessage;
3506                case LnConstants.OPS_BIT_FEEDBACK:
3507                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_RESPONSE_OPS_BIT_RD_FEEDBACK",
3508                            responseMessage,
3509                            convertToMixed(lopsa, hopsa),
3510                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_REPLY",
3511                                    cvNumber, cvData,
3512                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3513                                            StringUtil.twoHexFromInt(cvData)),
3514                                    StringUtil.to8Bits(cvData, true)))+hexMessage;
3515                case LnConstants.OPS_BIT_FEEDBACK | LnConstants.PCMD_RW:
3516                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_RESPONSE_OPS_BIT_WR_FEEDBACK",
3517                            responseMessage,
3518                            convertToMixed(lopsa, hopsa),
3519                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_REPLY",
3520                                    cvNumber, cvData,
3521                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3522                                            StringUtil.twoHexFromInt(cvData)),
3523                                    StringUtil.to8Bits(cvData, true)))+hexMessage;
3524                case 0:
3525                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_RESPONSE_UHLENBROCK_RD",
3526                            responseMessage,
3527                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_REPLY",
3528                                    cvNumber, cvData,
3529                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3530                                            StringUtil.twoHexFromInt(cvData)),
3531                                    StringUtil.to8Bits(cvData, true)))+hexMessage;
3532                case LnConstants.PCMD_RW:
3533                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_RESPONSE_UHLENBROCK_WR",
3534                            responseMessage,
3535                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_REPLY",
3536                                    cvNumber, cvData,
3537                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3538                                            StringUtil.twoHexFromInt(cvData)),
3539                                    StringUtil.to8Bits(cvData, true)))+hexMessage;
3540                default:
3541                    return Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_RESPONSE_UNKNOWN",
3542                            pcmd,
3543                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3544                                    StringUtil.twoHexFromInt(pcmd)),
3545                            responseMessage,
3546                            Bundle.getMessage("LN_MSG_SLOT_PROG_MODE_CV_INFO_HELPER_REPLY",
3547                                    cvNumber, cvData,
3548                                    Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3549                                            StringUtil.twoHexFromInt(cvData)),
3550                                    StringUtil.to8Bits(cvData, true)))+hexMessage;
3551            }
3552        }
3553    }
3554
3555    private static String interpretCmdStnCfgSlotRdWr(LocoNetMessage l, int command) {
3556
3557        /**
3558         * ************************************************
3559         * Configuration slot, holding op switches
3560         * ************************************************
3561         * <p>
3562         * NOTE: previously, this message provided specific text about the
3563         * meaning of each OpSw when it was closed. With the advent of newer
3564         * Digitrax command stations, the specific information was no longer
3565         * completely accurate. As such, this information now only shows bits as
3566         * "closed" or "thrown".
3567         */
3568        String thrown = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_OPSW_HELPER_THROWN");
3569        String closed = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_OPSW_HELPER_CLOSED");
3570
3571        String opswGroup1, opswGroup2, opswGroup3, opswGroup4,
3572                opswGroup5, opswGroup6, opswGroup7, opswGroup8;
3573        opswGroup1 = Bundle.getMessage("LN_MSG_SLOT_CMD_STN_CFG_HELPER_EIGHT_OPSWS",
3574                1, ((l.getElement(3) & 0x01) != 0 ? closed : thrown),
3575                2, ((l.getElement(3) & 0x02) != 0 ? closed : thrown),
3576                3, ((l.getElement(3) & 0x04) != 0 ? closed : thrown),
3577                4, ((l.getElement(3) & 0x08) != 0 ? closed : thrown),
3578                5, ((l.getElement(3) & 0x10) != 0 ? closed : thrown),
3579                6, ((l.getElement(3) & 0x20) != 0 ? closed : thrown),
3580                7, ((l.getElement(3) & 0x40) != 0 ? closed : thrown),
3581                8, thrown);
3582        opswGroup2 = Bundle.getMessage("LN_MSG_SLOT_CMD_STN_CFG_HELPER_EIGHT_OPSWS",
3583                9, ((l.getElement(4) & 0x01) != 0 ? closed : thrown),
3584                10, ((l.getElement(4) & 0x02) != 0 ? closed : thrown),
3585                11, ((l.getElement(4) & 0x04) != 0 ? closed : thrown),
3586                12, ((l.getElement(4) & 0x08) != 0 ? closed : thrown),
3587                13, ((l.getElement(4) & 0x10) != 0 ? closed : thrown),
3588                14, ((l.getElement(4) & 0x20) != 0 ? closed : thrown),
3589                15, ((l.getElement(4) & 0x40) != 0 ? closed : thrown),
3590                16, thrown);
3591        opswGroup3 = Bundle.getMessage("LN_MSG_SLOT_CMD_STN_CFG_HELPER_EIGHT_OPSWS",
3592                17, ((l.getElement(5) & 0x01) != 0 ? closed : thrown),
3593                18, ((l.getElement(5) & 0x02) != 0 ? closed : thrown),
3594                19, ((l.getElement(5) & 0x04) != 0 ? closed : thrown),
3595                20, ((l.getElement(5) & 0x08) != 0 ? closed : thrown),
3596                21, ((l.getElement(5) & 0x10) != 0 ? closed : thrown),
3597                22, ((l.getElement(5) & 0x20) != 0 ? closed : thrown),
3598                23, ((l.getElement(5) & 0x40) != 0 ? closed : thrown),
3599                24, thrown);
3600        opswGroup4 = Bundle.getMessage("LN_MSG_SLOT_CMD_STN_CFG_HELPER_EIGHT_OPSWS",
3601                25, ((l.getElement(6) & 0x01) != 0 ? closed : thrown),
3602                26, ((l.getElement(6) & 0x02) != 0 ? closed : thrown),
3603                27, ((l.getElement(6) & 0x04) != 0 ? closed : thrown),
3604                28, ((l.getElement(6) & 0x08) != 0 ? closed : thrown),
3605                29, ((l.getElement(6) & 0x10) != 0 ? closed : thrown),
3606                30, ((l.getElement(6) & 0x20) != 0 ? closed : thrown),
3607                31, ((l.getElement(6) & 0x40) != 0 ? closed : thrown),
3608                32, thrown);
3609        opswGroup5 = Bundle.getMessage("LN_MSG_SLOT_CMD_STN_CFG_HELPER_EIGHT_OPSWS",
3610                33, ((l.getElement(8) & 0x01) != 0 ? closed : thrown),
3611                34, ((l.getElement(8) & 0x02) != 0 ? closed : thrown),
3612                35, ((l.getElement(8) & 0x04) != 0 ? closed : thrown),
3613                36, ((l.getElement(8) & 0x08) != 0 ? closed : thrown),
3614                37, ((l.getElement(8) & 0x10) != 0 ? closed : thrown),
3615                38, ((l.getElement(8) & 0x20) != 0 ? closed : thrown),
3616                39, ((l.getElement(8) & 0x40) != 0 ? closed : thrown),
3617                40, thrown);
3618        opswGroup6 = Bundle.getMessage("LN_MSG_SLOT_CMD_STN_CFG_HELPER_EIGHT_OPSWS",
3619                41, ((l.getElement(9) & 0x01) != 0 ? closed : thrown),
3620                42, ((l.getElement(9) & 0x02) != 0 ? closed : thrown),
3621                43, ((l.getElement(9) & 0x04) != 0 ? closed : thrown),
3622                44, ((l.getElement(9) & 0x08) != 0 ? closed : thrown),
3623                45, ((l.getElement(9) & 0x10) != 0 ? closed : thrown),
3624                46, ((l.getElement(9) & 0x20) != 0 ? closed : thrown),
3625                47, ((l.getElement(9) & 0x40) != 0 ? closed : thrown),
3626                48, thrown);
3627        opswGroup7 = Bundle.getMessage("LN_MSG_SLOT_CMD_STN_CFG_HELPER_EIGHT_OPSWS",
3628                49, ((l.getElement(10) & 0x01) != 0 ? closed : thrown),
3629                50, ((l.getElement(10) & 0x02) != 0 ? closed : thrown),
3630                51, ((l.getElement(10) & 0x04) != 0 ? closed : thrown),
3631                52, ((l.getElement(10) & 0x08) != 0 ? closed : thrown),
3632                53, ((l.getElement(10) & 0x10) != 0 ? closed : thrown),
3633                54, ((l.getElement(10) & 0x20) != 0 ? closed : thrown),
3634                55, ((l.getElement(10) & 0x40) != 0 ? closed : thrown),
3635                56, thrown);
3636        opswGroup8 = Bundle.getMessage("LN_MSG_SLOT_CMD_STN_CFG_HELPER_EIGHT_OPSWS",
3637                57, ((l.getElement(11) & 0x01) != 0 ? closed : thrown),
3638                58, ((l.getElement(11) & 0x02) != 0 ? closed : thrown),
3639                59, ((l.getElement(11) & 0x04) != 0 ? closed : thrown),
3640                60, ((l.getElement(11) & 0x08) != 0 ? closed : thrown),
3641                61, ((l.getElement(11) & 0x10) != 0 ? closed : thrown),
3642                62, ((l.getElement(11) & 0x20) != 0 ? closed : thrown),
3643                63, ((l.getElement(11) & 0x40) != 0 ? closed : thrown),
3644                64, thrown);
3645        return Bundle.getMessage(((command == LnConstants.OPC_WR_SL_DATA)
3646                ? "LN_MSG_SLOT_CMD_STN_CFG_WRITE_REQ"
3647                : "LN_MSG_SLOT_CMD_STN_CFG_READ_REPORT"),
3648                opswGroup1, opswGroup2, opswGroup3, opswGroup4,
3649                opswGroup5, opswGroup6, opswGroup7, opswGroup8);
3650
3651    }
3652
3653    private static String interpretCmdStnExtCfgSlotRdWr(LocoNetMessage l, int command) {
3654    /*
3655     * ************************************************
3656     * Extended Configuration slot, holding op switches
3657     * ************************************************
3658     */
3659        String thrown = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_OPSW_HELPER_THROWN");
3660        String closed = Bundle.getMessage("LN_MSG_OPC_MULTI_SENSE_OPSW_HELPER_CLOSED");
3661
3662        String opswGroup1, opswGroup2, opswGroup3, opswGroup4,
3663                opswGroup5, opswGroup6, opswGroup7, opswGroup8;
3664        opswGroup1 = Bundle.getMessage("LN_MSG_SLOT_CMD_STN_CFG_HELPER_EIGHT_OPSWS",
3665                65, ((l.getElement(3) & 0x01) != 0 ? closed : thrown),
3666                66, ((l.getElement(3) & 0x02) != 0 ? closed : thrown),
3667                67, ((l.getElement(3) & 0x04) != 0 ? closed : thrown),
3668                68, ((l.getElement(3) & 0x08) != 0 ? closed : thrown),
3669                69, ((l.getElement(3) & 0x10) != 0 ? closed : thrown),
3670                70, ((l.getElement(3) & 0x20) != 0 ? closed : thrown),
3671                71, ((l.getElement(3) & 0x40) != 0 ? closed : thrown),
3672                72, thrown);
3673        opswGroup2 = Bundle.getMessage("LN_MSG_SLOT_CMD_STN_CFG_HELPER_EIGHT_OPSWS",
3674                73, ((l.getElement(4) & 0x01) != 0 ? closed : thrown),
3675                74, ((l.getElement(4) & 0x02) != 0 ? closed : thrown),
3676                75, ((l.getElement(4) & 0x04) != 0 ? closed : thrown),
3677                76, ((l.getElement(4) & 0x08) != 0 ? closed : thrown),
3678                77, ((l.getElement(4) & 0x10) != 0 ? closed : thrown),
3679                78, ((l.getElement(4) & 0x20) != 0 ? closed : thrown),
3680                79, ((l.getElement(4) & 0x40) != 0 ? closed : thrown),
3681                80, thrown);
3682        opswGroup3 = Bundle.getMessage("LN_MSG_SLOT_CMD_STN_CFG_HELPER_EIGHT_OPSWS",
3683                81, ((l.getElement(5) & 0x01) != 0 ? closed : thrown),
3684                82, ((l.getElement(5) & 0x02) != 0 ? closed : thrown),
3685                83, ((l.getElement(5) & 0x04) != 0 ? closed : thrown),
3686                84, ((l.getElement(5) & 0x08) != 0 ? closed : thrown),
3687                85, ((l.getElement(5) & 0x10) != 0 ? closed : thrown),
3688                86, ((l.getElement(5) & 0x20) != 0 ? closed : thrown),
3689                87, ((l.getElement(5) & 0x40) != 0 ? closed : thrown),
3690                88, thrown);
3691        opswGroup4 = Bundle.getMessage("LN_MSG_SLOT_CMD_STN_CFG_HELPER_EIGHT_OPSWS",
3692                89, ((l.getElement(6) & 0x01) != 0 ? closed : thrown),
3693                90, ((l.getElement(6) & 0x02) != 0 ? closed : thrown),
3694                91, ((l.getElement(6) & 0x04) != 0 ? closed : thrown),
3695                92, ((l.getElement(6) & 0x08) != 0 ? closed : thrown),
3696                93, ((l.getElement(6) & 0x10) != 0 ? closed : thrown),
3697                94, ((l.getElement(6) & 0x20) != 0 ? closed : thrown),
3698                95, ((l.getElement(6) & 0x40) != 0 ? closed : thrown),
3699                96, thrown);
3700        opswGroup5 = Bundle.getMessage("LN_MSG_SLOT_CMD_STN_CFG_HELPER_EIGHT_OPSWS",
3701                97, ((l.getElement(8) & 0x01) != 0 ? closed : thrown),
3702                98, ((l.getElement(8) & 0x02) != 0 ? closed : thrown),
3703                99, ((l.getElement(8) & 0x04) != 0 ? closed : thrown),
3704                100, ((l.getElement(8) & 0x08) != 0 ? closed : thrown),
3705                101, ((l.getElement(8) & 0x10) != 0 ? closed : thrown),
3706                102, ((l.getElement(8) & 0x20) != 0 ? closed : thrown),
3707                103, ((l.getElement(8) & 0x40) != 0 ? closed : thrown),
3708                104, thrown);
3709        opswGroup6 = Bundle.getMessage("LN_MSG_SLOT_CMD_STN_CFG_HELPER_EIGHT_OPSWS",
3710                105, ((l.getElement(9) & 0x01) != 0 ? closed : thrown),
3711                106, ((l.getElement(9) & 0x02) != 0 ? closed : thrown),
3712                107, ((l.getElement(9) & 0x04) != 0 ? closed : thrown),
3713                108, ((l.getElement(9) & 0x08) != 0 ? closed : thrown),
3714                109, ((l.getElement(9) & 0x10) != 0 ? closed : thrown),
3715                110, ((l.getElement(9) & 0x20) != 0 ? closed : thrown),
3716                111, ((l.getElement(9) & 0x40) != 0 ? closed : thrown),
3717                112, thrown);
3718        opswGroup7 = Bundle.getMessage("LN_MSG_SLOT_CMD_STN_CFG_HELPER_EIGHT_OPSWS",
3719                113, ((l.getElement(10) & 0x01) != 0 ? closed : thrown),
3720                114, ((l.getElement(10) & 0x02) != 0 ? closed : thrown),
3721                115, ((l.getElement(10) & 0x04) != 0 ? closed : thrown),
3722                116, ((l.getElement(10) & 0x08) != 0 ? closed : thrown),
3723                117, ((l.getElement(10) & 0x10) != 0 ? closed : thrown),
3724                118, ((l.getElement(10) & 0x20) != 0 ? closed : thrown),
3725                119, ((l.getElement(10) & 0x40) != 0 ? closed : thrown),
3726                120, thrown);
3727        opswGroup8 = Bundle.getMessage("LN_MSG_SLOT_CMD_STN_CFG_HELPER_EIGHT_OPSWS",
3728                121, ((l.getElement(11) & 0x01) != 0 ? closed : thrown),
3729                122, ((l.getElement(11) & 0x02) != 0 ? closed : thrown),
3730                123, ((l.getElement(11) & 0x04) != 0 ? closed : thrown),
3731                124, ((l.getElement(11) & 0x08) != 0 ? closed : thrown),
3732                125, ((l.getElement(11) & 0x10) != 0 ? closed : thrown),
3733                126, ((l.getElement(11) & 0x20) != 0 ? closed : thrown),
3734                127, ((l.getElement(11) & 0x40) != 0 ? closed : thrown),
3735                128, thrown);
3736        return Bundle.getMessage(((command == LnConstants.OPC_WR_SL_DATA)
3737                ? "LN_MSG_SLOT_CMD_STN_EXT_CFG_WRITE_REQ"
3738                : "LN_MSG_SLOT_CMD_STN_EXT_CFG_READ_REPORT"),
3739                opswGroup1, opswGroup2, opswGroup3, opswGroup4,
3740                opswGroup5, opswGroup6, opswGroup7, opswGroup8);
3741    }
3742
3743    private static String interpretStandardSlotRdWr(LocoNetMessage l, int id1, int id2, int command, int slot) {
3744
3745        /**
3746         * ************************************************
3747         * normal slot read/write message - see info above *
3748         * ************************************************
3749         */
3750        int trackStatus = l.getElement(7); // track status
3751        int stat = l.getElement(3); // slot status
3752        int adr = l.getElement(4); // loco address
3753        int spd = l.getElement(5); // command speed
3754        int dirf = l.getElement(6); // direction and F0-F4 bits
3755        String[] dirf0_4 = interpretF0_F4toStrings(dirf);
3756        int ss2 = l.getElement(8); // slot status 2 (tells how to use
3757        // ID1/ID2 & ADV Consist)
3758        int adr2 = l.getElement(9); // loco address high
3759        int snd = l.getElement(10); // Sound 1-4 / F5-F8
3760        String[] sndf5_8 = interpretF5_F8toStrings(snd);
3761
3762        String locoAdrStr = figureAddressIncludingAliasing(adr, adr2, ss2, id1, id2);
3763        return Bundle.getMessage(((command == LnConstants.OPC_WR_SL_DATA)
3764                ? "LN_MSG_SLOT_LOCO_INFO_WRITE"
3765                : "LN_MSG_SLOT_LOCO_INFO_READ"),
3766                slot,
3767                locoAdrStr,
3768                LnConstants.CONSIST_STAT(stat),
3769                LnConstants.LOCO_STAT(stat),
3770                LnConstants.DEC_MODE(stat),
3771                directionOfTravelString((dirf & LnConstants.DIRF_DIR) == 0),
3772                spd, // needs re-interpretation for some cases of slot consisting state
3773                dirf0_4[0],
3774                dirf0_4[1],
3775                dirf0_4[2],
3776                dirf0_4[3],
3777                dirf0_4[4],
3778                sndf5_8[0],
3779                sndf5_8[1],
3780                sndf5_8[2],
3781                sndf5_8[3],
3782                trackStatusByteToString(trackStatus),
3783                Bundle.getMessage("LN_MSG_SLOT_HELPER_SS2_SIMPLE",
3784                        Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
3785                                StringUtil.twoHexFromInt(ss2))),
3786                Bundle.getMessage("LN_MSG_SLOT_HELPER_ID1_ID2_AS_THROTTLE_ID",
3787                        idString(id1, id2)));
3788    }
3789
3790    private static String interpretOpcPanelResponse(LocoNetMessage l) {
3791        switch (l.getElement(1)) {
3792            case 0x12: {
3793                // Bit 3 (0x08 in hex) is set by every UR-92 we've ever captured.
3794                // The hypothesis is this indicates duplex enabled, but this has
3795                // not been confirmed with Digitrax.
3796                return Bundle.getMessage("LN_MSG_OPC_D7_TETHERLESS_REPORT_UR92",
3797                        l.getElement(3) & 0x07,
3798                        ((l.getElement(3) & 0x08) == 0x08
3799                        ? Bundle.getMessage("LN_MSG_HELPER_D7_UR92_DUPLEX")
3800                        : ""));
3801            }
3802            case 0x17: {
3803                return Bundle.getMessage("LN_MSG_OPC_D7_TETHERLESS_REPORT_UR90",
3804                        l.getElement(3) & 0x07);
3805            }
3806            case 0x1F: {
3807                return Bundle.getMessage("LN_MSG_OPC_D7_TETHERLESS_REPORT_UR91",
3808                        l.getElement(3) & 0x07);
3809            }
3810            case 0x22: {
3811                return Bundle.getMessage("LN_MSG_OPC_D7_TETHERLESS_REPORT_UR93",
3812                        l.getElement(3) & 0x07);
3813            }
3814           default: {
3815                return "";
3816            }
3817        }
3818    }
3819
3820    private static String interpretOpcLissyUpdate(LocoNetMessage l) {
3821        /*
3822         * OPC_LISSY_UPDATE   0xE4
3823         *
3824         * LISSY is an automatic train detection system made by Uhlenbrock.
3825         * All documentation appears to be in German.
3826         * Also used by LocoIO-based RFID readers eg. GCA51
3827         */
3828        log.debug("Message from LISSY: {}", Bundle.getMessage("LN_MONITOR_MESSAGE_RAW_HEX_INFO", l.toString()));
3829        switch (l.getElement(1)) {
3830            case 0x08: // Format LISSY message
3831                int unit = (l.getElement(4) & 0x7F);
3832                if ((l.getElement(3) & 0x40) != 0) { // Loco movement
3833                    int category = l.getElement(2) + 1;
3834                    int address = (l.getElement(6) & 0x7F) + 128 * (l.getElement(5) & 0x7F);
3835                    return Bundle.getMessage("LN_MSG_LISSY_IR_REPORT_LOCO_MOVEMENT",
3836                          unit,
3837                          Integer.toString(address),
3838                          Integer.toString(category),
3839                          ((l.getElement(3) & 0x20) == 0
3840                          ? Bundle.getMessage("LN_MSG_LISSY_IR_REPORT_HELPER_DIRECTION_NORTH")
3841                          : Bundle.getMessage("LN_MSG_LISSY_IR_REPORT_HELPER_DIRECTION_SOUTH")));
3842                } else { // other messages
3843                    switch (l.getElement(2)) {
3844                      case 0x00: // Loco speed
3845                          int speed = (l.getElement(6) & 0x7F) + 128 * (l.getElement(5) & 0x7F);
3846                          return Bundle.getMessage("LN_MSG_LISSY_IR_REPORT_LOCO_SPEED",
3847                                  unit,
3848                                  Integer.toString(speed));
3849
3850                      case 0x01: // Block status
3851                        return Bundle.getMessage("LN_MSG_LISSY_BLOCK_REPORT",
3852                                unit,
3853                                ((l.getElement(6) & 0x01) == 0
3854                                ? Bundle.getMessage("LN_MSG_LISSY_IR_REPORT_HELPER_BLOCK_FREE")
3855                                : Bundle.getMessage("LN_MSG_LISSY_IR_REPORT_HELPER_BLOCK_OCCUPIED")));
3856                      default:
3857                          break;
3858                    }
3859                }
3860                break;
3861
3862            case 0x0A: // Format special message
3863                int element = l.getElement(2) * 128 + l.getElement(3);
3864                int stat1 = l.getElement(5);
3865                int stat2 = l.getElement(6);
3866                String status;
3867                switch (stat1 & 0x30) {
3868                    case 0x30:
3869                        status = Bundle.getMessage("LN_MSG_SE_REPORT_HELPER_BOTH_RES");
3870                        break;
3871                    case 0x10:
3872                        status = Bundle.getMessage("LN_MSG_SE_REPORT_HELPER_AX_RES");
3873                        break;
3874                    case 0x20:
3875                        status = Bundle.getMessage("LN_MSG_SE_REPORT_HELPER_XA_RES");
3876                        break;
3877                    default:
3878                        status = Bundle.getMessage("LN_MSG_SE_REPORT_HELPER_NO_RES");
3879                        break;
3880                }
3881
3882                return Bundle.getMessage("LN_MSG_SE_REPORT",
3883                        (element + 1), element,
3884                        l.getElement(7), l.getElement(8),
3885                        status,
3886                        Bundle.getMessage(((stat2 & 0x01) != 0)
3887                                ? "LN_MSG_SWITCH_STATE_THROWN"
3888                                : "LN_MSG_SWITCH_STATE_CLOSED"),
3889                        Bundle.getMessage(((stat1 & 0x01) != 0)
3890                                ? "LN_MSG_SE_REPORT_HELPER_OCCUPIED"
3891                                : "LN_MSG_SE_REPORT_HELPER_UNOCCUPIED"));
3892            case 0x09:
3893                if (l.getElement(4) == 0x00) {
3894                    return Bundle.getMessage("LN_MSG_UNRECOGNIZED_SIG_STATE_REPORT_MAY_BE_FROM_CML_HW")+
3895                                Bundle.getMessage("LN_MONITOR_MESSAGE_RAW_HEX_INFO", l.toString());
3896                }
3897                break;
3898            case 0x0C:
3899            case 0x0E:
3900                if (l.getElement(2) == 0x41 ) {
3901                    // RFID-5 or 7 reader report
3902                    // RFID-7 reader report [E4 0E 41 00 02 04 3A 4B 4A 60 60 01 50 38]
3903                    // elem[3] = sensorAddr >> 7; addr.high
3904                    // elem[4] = sensorAddr & 0x7F; addr.low
3905                    StringBuilder tg = new StringBuilder();
3906                    int max = l.getElement(1) - 2; // GCA51 RFID-7 elem(1) = size = 0x0E; RFID-5 elem(1) = size = 0x0C
3907                    int rfidHi = l.getElement(max); // MSbits are transmitted via element(max)
3908                    for (int j = 5; j < max; j++) {
3909                        int shift = j-5;
3910                        int hi = 0x0;
3911                        if(((rfidHi >> shift) & 0x1) == 1) hi = 0x80;
3912                        tg.append(String.format("%1$02X", l.getElement(j) + hi));
3913                    }
3914                    int portAddress = l.getElement(3) << 7 | l.getElement(4);
3915                    int msgType = 7;
3916                    if (max == 9) msgType = 5;
3917                    return Bundle.getMessage("LN_MSG_LISSY_RFIDX_REPORT", msgType, portAddress, tg.toString());
3918                }
3919                break;
3920            default:
3921                break;
3922        }
3923        return "";
3924    }
3925
3926    private static String interpretOpcImmPacket(LocoNetMessage l) {
3927        String result;
3928        result = "";
3929
3930        /*
3931         * OPC_IMM_PACKET   0xED
3932         */
3933        if (l.getElement(1) == 0x0F) { // length = 15
3934            // check for a specific type - Uhlenbrock LNSV Programming messages format
3935            result = interpretLncvMessage(l);
3936            if (result.length() > 0) {
3937                return result;
3938            }
3939
3940            return "";
3941        }
3942
3943        /* Else:
3944         * OPC_IMM_PACKET   0xED   ;SEND n-byte packet immediate LACK
3945         *                         ; Follow on message: LACK
3946         *                         ; <0xED>,<0B>,<7F>,<REPS>,<DHI>,<IM1>,<IM2>,
3947         *                         ;        <IM3>,<IM4>,<IM5>,<CHK>
3948         *                         ;   <DHI>=<0,0,1,IM5.7-IM4.7,IM3.7,IM2.7,IM1.7>
3949         *                         ;   <REPS>  D4,5,6=#IM bytes,
3950         *                         ;           D3=0(reserved);
3951         *                         ;           D2,1,0=repeat CNT
3952         *                         ; IF Not limited MASTER then
3953         *                         ;   LACK=<B4>,<7D>,<7F>,<chk> if CMD ok
3954         *                         ; IF limited MASTER then Lim Masters respond
3955         *                         ;   with <B4>,<7E>,<lim adr>,<chk>
3956         *                         ; IF internal buffer BUSY/full respond
3957         *                         ;   with <B4>,<7D>,<0>,<chk>
3958         *                         ;   (NOT IMPLEMENTED IN DT200)
3959         *
3960         * This sends a raw NMRA packet across the LocoNet.
3961         *
3962         * Page 11 of LocoNet Personal Edition v1.0.
3963         *
3964         * Decodes for the F9-F28 functions taken from the NMRA standards and
3965         * coded by Leo Bicknell.
3966         */
3967
3968        // sendPkt = (sendPktMsg *) msgBuf;
3969        int val7f = l.getElement(2);
3970        /* fixed value of 0x7f */
3971
3972        int reps = l.getElement(3);
3973        /* repeat count */
3974
3975        int dhi = l.getElement(4);
3976        /* high bits of data bytes */
3977
3978        int im1 = l.getElement(5);
3979        int im2 = l.getElement(6);
3980        int im3 = l.getElement(7);
3981        int im4 = l.getElement(8);
3982        int im5 = l.getElement(9);
3983        int mobileDecoderAddress = -999;
3984        int nmraInstructionType = -999;
3985        int nmraSubInstructionType = -999;
3986        int playableWhistleLevel = -999;
3987
3988        // see if it really is a 'Send Packet' as defined in LocoNet PE
3989        if ((val7f == 0x7f) && (l.getElement(1) == 0x0B)) {
3990            int len = ((reps & 0x70) >> 4);
3991            if (len < 2) {
3992                return ""; // no valid NMRA packets of less than 2 bytes.
3993            }
3994            // duplication of packet data as packetInt was deemed necessary
3995            // due to issues with msBit loss when converting from "byte" to
3996            // integral forms
3997            byte[] packet = new byte[len];
3998            int[] packetInt = new int[len];
3999            packet[0] = (byte) (im1 + ((dhi & 0x01) != 0 ? 0x80 : 0));
4000            packetInt[0] = (im1 + ((dhi & 0x01) != 0 ? 0x80 : 0));
4001
4002            // len >= 2 always true at this point
4003            packet[1] = (byte) (im2 + ((dhi & 0x02) != 0 ? 0x80 : 0));
4004            packetInt[1] = (im2 + ((dhi & 0x02) != 0 ? 0x80 : 0));
4005
4006            if (len >= 3) {
4007                packet[2] = (byte) (im3 + ((dhi & 0x04) != 0 ? 0x80 : 0));
4008                packetInt[2] = (im3 + ((dhi & 0x04) != 0 ? 0x80 : 0));
4009            }
4010            if (len >= 4) {
4011                packet[3] = (byte) (im4 + ((dhi & 0x08) != 0 ? 0x80 : 0));
4012                packetInt[3] = (im4 + ((dhi & 0x08) != 0 ? 0x80 : 0));
4013            }
4014            if (len >= 5) {
4015                packet[4] = (byte) (im5 + ((dhi & 0x10) != 0 ? 0x80 : 0));
4016                packetInt[4] = (im5 + ((dhi & 0x10) != 0 ? 0x80 : 0));
4017            }
4018
4019            int address;
4020            // compute some information which is useful for decoding
4021            // the "Playable" whistle message
4022            // Information reverse-engineered by B. Milhaupt and used with permission
4023            if ((packetInt[0] & 0x80) == 0x0) {
4024                // immediate packet addresses a 7-bit multi-function (mobile) decoder
4025                mobileDecoderAddress = packetInt[0];
4026                nmraInstructionType = (packetInt[1] & 0xE) >> 5;
4027                nmraSubInstructionType = (packetInt[1] & 0x1f);
4028                if ((nmraSubInstructionType == 0x1d) && (packetInt[2] == 0x7f)) {
4029                    playableWhistleLevel = packetInt[3];
4030                }
4031            } else if ((packetInt[0] & 0xC0) == 0xC0) {
4032                // immediate packet addresses a 14-bit multi-function (mobile) decoder
4033                mobileDecoderAddress = ((packetInt[0] & 0x3F) << 8) + packetInt[1];
4034                nmraInstructionType = (packetInt[2] & 0xE0) >> 5;
4035                nmraSubInstructionType = (packetInt[2] & 0x1f);
4036                if ((nmraSubInstructionType == 0x1d) && (packetInt[3] == 0x7f)) {
4037                    playableWhistleLevel = packetInt[4];
4038                }
4039            } else {
4040                // immediate packet not addressed to a multi-function (mobile) decoder
4041            }
4042            if ((mobileDecoderAddress >= 0)
4043                    && (nmraInstructionType == 1)
4044                    && (nmraSubInstructionType == 0x1D)) {
4045                // the "Playable" whistle message
4046                // Information reverse-engineered by B. Milhaupt and used with permission
4047                return Bundle.getMessage("LN_MSG_PLAYABLE_WHISTLE_CONTROL",
4048                        Integer.toString(mobileDecoderAddress),
4049                        playableWhistleLevel,
4050                        (reps & 0x7));
4051            }
4052
4053            // F9-F28 w/a long address.
4054            if ((packetInt[0] & 0xC0) == 0xC0) {
4055                address = ((packetInt[0] & 0x3F) << 8) + packetInt[1];
4056
4057                if ((packetInt[2] & 0xFF) == 0xDF) {
4058                    // Functions 21-28
4059                    return Bundle.getMessage("LN_MSG_SEND_PACKET_IMM_SET_F21_TO_F28",
4060                            Integer.toString(address),
4061                            Bundle.getMessage(((packetInt[3] & 0x01) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4062                            Bundle.getMessage(((packetInt[3] & 0x02) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4063                            Bundle.getMessage(((packetInt[3] & 0x04) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4064                            Bundle.getMessage(((packetInt[3] & 0x08) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4065                            Bundle.getMessage(((packetInt[3] & 0x10) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4066                            Bundle.getMessage(((packetInt[3] & 0x20) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4067                            Bundle.getMessage(((packetInt[3] & 0x40) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4068                            Bundle.getMessage(((packetInt[3] & 0x80) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")));
4069                } else if ((packetInt[2] & 0xFF) == 0xDE) {
4070                    // Functions 13-20
4071                    return Bundle.getMessage("LN_MSG_SEND_PACKET_IMM_SET_F13_TO_F20",
4072                            Integer.toString(address),
4073                            Bundle.getMessage((((packetInt[3] & 0x01) != 0) ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4074                            Bundle.getMessage((((packetInt[3] & 0x02) != 0) ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4075                            Bundle.getMessage((((packetInt[3] & 0x04) != 0) ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4076                            Bundle.getMessage((((packetInt[3] & 0x08) != 0) ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4077                            Bundle.getMessage((((packetInt[3] & 0x10) != 0) ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4078                            Bundle.getMessage((((packetInt[3] & 0x20) != 0) ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4079                            Bundle.getMessage((((packetInt[3] & 0x40) != 0) ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4080                            Bundle.getMessage((((packetInt[3] & 0x80) != 0) ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")));
4081                } else if ((packetInt[2] & 0xF0) == 0xA0) {
4082                    // Functions 9-12
4083                    return Bundle.getMessage("LN_MSG_SEND_PACKET_IMM_SET_F9_TO_F12",
4084                            Integer.toString(address),
4085                            Bundle.getMessage((((packetInt[2] & 0x01) != 0) ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4086                            Bundle.getMessage((((packetInt[2] & 0x02) != 0) ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4087                            Bundle.getMessage((((packetInt[2] & 0x04) != 0) ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4088                            Bundle.getMessage((((packetInt[2] & 0x08) != 0) ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")));
4089                } else {
4090                    return Bundle.getMessage("LN_MSG_OPC_IMM_PKT_GENERIC",
4091                            ((reps & 0x70) >> 4),
4092                            (reps & 0x07),
4093                            reps,
4094                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
4095                                    StringUtil.twoHexFromInt(dhi)),
4096                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
4097                                    StringUtil.twoHexFromInt(im1)),
4098                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
4099                                    StringUtil.twoHexFromInt(im2)),
4100                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
4101                                    StringUtil.twoHexFromInt(im3)),
4102                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
4103                                    StringUtil.twoHexFromInt(im4)),
4104                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
4105                                    StringUtil.twoHexFromInt(im5)),
4106                            NmraPacket.format(packet));
4107                }
4108            } else { // F9-F28 w/a short address.
4109                address = packetInt[0];
4110                if ((packetInt[1] & 0xFF) == 0xDF) {
4111                    // Functions 21-28
4112                    return Bundle.getMessage("LN_MSG_SEND_PACKET_IMM_SET_F21_TO_F28",
4113                            address,
4114                            Bundle.getMessage(((packetInt[2] & 0x01) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4115                            Bundle.getMessage(((packetInt[2] & 0x02) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4116                            Bundle.getMessage(((packetInt[2] & 0x04) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4117                            Bundle.getMessage(((packetInt[2] & 0x08) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4118                            Bundle.getMessage(((packetInt[2] & 0x10) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4119                            Bundle.getMessage(((packetInt[2] & 0x20) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4120                            Bundle.getMessage(((packetInt[2] & 0x40) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4121                            Bundle.getMessage(((packetInt[2] & 0x80) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")));
4122
4123                } else if ((packetInt[1] & 0xFF) == 0xDE) {
4124                    // Functions 13-20
4125                    return Bundle.getMessage("LN_MSG_SEND_PACKET_IMM_SET_F13_TO_F20",
4126                            address,
4127                            Bundle.getMessage(((packetInt[2] & 0x01) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4128                            Bundle.getMessage(((packetInt[2] & 0x02) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4129                            Bundle.getMessage(((packetInt[2] & 0x04) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4130                            Bundle.getMessage(((packetInt[2] & 0x08) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4131                            Bundle.getMessage(((packetInt[2] & 0x10) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4132                            Bundle.getMessage(((packetInt[2] & 0x20) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4133                            Bundle.getMessage(((packetInt[2] & 0x40) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4134                            Bundle.getMessage(((packetInt[2] & 0x80) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")));
4135                } else if ((packetInt[1] & 0xF0) == 0xA0) {
4136                    // Functions 9-12
4137                    return Bundle.getMessage("LN_MSG_SEND_PACKET_IMM_SET_F9_TO_F12",
4138                            address,
4139                            Bundle.getMessage(((packetInt[1] & 0x01) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4140                            Bundle.getMessage(((packetInt[1] & 0x02) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4141                            Bundle.getMessage(((packetInt[1] & 0x04) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4142                            Bundle.getMessage(((packetInt[1] & 0x08) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")));
4143                } else {
4144                    // Unknown
4145                    if ((packetInt[0] & 0xC0) == 0x80 ) {
4146                        /*
4147                        * 2.4.7 Extended Decoder Control Packet address for
4148                        * operations mode programming and Aspect Setting.
4149                        * 10AAAAAA 0 0AAA0AA1
4150                        * Packets 3 bytes in length are Accessory Aspect packets (2.4.3)
4151                        *  {preamble} 10AAAAAA 0 0AAA0AA1 0 XXXXXXXX 0 EEEEEEEE 1
4152                        * Please note that the use of 0 in bit 3 of byte 2 is to
4153                        * ensure that this packet cannot be confused with the
4154                        * legacy accessory-programming packets. The resulting packet
4155                        * would be:
4156                        * {preamble} 10AAAAAA 0 0AAA0AA1 0 (1110CCVV 0 VVVVVVVV 0 DDDDDDDD) 0 EEEEEEEE 1
4157                        * A5 43 00
4158                        * 10100101 0010011 00000000
4159                        * Signal Decoder Address (Configuration Variable Access Instruction) Error Byte
4160                        */
4161                        log.debug("Is an Extended Accessory Ops-mode CV access or Set Signal Aspect");
4162                        log.debug("   LocoNet message: {} {} {} {} {} {} {} {} {} {} {}",
4163                                StringUtil.twoHexFromInt(l.getElement(0)),
4164                                StringUtil.twoHexFromInt(l.getElement(1)),
4165                                StringUtil.twoHexFromInt(l.getElement(2)),
4166                                StringUtil.twoHexFromInt(l.getElement(3)),
4167                                StringUtil.twoHexFromInt(l.getElement(4)),
4168                                StringUtil.twoHexFromInt(l.getElement(5)),
4169                                StringUtil.twoHexFromInt(l.getElement(6)),
4170                                StringUtil.twoHexFromInt(l.getElement(7)),
4171                                StringUtil.twoHexFromInt(l.getElement(8)),
4172                                StringUtil.twoHexFromInt(l.getElement(9)),
4173                                StringUtil.twoHexFromInt(l.getElement(10))
4174                                );
4175                        if (packetInt.length == 5) {
4176                            log.debug("   NMRA packet: {} {} {} {} {}",
4177                                    StringUtil.twoHexFromInt(packetInt[0]),
4178                                    StringUtil.twoHexFromInt(packetInt[1]),
4179                                    StringUtil.twoHexFromInt(packetInt[2]),
4180                                    StringUtil.twoHexFromInt(packetInt[3]),
4181                                    StringUtil.twoHexFromInt(packetInt[4])
4182                                    );
4183                        } else if (packetInt.length == 3) {
4184                            log.debug("   NMRA packet: {} {} {}",
4185                                    StringUtil.twoHexFromInt(packetInt[0]),
4186                                    StringUtil.twoHexFromInt(packetInt[1]),
4187                                    StringUtil.twoHexFromInt(packetInt[2])
4188                                    );
4189                        } else {
4190                            log.warn(" Unknown Extended Accessory Packet length [{}])",packetInt.length);
4191                            return "";
4192                        }
4193                        if ((packetInt[2] & 0xF0) == 0xF0) {
4194                            /*
4195                            * 2.3.7.2 Configuration Variable Access Instruction - Short Form
4196                            * This instruction has the format of:
4197                            * {instruction bytes} = 1111GGGG 0 DDDDDDDD 0 DDDDDDDD
4198                            * The 8 bit data DDDDDDDD is placed in the configuration
4199                            * variable identified by GGGG according
4200                            */
4201                            log.debug("Is an Short-form Extended Accessory Ops-mode CV access");
4202                        }
4203                        if ((packetInt[2] & 0xf0) == 0xe0) {
4204                            /*
4205                            * 2.3.7.3 Configuration Variable Access Instruction - Long Form
4206                            * The long form allows the direct manipulation of all CVs8. This
4207                            * instruction is valid both when the Digital Decoder has its
4208                            * long address active and short address active. Digital Decoders
4209                            * shall not act on this instruction if sent to its consist
4210                            * address.
4211                            *
4212                            * The format of the instructions using Direct CV
4213                            * addressing is:
4214                            *   {instruction bytes}= 1110GGVV 0 VVVVVVVV 0 DDDDDDDD
4215                            *
4216                            * The actual Configuration Variable desired is selected
4217                            * via the 10-bit address with the 2-bit address (VV) in
4218                            * the first data byte being the most significant bits of
4219                            * the address. The Configuration variable being addressed
4220                            * is the provided 10-bit address plus 1. For example, to
4221                            * address CV1 the 10 bit address is "00 00000000".
4222                            *
4223                            * The defined values for Instruction type (CC) are:
4224                            *       GG=00 Reserved for future use
4225                            *       GG=01 Verify byte 505
4226                            *       GG=11 Write byte
4227                            *       GG=10 Bit manipulation
4228                            * */
4229                            int addr = getExtendedAccessoryAddressFromDCCPacket(packetInt);
4230                            log.debug("Long-format Extended Accessory Ops-mode CV access: Extended Acceccory Address {}", addr);
4231                            int cvnum = 1 + ((packetInt[2] & 0x03) << 8) + (packetInt[3] & 0xff);
4232                            switch (packetInt[2] & 0x0C) {
4233                                case 0x04:
4234                                    //  GG=01 Verify byte
4235                                    /*
4236                                     * Type = "01" VERIFY BYTE
4237                                     *
4238                                     * The contents of the Configuration Variable as indicated
4239                                     * by the 10-bit address are compared with the data byte
4240                                     * (DDDDDDDD). If the decoder successfully receives this
4241                                     * packet and the values are identical, the Digital
4242                                     * Decoder shall respond with the contents of the CV as
4243                                     * the Decoder Response Transmission, if enabled.
4244                                    */
4245                                    log.debug("CV # {}, Verify Byte: {}", cvnum, packetInt[4]);
4246                                    return Bundle.getMessage("LN_MSG_EXTEND_ACCY_CV_VERIFY",
4247                                            addr, cvnum, packetInt[4] );
4248                                case 0x08:
4249                                    // GG=10 Bit manipulation
4250                                    /*
4251                                     * Type = "10" BIT MANIPULATION.
4252                                     *
4253                                     * The bit manipulation instructions use a special
4254                                     * format for the data byte (DDDDDDDD): 111FDBBB, where
4255                                     * BBB represents the bit position within the CV,
4256                                     * D contains the value of the bit to be verified
4257                                     * or written, and F describes whether the
4258                                     * operation is a verify bit or a write bit
4259                                     * operation.
4260                                     *
4261                                     * F = "1" : WRITE BIT
4262                                     * F = "0" : VERIFY BIT
4263                                     * The VERIFY BIT and WRITE BIT instructions operate
4264                                     * in a manner similar to the VERIFY BYTE and WRITE
4265                                     * BYTE instructions (but operates on a single bit).
4266                                     * Using the same criteria as the VERIFY BYTE
4267                                     * instruction, an operations mode acknowledgment
4268                                     * will be generated in response to a VERIFY BIT
4269                                     * instruction if appropriate. Using the same
4270                                     * criteria as the WRITE BYTE instruction, a
4271                                     * configuration variable access acknowledgment
4272                                     * will be generated in response to the second
4273                                     * identical WRITE BIT instruction if appropriate.
4274                                     */
4275                                    if ((packetInt[4]& 0xE0) != 0xE0) {
4276                                        break;
4277                                    }
4278                                    log.debug("CV # {}, Bit Manipulation: {} {} (of bits 0-7) with {}",
4279                                            cvnum, (packetInt[4] & 0x10) == 0x10 ? "Write" : "Verify",
4280                                            (packetInt[4] & 0x7),
4281                                            (packetInt[4] >> 3) & 0x1);
4282
4283                                    // "Extended Accessory Decoder CV Bit {} bit,
4284                                    // Address {}, CV {}, bit # {} (of bits 0-7)
4285                                    // with value {}.\n"
4286                                    return Bundle.getMessage("LN_MSG_EXTEND_ACCY_CV_BIT_ACCESS",
4287                                            ((packetInt[4] & 0x10) == 0x10 ? "Write" : "Verify"),
4288                                            addr, cvnum, (packetInt[4] & 0x7),
4289                                            ((packetInt[4] >>3) & 0x1) );
4290                                case 0x0c:
4291                                    // GG=11 Write byte
4292                                    /*
4293                                     * Type = "11" WRITE BYTE
4294                                     *
4295                                     * The contents of the Configuration Variable as indicated by the 10-bit
4296                                     * address are replaced by the data byte (DDDDDDDD). Two identical
4297                                     * packets are needed before the decoder shall modify a
4298                                     * configuration variable. These two packets need not be back
4299                                     * to back on the track. However any other packet to the same
4300                                     * decoder will invalidate the write operation. (This includes
4301                                     * broadcast packets.) If the decoder successfully receives
4302                                     * this second identical packet, it shall respond with a
4303                                     * configuration variable access acknowledgment.
4304                                     */
4305                                    log.debug("CV # {}, Write Byte: {}", cvnum, packetInt[4]);
4306                                    return Bundle.getMessage("LN_MSG_EXTEND_ACCY_CV_WRITE",
4307                                            addr, cvnum, packetInt[4] );
4308                                case 0x0:
4309                                default:
4310                                    // GG=00 Reserved for future use
4311                                    log.debug("CV # {}, Reserved (GG=0); {}", cvnum, packetInt[4]);
4312                            }
4313                        } else if (packetInt.length == 3) {
4314                            int addr = getExtendedAccessoryAddressFromDCCPacket(packetInt);
4315                            return Bundle.getMessage("LN_MSG_EXTEND_ACCY_SET_ASPECT",
4316                                    addr, addr - 4, packetInt[2] );
4317                        }
4318                    }
4319                    return Bundle.getMessage("LN_MSG_OPC_IMM_PKT_GENERIC",
4320                            ((reps & 0x70) >> 4),
4321                            (reps & 0x07),
4322                            reps,
4323                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
4324                                    StringUtil.twoHexFromInt(dhi)),
4325                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
4326                                    StringUtil.twoHexFromInt(im1)),
4327                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
4328                                    StringUtil.twoHexFromInt(im2)),
4329                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
4330                                    StringUtil.twoHexFromInt(im3)),
4331                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
4332                                    StringUtil.twoHexFromInt(im4)),
4333                            Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
4334                                    StringUtil.twoHexFromInt(im5)),
4335                            NmraPacket.format(packet));
4336                }
4337            } // else { // F9-F28 w/a short address.
4338        } else if (l.getElement(1) == 0x1F) {
4339            if (l.getElement(2) == 0x01 && l.getElement(3) == 0x49 && l.getElement(4) == 0x42
4340                    && l.getElement(6) != 0x5E && l.getElement(10) == 0x70 && l.getElement(11) == 0x00 && l.getElement(15) == 0x10) {
4341                // Uhlenbrock IB-COM / Intellibox I and II read or write CV value on programming track
4342                String cv = Integer.toString(l.getElement(8) * 256 + ((l.getElement(5) & 0x02) * 64) + l.getElement(7));
4343                int val = l.getElement(9) + 16 * (l.getElement(5) & 0x08);
4344                switch (l.getElement(6)) {
4345                    case 0x6C:
4346                        return Bundle.getMessage("LN_MSG_UHLEN_READ_CV_REG_MODE_FROM_PT", cv);
4347                    case 0x6D:
4348                        return Bundle.getMessage("LN_MSG_UHLEN_WRITE_CV_REG_MODE_FROM_PT", cv);
4349                    case 0x6E:
4350                        return Bundle.getMessage("LN_MSG_UHLEN_READ_CV_PAGED_MODE_FROM_PT", cv);
4351                    case 0x6F:
4352                        return Bundle.getMessage("LN_MSG_UHLEN_WRITE_CV_PAGED_MODE_FROM_PT", cv);
4353                    case 0x71:
4354                        return Bundle.getMessage("LN_MSG_UHLEN_WRITE_CV_DIRECT_BYTE_MODE_FROM_PT",
4355                                cv, val);
4356                    case 0x70: // observed on Intellibox II, even though it does not work on IB-COM
4357                    case 0x72:
4358                        return Bundle.getMessage("LN_MSG_UHLEN_READ_CV_DIRECT_BYTE_MODE_FROM_PT", cv);
4359                    default:
4360                        break;
4361                }
4362                return "";
4363            } else if (l.getElement(2) == 0x01 && l.getElement(3) == 0x49 && l.getElement(4) == 0x42
4364                    && l.getElement(6) == 0x5E) {
4365                // Uhlenbrock IB-COM / Intellibox I and II write CV value on main track
4366                int addr = l.getElement(8) * 256 + ((l.getElement(5) & 0x02) * 64) + l.getElement(7);
4367                String cv = Integer.toString(l.getElement(11) * 256 + ((l.getElement(5) & 0x08) << 4) + l.getElement(9));
4368                int val = ((l.getElement(10) & 0x02) << 6) + l.getElement(12);
4369                return Bundle.getMessage("LN_MSG_UHLEN_CV_OPS_MODE_WRITE",
4370                        addr, cv, val);
4371            }
4372        }
4373        return ""; // not an understood message.
4374    }
4375
4376    /*
4377     * Returns the Digitrax Extended Accessory Packet Address
4378     */
4379    private static int getExtendedAccessoryAddressFromDCCPacket(int[] packetInt) {
4380        return ( 1 + ((packetInt[0] & 0x3F) << 2) +
4381                ((( ~ packetInt[1]) & 0x70) << 4)
4382                + ((packetInt[1] & 0x06) >> 1));
4383    }
4384
4385    private static String interpretOpcPr3Mode(LocoNetMessage l) {
4386        /*
4387         * Sets the operating mode of the PR3 device, if present.
4388         *
4389         * Information reverse-engineered by B. Milhaupt and used with permission
4390         */
4391
4392        if ((l.getElement(1) == 0x10) && ((l.getElement(2) & 0x7c) == 0)
4393                && (l.getElement(3) == 0) && (l.getElement(4) == 0)) {
4394            // set PR3 mode of operation, where LS 2 bits of byte 2 are encoded as:
4395            // 0x00 Set the PR3 mode to MS100 interface mode with PR3 LocoNet termination disabled
4396            // 0x01 Set the PR3 to decoder programming track mode
4397            // 0x03 Set the PR3 to MS100 interface mode with PR3 LocoNet termination enabled
4398
4399            switch (l.getElement(2) & 0x3) {
4400                case 0x00: {
4401                    return Bundle.getMessage("LN_MSG_SET_PR3_MODE_LOCONET_IF_WITHOUT_TERM");
4402                }
4403                case 0x02: {
4404                    return Bundle.getMessage("LN_MSG_SET_PR3_MODE_PR3_PROGRAMMING_TRACK_ONLY");
4405                }
4406                case 0x03: {
4407                    return Bundle.getMessage("LN_MSG_SET_PR3_MODE_LN_MSG_SET_PR3_MODE_LOCONET_IF_WITH_TERM");
4408                }
4409                default: {
4410                    break;
4411                }
4412            }
4413        }
4414        return "";
4415    }
4416
4417    private static String interpretIb2Special(LocoNetMessage l) {
4418        // Intellibox function control message for mobile decoder F0-F28 (IB-I) and F13-F28 (IB-II)
4419        if ((l.getElement(1) == LnConstants.RE_IB2_SPECIAL_FUNCS_TOKEN)
4420                && ((l.getElement(3) == LnConstants.RE_IB1_SPECIAL_F5_F11_TOKEN)
4421                || (l.getElement(3) == LnConstants.RE_IB2_SPECIAL_F13_F19_TOKEN)
4422                || (l.getElement(3) == LnConstants.RE_IB2_SPECIAL_F21_F27_TOKEN))) {
4423            // Intellibox-I function control message for mobile decoder F5 thru F27 except F12 and F20
4424            // Intellibox-II function control message for mobile decoder F13 thru F27 except F20
4425            // Note: Intellibox-II documentation implies capability to control
4426            // MANY more functions.  This capability may be extended by
4427            // additional tokens in element 3, including the special-case encoding
4428            // for the "eighth bit" as handled in the following case, below,
4429            // for F12, F20 & F28
4430            int funcOffset = 5 + 8 * (l.getElement(3) - LnConstants.RE_IB1_SPECIAL_F5_F11_TOKEN);
4431            String encodingType;
4432            if (l.getElement(3) == LnConstants.RE_IB1_SPECIAL_F5_F11_TOKEN) {
4433                encodingType = Bundle.getMessage("LN_MSG_INTELLIBOX_FUNC_CTL_HELPER_IB1");
4434            } else {
4435                encodingType = Bundle.getMessage("LN_MSG_INTELLIBOX_FUNC_CTL_HELPER_IB2");
4436            }
4437            String funcInfo[] = new String[7];
4438            int mask = 1;
4439            for (int i = 0; i < 7; i++) {
4440                // handle 7 bits of data
4441                funcInfo[i] = Bundle.getMessage("LN_MSG_INTELLIBOX_FUNC_CTL_HELPER_INDIV_FUNC",
4442                        funcOffset + i,
4443                        Bundle.getMessage(((l.getElement(4) & mask) != 0)
4444                                ? "LN_MSG_FUNC_ON"
4445                                : "LN_MSG_FUNC_OFF"));
4446                mask *= 2;
4447            }
4448            return Bundle.getMessage("LN_MSG_INTELLIBOX_FUNC_CTL",
4449                    encodingType, l.getElement(2), funcInfo[0],
4450                    funcInfo[1], funcInfo[2], funcInfo[3],
4451                    funcInfo[4], funcInfo[5], funcInfo[6]);
4452        } else if ((l.getElement(1) == LnConstants.RE_IB2_SPECIAL_FUNCS_TOKEN)
4453                && (l.getElement(3) == LnConstants.RE_IB2_SPECIAL_F20_F28_TOKEN)) {
4454            // Special-case for F12, F20 and F28, since the tokens from the previous case
4455            // can only encode 7 bits of data in element(4).
4456            return Bundle.getMessage("LN_MSG_INTELLIBOX_SPECIAL_FUNC_CTL",
4457                    l.getElement(2),
4458                    Bundle.getMessage(((l.getElement(4) & LnConstants.RE_IB2_SPECIAL_F12_MASK) != 0)
4459                            ? "LN_MSG_FUNC_ON"
4460                            : "LN_MSG_FUNC_OFF"),
4461                    Bundle.getMessage(((l.getElement(4) & LnConstants.RE_IB2_SPECIAL_F20_MASK) != 0)
4462                            ? "LN_MSG_FUNC_ON"
4463                            : "LN_MSG_FUNC_OFF"),
4464                    Bundle.getMessage(((l.getElement(4) & LnConstants.RE_IB2_SPECIAL_F28_MASK) != 0)
4465                            ? "LN_MSG_FUNC_ON"
4466                            : "LN_MSG_FUNC_OFF"));
4467        } else if ((l.getElement(1) == LnConstants.RE_IB2_SPECIAL_FUNCS_TOKEN)
4468                && (l.getElement(3) == LnConstants.RE_IB1_SPECIAL_F0_F4_TOKEN)) {
4469            // For Intellibox-I "one" with SW version 2.x - Special-case for F0 to F4
4470            String funcInfo[] = new String[7];
4471            funcInfo[0] = Bundle.getMessage("LN_MSG_INTELLIBOX_FUNC_CTL_HELPER_INDIV_FUNC",
4472                    0,
4473                    (l.getElement(4) & LnConstants.RE_IB1_F0_MASK) == 0 ? Bundle.getMessage("LN_MSG_FUNC_ON")
4474                    : Bundle.getMessage("LN_MSG_FUNC_OFF"));
4475            int mask = 1;
4476            for (int i = 0; i < 4; i++) {
4477                // handle 7 bits of data
4478                funcInfo[i + 1] = Bundle.getMessage("LN_MSG_INTELLIBOX_FUNC_CTL_HELPER_INDIV_FUNC",
4479                        i + 1,
4480                        Bundle.getMessage(((l.getElement(4) & mask) != 0)
4481                                ? "LN_MSG_FUNC_ON"
4482                                : "LN_MSG_FUNC_OFF"));
4483                mask *= 2;
4484            }
4485            return Bundle.getMessage("LN_MSG_INTELLIBOX_FUNC_CTL_F0_TO_F4",
4486                    l.getElement(2),
4487                    funcInfo[0], funcInfo[1], funcInfo[2], funcInfo[3],
4488                    funcInfo[4]);
4489        }
4490        // Because the usage of other tokens in message element(3) are not yet
4491        // understood, let execution fall thru to the "default" case
4492        return "";
4493    }
4494
4495    private static String interpretIb2F9_to_F12(LocoNetMessage l) {
4496        // Intellibox-II function control message for mobile decoder F9 thru F12.
4497        int slot = l.getElement(1);
4498        int funcs = l.getElement(2);
4499        return Bundle.getMessage("LN_MSG_INTELLIBOX_SLOT_SET_F9_TO_F12",
4500                slot,
4501                Bundle.getMessage(((funcs & LnConstants.RE_IB2_F9_MASK) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4502                Bundle.getMessage(((funcs & LnConstants.RE_IB2_F10_MASK) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4503                Bundle.getMessage(((funcs & LnConstants.RE_IB2_F11_MASK) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")),
4504                Bundle.getMessage(((funcs & LnConstants.RE_IB2_F12_MASK) != 0 ? "LN_MSG_FUNC_ON" : "LN_MSG_FUNC_OFF")));
4505    }
4506
4507    /**
4508     * Convert bytes from LocoNet packet into a locomotive address.
4509     *
4510     * @param a1 Byte containing the upper bits.
4511     * @param a2 Byte containing the lower bits.
4512     * @return a locomotive address in the range of 0-16383
4513     */
4514    static private int LOCO_ADR(int a1, int a2) {
4515        return (((a1 & 0x7f) * 128) + (a2 & 0x7f));
4516    }
4517
4518    /**
4519     * Convert bytes from LocoNet packet into a 1-based address for a sensor or
4520     * turnout.
4521     *
4522     * @param a1 Byte containing the upper bits
4523     * @param a2 Byte containing the lower bits
4524     * @return 1-4096 address
4525     */
4526    static private int SENSOR_ADR(int a1, int a2) {
4527        return (((a2 & 0x0f) * 128) + (a1 & 0x7f)) + 1;
4528    }
4529
4530    /*
4531     * Take an int and convert it to a dotted version number
4532     * as used by the LocoIO protocol.
4533     * Example:  123 => 1.2.3
4534     */
4535    /**
4536     * Take the LocoIO version number and convert to human friendly format, like
4537     * "1.4.8" or "9.1".
4538     *
4539     * @param val The LocoIO version.
4540     * @return String with human readable format
4541     */
4542    public static String dotme(int val) {
4543        if ((val >= 0) && (val < 10)) {
4544            return Bundle.getMessage("LN_MSG_LOCOIO_HELPER_FIRMWARE_REV_DOTTED_ONE_DIGIT", val);
4545        } else if ((val >= 10) && (val < 100)) {
4546            return Bundle.getMessage("LN_MSG_LOCOIO_HELPER_FIRMWARE_REV_DOTTED_TWO_DIGITS", val / 10, val % 10);
4547        } else if ((val >= 100) && (val < 1000)) {
4548            int hundreds = val / 100;
4549            int tens = (val - (hundreds * 100)) / 10;
4550            int ones = val % 10;
4551            return Bundle.getMessage("LN_MSG_LOCOIO_HELPER_FIRMWARE_REV_DOTTED_THREE_DIGITS", hundreds, tens, ones);
4552        }
4553        return Bundle.getMessage("LN_MSG_LOCOIO_HELPER_FIRMWARE_REV_OUT_OF_RANGE", val);
4554    }
4555
4556    /**
4557     * Convert throttle ID to a human friendly format.
4558     *
4559     * @param id1 Byte #1 of the ID
4560     * @param id2 Byte #2 of the ID
4561     * @return String with human friendly format, without the influence of
4562     *         Locale
4563     */
4564    private static String idString(int id1, int id2) {
4565        /* the decimalIdValueWithoutLocale_SpecificFormatting variable
4566        is used to generate a string representation of the ID value
4567        without any local-specific formatting.  In other words, in a
4568        us_EN locale, we want "14385", not "14,385".
4569         */
4570        String decimalIdValueWithoutLocale_SpecificFormatting
4571                = Integer.toString(((id2 & 0x7F) * 128 + (id1 & 0x7F)));
4572
4573        String s = Bundle.getMessage("LN_MSG_THROTTLE_ID",
4574                Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
4575                        StringUtil.twoHexFromInt(id2 & 0x7F)),
4576                Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
4577                        StringUtil.twoHexFromInt(id1 & 0x7F)),
4578                decimalIdValueWithoutLocale_SpecificFormatting);
4579        return s;
4580    }
4581
4582    /**
4583     * Create a string representation of the loco address in
4584     * addressLow and addressHigh in a form appropriate for the type of address (2
4585     * or 4 digit) using the Digitrax 'mixed mode' if necessary.
4586     * <p>
4587     * "Mixed mode" is used by DT100 and DT200 throttles to display loco
4588     * addresses between 100 and 127 as a two-digit displayable value, where the
4589     * left digit is either 'a', 'b', or 'c', (for addresses in the 10x, 11x,
4590     * and 12x ranges, respectively), and the right digit is the "x" from the
4591     * ranges above.
4592     *
4593     * @param addressLow  the least-significant 7 bits of the loco address
4594     * @param addressHigh the most-significant 7 bits of the loco address
4595     * @return a String containing the address, using Digitrax 'mixed mode'
4596     *         representation of the loco address, if appropriate
4597     */
4598    public static String convertToMixed(int addressLow, int addressHigh) {
4599        // if we have a 2 digit decoder address, proceed accordingly
4600        switch (addressHigh) {
4601            case 0x7d:
4602                log.debug("addressLow / 10 = {}", addressLow / 10);
4603                switch (addressLow) {
4604                    case 100: case 101: case 102: case 103: case 104: case 105:
4605                    case 106: case 107: case 108: case 109:
4606                        // N (short, alternately 'An') (or long address NN)
4607                        return Bundle.getMessage("LN_MSG_HELPER_IS_ALTERNATE_SHORT_AND_LONG_ADDRESS_Ax",
4608                                addressLow,
4609                                addressLow-100,
4610                                String.valueOf(LOCO_ADR(addressHigh, addressLow)));
4611                                // Note: .toString intentionally used here to remove the "internationalized"
4612                                // presentation of integers, which, in US English, adds a "," between
4613                                // the thousands digit and the hundreds digit.  This comma is undesired
4614                                // in this application.
4615                    case 110: case 111: case 112: case 113: case 114: case 115:
4616                    case 116: case 117: case 118: case 119:
4617                        // N (short, alternately 'Bn') (or long address NN)
4618                        return Bundle.getMessage("LN_MSG_HELPER_IS_ALTERNATE_SHORT_AND_LONG_ADDRESS_Bx",
4619                                addressLow,
4620                                addressLow-110,
4621                                String.valueOf(LOCO_ADR(addressHigh, addressLow)));
4622                                // Note: .toString intentionally used here to remove the "internationalized"
4623                                // presentation of integers, which, in US English, adds a "," between
4624                                // the thousands digit and the hundreds digit.  This comma is undesired
4625                                // in this application.
4626                    case 120: case 121: case 122: case 123: case 124: case 125:
4627                    case 126: case 127:
4628                        // N (short, alternately 'Cn') (or long address NN)
4629                        return Bundle.getMessage("LN_MSG_HELPER_IS_ALTERNATE_SHORT_AND_LONG_ADDRESS_Cx",
4630                                addressLow,
4631                                addressLow-120,
4632                                String.valueOf(LOCO_ADR(addressHigh, addressLow)));
4633                                // Note: .toString intentionally used here to remove the "internationalized"
4634                                // presentation of integers, which, in US English, adds a "," between
4635                                // the thousands digit and the hundreds digit.  This comma is undesired
4636                                // in this application.
4637                    default:
4638                        // N (short) (or long address NN)
4639                        return Bundle.getMessage("LN_MSG_HELPER_IS_SHORT_AND_LONG_ADDRESS",
4640                                addressLow,
4641                                String.valueOf(LOCO_ADR(addressHigh, addressLow)));
4642                                // Note: .toString intentionally used here to remove the "internationalized"
4643                                // presentation of integers, which, in US English, adds a "," between
4644                                // the thousands digit and the hundreds digit.  This comma is undesired
4645                                // in this application.
4646                }
4647
4648            case 0x00:
4649            case 0x7f:
4650                switch (addressLow) {
4651                    case 100: case 101: case 102: case 103: case 104: case 105:
4652                    case 106: case 107: case 108: case 109:
4653                        // N (short, alternately 'An')
4654                        return Bundle.getMessage("LN_MSG_HELPER_IS_ALTERNATE_SHORT_ADDRESS_Ax",
4655                                addressLow,
4656                                addressLow-100);
4657                    case 110: case 111: case 112: case 113: case 114: case 115:
4658                    case 116: case 117: case 118: case 119:
4659                        // N (short, alternately 'Bn')
4660                        return Bundle.getMessage("LN_MSG_HELPER_IS_ALTERNATE_SHORT_ADDRESS_Bx",
4661                                addressLow,
4662                                addressLow-110);
4663                    case 120: case 121: case 122: case 123: case 124: case 125:
4664                    case 126: case 127:
4665                        // N (short, alternately 'Cn')
4666                        return Bundle.getMessage("LN_MSG_HELPER_IS_ALTERNATE_SHORT_ADDRESS_Cx",
4667                                addressLow,
4668                                addressLow-120);
4669                    default:
4670                        // N (short)
4671                        return Bundle.getMessage("LN_MSG_HELPER_IS_SHORT_ADDRESS",
4672                                addressLow);
4673                }
4674            default:
4675                // return the full 4 digit address
4676                return String.valueOf(LOCO_ADR(addressHigh, addressLow));
4677                // Note: .toString intentionally used here to remove the "internationalized"
4678                // presentation of integers, which, in US English, adds a "," between
4679                // the thousands digit and the hundreds digit.  This comma is undesired
4680                // in this application.
4681        }
4682    }
4683
4684    private static String trackStatusByteToString(int trackStatusByte) {
4685        return Bundle.getMessage("LN_MSG_SLOT_HELPER_TRK_STAT",
4686                (((trackStatusByte & LnConstants.GTRK_MLOK1) != 0)
4687                        ? Bundle.getMessage("LN_MSG_SLOT_HELPER_TRK_STATUS_LN1_1")
4688                        : Bundle.getMessage("LN_MSG_SLOT_HELPER_TRK_STATUS_DT200")),
4689                (((trackStatusByte & LnConstants.GTRK_POWER) != 0)
4690                        ? Bundle.getMessage("LN_MSG_SLOT_HELPER_TRK_STATUS_TRK_PWR_ON")
4691                        : Bundle.getMessage("LN_MSG_SLOT_HELPER_TRK_STATUS_TRK_PWR_OFF")),
4692                (((trackStatusByte & LnConstants.GTRK_IDLE) != 0)
4693                        ? Bundle.getMessage("LN_MSG_SLOT_HELPER_TRK_STATUS_TRK_PWR_RUNNING")
4694                        : Bundle.getMessage("LN_MSG_SLOT_HELPER_TRK_STATUS_TRK_PWR_PAUSED")),
4695                (((trackStatusByte & LnConstants.GTRK_PROG_BUSY) != 0)
4696                        ? Bundle.getMessage("LN_MSG_SLOT_HELPER_TRK_STATUS_PRG_BUSY")
4697                        : Bundle.getMessage("LN_MSG_SLOT_HELPER_TRK_STATUS_PRG_AVAILABLE"))
4698        );
4699    }
4700
4701    /**
4702     * Return a string which is formatted by a bundle Resource Name.
4703     *
4704     * @param hour    fast-clock hour
4705     * @param minute  fast-clock minute
4706     * @return a formatted string containing the time
4707     */
4708    private static String fcTimeToString(int hour, int minute) {
4709        return Bundle.getMessage("LN_MSG_SLOT_HELPER_FC_TIME",
4710                LocalTime.of(hour, minute).toString());
4711    }
4712
4713    protected static String[] interpretF0_F4toStrings(int dirf) {
4714        String[] s = new String[5];
4715
4716        s[0] = (((dirf & LnConstants.DIRF_F0) == LnConstants.DIRF_F0)
4717                ? Bundle.getMessage("LN_MSG_FUNC_ON")
4718                : Bundle.getMessage("LN_MSG_FUNC_OFF"));
4719        s[1] = (((dirf & LnConstants.DIRF_F1) == LnConstants.DIRF_F1)
4720                ? Bundle.getMessage("LN_MSG_FUNC_ON")
4721                : Bundle.getMessage("LN_MSG_FUNC_OFF"));
4722        s[2] = (((dirf & LnConstants.DIRF_F2) == LnConstants.DIRF_F2)
4723                ? Bundle.getMessage("LN_MSG_FUNC_ON")
4724                : Bundle.getMessage("LN_MSG_FUNC_OFF"));
4725        s[3] = (((dirf & LnConstants.DIRF_F3) == LnConstants.DIRF_F3)
4726                ? Bundle.getMessage("LN_MSG_FUNC_ON")
4727                : Bundle.getMessage("LN_MSG_FUNC_OFF"));
4728        s[4] = (((dirf & LnConstants.DIRF_F4) == LnConstants.DIRF_F4)
4729                ? Bundle.getMessage("LN_MSG_FUNC_ON")
4730                : Bundle.getMessage("LN_MSG_FUNC_OFF"));
4731        return s;
4732    }
4733
4734    protected static String directionOfTravelString(boolean isForward) {
4735        return Bundle.getMessage(isForward ? "LN_MSG_DIRECTION_FWD"
4736                : "LN_MSG_DIRECTION_REV");
4737    }
4738
4739    protected static String[] interpretF5_F8toStrings(int snd) {
4740        String[] s = new String[4];
4741
4742        s[0] = (((snd & LnConstants.SND_F5) == LnConstants.SND_F5)
4743                ? Bundle.getMessage("LN_MSG_FUNC_ON")
4744                : Bundle.getMessage("LN_MSG_FUNC_OFF"));
4745
4746        s[1] = (((snd & LnConstants.SND_F6) == LnConstants.SND_F6)
4747                ? Bundle.getMessage("LN_MSG_FUNC_ON")
4748                : Bundle.getMessage("LN_MSG_FUNC_OFF"));
4749
4750        s[2] = (((snd & LnConstants.SND_F7) == LnConstants.SND_F7)
4751                ? Bundle.getMessage("LN_MSG_FUNC_ON")
4752                : Bundle.getMessage("LN_MSG_FUNC_OFF"));
4753
4754        s[3] = (((snd & LnConstants.SND_F8) == LnConstants.SND_F8)
4755                ? Bundle.getMessage("LN_MSG_FUNC_ON")
4756                : Bundle.getMessage("LN_MSG_FUNC_OFF"));
4757
4758        return s;
4759    }
4760
4761    private static String figureAddressIncludingAliasing(int adr, int adr2, int ss2, int id1, int id2) {
4762
4763        /*
4764         * Build loco address string. String will be a simple
4765         * number, unless the address is between 100 and 127
4766         * (inclusive), where a Digitrax "mixed mode" version
4767         * of the address will be appended.
4768         */
4769        String mixedAdrStr = convertToMixed(adr, adr2);
4770
4771        /*
4772         * If the address is a command station "alias" condition,
4773         * then note it in the string.
4774         */
4775        if (adr2 == 0x7f) {
4776            if ((ss2 & LnConstants.STAT2_ALIAS_MASK) == LnConstants.STAT2_ID_IS_ALIAS) {
4777                /* this is an aliased address and we have the alias */
4778                return Bundle.getMessage("LN_MSG_LOCO_ADDR_HELPER_ALIAS_2_DIGIT_WITH_KNOWN_4_DIGIT",
4779                        Integer.toString(LOCO_ADR(id2, id1)), mixedAdrStr);
4780            } else {
4781                /* this is an aliased address and we don't have the alias */
4782                return Bundle.getMessage("LN_MSG_LOCO_ADDR_HELPER_ALIAS_2_DIGIT_WITH_UNKNOWN_4_DIGIT",
4783                        mixedAdrStr);
4784            }
4785        } else {
4786            /* a regular address which is not an alias */
4787            return mixedAdrStr;
4788        }
4789    }
4790
4791    public static String getDeviceNameFromIPLInfo(int manuf, int type) {
4792        if (manuf != LnConstants.RE_IPL_MFR_DIGITRAX) {
4793            return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_UNDEFINED_MFG_PROD",
4794                    manuf, type);
4795        }
4796        switch (type) {
4797            case LnConstants.RE_IPL_DIGITRAX_HOST_ALL:
4798                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_ALLDEVICES");
4799            case LnConstants.RE_IPL_DIGITRAX_HOST_LNRP:
4800                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_LNRP");
4801            case LnConstants.RE_IPL_DIGITRAX_HOST_UT4:
4802                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_UT4");
4803            case LnConstants.RE_IPL_DIGITRAX_HOST_UT6:
4804                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_UT6");
4805            case LnConstants.RE_IPL_DIGITRAX_HOST_WTL12:
4806                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_WTL12");
4807            case LnConstants.RE_IPL_DIGITRAX_HOST_DCS210:
4808                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_DCS210");
4809            case LnConstants.RE_IPL_DIGITRAX_HOST_DCS210PLUS:
4810                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_DCS210PLUS");
4811            case LnConstants.RE_IPL_DIGITRAX_HOST_DCS240:
4812                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_DCS240");
4813            case LnConstants.RE_IPL_DIGITRAX_HOST_DCS240PLUS:
4814                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_DCS240PLUS");
4815            case LnConstants.RE_IPL_DIGITRAX_HOST_PR3:
4816                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_PR3");
4817            case LnConstants.RE_IPL_DIGITRAX_HOST_DT402:
4818                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_DT402");
4819            case LnConstants.RE_IPL_DIGITRAX_HOST_DT500:
4820                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_DT500");
4821            case LnConstants.RE_IPL_DIGITRAX_HOST_DT602:
4822                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_DT602");
4823            case LnConstants.RE_IPL_DIGITRAX_HOST_DCS51:
4824                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_DCS51");
4825            case LnConstants.RE_IPL_DIGITRAX_HOST_DCS52:
4826                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_DCS52");
4827            case LnConstants.RE_IPL_DIGITRAX_HOST_UR92:
4828                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_UR92");
4829            case LnConstants.RE_IPL_DIGITRAX_HOST_UR93:
4830                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_UR93");
4831            case LnConstants.RE_IPL_DIGITRAX_HOST_PR4:
4832                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_PR4");
4833            case LnConstants.RE_IPL_DIGITRAX_HOST_LNWI:
4834                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_LNWI");
4835            case LnConstants.RE_IPL_DIGITRAX_HOST_BXP88:
4836                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_BXP88");
4837            case LnConstants.RE_IPL_DIGITRAX_HOST_BXPA1:
4838                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_BXPA1");
4839            case LnConstants.RE_IPL_DIGITRAX_HOST_DS74:
4840                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_DS74");
4841            case LnConstants.RE_IPL_DIGITRAX_HOST_DS78V:
4842                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_DS78V");
4843            case LnConstants.RE_IPL_DIGITRAX_HOST_DB210:
4844                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_DB210");
4845            case LnConstants.RE_IPL_DIGITRAX_HOST_DB210OPTO:
4846                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_DB210OPTO");
4847            case LnConstants.RE_IPL_DIGITRAX_HOST_DB220:
4848                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_DB220");
4849            case LnConstants.RE_IPL_DIGITRAX_HOST_PM74:
4850                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_PM74");
4851            case LnConstants.RE_IPL_DIGITRAX_HOST_SE74:
4852                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_SE74");
4853            case LnConstants.RE_IPL_DIGITRAX_HOST_BDL716:
4854                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_BDL716");
4855
4856            default:
4857                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_HOST_UNKNOWN", type);
4858        }
4859    }
4860
4861    public static String getSlaveNameFromIPLInfo(int manuf, int slaveNum) {
4862        if (manuf != LnConstants.RE_IPL_MFR_DIGITRAX) {
4863            return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_UNDEFINED_MFG_PROD",
4864                    manuf, slaveNum);
4865        }
4866        switch (slaveNum) {
4867            case LnConstants.RE_IPL_DIGITRAX_SLAVE_ALL:
4868                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_SLAVE_ALLDEVICES");
4869            case LnConstants.RE_IPL_DIGITRAX_SLAVE_RF24:
4870                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_SLAVE_RF24");
4871            default:
4872                return Bundle.getMessage("LN_MSG_IPL_DEVICE_HELPER_DIGITRAX_SLAVE_UNKNOWN", slaveNum);
4873        }
4874    }
4875
4876    /**
4877     * Interpret messages with Opcode of OPC_ALM_READ, OPC_ALM_WRITE.
4878     *
4879     * @param l LocoNet Message to interpret
4880     * @return String containing interpreted message or empty string if
4881     *      message is not interpretable.
4882     */
4883    public static String interpretAlm(LocoNetMessage l) {
4884        if (l.getElement(1) == 0x10) {
4885            String ret;
4886            ret = jmri.jmrix.loconet.alm.almi.Almi.interpretAlm(l);
4887            if (ret.length() > 1) {
4888                return ret;
4889            }
4890        }
4891
4892        if (l.getElement(1) == 0x15) {
4893            int slot = ( (l.getElement(2) & 0x07 ) *128) + l.getElement(3); // slot number for this request
4894
4895            String result = interpretExtendedSlotRdWr(l, slot) ;
4896            if (result.length() > 0) {
4897                return result;
4898            }
4899        }
4900        return "";
4901
4902    }
4903    private static String interpretOpcExpMoveSlots(LocoNetMessage l) {
4904        int src = ((l.getElement(1) & 0x03) * 128) + (l.getElement(2) & 0x7f);
4905        int dest = ((l.getElement(3) & 0x03) * 128) + (l.getElement(4) & 0x7f);
4906
4907        if ((src >= 0x79) && (src <= 0x7f)) {
4908            return "";
4909        }
4910        if ((dest >= 0x79) && (dest <= 0x7f)) {
4911            return "";
4912        }
4913        
4914        if ((l.getElement(1) & 0x78) != 0x38) {
4915            // Ignore if message is not one from a DCS240 (or newer) command
4916            // station's "Expanded" slot messaging.   The message is probably
4917            // from an Intellibox or other non-Digitrax command station.
4918            return "";
4919        }
4920
4921        boolean isSettingStatus = ((l.getElement(3) & 0b01110000) == 0b01100000);
4922        if (isSettingStatus) {
4923            int stat = l.getElement(4);
4924            return Bundle.getMessage("LN_MSG_OPC_EXP_SET_STATUS",
4925                    src,
4926                    LnConstants.CONSIST_STAT(stat),
4927                    LnConstants.LOCO_STAT(stat),
4928                    LnConstants.DEC_MODE(stat));
4929        }
4930        boolean isUnconsisting = ((l.getElement(3) & 0b01110000) == 0b01010000);
4931        if (isUnconsisting) {
4932            // source and dest same, returns slot contents
4933            return Bundle.getMessage("LN_MSG_OPC_EXP_UNCONSISTING",
4934                    src);
4935        }
4936        boolean isConsisting = ((l.getElement(3) & 0b01110000) == 0b01000000);
4937        if (isConsisting) {
4938            //add dest to src, returns dest slot contents
4939            return Bundle.getMessage("LN_MSG_OPC_EXP_CONSISTING",
4940                    src,dest);
4941        }
4942       /* check special cases */
4943        if (src == 0) {
4944            /* DISPATCH GET */
4945            return Bundle.getMessage("LN_MSG_MOVE_SL_GET_DISP");
4946        } else if (src == dest) {
4947            /* IN USE */
4948            return Bundle.getMessage("LN_MSG_MOVE_SL_NULL_MOVE", src);
4949        } else if (dest == 0) {
4950            /* DISPATCH PUT */
4951            return Bundle.getMessage("LN_MSG_MOVE_SL_DISPATCH_PUT", src);
4952        } else {
4953            /* general move */
4954            return Bundle.getMessage("LN_MSG_MOVE_SL_MOVE", src, dest);
4955        }
4956    }
4957
4958    private static String interpretPocExpLocoSpdDirFunction(LocoNetMessage l) {
4959        int slot = ((l.getElement(1) & 0x03) * 128) + (l.getElement(2) & 0x7f);
4960        if ((l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_SPEED) == LnConstants.OPC_EXP_SEND_SPEED_AND_DIR_FWD) {
4961            // speed and direction
4962            int spd = l.getElement(4);
4963            String direction = Bundle.getMessage((l.getElement(1) & LnConstants.OPC_EXP_SEND_SPEED_AND_DIR_REV) != 0
4964                    ? "LN_MSG_DIRECTION_REV" : "LN_MSG_DIRECTION_FWD");
4965            String throttleID = Integer.toHexString(l.getElement(3));
4966            return Bundle.getMessage("LN_MSG_OPC_EXP_SPEED_DIRECTION", slot, spd, direction, throttleID);
4967        }
4968        // Build a string for the functions on off
4969        String[] fn = new String[8];
4970        for (int bitIndex = 0; bitIndex < 8; bitIndex++) {
4971            fn[bitIndex] = (l.getElement(4) >> (7 - bitIndex) & 1) == 1 ? Bundle.getMessage("LN_MSG_FUNC_ON")
4972                    : Bundle.getMessage("LN_MSG_FUNC_OFF");
4973        }
4974        if ((l.getElement(1) &
4975                LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F0F6) {
4976            return Bundle.getMessage("LN_MSG_OPC_EXP_FUNCTIONS_F0_F6", slot, fn[3], fn[7], fn[6], fn[5], fn[4], fn[2],
4977                    fn[1]);
4978        } else if ((l.getElement(1) &
4979                LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F7F13) {
4980            return Bundle.getMessage("LN_MSG_OPC_EXP_FUNCTIONS_F7_F13", slot, fn[7], fn[6], fn[5], fn[4], fn[3], fn[2],
4981                    fn[1]);
4982        } else if ((l.getElement(1) &
4983                LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F14F20) {
4984            return Bundle.getMessage("LN_MSG_OPC_EXP_FUNCTIONS_F14_F20",slot, fn[7], fn[6], fn[5], fn[4], fn[3], fn[2],
4985                    fn[1]);
4986        } else if ((l.getElement(1) &
4987                LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F21F28_F28OFF) {
4988            return Bundle.getMessage("LN_MSG_OPC_EXP_FUNCTIONS_F21_F28",slot, fn[7], fn[6], fn[5], fn[4], fn[3], fn[2],
4989                    fn[1], Bundle.getMessage("LN_MSG_FUNC_OFF"));
4990        } else if ((l.getElement(1) &
4991                LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F21F28_F28ON) {
4992            return Bundle.getMessage("LN_MSG_OPC_EXP_FUNCTIONS_F21_F28", slot, fn[7], fn[6], fn[5], fn[4], fn[3], fn[2],
4993                    fn[1], Bundle.getMessage("LN_MSG_FUNC_ON"));
4994        }
4995        return "";
4996    }
4997
4998    private static String interpretExtendedSlotRdWr(LocoNetMessage l, int slot) {
4999        /**
5000         * ************************************************
5001         * extended slot read/write message               *
5002         * ************************************************
5003         */
5004        /*
5005         * If its a "Special" slot (Stats etc) use a different routine
5006         */
5007        if (slot > 247 && slot < 253) {
5008            return interpretExtendedSlot_StatusData(l,slot);
5009        }
5010        int trackStatus = l.getElement(7); // track status
5011        int id1 =  l.getElement(19);
5012        int id2 = l.getElement(18);
5013        int command = l.getOpCode();
5014        int stat = l.getElement(4); // slot status
5015        //int adr = l.getElement(5) + 128 * l.getElement(6); // loco address
5016        int adr = l.getElement(5);
5017        int spd = l.getElement(8); // command speed
5018        int dirf = l.getElement(10) & 0b00111111; // direction and F0-F4 bits
5019        String[] dirf0_4 = interpretF0_F4toStrings(dirf);
5020        int ss2 = l.getElement(18); // slot status 2 (tells how to use
5021        // ID1/ID2 & ADV Consist)
5022        int adr2 = l.getElement(6); // loco address high
5023        int snd = l.getElement(10); // Sound 1-4 / F5-F8
5024        String[] sndf5_8 = interpretF5_F8toStrings(snd);
5025
5026        String locoAdrStr = figureAddressIncludingAliasing(adr, adr2, ss2, id1, id2);
5027        return Bundle.getMessage(((command == 0xEE)
5028                ? "LN_MSG_SLOT_LOCO_INFO_WRITE"
5029                : "LN_MSG_SLOT_LOCO_INFO_READ"),
5030                slot,
5031                locoAdrStr,
5032                LnConstants.CONSIST_STAT(stat),
5033                LnConstants.LOCO_STAT(stat),
5034                LnConstants.DEC_MODE(stat),
5035                directionOfTravelString((dirf & LnConstants.DIRF_DIR) == 0),
5036                spd, // needs re-interpretation for some cases of slot consisting state
5037                dirf0_4[0],
5038                dirf0_4[1],
5039                dirf0_4[2],
5040                dirf0_4[3],
5041                dirf0_4[4],
5042                sndf5_8[0],
5043                sndf5_8[1],
5044                sndf5_8[2],
5045                sndf5_8[3],
5046                trackStatusByteToString(trackStatus),
5047                Bundle.getMessage("LN_MSG_SLOT_HELPER_SS2_SIMPLE",
5048                        Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
5049                                StringUtil.twoHexFromInt(ss2))),
5050                Bundle.getMessage("LN_MSG_SLOT_HELPER_ID1_ID2_AS_THROTTLE_ID",
5051                        idString(id1, id2)));
5052    }
5053
5054    /**
5055     * Interprets an Enhanced Slot Report message in the "Query Mode" range of
5056     * slot numbers.
5057     *
5058     * Only the primary slot numbers are interpreted, not any "aliases".
5059     *
5060     * @param l Enhanced Slot report LocoNetMessage to be interpreted
5061     * @param slot
5062     * @return String showing interpretation.
5063     */
5064    private static String interpretExtendedSlot_StatusData(LocoNetMessage l, int slot) {
5065       String baseInfo = "";
5066       String detailInfo = "";
5067       switch (slot) {
5068           case 248:
5069
5070                baseInfo = interpretExtendedSlot_StatusData_Base_Detail(l, slot); // Basic Identifying information
5071                detailInfo = interpretExtendedSlot_Query_Mode_248(l);  // Flags
5072
5073                break;
5074           case 249:
5075                baseInfo = interpretExtendedSlot_StatusData_Base(l, slot); // Basic Identifying information
5076                detailInfo = interpretExtendedSlot_Query_Mode_249(l); // Electrical properties
5077
5078                break;
5079            case 250:
5080                baseInfo = interpretExtendedSlot_StatusData_Base(l, slot); // Basic Identifying information
5081                detailInfo = interpretExtendedSlot_Query_Mode_250(l); // Slots info
5082                break;
5083            case 251:
5084                baseInfo = interpretExtendedSlot_StatusData_Base(l, slot); // Basic Identifying information
5085                detailInfo = interpretExtendedSlot_Query_Mode_251(l); // LocoNet events and messages and stats
5086                break;
5087            case 252:
5088                baseInfo = interpretExtendedSlot_StatusData_Base(l, slot); // Basic Identifying information
5089                detailInfo = interpretExtendedSlot_Query_Mode_252(l); // DCC track status info
5090                break;
5091            default:
5092                baseInfo = "Wrong Slot # ("+Integer.toString(slot)+")";
5093        }
5094       return Bundle.getMessage("LN_MSG_OPC_EXP_QUERY_MODE_OVERALL",
5095               slot, baseInfo, detailInfo);
5096    }
5097
5098    /**
5099     * Interpret the base information in bytes 16,18,19
5100     * for slots 249,250,251, but not 248.
5101     *
5102     * @param l LocoNetMessage to be interpreted
5103     * @param slot slot number
5104     * @return formatted message
5105     */
5106    private static String interpretExtendedSlot_StatusData_Base(LocoNetMessage l, int slot) {
5107        String hwType = LnConstants.IPL_NAME(l.getElement(16));
5108        int hwSerial = ((l.getElement(19) & 0x3f) * 128 ) + l.getElement(18);
5109        String serNumHex = "0000"+Integer.toHexString(hwSerial).toUpperCase();
5110        serNumHex = serNumHex.substring(serNumHex.length()-4);
5111
5112        return Bundle.getMessage("LN_MSG_OPC_EXP_SPECIALSTATUS_BASE",
5113                hwType,
5114                hwSerial + "(0x" + serNumHex + ")");
5115    }
5116
5117    /**
5118     * Interpret slot 248 base details.
5119     *
5120     * @param l LocoNetMessage to be interpreted
5121     * @param slot slot number
5122     * @return formatted message
5123     */
5124    private static String interpretExtendedSlot_StatusData_Base_Detail(LocoNetMessage l, int slot) {
5125        String hwType = LnConstants.IPL_NAME(l.getElement(14));
5126        if ((l.getElement(19) & 0x40) == 0x40) {
5127            hwType = hwType + Bundle.getMessage("LN_MSG_COMMAND_STATION");
5128        }
5129        int hwSerial = ((l.getElement(19) & 0x3f) * 128 ) + l.getElement(18);
5130        String serNumHex = "0000"+Integer.toHexString(hwSerial).toUpperCase();
5131        serNumHex = serNumHex.substring(serNumHex.length()-4);
5132
5133        float hwVersion = ((float)(l.getElement(17) & 0x78) / 8 ) + ((float)(l.getElement(17) & 0x07) / 10 ) ;
5134        float swVersion = ((float)(l.getElement(16) & 0x78) / 8 ) + ((float)(l.getElement(16) & 0x07) / 10 ) ;
5135        return Bundle.getMessage("LN_MSG_OPC_EXP_SPECIALSTATUS_BASEDETAIL",
5136                hwType,
5137                hwSerial + "(0x" + serNumHex + ")",
5138                hwVersion, swVersion);
5139    }
5140
5141    private static String queryOnOff(int val, int bit) {
5142        return (((val & 1 << bit) == 1 << bit)?Ln_On:Ln_Off);
5143    }
5144
5145    /**
5146     * Interprets _some_ of the data in Query Mode report of slot 248 (and aliases!)
5147     * - "Flags" info.
5148     *
5149     * @param l LocoNetMessage to be interpreted
5150     * @return formatted message
5151     */
5152    private static String interpretExtendedSlot_Query_Mode_248(LocoNetMessage l) {
5153
5154        int b = l.getElement(4);
5155        String lnetVmin = Bundle.getMessage("LNET_QUERY_LNETVMIN", queryOnOff(b, 6));
5156        String overTemp = Bundle.getMessage("LNET_QUERY_OVERTEMP", queryOnOff(b, 5));
5157        String fuseBad = Bundle.getMessage("LNET_QUERY_FUSEBAD", queryOnOff(b, 4));
5158        String rsynMax = Bundle.getMessage("LNET_QUERY_RSYNMAX", queryOnOff(b, 3));
5159        String vinHi = Bundle.getMessage("LNET_QUERY_VINHI", queryOnOff(b, 2));
5160        String vinLo = Bundle.getMessage("LNET_QUERY_VINLO", queryOnOff(b, 1));
5161        String iTrk = Bundle.getMessage("LNET_QUERY_ITRK", queryOnOff(b, 0));
5162
5163        b = l.getElement(5);
5164        String usbLink = Bundle.getMessage("LNET_QUERY_ULINK", queryOnOff(b, 5));
5165        String iLim = Bundle.getMessage("LNET_QUERY_ILIM", queryOnOff(b, 3));
5166        String PTrkMaxI = Bundle.getMessage("LNET_QUERY_PTRKMAXI", queryOnOff(b, 2));
5167        String PtrkIsol = Bundle.getMessage("LNET_QUERY_PTRKISOL", queryOnOff(b, 1));
5168
5169        return Bundle.getMessage("LN_MSG_OPC_EXP_SPECIALSTATUS_FLAGS",
5170                rsynMax, usbLink, iTrk, vinLo, vinHi, fuseBad,
5171                overTemp, lnetVmin, PtrkIsol, PTrkMaxI, iLim);
5172    }
5173
5174    /**
5175     * Interprets _some_ of the data in Query Mode report of slot 249 (and aliases!)
5176     * - "Electrical" info.
5177     *
5178     * @param l LocoNetMessage to be interpreted
5179     * @return formatted message
5180     */
5181    private static String interpretExtendedSlot_Query_Mode_249(LocoNetMessage l) {
5182        float voltsTrack = ((float)l.getElement(4)) * 2 / 10 ;
5183        float voltsIn = ((float)l.getElement(5)) * 2 / 10;
5184        float ampsIn = ((float)l.getElement(6)) / 10;
5185        float ampsLimit = ((float)l.getElement(7)) / 10;
5186        float voltsRsLoaded = ((float)l.getElement(12)) * 2 / 10;
5187        float voltsRsUnLoaded = ((float)l.getElement(10)) * 2 / 10;
5188        return Bundle.getMessage("LN_MSG_OPC_EXP_SPECIALSTATUS_ELECTRIC",
5189                voltsTrack,
5190                voltsIn,
5191                ampsIn,
5192                ampsLimit,
5193                voltsRsLoaded,
5194                voltsRsUnLoaded);
5195    }
5196
5197    /**
5198     * Interprets _some_ of the data in Query Mode report of slot 250 (and aliases!)
5199     * - "Slots" info.
5200     *
5201     * @param l LocoNetMessage to be interpreted
5202     * @return formatted message
5203     */
5204    private static String interpretExtendedSlot_Query_Mode_250(LocoNetMessage l) {
5205        int msgInUse = (l.getElement(4) + ( l.getElement(5)  * 128)) ;
5206        int msgIdle = (l.getElement(6) + ( l.getElement(7) * 128)) ;
5207        int msgFree = (l.getElement(8) + ( l.getElement(9) * 128)) ;
5208        int ctop =  (l.getElement(10) + ( l.getElement(11) * 128)) ;
5209        int cup =  (l.getElement(12) + ( l.getElement(13) * 128)) ;
5210
5211        return Bundle.getMessage("LN_MSG_OPC_EXP_SPECIALSTATUS_SLOTS",
5212                msgInUse, msgIdle, msgFree, ctop, cup);
5213    }
5214
5215    /**
5216     * Interprets _some_ of the data in Query Mode report of slot 251 (and aliases!)
5217     * - "LocoNet message" info.
5218     *
5219     * @param l LocoNetMessage to be interpreted
5220     * @return formatted message
5221     */
5222    private static String interpretExtendedSlot_Query_Mode_251(LocoNetMessage l) {
5223        int msgTotal = (l.getElement(4) + ( l.getElement(5) * 128)) ;
5224        int msgErrors = (l.getElement(6) + ( l.getElement(7) * 128)) ;
5225        int sleeps = (l.getElement(10) + ( l.getElement(11) * 128));
5226
5227        return Bundle.getMessage("LN_MSG_OPC_EXP_SPECIALSTATUS_LOCONET",
5228                msgTotal, msgErrors, sleeps);
5229    }
5230
5231    /**
5232     * Interprets _some_ of the data in Query Mode report of slot 252 (and aliases!)
5233     * - "DCC status" info.
5234     *
5235     * @param l LocoNetMessage to be interpreted
5236     * @return formatted message
5237     */
5238    private static String interpretExtendedSlot_Query_Mode_252(LocoNetMessage l) {
5239        int flt = (l.getElement(4) & 0x7f) + ((l.getElement(5) & 0x7f) << 7);
5240        int arv = (l.getElement(6) & 0x7f) + ((l.getElement(7) & 0x7f) << 7);
5241        int dst = (l.getElement(8) & 0x7f) + ((l.getElement(9) & 0x7f) << 7);
5242        return Bundle.getMessage("LN_MSG_OPC_EXP_QUERY_LOCONET_STAT2_LOCONET",
5243                flt, arv, dst);
5244    }
5245
5246    private static final String ds54sensors[] = {"AuxA", "SwiA", "AuxB", "SwiB", "AuxC", "SwiC", "AuxD", "SwiD"};    // NOI18N
5247    private static final String ds64sensors[] = {"A1", "S1", "A2", "S2", "A3", "S3", "A4", "S4"};                    // NOI18N
5248    private static final String se8csensors[] = {"DS01", "DS02", "DS03", "DS04", "DS05", "DS06", "DS07", "DS08"};    // NOI18N
5249
5250    private final static Logger log = LoggerFactory.getLogger(LocoNetMessageInterpret.class);
5251}