Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/org/openlcb/MessageTypeIdentifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ public enum MessageTypeIdentifier {
IdentifyEventsGlobal ( false, false, true, 0, 2, 11, 0, "IdentifyEventsGlobal"),

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

TractionControlRequest ( true, false, false, 0, 1, 15, 3, "TractionControlRequest" ),
TractionControlReply ( true, false, false, 0, 0, 15, 1, "TractionControlReply" ),
Expand Down
261 changes: 261 additions & 0 deletions src/org/openlcb/implementations/LocationServiceUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
package org.openlcb.implementations;

import org.openlcb.*;
import org.openlcb.implementations.throttle.Float16;

import java.util.*;
import net.jcip.annotations.*;

/**
* A set of utility functions and classes for
* working with Location Services messages
*
* @author Bob Jacobsen Copyright (C) 2025
*/

public class LocationServiceUtils {

static public Content parse(Message inputMessage) {

// first, check the message type
if (! (inputMessage instanceof ProducerConsumerEventReportMessage)) return null;

ProducerConsumerEventReportMessage msg = (ProducerConsumerEventReportMessage) inputMessage;
byte[] payload = msg.getPayloadArray();

// check for minimal message length
if (payload.length < 8) return null;

// check for right event ID
byte[] eid = msg.getEventID().getContents();
if (eid[0] != 0x01 || eid[1] != 0x02) return null;

// This is Location Services EWP, process it

// process first section
int overallFlags = payload[0]<<8+payload[1];
NodeID scannerReporting = new NodeID(new byte[]{
eid[2], eid[3], eid[4], eid[5], eid[6], eid[7]
});
NodeID scannedDevice = new NodeID(new byte[]{
payload[2], payload[3], payload[4], payload[5], payload[6], payload[7]
});

List<Block> blocks = parseBlock(payload, 8, new ArrayList<Block>());

Content retval = new Content(scannerReporting, scannedDevice, overallFlags, blocks);
return retval;
}

static private List<Block> parseBlock(byte[] payload, int offset, ArrayList<Block> list) {

if (offset >= payload.length) return list;

int length = payload[offset];

if (length == 0) {
list.add(new Block(Block.Type.RESERVED, new byte[0]));
} else {
// here we parse the block into something useful
Block.Type type = Block.Type.get((int) payload[offset+1]);
byte[] content = Arrays.copyOfRange(payload, offset+1, offset+1+length);
switch (type) {
case ANALOG :
list.add(new AnalogBlock(content));
break;

default:
list.add(new Block(type, content));
}
}

return parseBlock(payload, offset+length+1, list);
}

/**
* Accessors for the parse contents
*/
@Immutable
static public class Content {
// The nodeID of the scanner making the report
NodeID scannerReporting;
public NodeID getScannerReporting() { return scannerReporting; }

// The nodeID of the scanned device
NodeID scannedDevice;
public NodeID getScannedDevice() { return scannedDevice; }

// The overall flags
int overallFlags;
public int getOverallFlags() {return overallFlags; }

// The blocks of content
List<Block> blocks;
public List<Block> getBlocks() { return blocks; }

public Content(NodeID scannerReporting, NodeID scannedDevice, int overallFlags, List<Block> blocks) {
this.scannerReporting = scannerReporting;
this.scannedDevice = scannedDevice;
this.overallFlags = overallFlags;
this.blocks = blocks;
}

}

/**
* Generic accessor for the contents of a Block
*/
@Immutable
static public class Block {
public enum Type {
RESERVED(0, "Reserved"),
READABLE(1, "Readable"),
RFID(2, "RFID"),
QR(3, "QR"),
RAILCOM(4, "RailCom"),
TRANSPONDING(5, "Transponding"),
POSITION(6, "Position"),
DCCADDRESS(7, "DccAddress"),
SETSPEED(8, "Set Speed"),
COMMANDEDSPEED(9, "Commanded Speed"),
ACTUALSPEED(10, "Actual Speed"),
ANALOG(11, "Analog");

Type(int code, String name) {
this.code = code;
this.name = name;

getMap().put(code, this);
}

int code;
String name;

public String toString() {
return name;
}

public static Type get(Integer type) {
return mapping.get(type);
}

private static Map<Integer, Type> mapping;
private static Map<Integer, Type> getMap() {
if (mapping == null)
mapping = new java.util.HashMap<Integer, Type>();
return mapping;
}
}

Block(Type type, byte[] content) {
this.length = content.length;
this.type = type;
this.content = content;
}

int length;
public int getLength() { return length; }
Type type;
public Type getType() { return type; }
byte[] content;
public byte[] getContent() { return content; }
}


/**
* Accessor for the contents of a Block with Readable (String) contents
*/
@Immutable
static public class ReadableBlock extends Block {

ReadableBlock(byte[] content) {
super(Block.Type.READABLE, content);

// [1] through end are the user string (UTF8)
try {
text = new String(Arrays.copyOfRange(content, 1, content.length),"UTF-8");
} catch (java.io.UnsupportedEncodingException ex) {
text = "<UTF8 Error>";
}
}

String text;
public String getText() { return text; }
}


/**
* Accessor for the contents of a Block with Analog contents
*/
@Immutable
static public class AnalogBlock extends Block {
public enum Unit {
UNKNOWN(0, "Unknown"),
VOLTS(1, "Volts"),
AMPERES(2, "Amperes"),
WATTS(3, "Watts"),
OHMS(4, "OHMS"),
DEGREESC(5, "Degrees C"),
SECONDS(6, "Seconds"),
METERS(7, "Meters"),
METERS2(8, "Meters^2"),
METERS3(9, "Meters^3"),
METERSPERSECOND(10, "Meters/Second"),
METERSPERSECOND2(11, "Meters/Second^2"),
KILOGRAMS(12, "Kilograms"),
NEWTONS(13, "Newtons");

Unit(int code, String name) {
this.code = code;
this.name = name;

getMap().put(code, this);
}

int code;
String name;

public String toString() {
return name;
}

public static Unit get(Integer unit) {
return mapping.get(unit);
}

private static Map<Integer, Unit> mapping;
private static Map<Integer, Unit> getMap() {
if (mapping == null)
mapping = new java.util.HashMap<Integer, Unit>();
return mapping;
}

}

AnalogBlock(byte[] content) {
super(Block.Type.ANALOG, content);

// decode contents of this block

// [1], [2] are the Float16 value
value = new Float16(content[1], content[2]).getFloat();
// [3] is the unit
unit = Unit.get((int)content[3]);

// [4] through end are the user string (UTF8)
try {
text = new String(Arrays.copyOfRange(content, 4, content.length),"UTF-8");
} catch (java.io.UnsupportedEncodingException ex) {
text = "<UTF8 Error>";
}
}

Unit unit;
public Unit getUnit() { return unit; }
double value;
public double getValue() { return value; }
String text;
public String getText() { return text; }
}

}
14 changes: 7 additions & 7 deletions test/org/openlcb/can/MessageBuilderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -251,11 +251,11 @@ public void testProducerConsumerEventReportMessageShortPayload() {
Assert.assertEquals("count", 2, list.size());

CanFrame f0 = list.get(0);
Assert.assertEquals("header", toHexString(0x195B7123), toHexString(f0.getHeader()));
Assert.assertEquals("header", toHexString(0x19F16123), toHexString(f0.getHeader()));
compareContent(event.getContents(), f0);

CanFrame f1 = list.get(1);
Assert.assertEquals("header", toHexString(0x195B5123), toHexString(f1.getHeader()));
Assert.assertEquals("header", toHexString(0x19F14123), toHexString(f1.getHeader()));
compareContent(data, f1);

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

CanFrame f0 = list.get(0);
Assert.assertEquals("header", toHexString(0x195B7123), toHexString(f0.getHeader()));
Assert.assertEquals("header", toHexString(0x19F16123), toHexString(f0.getHeader()));
compareContent(event.getContents(), f0);

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

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

// check that the frames code back to the original Message
Expand Down Expand Up @@ -815,7 +815,7 @@ public void testBogusMti() {
}

@Test
public void testAccumulateSniipReply() {
public void testAccumulateSnipReply() {
// start frame
OpenLcbCanFrame frame = new OpenLcbCanFrame(0x123);
frame.setHeader(0x19A08071);
Expand Down Expand Up @@ -860,7 +860,7 @@ public void testAccumulateSniipReply() {
}

@Test
public void testAccumulateLongSniipReply() {
public void testAccumulateLongSnipReply() {
// note short frame at end of MFG info
// as seen from real Signal32

Expand Down
Loading