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