Skip to content

Commit 7f98ba4

Browse files
authored
Merge pull request #289 from bobjacobsen/EWP-and-LocationServices
Location Services parser and EWP update
2 parents 4bf278f + a03b1a8 commit 7f98ba4

4 files changed

Lines changed: 443 additions & 11 deletions

File tree

src/org/openlcb/MessageTypeIdentifier.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,10 @@ public enum MessageTypeIdentifier {
5959
IdentifyEventsGlobal ( false, false, true, 0, 2, 11, 0, "IdentifyEventsGlobal"),
6060

6161
LearnEvent ( false, true, true, 0, 1, 12, 0, "LearnEvent"),
62-
ProducerConsumerEventReport ( false, true, true, 0, 1, 13, 0, "ProducerConsumerEventReport"), // This is also the CAN PCER-only
63-
PCERfirst ( false, true, true, 0, 1, 13, 3, "PCERfirst"), // This is CAN only
64-
PCERmiddle ( false, true, true, 0, 1, 13, 2, "PCERmiddle"), // This is CAN only
65-
PCERlast ( false, true, true, 0, 1, 13, 1, "PCERlast"), // This is CAN only
62+
ProducerConsumerEventReport ( false, true, true, 0, 1, 13, 0, "ProducerConsumerEventReport"), // This is the CAN PCER-no-Payload
63+
PCERfirst ( false, true, true, 0, 3, 24, 2, "PCERfirst"), // This is CAN only
64+
PCERmiddle ( false, true, true, 0, 3, 24, 1, "PCERmiddle"), // This is CAN only
65+
PCERlast ( false, true, true, 0, 3, 24, 0, "PCERlast"), // This is CAN only
6666

6767
TractionControlRequest ( true, false, false, 0, 1, 15, 3, "TractionControlRequest" ),
6868
TractionControlReply ( true, false, false, 0, 0, 15, 1, "TractionControlReply" ),
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
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+
}

test/org/openlcb/can/MessageBuilderTest.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,11 @@ public void testProducerConsumerEventReportMessageShortPayload() {
251251
Assert.assertEquals("count", 2, list.size());
252252

253253
CanFrame f0 = list.get(0);
254-
Assert.assertEquals("header", toHexString(0x195B7123), toHexString(f0.getHeader()));
254+
Assert.assertEquals("header", toHexString(0x19F16123), toHexString(f0.getHeader()));
255255
compareContent(event.getContents(), f0);
256256

257257
CanFrame f1 = list.get(1);
258-
Assert.assertEquals("header", toHexString(0x195B5123), toHexString(f1.getHeader()));
258+
Assert.assertEquals("header", toHexString(0x19F14123), toHexString(f1.getHeader()));
259259
compareContent(data, f1);
260260

261261
// check that the frames code back to the original Message
@@ -275,15 +275,15 @@ public void testProducerConsumerEventReportMessageLongPayload() {
275275
Assert.assertEquals("count", 3, list.size());
276276

277277
CanFrame f0 = list.get(0);
278-
Assert.assertEquals("header", toHexString(0x195B7123), toHexString(f0.getHeader()));
278+
Assert.assertEquals("header", toHexString(0x19F16123), toHexString(f0.getHeader()));
279279
compareContent(event.getContents(), f0);
280280

281281
CanFrame f1 = list.get(1);
282-
Assert.assertEquals("header", toHexString(0x195B6123), toHexString(f1.getHeader()));
282+
Assert.assertEquals("header", toHexString(0x19F15123), toHexString(f1.getHeader()));
283283
compareContent(new byte[]{1,2,3,4,5,6,7,8}, f1);
284284

285285
CanFrame f2 = list.get(2);
286-
Assert.assertEquals("header", toHexString(0x195B5123), toHexString(f2.getHeader()));
286+
Assert.assertEquals("header", toHexString(0x19F14123), toHexString(f2.getHeader()));
287287
compareContent(new byte[]{9}, f2);
288288

289289
// check that the frames code back to the original Message
@@ -815,7 +815,7 @@ public void testBogusMti() {
815815
}
816816

817817
@Test
818-
public void testAccumulateSniipReply() {
818+
public void testAccumulateSnipReply() {
819819
// start frame
820820
OpenLcbCanFrame frame = new OpenLcbCanFrame(0x123);
821821
frame.setHeader(0x19A08071);
@@ -860,7 +860,7 @@ public void testAccumulateSniipReply() {
860860
}
861861

862862
@Test
863-
public void testAccumulateLongSniipReply() {
863+
public void testAccumulateLongSnipReply() {
864864
// note short frame at end of MFG info
865865
// as seen from real Signal32
866866

0 commit comments

Comments
 (0)