1+ package org .openlcb .implementations ;
2+
3+ import org .openlcb .*;
4+ import org .openlcb .implementations .throttle .Float16 ;
5+
6+ import java .util .*;
7+ import net .jcip .annotations .*;
8+
9+ /**
10+ * A set of utility functions and classes for
11+ * working with Location Services messages
12+ *
13+ * @author Bob Jacobsen Copyright (C) 2025
14+ */
15+
16+ public class LocationServiceUtils {
17+
18+ static public Content parse (Message inputMessage ) {
19+
20+ // first, check the message type
21+ if (! (inputMessage instanceof ProducerConsumerEventReportMessage )) return null ;
22+
23+ ProducerConsumerEventReportMessage msg = (ProducerConsumerEventReportMessage ) inputMessage ;
24+ byte [] payload = msg .getPayloadArray ();
25+
26+ // check for minimal message length
27+ if (payload .length < 8 ) return null ;
28+
29+ // check for right event ID
30+ byte [] eid = msg .getEventID ().getContents ();
31+ if (eid [0 ] != 0x01 || eid [1 ] != 0x02 ) return null ;
32+
33+ // This is Location Services EWP, process it
34+
35+ // process first section
36+ int overallFlags = payload [0 ]<<8 +payload [1 ];
37+ NodeID scannerReporting = new NodeID (new byte []{
38+ eid [2 ], eid [3 ], eid [4 ], eid [5 ], eid [6 ], eid [7 ]
39+ });
40+ NodeID scannedDevice = new NodeID (new byte []{
41+ payload [2 ], payload [3 ], payload [4 ], payload [5 ], payload [6 ], payload [7 ]
42+ });
43+
44+ List <Block > blocks = parseBlock (payload , 8 , new ArrayList <Block >());
45+
46+ Content retval = new Content (scannerReporting , scannedDevice , overallFlags , blocks );
47+ return retval ;
48+ }
49+
50+ static private List <Block > parseBlock (byte [] payload , int offset , ArrayList <Block > list ) {
51+
52+ if (offset >= payload .length ) return list ;
53+
54+ int length = payload [offset ];
55+
56+ if (length == 0 ) {
57+ list .add (new Block (Block .Type .RESERVED , new byte [0 ]));
58+ } else {
59+ // here we parse the block into something useful
60+ Block .Type type = Block .Type .get ((int ) payload [offset +1 ]);
61+ byte [] content = Arrays .copyOfRange (payload , offset +1 , offset +1 +length );
62+ switch (type ) {
63+ case ANALOG :
64+ list .add (new AnalogBlock (content ));
65+ break ;
66+
67+ default :
68+ list .add (new Block (type , content ));
69+ }
70+ }
71+
72+ return parseBlock (payload , offset +length +1 , list );
73+ }
74+
75+ /**
76+ * Accessors for the parse contents
77+ */
78+ @ Immutable
79+ static public class Content {
80+ // The nodeID of the scanner making the report
81+ NodeID scannerReporting ;
82+ public NodeID getScannerReporting () { return scannerReporting ; }
83+
84+ // The nodeID of the scanned device
85+ NodeID scannedDevice ;
86+ public NodeID getScannedDevice () { return scannedDevice ; }
87+
88+ // The overall flags
89+ int overallFlags ;
90+ public int getOverallFlags () {return overallFlags ; }
91+
92+ // The blocks of content
93+ List <Block > blocks ;
94+ public List <Block > getBlocks () { return blocks ; }
95+
96+ public Content (NodeID scannerReporting , NodeID scannedDevice , int overallFlags , List <Block > blocks ) {
97+ this .scannerReporting = scannerReporting ;
98+ this .scannedDevice = scannedDevice ;
99+ this .overallFlags = overallFlags ;
100+ this .blocks = blocks ;
101+ }
102+
103+ }
104+
105+ /**
106+ * Generic accessor for the contents of a Block
107+ */
108+ @ Immutable
109+ static public class Block {
110+ public enum Type {
111+ RESERVED (0 , "Reserved" ),
112+ READABLE (1 , "Readable" ),
113+ RFID (2 , "RFID" ),
114+ QR (3 , "QR" ),
115+ RAILCOM (4 , "RailCom" ),
116+ TRANSPONDING (5 , "Transponding" ),
117+ POSITION (6 , "Position" ),
118+ DCCADDRESS (7 , "DccAddress" ),
119+ SETSPEED (8 , "Set Speed" ),
120+ COMMANDEDSPEED (9 , "Commanded Speed" ),
121+ ACTUALSPEED (10 , "Actual Speed" ),
122+ ANALOG (11 , "Analog" );
123+
124+ Type (int code , String name ) {
125+ this .code = code ;
126+ this .name = name ;
127+
128+ getMap ().put (code , this );
129+ }
130+
131+ int code ;
132+ String name ;
133+
134+ public String toString () {
135+ return name ;
136+ }
137+
138+ public static Type get (Integer type ) {
139+ return mapping .get (type );
140+ }
141+
142+ private static Map <Integer , Type > mapping ;
143+ private static Map <Integer , Type > getMap () {
144+ if (mapping == null )
145+ mapping = new java .util .HashMap <Integer , Type >();
146+ return mapping ;
147+ }
148+ }
149+
150+ Block (Type type , byte [] content ) {
151+ this .length = content .length ;
152+ this .type = type ;
153+ this .content = content ;
154+ }
155+
156+ int length ;
157+ public int getLength () { return length ; }
158+ Type type ;
159+ public Type getType () { return type ; }
160+ byte [] content ;
161+ public byte [] getContent () { return content ; }
162+ }
163+
164+
165+ /**
166+ * Accessor for the contents of a Block with Readable (String) contents
167+ */
168+ @ Immutable
169+ static public class ReadableBlock extends Block {
170+
171+ ReadableBlock (byte [] content ) {
172+ super (Block .Type .READABLE , content );
173+
174+ // [1] through end are the user string (UTF8)
175+ try {
176+ text = new String (Arrays .copyOfRange (content , 1 , content .length ),"UTF-8" );
177+ } catch (java .io .UnsupportedEncodingException ex ) {
178+ text = "<UTF8 Error>" ;
179+ }
180+ }
181+
182+ String text ;
183+ public String getText () { return text ; }
184+ }
185+
186+
187+ /**
188+ * Accessor for the contents of a Block with Analog contents
189+ */
190+ @ Immutable
191+ static public class AnalogBlock extends Block {
192+ public enum Unit {
193+ UNKNOWN (0 , "Unknown" ),
194+ VOLTS (1 , "Volts" ),
195+ AMPERES (2 , "Amperes" ),
196+ WATTS (3 , "Watts" ),
197+ OHMS (4 , "OHMS" ),
198+ DEGREESC (5 , "Degrees C" ),
199+ SECONDS (6 , "Seconds" ),
200+ METERS (7 , "Meters" ),
201+ METERS2 (8 , "Meters^2" ),
202+ METERS3 (9 , "Meters^3" ),
203+ METERSPERSECOND (10 , "Meters/Second" ),
204+ METERSPERSECOND2 (11 , "Meters/Second^2" ),
205+ KILOGRAMS (12 , "Kilograms" ),
206+ NEWTONS (13 , "Newtons" );
207+
208+ Unit (int code , String name ) {
209+ this .code = code ;
210+ this .name = name ;
211+
212+ getMap ().put (code , this );
213+ }
214+
215+ int code ;
216+ String name ;
217+
218+ public String toString () {
219+ return name ;
220+ }
221+
222+ public static Unit get (Integer unit ) {
223+ return mapping .get (unit );
224+ }
225+
226+ private static Map <Integer , Unit > mapping ;
227+ private static Map <Integer , Unit > getMap () {
228+ if (mapping == null )
229+ mapping = new java .util .HashMap <Integer , Unit >();
230+ return mapping ;
231+ }
232+
233+ }
234+
235+ AnalogBlock (byte [] content ) {
236+ super (Block .Type .ANALOG , content );
237+
238+ // decode contents of this block
239+
240+ // [1], [2] are the Float16 value
241+ value = new Float16 (content [1 ], content [2 ]).getFloat ();
242+ // [3] is the unit
243+ unit = Unit .get ((int )content [3 ]);
244+
245+ // [4] through end are the user string (UTF8)
246+ try {
247+ text = new String (Arrays .copyOfRange (content , 4 , content .length ),"UTF-8" );
248+ } catch (java .io .UnsupportedEncodingException ex ) {
249+ text = "<UTF8 Error>" ;
250+ }
251+ }
252+
253+ Unit unit ;
254+ public Unit getUnit () { return unit ; }
255+ double value ;
256+ public double getValue () { return value ; }
257+ String text ;
258+ public String getText () { return text ; }
259+ }
260+
261+ }
0 commit comments