-
Notifications
You must be signed in to change notification settings - Fork 326
/
LocoNetMessageInterpret.java
5206 lines (4864 loc) · 259 KB
/
LocoNetMessageInterpret.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package jmri.jmrix.loconet.messageinterp;
import java.time.LocalTime;
import java.util.ArrayList;
import jmri.InstanceManager;
import jmri.NmraPacket;
import jmri.Reporter;
import jmri.ReporterManager;
import jmri.Sensor;
import jmri.SensorManager;
import jmri.Turnout;
import jmri.TurnoutManager;
import jmri.jmrix.loconet.LnConstants;
import jmri.jmrix.loconet.LocoNetMessage;
import jmri.jmrix.loconet.lnsvf2.LnSv2MessageContents;
import jmri.jmrix.loconet.uhlenbrock.LncvMessageContents;
import jmri.util.StringUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A utility class for formatting LocoNet packets into human-readable text.
* <p>
* Note that the formatted strings end in a \n, and may contain more than one
* line separated by \n. Someday this should be converted to proper Java line
* handling.
* <p>
* Much of this file is a Java-recoding of the display.c file from the llnmon
* package of John Jabour. Some of the conversions involve explicit decoding of
* structs defined in loconet.h in that same package. Those parts are (C)
* Copyright 2001 Ron W. Auld. Use of these parts is by direct permission of the
* author.
* <p>
* This class is derived from and replaces JMRI's
* jmri.jmrix.loconet.locomon.Llnmon.java .
* <p>
* Many major comment blocks here are quotes from the Digitrax LocoNet(r) OPCODE
* SUMMARY: found in the LocoNet(r) Personal Edition 1.
* <p>
* Some of the message formats used in this class are Copyright Digitrax, Inc.
* and used with permission as part of the JMRI project. That permission does
* not extend to uses in other software products. If you wish to use this code,
* algorithm or these message formats outside of JMRI, please contact Digitrax
* Inc for separate permission.
* <p>
* Reverse engineering of OPC_MULTI_SENSE was provided by Al Silverstein, used
* with permission.
* <p>
* Reverse engineering of the Duplex Group/Password/Channel management was
* provided by Leo Bicknell with help from B. Milhaupt, used with permission.
* <p>
* Reverse-engineering of device-specific OpSw messages, throttle text message,
* and throttle semaphore message was provided by B. Milhaupt, used with
* permission.
*
* Reverse-engineering of device-specific LNSV messages was provided by K. Drenth,
* used with permission.
*
* @author Bob Jacobsen Copyright 2001, 2002, 2003
* @author B. Milhaupt Copyright 2015, 2016, 2018, 2022
* @author Randall Wood Copyright 2016
* @author Michael Richardson Copyright (C) 2021
*/
public class LocoNetMessageInterpret {
private static final String Ln_Off = Bundle.getMessage("LN_MSG_OFF");
private static final String Ln_On = Bundle.getMessage("LN_MSG_ON");
/**
* Format the message into a text string.
* <p>
* Where the code is unable to determine a correct interpretation, the returned
* string contains a message indicating that the message is not decoded followed
* by the individual bytes of the message (in hexadecimal).
*
* @param l Message to parse
* @param turnoutPrefix "System Name+ prefix which designates the connection's
* Turnouts, such as "LT"
* @param sensorPrefix "System Name+ prefix which designates the connection's
* Turnouts, such as "LS"
* @param reporterPrefix "System Name+ prefix which designates the connection's
* Turnouts, such as "LR"
* @return String representation of the interpretation of the message
*/
public static String interpretMessage(LocoNetMessage l, String turnoutPrefix, String sensorPrefix, String reporterPrefix) {
String result;
result = "";
/*
* 2 Byte MESSAGE OPCODES
* ; FORMAT = <OPC>,<CKSUM>
* ;
*
* 4 byte MESSAGE OPCODES
* ; FORMAT = <OPC>,<ARG1>,<ARG2>,<CKSUM>
* :
* CODES 0xA8 to 0xAF have responses
* CODES 0xB8 to 0xBF have responses
*
* 6 byte MESSAGE OPCODES
* ; FORMAT = <OPC>,<ARG1>,<ARG2>,<ARG3>,<ARG4>,<CKSUM>
* :
* CODES 0xC8 to 0xCF have responses
* CODES 0xD8 to 0xDF have responses
*/
switch (l.getOpCode()) {
/*
* OPC_IDLE 0x85 ;FORCE IDLE state, Broadcast emergency STOP
*
* Page 8 of LocoNet Personal Edition v1.0.
*/
case LnConstants.OPC_IDLE: {
return Bundle.getMessage("LN_MSG_IDLE");
}
/*
* OPC_GPON 0x83 ;GLOBAL power ON request
*
* Page 8 of LocoNet Personal Edition v1.0.
*/
case LnConstants.OPC_GPON: {
return Bundle.getMessage("LN_MSG_GPON");
}
/*
* OPC_GPOFF 0x82 ;GLOBAL power OFF request
*
* Page 8 of LocoNet Personal Edition v1.0.
*/
case LnConstants.OPC_GPOFF: {
return Bundle.getMessage("LN_MSG_GPOFF");
}
/*
* OPC_GPBUSY 0x81 ;MASTER busy code, NULL
*
* Page 8 of LocoNet Personal Edition v1.0.
*/
case LnConstants.OPC_GPBUSY: {
return Bundle.getMessage("LN_MSG_MASTER_BUSY");
}
case LnConstants.OPC_RE_LOCORESET_BUTTON: {
return Bundle.getMessage("LN_MSG_RE_LOCO_RESET");
}
/*
* OPC_LOCO_ADR 0xBF ; REQ loco ADR
* ; Follow on message: <E7>SLOT READ
* ; <0xBF>,<0>,<ADR>,<CHK> REQ loco ADR
* ; DATA return <E7>, is SLOT#, DATA that ADR was
* : found in.
* ; IF ADR not found, MASTER puts ADR in FREE slot
* ; and sends DATA/STATUS return <E7>......
* ; IF no FREE slot, Fail LACK,0 is returned
* ; [<B4>,<3F>,<0>,<CHK>]
*
* Page 8 of LocoNet Personal Edition v1.0.
*/
case LnConstants.OPC_LOCO_ADR: {
String locoAddress = convertToMixed(l.getElement(2), l.getElement(1));
return Bundle.getMessage("LN_MSG_REQ_SLOT_FOR_ADDR",
locoAddress);
}
case LnConstants.OPC_EXP_REQ_SLOT: {
String locoAddress = convertToMixed(l.getElement(2), l.getElement(1));
return Bundle.getMessage("LN_MSG_REQ_EXP_SLOT_FOR_ADDR",
locoAddress);
}
/*
* OPC_SW_ACK 0xBD ; REQ SWITCH WITH acknowledge function (not DT200)
* ; Follow on message: LACK
* ; <0xBD>,<SW1>,<SW2>,<CHK> REQ SWITCH function
* ; <SW1> =<0,A6,A5,A4- A3,A2,A1,A0>
* ; 7 ls adr bits.
* ; A1,A0 select 1 of 4 input pairs
* ; in a DS54
* ; <SW2> =<0,0,DIR,ON- A10,A9,A8,A7>
* ; Control bits and 4 MS adr bits.
* ; DIR=1 for Closed/GREEN
* ; =0 for Thrown/RED
* ; ON=1 for Output ON
* ; =0 FOR output OFF
* ; response is:
* ; <0xB4><3D><00> if DCS100 FIFO is full, rejected.
* ; <0xB4><3D><7F> if DCS100 accepted
*
* Page 8 of LocoNet Personal Edition v1.0.
*/
case LnConstants.OPC_SW_ACK: {
result = interpretOpcSwAck(l, turnoutPrefix);
if (result.length() > 0) {
return result;
}
break;
}
/*
* OPC_SW_STATE 0xBC ; REQ state of SWITCH
* ; Follow on message: LACK
* ; <0xBC>,<SW1>,<SW2>,<CHK> REQ state of SWITCH
*
* Page 8 of LocoNet Personal Edition v1.0.
*/
case LnConstants.OPC_SW_STATE: {
result = interpretOpcSwState(l, turnoutPrefix);
if (result.length() > 0) {
return result;
}
break;
}
/*
* OPC_RQ_SL_DATA 0xBB ; Request SLOT DATA/status block
* ; Follow on message: <E7>SLOT READ
* ; <0xBB>,<SLOT>,<0>,<CHK> Request SLOT DATA/status block.
*
* Page 8 of LocoNet Personal Edition v1.0.
*/
case LnConstants.OPC_RQ_SL_DATA: {
result = interpretOpcRqSlData(l);
if (result.length() > 0) {
return result;
}
break;
}
/*
* OPC_MOVE_SLOTS 0xBA ; MOVE slot SRC to DEST
* ; Follow on message: <E7>SLOT READ
* ; <0xBA>,<SRC>,<DEST>,<CHK> Move SRC to DEST if
* ; SRC or LACK etc is NOT IN_USE, clr SRC
* ; SPECIAL CASES:
* ; If SRC=0 ( DISPATCH GET) , DEST=dont care,
* ; Return SLOT READ DATA of DISPATCH Slot
* ; IF SRC=DEST (NULL move) then SRC=DEST is set to
* ; IN_USE , if legal move.
* ; If DEST=0, is DISPATCH Put, mark SLOT as DISPATCH
* ; RETURN slot status <0xE7> of DESTINATION slot
* ; DEST if move legal
* ; RETURN Fail LACK code if illegal move
* ; <B4>,<3A>,<0>,<chk>, illegal to move to/from
* ; slots 120/127
*
* Page 8 of LocoNet Personal Edition v1.0.
*/
case LnConstants.OPC_MOVE_SLOTS: {
result = interpretOpcMoveSlots(l);
if (result.length() > 0) {
return result;
}
break;
}
// case LnConstants.OPC_EXP_SLOT_MOVE: {
// result = interpretOpcExpMoveSlots(l);
// if (result.length() > 0) {
// return result;
// }
// break;
// }
/*
* OPC_LINK_SLOTS 0xB9 ; LINK slot ARG1 to slot ARG2=
* ; Follow on message: <E7>SLOT READ=
* ; <0xB9>,<SL1>,<SL2>,<CHK> SLAVE slot SL1 to slot SL2
* ; Master LINKER sets the SL_CONUP/DN flags
* ; appropriately. Reply is return of SLOT Status
* ; <0xE7>. Inspect to see result of Link, invalid
* ; Link will return Long Ack Fail <B4>,<39>,<0>,<CHK>
*
* Page 9 of LocoNet Personal Edition v1.0.
*/
case LnConstants.OPC_LINK_SLOTS: {
int src = l.getElement(1);
int dest = l.getElement(2);
return Bundle.getMessage("LN_MSG_LINK_SLOTS", src, dest);
}
/*
* OPC_UNLINK_SLOTS 0xB8 ;UNLINK slot ARG1 from slot ARG2
* ; Follow on message: <E7>SLOT READ
* ; <0xB8>,<SL1>,<SL2>,<CHK> UNLINK slot SL1 from SL2
* ; UNLINKER executes unlink STRATEGY and returns new SLOT#
* ; DATA/STATUS of unlinked LOCO . Inspect data to evaluate UNLINK
*
* Page 9 of LocoNet Personal Edition v1.0.
*/
case LnConstants.OPC_UNLINK_SLOTS: {
int src = l.getElement(1);
int dest = l.getElement(2);
return Bundle.getMessage("LN_MSG_UNLINK_SLOTS", src, dest);
} // case LnConstants.OPC_UNLINK_SLOTS
/*
* OPC_CONSIST_FUNC 0xB6 ; SET FUNC bits in a CONSIST uplink element
* ; <0xB6>,<SLOT>,<DIRF>,<CHK> UP consist FUNC bits
* ; NOTE this SLOT adr is considered in UPLINKED slot space.
*
* Page 9 of LocoNet Personal Edition v1.0.
*/
case LnConstants.OPC_CONSIST_FUNC: {
result = interpretOpcConsistFunc(l);
if (result.length() > 0) {
return result;
}
break;
}
/*
* OPC_SLOT_STAT1 0xB5 ; WRITE slot stat1
* ; <0xB5>,<SLOT>,<STAT1>,<CHK> WRITE stat1
*
* Page 9 of LocoNet Personal Edition v1.0.
*/
case LnConstants.OPC_SLOT_STAT1: {
int slot = l.getElement(1);
int stat = l.getElement(2);
return Bundle.getMessage("LN_MSG_SLOT_STAT1", slot, stat,
Bundle.getMessage("LN_MSG_HEXADECIMAL_REPRESENTATION",
StringUtil.twoHexFromInt(stat)), LnConstants.CONSIST_STAT(stat),
LnConstants.LOCO_STAT(stat), LnConstants.DEC_MODE(stat));
}
/*
* OPC_LONG_ACK 0xB4 ; Long acknowledge
* ; <0xB4>,<LOPC>,<ACK1>,<CHK> Long acknowledge
* ; <LOPC> is COPY of OPCODE responding to (msb=0).
* ; LOPC=0 (unused OPC) is also VALID fail code
* ; <ACK1> is appropriate response code for the OPCode
*
* Page 9 of LocoNet Personal Edition v1.0.
*/
case LnConstants.OPC_LONG_ACK: {
result = interpretLongAck(l);
if (result.length() > 0) {
return result;
}
break;
}
/*
* OPC_INPUT_REP 0xB2 ; General SENSOR Input codes
* ; <0xB2>, <IN1>, <IN2>, <CHK>
* ; <IN1> =<0,A6,A5,A4- A3,A2,A1,A0>,
* ; 7 ls adr bits.
* ; A1,A0 select 1 of 4 inputs pairs in a DS54.
* ; <IN2> =<0,X,I,L- A10,A9,A8,A7>,
* ; Report/status bits and 4 MS adr bits.
* ; "I"=0 for DS54 "aux" inputs
* ; =1 for "switch" inputs mapped to 4K SENSOR space.
* ;
* ; (This is effectively a least significant adr bit when
* ; using DS54 input configuration)
* ;
* ; "L"=0 for input SENSOR now 0V (LO),
* ; =1 for Input sensor >=+6V (HI)
* ; "X"=1, control bit,
* ; =0 is RESERVED for future!
*
* Page 9 of LocoNet Personal Edition v1.0.
*/
case LnConstants.OPC_INPUT_REP: {
result = interpretOpcInputRep(l, sensorPrefix);
if (result.length() > 0) {
return result;
}
break;
} // case LnConstants.OPC_INPUT_REP
/*
* OPC_SW_REP 0xB1 ; Turnout SENSOR state REPORT
* ; <0xB1>,<SN1>,<SN2>,<CHK> SENSOR state REPORT
* ; <SN1> =<0,A6,A5,A4- A3,A2,A1,A0>,
* ; 7 ls adr bits.
* ; A1,A0 select 1 of 4 input pairs in a DS54
* ; <SN2> =<0,1,I,L- A10,A9,A8,A7>
* ; Report/status bits and 4 MS adr bits.
* ; this <B1> opcode encodes input levels
* ; for turnout feedback
* ; "I" =0 for "aux" inputs (normally not feedback),
* ; =1 for "switch" input used for
* ; turnout feedback for DS54
* ; ouput/turnout # encoded by A0-A10
* ; "L" =0 for this input 0V (LO),
* ; =1 this input > +6V (HI)
* ;
* ; alternately;
* ;
* ; <SN2> =<0,0,C,T- A10,A9,A8,A7>
* ; Report/status bits and 4 MS adr bits.
* ; this <B1> opcode encodes current OUTPUT levels
* ; "C" =0 if "Closed" ouput line is OFF,
* ; =1 "closed" output line is ON
* ; (sink current)
* ; "T" =0 if "Thrown" output line is OFF,
* ; =1 "thrown" output line is ON
* ; (sink I)
*
* Page 9 of LocoNet Personal Edition v1.0.
*/
case LnConstants.OPC_SW_REP: {
result = interpretOpcSwRep(l, turnoutPrefix);
if (result.length() > 0) {
return result;
}
break;
}
/*
* OPC_SW_REQ 0xB0 ; REQ SWITCH function
* ; <0xB0>,<SW1>,<SW2>,<CHK> REQ SWITCH function
* ; <SW1> =<0,A6,A5,A4- A3,A2,A1,A0>,
* ; 7 ls adr bits.
* ; A1,A0 select 1 of 4 input pairs in a DS54
* ; <SW2> =<0,0,DIR,ON- A10,A9,A8,A7>
* ; Control bits and 4 MS adr bits.
* ; DIR =1 for Closed,/GREEN,
* ; =0 for Thrown/RED
* ; ON =1 for Output ON,
* ; =0 FOR output OFF
* ;
* ; Note-Immediate response of <0xB4><30><00> if command failed,
* ; otherwise no response "A" CLASS codes
*
* Page 9 of LocoNet Personal Edition v1.0.
* Page 12 special form Broadcast.
* Page 13 special form LocoNet interrogate.
*/
case LnConstants.OPC_SW_REQ: {
result = interpretOpcSwReq(l, turnoutPrefix);
if (result.length() > 0) {
return result;
}
break;
}
/*
* OPC_LOCO_SND 0xA2 ;SET SLOT sound functions
*
* Page 10 of LocoNet Personal Edition v1.0.
*/
case LnConstants.OPC_LOCO_SND: {
result = interpretOpcLocoSnd(l);
if (result.length() > 0) {
return result;
}
break;
} // case LnConstants.OPC_LOCO_SND
/*
* OPC_LOCO_DIRF 0xA1 ;SET SLOT dir, F0-4 state
*
* Page 10 of LocoNet Personal Edition v1.0.
*/
case LnConstants.OPC_LOCO_DIRF: {
result = interpretOpcLocoDirf(l);
if (result.length() > 0) {
return result;
}
break;
}
/*
* OPC_LOCO_SPD 0xA0 ;SET SLOT speed e.g. <0xA0><SLOT#><SPD><CHK>
*
* Page 10 of LocoNet Personal Edition v1.0.
*/
case LnConstants.OPC_LOCO_SPD: {
result = interpretOpcLocoSpd(l);
if (result.length() > 0) {
return result;
}
break;
}
case LnConstants.OPC_EXP_SEND_FUNCTION_OR_SPEED_AND_DIR: {
result = interpretPocExpLocoSpdDirFunction(l);
if (result.length() > 0) {
return result;
}
break;
}
/*
* OPC_PANEL_QUERY 0xDF messages used by throttles to discover
* panels
*
* This op code is not documented by Digitrax. Some reverse engineering
* performed by Leo Bicknell. The opcode "name" OPC_PANEL_QUERY
* is not necessarily the name used by Digitrax.
*/
case LnConstants.OPC_PANEL_QUERY: {
result = interpretOpcPanelQuery(l);
if (result.length() > 0) {
return result;
}
break;
}
/*
* OPC_PANEL_RESPONSE 0xD7 messages used by throttles to discover
* panels
*
* This op code is not documented by Digitrax. Reverse engineering
* performed by Leo Bicknell. The opcode "name" OPC_PANEL_RESPONSE
* is not necessarily the name used by Digitrax.
*/
case LnConstants.OPC_PANEL_RESPONSE: {
result = interpretOpcPanelResponse(l);
if (result.length() > 0) {
return result;
}
break;
}
/*
* OPC_MULTI_SENSE 0xD0 messages about power management and
* transponding
*
* If byte 1 high nibble is 0x20 or 0x00 this is a transponding
* message
*
* This op code is not documented by Digitrax. Reverse engineering
* performed by Al Silverstein, and corrections added by B. Milhaupt.
*/
case LnConstants.OPC_MULTI_SENSE: {
result = interpretOpcMultiSense(l, reporterPrefix);
if (result.length() > 0) {
return result;
}
break;
}
/*
* ********************************************************************************************
* OPC_MULTI_SENSE_LONG 0xE0 messages about transponding.
*
* This op code is not documented by Digitrax. The use of this message was observed when using a
* Digikeijs 5088RC. With a capable decoder, this message contains additional Railcom information
* (direction, speed, QoS) compared to the standard OPC_MULTI_SENSE message.
*
* Reverse engineering performed by Michael Richardson.
* ********************************************************************************************
*/
case LnConstants.OPC_MULTI_SENSE_LONG: {
result = interpretOpcMultiSenseLong(l, reporterPrefix);
if (result.length() > 0) {
return result;
}
break;
}
/*
* ********************************************************************************************
* OPC_WR_SL_DATA 0xEF ; WRITE SLOT DATA, 10 bytes * ; Follow on
* message: LACK * ; <0xEF>,<0E>,<SLOT#>,<STAT>,<ADR>,<SPD>,<DIRF>,
* * ; <TRK>,<SS2>,<ADR2>,<SND>,<ID1>,<ID2>,<CHK> * ; SLOT DATA
* WRITE, 10 bytes data /14 byte MSG *
* **********************************************************************************************
* OPC_SL_RD_DATA 0xE7 ; SLOT DATA return, 10 bytes * ;
* <0xE7>,<0E>,<SLOT#>,<STAT>,<ADR>,<SPD>,<DIRF>, * ;
* <TRK>,<SS2>,<ADR2>,<SND>,<ID1>,<ID2>,<CHK> * ; SLOT DATA READ, 10
* bytes data /14 byte MSG * ; * ; NOTE; If STAT2.2=0 EX1/EX2
* encodes an ID#, * ; [if STAT2.2=1 the STAT.3=0 means EX1/EX2 * ;
* are ALIAS] * ; * ; ID1/ID2 are two 7 bit values encoding a 14 bit
* * ; unique DEVICE usage ID. * ; * ; 00/00 - means NO ID being
* used * ; * ; 01/00 - ID shows PC usage. * ; to Lo nibble is TYP
* PC# * ; 7F/01 (PC can use hi values) * ; * ; 00/02 -SYSTEM
* reserved * ; to * ; 7F/03 * ; * ; 00/04 -NORMAL throttle RANGE *
* ; to * ; 7F/7E *
* **********************************************************************************************
* Notes: * The SLOT DATA bytes are, in order of TRANSMISSION for
* <E7> READ or <EF> WRITE. * NOTE SLOT 0 <E7> read will return
* MASTER config information bytes. * * 0) SLOT NUMBER: * * ; 0-7FH,
* 0 is special SLOT, * ; 070H-07FH DIGITRAX reserved: * * 1) SLOT
* STATUS1: * * D7-SL_SPURGE ; 1=SLOT purge en, * ; ALSO adrSEL
* (INTERNAL use only) (not seen on NET!) * * D6-SL_CONUP ;
* CONDN/CONUP: bit encoding-Control double linked Consist List * ;
* 11=LOGICAL MID CONSIST , Linked up AND down * ; 10=LOGICAL
* CONSIST TOP, Only linked downwards * ; 01=LOGICAL CONSIST
* SUB-MEMBER, Only linked upwards * ; 00=FREE locomotive, no
* CONSIST indirection/linking * ; ALLOWS "CONSISTS of CONSISTS".
* Uplinked means that * ; Slot SPD number is now SLOT adr of
* SPD/DIR and STATUS * ; of consist. i.e. is ;an Indirect pointer.
* This Slot * ; has same BUSY/ACTIVE bits as TOP of Consist. TOP is
* * ; loco with SPD/DIR for whole consist. (top of list). * ;
* BUSY/ACTIVE: bit encoding for SLOT activity * * D5-SL_BUSY ;
* 11=IN_USE loco adr in SLOT -REFRESHED * * D4-SL_ACTIVE ; 10=IDLE
* loco adr in SLOT -NOT refreshed * ; 01=COMMON loco adr IN SLOT
* -refreshed * ; 00=FREE SLOT, no valid DATA -not refreshed * *
* D3-SL_CONDN ; shows other SLOT Consist linked INTO this slot, see
* SL_CONUP * * D2-SL_SPDEX ; 3 BITS for Decoder TYPE encoding for
* this SLOT * * D1-SL_SPD14 ; 011=send 128 speed mode packets * *
* D0-SL_SPD28 ; 010=14 step MODE * ; 001=28 step. Generate Trinary
* packets for this * ; Mobile ADR * ; 000=28 step. 3 BYTE PKT
* regular mode * ; 111=128 Step decoder, Allow Advanced DCC
* consisting * ; 100=28 Step decoder ,Allow Advanced DCC consisting
* * * 2) SLOT LOCO ADR: * * LOCO adr Low 7 bits (byte sent as ARG2
* in ADR req opcode <0xBF>) * * 3) SLOT SPEED: * 0x00=SPEED 0 ,STOP
* inertially * 0x01=SPEED 0 EMERGENCY stop * 0x02->0x7F increasing
* SPEED,0x7F=MAX speed * (byte also sent as ARG2 in SPD opcode
* <0xA0> ) * * 4) SLOT DIRF byte: (byte also sent as ARG2 in DIRF
* opcode <0xA1>) * * D7-0 ; always 0 * D6-SL_XCNT ; reserved , set
* 0 * D5-SL_DIR ; 1=loco direction FORWARD * D4-SL_F0 ;
* 1=Directional lighting ON * D3-SL_F4 ; 1=F4 ON * D2-SL_F3 ; 1=F3
* ON * D1-SL_F2 ; 1=F2 ON * D0-SL_F1 ; 1=F1 ON * * * * * 5) TRK
* byte: (GLOBAL system /track status) * * D7-D4 Reserved * D3
* GTRK_PROG_BUSY 1=Programming TRACK in this Master is BUSY. * D2
* GTRK_MLOK1 1=This Master IMPLEMENTS LocoNet 1.1 capability, *
* 0=Master is DT200 * D1 GTRK_IDLE 0=TRACK is PAUSED, B'cast EMERG
* STOP. * D0 GTRK_POWER 1=DCC packets are ON in MASTER, Global
* POWER up * * 6) SLOT STATUS: * * D3 1=expansion IN ID1/2,
* 0=ENCODED alias * D2 1=Expansion ID1/2 is NOT ID usage * D0
* 1=this slot has SUPPRESSED ADV consist-7) * * 7) SLOT LOCO ADR
* HIGH: * * Locomotive address high 7 bits. If this is 0 then Low
* address is normal 7 bit NMRA SHORT * address. If this is not zero
* then the most significant 6 bits of this address are used in *
* the first LONG address byte ( matching CV17). The second DCC LONG
* address byte matches CV18 * and includes the Adr Low 7 bit value
* with the LS bit of ADR high in the MS postion of this * track adr
* byte. * * Note a DT200 MASTER will always interpret this as 0. *
* * 8) SLOT SOUND: * * Slot sound/ Accesory Function mode II
* packets. F5-F8 * (byte also sent as ARG2 in SND opcode) * * D7-D4
* reserved * D3-SL_SND4/F8 * D2-SL_SND3/F7 * D1-SL_SND2/F6 *
* D0-SL_SND1/F5 1= SLOT Sound 1 function 1active (accessory 2) * *
* 9) EXPANSION RESERVED ID1: * * 7 bit ls ID code written by
* THROTTLE/PC when STAT2.4=1 * * 10) EXPANSION RESERVED ID2: * * 7
* bit ms ID code written by THROTTLE/PC when STAT2.4=1 *
* ********************************************************************************************
* page 10 of LocoNet PE
*/
case LnConstants.OPC_WR_SL_DATA:
case LnConstants.OPC_SL_RD_DATA: {
result = interpretOpcWrSlDataOpcSlRdData(l);
if (result.length() > 0) {
return result;
}
break;
}
case LnConstants.OPC_ALM_WRITE:
case LnConstants.OPC_ALM_READ: {
// case OPC_EXP_RD_SL_DATA: // NOTE: Duplicate of definition of OPC_ALM_WRITE!
// case OPC_EXP_WR_SL_DATA: // NOTE: Duplicate of definition of OPC_ALM_READ!
result = interpretAlm(l);
if (result.length() > 0) {
return result;
}
break;
}
/*
* OPC_PEER_XFER 0xE5 ; move 8 bytes PEER to PEER, SRC->DST NO resp
* ; <0xE5>,<10>,<SRC>,<DSTL><DSTH>,<PXCT1>,<D1>,<D2>,<D3>,<D4>,
* ; <PXCT2>,<D5>,<D6>,<D7>,<D8>,<CHK>
* ; SRC/DST are 7 bit args. DSTL/H=0 is BROADCAST msg
* ; SRC=0 is MASTER
* ; SRC=0x70-0x7E are reserved
*
* Page 10 of LocoNet Personal Edition v1.0.
*
* Duplex group management reverse engineered by Leo Bicknell, with input from
* B. Milhaupt.
*/
case LnConstants.OPC_PEER_XFER: {
result = interpretOpcPeerXfer(l, reporterPrefix);
if (result.length() > 0) {
return result;
}
break;
}
// 0xE4
case LnConstants.OPC_LISSY_UPDATE: {
result = interpretOpcLissyUpdate(l);
if (result.length() > 0) {
return result;
}
break;
}
// 0xED
case LnConstants.OPC_IMM_PACKET: {
result = interpretOpcImmPacket(l);
if (result.length() > 0) {
return result;
}
break;
}
// 0xD3
case LnConstants.RE_OPC_PR3_MODE: {
result = interpretOpcPr3Mode(l);
if (result.length() > 0) {
return result;
}
break;
}
// 0xA3
case LnConstants.RE_OPC_IB2_F9_F12: {
result = interpretIb2F9_to_F12(l);
if (result.length() > 0) {
return result;
}
break;
}
// TODO: put this back for intellibox cmd station.
// it conflicts with loconet speed/dir etc.
// 0xD4
case LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL: {
result = interpretIb2Special(l);
if (result.length() > 0) {
return result;
}
result = interpretOpcExpMoveSlots(l);
if (result.length() > 0) {
return result;
}
break;
}// case LnConstants.RE_OPC_IB2_SPECIAL: { //0xD4
//$FALL-THROUGH$
default:
break;
} // end switch over opcode type
return Bundle.getMessage("LN_MSG_UNKNOWN_MESSAGE") +
Bundle.getMessage("LN_MONITOR_MESSAGE_RAW_HEX_INFO", l.toString());
}
private static String interpretOpcPeerXfer20_1(LocoNetMessage l) {
switch (l.getElement(3)) {
case 0x08: {
return Bundle.getMessage("LN_MSG_DUPLEX_RECEIVER_QUERY");
}
case 0x10: {
return Bundle.getMessage("LN_MSG_DUPLEX_RECEIVER_RESPONSE");
}
default: {
break;
}
}
return "";
}
private static String interpretOpcPeerXfer20_2(LocoNetMessage l) {
switch (l.getElement(3)) {
case 0x00: {
int channel = l.getElement(5) | ((l.getElement(4) & 0x01) << 7);
return Bundle.getMessage("LN_MSG_DUPLEX_CHANNEL_SET",
Integer.toString(channel));
}
case 0x08: {
return Bundle.getMessage("LN_MSG_DUPLEX_CHANNEL_QUERY");
}
case 0x10: {
int channel = l.getElement(5) | ((l.getElement(4) & 0x01) << 7);
return Bundle.getMessage("LN_MSG_DUPLEX_CHANNEL_REPORT",
Integer.toString(channel));
}
default: {
break;
}
}
return "";
}
private static String interpretOpcPeerXfer20_3(LocoNetMessage l) {
// Characters appear to be 8 bit values, but transmitted over a 7 bit
// encoding, so high order bits are stashed in element 4 and 9.
char[] groupNameArray = {(char) (l.getElement(5) | ((l.getElement(4) & 0x01) << 7)),
(char) (l.getElement(6) | ((l.getElement(4) & 0x02) << 6)),
(char) (l.getElement(7) | ((l.getElement(4) & 0x04) << 5)),
(char) (l.getElement(8) | ((l.getElement(4) & 0x08) << 4)),
(char) (l.getElement(10) | ((l.getElement(9) & 0x01) << 7)),
(char) (l.getElement(11) | ((l.getElement(9) & 0x02) << 6)),
(char) (l.getElement(12) | ((l.getElement(9) & 0x04) << 5)),
(char) (l.getElement(13) | ((l.getElement(9) & 0x08) << 4))};
String groupName = new String(groupNameArray);
// The pass code is stuffed in here, each digit in 4 bits. But again, it's a
// 7 bit encoding, so the MSB of the "upper" half is stuffed into byte 14.
int p1 = ((l.getElement(14) & 0x01) << 3) | ((l.getElement(15) & 0x70) >> 4);
int p2 = l.getElement(15) & 0x0F;
int p3 = ((l.getElement(14) & 0x02) << 2) | ((l.getElement(16) & 0x70) >> 4);
int p4 = l.getElement(16) & 0x0F;
// It's not clear you can set A-F from throttles or Digitrax's tools, but
// they do take and get returned if you send them on the wire...
String passcode = StringUtil.twoHexFromInt(p1) + StringUtil.twoHexFromInt(p2)
+ StringUtil.twoHexFromInt(p3) + StringUtil.twoHexFromInt(p4);
// The MSB is stuffed elsewhere again...
int channel = l.getElement(17) | ((l.getElement(14) & 0x04) << 5);
// The MSB is stuffed elsewhere one last time.
int id = l.getElement(18) | ((l.getElement(14) & 0x08) << 4);
switch (l.getElement(3)) {
case 0x00: {
return Bundle.getMessage("LN_MSG_DUPLEX_NAME_WRITE",
groupName);
}
case 0x08: {
return Bundle.getMessage("LN_MSG_DUPLEX_NAME_QUERY");
}
case 0x10: {
return Bundle.getMessage("LN_MSG_DUPLEX_NAME_REPORT",
groupName, passcode, channel, id);
}
default: {
break;
}
}
return "";
}
private static String interpretOpcPeerXfer20_4(LocoNetMessage l) {
// The MSB is stuffed elsewhere again...
int id = l.getElement(5) | ((l.getElement(4) & 0x01) << 7);
switch (l.getElement(3)) {
case 0x00: {
return Bundle.getMessage("LN_MSG_DUPLEX_ID_SET", id);
}
case 0x08: {
return Bundle.getMessage("LN_MSG_DUPLEX_ID_QUERY");
}
case 0x10: {
return Bundle.getMessage("LN_MSG_DUPLEX_ID_REPORT", id);
}
default: {
break;
}
}
return "";
}
private static String interpretOpcPeerXfer20_7(LocoNetMessage l) {
if (l.getElement(3) == 0x08) {
return Bundle.getMessage("LN_MSG_DUPLEX_PASSWORD_QUERY");
}
if ((l.getElement(5) < 0x30) || (l.getElement(5) > 0x3c)
|| (l.getElement(6) < 0x30) || (l.getElement(6) > 0x3c)
|| (l.getElement(7) < 0x30) || (l.getElement(7) > 0x3c)
|| (l.getElement(8) < 0x30) || (l.getElement(8) > 0x3c)) {
return "";
}
char[] groupPasswordArray = {(char) l.getElement(5),
(char) l.getElement(6),
(char) l.getElement(7),
(char) l.getElement(8)};
if ((groupPasswordArray[0] > 0x39) && (groupPasswordArray[0] < 0x3d)) {
groupPasswordArray[0] += ('A' - '9' - 1);
}
if ((groupPasswordArray[1] > 0x39) && (groupPasswordArray[1] < 0x3d)) {
groupPasswordArray[1] += ('A' - '9' - 1);
}
if ((groupPasswordArray[2] > 0x39) && (groupPasswordArray[2] < 0x3d)) {
groupPasswordArray[2] += ('A' - '9' - 1);
}
if ((groupPasswordArray[3] > 0x39) && (groupPasswordArray[3] < 0x3d)) {
groupPasswordArray[3] += ('A' - '9' - 1);
}
String groupPassword = new String(groupPasswordArray);
switch (l.getElement(3)) {
case 0x00: {
return Bundle.getMessage("LN_MSG_DUPLEX_PASSWORD_SET", groupPassword);
}
case 0x10: {
return Bundle.getMessage("LN_MSG_DUPLEX_PASSWORD_REPORT", groupPassword);
}
default: {
break;
}
}
return "";
}
private static String interpretOpcPeerXfer20_10(LocoNetMessage l) {
switch (l.getElement(3)) {
case 0x08: {
return Bundle.getMessage("LN_MSG_DUPLEX_CHANNEL_SCAN_QUERY", l.getElement(5));
}
case 0x10: {
// High order bit stashed in another element again.
int level = (l.getElement(6) & 0x7F) + ((l.getElement(4) & 0x02) << 6);
return Bundle.getMessage("LN_MSG_DUPLEX_CHANNEL_SCAN_REPORT", l.getElement(5),
level);
}
default: {
break;
}
}
return "";
}
private static String interpretOpcPeerXfer20_8(LocoNetMessage l) {
/*
* **********************************************************************************
* IPL-capable device ping - OPC_RE_IPL (Device Ping Operations) * The
* message bytes as assigned as follows:
* <p>
* <E5> <14> <08> <GR_OP_T> <DI_F2> <DI_Ss0>
* <DI_Ss1> ...
* <p>
* <DI_Ss2> <DI_Ss3> <DI_U1> <00> <00> <DI_U2>
* <DI_U3> ...
* <p>
* <00> <00><00> <00><00> <CHK> * where:
* <p>
* <DI_F2> encodes additional bits for the Slave device serial number. *
* bits 7-4 always 0000b * bit 3 Bit 31 of Slave Device Serial Number *
* bit 2 Bit 23 of Slave Device Serial Number * bit 1 Bit 15 of Slave
* device Serial Number * bit 0 Bit 7 of Slave device Serial Number
* <p>
* <DI_Ss0> encodes 7 bits of the 32 bit Host device serial number: *
* bit 7 always 0 * bits 6-0 Bits 6:0 of Slave device serial number
* <p>
* <DI_Ss1> encodes 7 bits of the 32 bit Host device serial number: *
* bit 7 always 0 * bits 6-0 Bits 14:8 of Slave device serial number
* <p>
* <DI_Ss2> encodes 7 bits of the 32 bit Host device serial number: *
* bit 7 always 0 * bits 6-0 Bits 22:16 of Slave device serial number
* <p>
* <DI_Ss3> encodes 7 bits of the 32 bit Host device serial number: *
* bit 7 always 0 * bits 6-0 Bits 30:24 of Slave device serial number
* <p>
* <DI_U1> unknown data * when <GR_OP_T> = 0x08 * is always 0 * when
* <GR_OP_T> = 0x10 * is not reverse-engineered and may be non-zero.
* <p>
* <DI_U2> unknown data * when <GR_OP_T> = 0x08 * is always 0 * when
* <GR_OP_T> = 0x10 * is not reverse-engineered and may be non-zero.
* <p>
* <DI_U3> unknown data * when <GR_OP_T> = 0x08 * is always 0 * when
* <GR_OP_T> = 0x10 * is not reverse-engineered and may be non-zero. * *
* Information reverse-engineered by B. Milhaupt and used with
* permission *
* **********************************************************************************
*/
/* OPC_RE_IPL (IPL Ping Operation) */
// Operations related to DigiIPL Device "Ping" operations
//
// "Ping" request issued from DigiIPL ver 1.09 issues this message on LocoNet.
// The LocoNet request message encodes a serial number but NOT a device type.
//
// Depending on which devices are selected in DigiIPL when the "ping"
// is selected, (and probably the S/Ns of the devices attached to the LocoNet,
// the response is as follows:
// DT402D LocoNet message includes the serial number from the DT402D's
// Slave (RF24) serial number. If a UR92 is attached to LocoNet,
// it will send the message via its RF link to the addressed
// DT402D. (UR92 apparantly assumes that the long 802.15.4
// address of the DT402D is based on the serial number embedded
// in the LocoNet message, with the MS 32 bits based on the UR92
// long address MS 32 bits). If more than one UR92 is attached
// to LocoNet, all will pass the message to the RF interface.
// UR92 LocoNet message includes the Slave serial number from the UR92.
// These messages are not passed to the RF link by the addressed
// UR92. If more than one UR92 is attached to LocoNet, and the
// addressed UR92 hears the RF version of the LocoNet message, it
// will respond via the RF interface with an acknowledge packet,
// and a UR92 (not sure which one) responds on LocoNet with a
// Ping report <e5><14><08><10>.
// PR3 LocoNet message includes an effective serial number of all
// zeros. There is no LocoNet message reply generated to a
// request to a PR3 S/N, but there will be a reply on the PR3's
// computer interface if the ping request was sent via the PR3's
// computer interface (i.e. not from some other LocoNet agent).
// UT4D While it has been suggested that the UT4D supports firmware
// updates, the UT4D does not respond to the Ping message.
// LNRP While it has been suggested that the LNRP supports firmware
// updates, the LNRP does not respond to the Ping message.
//
// Ping Report values:
// <unkn1> Seems always to be <0C>. None of the bytes relate to
// Duplex Channel Number.
// <unkn2> Matches byte 15 of the MAC payload of the reply sent by the