Skip to content

Commit

Permalink
Decodes traction request and reply messages for the monitor window (#195
Browse files Browse the repository at this point in the history
)

This PR adds toString() methods to the TractionControlRequest and TractionControlReply messages. These tostring methods are used by the OpenLCB/LCC monitor log in JMRI. The decoding interprets the message command, subcommand and renders the arguments in text form.

Adds unit tests for untested parts of the traction messages, including creators and accessors.
Adds a few missing details from the TractionWM to the traction request/reply classes.

Misc supporting changes:
- Fixes bugs in the Utilities.NetworkToHostUintXX methods.
- Adds 24-bit Utilities.NetworkToHostUintXX method.
- Adds a constructor to NodeID that takes a long argument.
- Fixes missing rounding in the Float16 constructor (it was truncating to zero instead of rounding).
- Adds isPositive to Float16 class.

===

* Adds isPositive to Float16.

Fixes a rounding problem in the LSB when conmvertingvalue from double.

* Fixes array index out of bounds exceptions in Utilities.
Adds support for UInt24 parsing and rendering.

* Starts adding toString() method for TractionControlRequest.
Adds unit tests for set/get speed, function and estop commands.

* Adds a constructor to NodeID to create it from a long containind 48 bits of data.

* Adds printouts and tests for controller change messages.

* Adds tostring and tests for listener and management messages.

* Adds tostring to traction reply message.
Adds unit tests for traction reply class.

* Implements generating an ESTOP message by sending a nan float value to the speed versionedvalue.
  • Loading branch information
balazsracz authored Mar 6, 2022
1 parent 84e7990 commit 3e2078f
Show file tree
Hide file tree
Showing 11 changed files with 792 additions and 18 deletions.
7 changes: 7 additions & 0 deletions src/org/openlcb/NodeID.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ public NodeID(@NonNull String value) {
this.contents[i] = contents[i];
}

@CheckReturnValue
public NodeID(long value) {
byte[] c = new byte[BYTECOUNT];
Utilities.HostToNetworkUint48(c, 0, value);
contents = c;
}

byte[] contents;

@CheckReturnValue
Expand Down
23 changes: 19 additions & 4 deletions src/org/openlcb/Utilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ static public byte[] bytesFromHexString(String s) {
}

static public int NetworkToHostUint8(byte[] arr, int offset) {
if (arr == null || arr.length < offset) {
if (arr == null || arr.length <= offset) {
return 0;
}
int r = arr[offset];
Expand All @@ -141,7 +141,7 @@ static public void HostToNetworkUint8(byte[] arr, int offset, int value) {
}

static public int NetworkToHostUint16(byte[] arr, int offset) {
if (arr == null || arr.length < (offset+1)) {
if (arr == null || arr.length <= (offset+1)) {
return 0;
}
return ((((int)arr[offset]) & 0xff) << 8) |
Expand All @@ -153,8 +153,23 @@ static public void HostToNetworkUint16(byte[] arr, int offset, int value) {
arr[offset+1] = (byte) (value & 0xff);
}

static public int NetworkToHostUint24(byte[] arr, int offset) {
if (arr == null || arr.length <= (offset+2)) {
return 0;
}
return (((((int)arr[offset]) & 0xff) << 16) |
((((int)arr[offset+1]) & 0xff) << 8) |
(((int)arr[offset+2]) & 0xff));
}

static public void HostToNetworkUint24(byte[] arr, int offset, int value) {
arr[offset] = (byte) ((value >> 16) & 0xff);
arr[offset+1] = (byte) ((value >> 8) & 0xff);
arr[offset+2] = (byte) (value & 0xff);
}

static public long NetworkToHostUint32(byte[] arr, int offset) {
if (arr == null || arr.length < (offset+3)) {
if (arr == null || arr.length <= (offset+3)) {
return 0;
}
long ret = 0;
Expand All @@ -176,7 +191,7 @@ static public void HostToNetworkUint32(byte[] arr, int offset, long value) {
}

static public long NetworkToHostUint48(byte[] arr, int offset) {
if (arr == null || arr.length < (offset+5)) {
if (arr == null || arr.length <= (offset+5)) {
return 0;
}
long ret = 0;
Expand Down
9 changes: 7 additions & 2 deletions src/org/openlcb/implementations/throttle/Float16.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public Float16(double d, boolean positive) {
}
}

int ch = ((int)(d*1024.))&0x3FF;
int ch = ((int)(d*1024. + 0.5))&0x3FF;
if ((((int)(d*1024.))&0x400) != 0x400) logger.log(Level.WARNING, "normalization failed with d={0} exp={1}", new Object[]{d, exp});
int bits = ch | (exp<<10);
if (!positive) bits = bits | 0x8000;
Expand Down Expand Up @@ -92,5 +92,10 @@ public float getFloat() {
int sign = ( (byte1 & 0x80) !=0 ) ? -1 : +1;
return (float)(((double)ch)/1024.0*(Math.pow(2, exp)))*sign;
}


/// @return true if this is a positive number (meaning forward speed), false if it is
/// negative (reverse speed).
public boolean isPositive() {
return (byte1 & 0x80) == 0;
}
}
11 changes: 8 additions & 3 deletions src/org/openlcb/implementations/throttle/TractionThrottle.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,14 @@ public class TractionThrottle extends MessageDecoder {
@Override
public void update(Float t) {
if (!enabled) return;

Message m = TractionControlRequestMessage.createSetSpeed(iface.getNodeId(),
trainNode.getNodeId(), Math.copySign(1.0, t) >= 0, t);
Message m;
if (Float.isNaN(t)) {
m = TractionControlRequestMessage.createSetEstop(iface.getNodeId(),
trainNode.getNodeId());
} else {
m = TractionControlRequestMessage.createSetSpeed(iface.getNodeId(),
trainNode.getNodeId(), Math.copySign(1.0, t) >= 0, t);
}
iface.getOutputConnection().put(m, TractionThrottle.this);

}
Expand Down
168 changes: 168 additions & 0 deletions src/org/openlcb/messages/TractionControlReplyMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
import org.openlcb.MessageDecoder;
import org.openlcb.MessageTypeIdentifier;
import org.openlcb.NodeID;
import org.openlcb.Utilities;
import org.openlcb.implementations.throttle.Float16;
import static org.openlcb.messages.TractionControlRequestMessage.speedToDebugString;
import static org.openlcb.messages.TractionControlRequestMessage.consistFlagsToDebugString;

/**
* Traction Control Reply message implementation.
Expand All @@ -21,6 +24,8 @@
public class TractionControlReplyMessage extends AddressedPayloadMessage {
private final static Logger logger = Logger.getLogger(TractionControlReplyMessage.class.getName());
public final static byte CMD_GET_SPEED = TractionControlRequestMessage.CMD_GET_SPEED;
public final static int GET_SPEED_FLAG_ESTOP = 0x01;

public final static byte CMD_GET_FN = TractionControlRequestMessage.CMD_GET_FN;

public final static byte CMD_CONTROLLER = TractionControlRequestMessage.CMD_CONTROLLER;
Expand All @@ -30,6 +35,7 @@ public class TractionControlReplyMessage extends AddressedPayloadMessage {

public final static byte CMD_MGMT = TractionControlRequestMessage.CMD_MGMT;
public final static byte SUBCMD_MGMT_RESERVE = TractionControlRequestMessage.SUBCMD_MGMT_RESERVE;
public final static byte SUBCMD_MGMT_HEARTBEAT = 0x03;

public final static byte CMD_CONSIST = TractionControlRequestMessage.CMD_CONSIST;
public final static byte SUBCMD_CONSIST_ATTACH = TractionControlRequestMessage.SUBCMD_CONSIST_ATTACH;
Expand Down Expand Up @@ -104,6 +110,26 @@ public int getFnVal() {
return retval;
}

/**
* Extract the consisted train's node ID from the consist attach/detach response.
* @return the consisted train's node ID.
*/
@Nullable
public NodeID getConsistAttachNodeID() {
if (payload.length < 8) return null;
byte[] id = new byte[6];
System.arraycopy(payload, 2, id, 0, 6);
return new NodeID(id);
}

/**
* Extract the consistattach/detach response code
* @return a 16-bit openlcb error code, 0 for success.
*/
public int getConsistAttachCode() {
return Utilities.NetworkToHostUint16(payload, 8);
}

/** @return the length of the consist list.
* Valid only for consist query reply message
*/
Expand Down Expand Up @@ -155,4 +181,146 @@ public void applyTo(MessageDecoder decoder, Connection sender) {
public MessageTypeIdentifier getEMTI() {
return MessageTypeIdentifier.TractionControlReply;
}

@Override
public String toString() {
StringBuilder p = new StringBuilder(getSourceNodeID().toString());
p.append(" - ");
p.append(getDestNodeID());
p.append(" ");
p.append(getEMTI().toString());
p.append(" ");
try {
switch (getCmd()) {
case CMD_GET_SPEED: {
p.append("speed reply ");
p.append(speedToDebugString(getSetSpeed()));
if ((payload.length >= 4) && ((payload[3] & GET_SPEED_FLAG_ESTOP) != 0)) {
p.append(" estop");
}
if (payload.length >= 6) {
p.append(" commanded speed ");
p.append(speedToDebugString(getCommandedSpeed()));
}
if (payload.length >= 8) {
p.append(" actual speed ");
p.append(speedToDebugString(getActualSpeed()));
}
break;
}
case CMD_GET_FN: {
int fn = Utilities.NetworkToHostUint24(payload, 1);
int val = Utilities.NetworkToHostUint16(payload, 4);
p.append(String.format("fn %d is %d", fn, val));
break;
}
case CMD_CONTROLLER: {
switch(getSubCmd()) {
case SUBCMD_CONTROLLER_ASSIGN: {
p.append("controller assign");
int flags = Utilities.NetworkToHostUint8(payload, 2);
if(flags == 0) {
p.append(" OK");
} else {
p.append(String.format(" fail 0x%02x", flags));
}
break;
}
case SUBCMD_CONTROLLER_QUERY: {
long nid = Utilities.NetworkToHostUint48(payload, 3);
p.append("controller is ");
p.append(new NodeID(nid).toString());
int flags = Utilities.NetworkToHostUint8(payload, 2);
if(flags != 0) {
p.append(String.format(" flags 0x%02x", flags));
}
break;
}
case SUBCMD_CONTROLLER_CHANGE: {
p.append("change controller reply");
int flags = Utilities.NetworkToHostUint8(payload, 2);
if (flags == 0) {
p.append(" OK");
} else {
p.append(String.format(" reject 0x%02x", flags));
}
break;
}
default:
return super.toString();
}
break;
}
case CMD_CONSIST: {
switch (getSubCmd()) {
case SUBCMD_CONSIST_ATTACH: {
long nid = Utilities.NetworkToHostUint48(payload, 2);
p.append("listener attach ");
p.append(new NodeID(nid).toString());
int code = Utilities.NetworkToHostUint16(payload, 8);
p.append(String.format(" result 0x%04x", code));
break;
}
case SUBCMD_CONSIST_DETACH: {
long nid = Utilities.NetworkToHostUint48(payload, 2);
p.append("listener detach ");
p.append(new NodeID(nid).toString());
int code = Utilities.NetworkToHostUint16(payload, 8);
p.append(String.format(" result 0x%04x", code));
break;
}
case SUBCMD_CONSIST_QUERY: {
p.append("listener is");
int count = Utilities.NetworkToHostUint8(payload, 2);
p.append(String.format(" count %d", payload[2] & 0xff));
if (payload.length >= 4) {
p.append(String.format(" index %d", payload[3] & 0xff));
}
if ((payload.length >= 5) && (payload[4] != 0)) {
p.append(" flags ");
p.append(consistFlagsToDebugString(payload[4]));
}
if (payload.length >= 11) {
p.append(" is ");
p.append(new NodeID(Utilities.NetworkToHostUint48(payload, 5)).toString());
}
break;
}
default:
return super.toString();
}
break;
}
case CMD_MGMT: {
switch (getSubCmd()) {
case SUBCMD_MGMT_RESERVE: {
p.append("reserve reply");
if (payload[2] == 0) {
p.append(" OK");
} else {
p.append(String.format(" error 0x%02x", payload[2] & 0xff));
}
break;
}
case SUBCMD_MGMT_HEARTBEAT: {
p.append("heartbeat request");
if (payload.length >= 3) {
p.append(String.format(" in %d seconds", payload[2] & 0xff));
}
break;
}
default:
return super.toString();
}
break;
}
default:
return super.toString();
}

} catch (ArrayIndexOutOfBoundsException e) {
return super.toString();
}
return p.toString();
}
}
Loading

0 comments on commit 3e2078f

Please sign in to comment.