diff --git a/src/org/openlcb/implementations/throttle/TractionThrottle.java b/src/org/openlcb/implementations/throttle/TractionThrottle.java index 91ba64d7..ed9ff614 100644 --- a/src/org/openlcb/implementations/throttle/TractionThrottle.java +++ b/src/org/openlcb/implementations/throttle/TractionThrottle.java @@ -16,6 +16,8 @@ import org.openlcb.messages.TractionControlReplyMessage; import org.openlcb.messages.TractionControlRequestMessage; +import static org.openlcb.messages.TractionControlRequestMessage.CONSIST_FLAG_LISTENERS; + /** * Traction protocol based implementation of the throttle. This differs from * {@link org.openlcb.implementations.throttle.ThrottleImplementation} in that @@ -106,7 +108,11 @@ public void refresh() { */ public void release() { if (!assigned) return; - Message m = TractionControlRequestMessage.createReleaseController(iface.getNodeId(), + Message m = TractionControlRequestMessage.createConsistDetach(iface.getNodeId(), + trainNode.getNodeId(),iface.getNodeId()); + iface.getOutputConnection().put(m, this); + + m = TractionControlRequestMessage.createReleaseController(iface.getNodeId(), trainNode.getNodeId()); iface.getOutputConnection().put(m, this); assigned = false; @@ -130,12 +136,29 @@ private void assign() { iface.getOutputConnection().put(m, this); } + /** + * Invoked when the controller assign reply message arrives from the train node with a + * successful result. + */ private void assignComplete() { assigned = true; setStatus("Enabled."); setEnabled(true); // Refreshes functions and other settings after getting the definite promise from the node. refresh(); + Message m = TractionControlRequestMessage.createConsistAttach(iface.getNodeId(), + trainNode.getNodeId(),iface.getNodeId(),CONSIST_FLAG_LISTENERS); + iface.getOutputConnection().put(m, this); + } + + /** Invoked when a heartbeat request message comes from the train node. + * Sends back a noop traction request. + * @param msg heartbeat request message + */ + private void handleHeartbeatMessage(TractionControlReplyMessage msg) { + Message m = TractionControlRequestMessage.createNoop(iface.getNodeId(), + trainNode.getNodeId()); + iface.getOutputConnection().put(m, this); } /** @@ -221,7 +244,7 @@ public VersionedValue getFunction(int fn) { private synchronized FunctionInfo getFunctionInfo(int fn) { FunctionInfo v = functions.get(fn); if (v == null) { - logger.warning("Creating function " + fn); + logger.fine("Creating function " + fn); v = new FunctionInfo(fn); functions.put(fn, v); if (!pendingAssign) { @@ -261,7 +284,7 @@ public void handleTractionControlReply(TractionControlReplyMessage msg, Connecti if (msg.getCmd() == TractionControlReplyMessage.CMD_GET_FN) { int fn = msg.getFnNumber(); int val = msg.getFnVal(); - logger.warning("Function response: train function " + fn + " value " + val); + logger.fine("Function response: train function " + fn + " value " + val); getFunctionInfo(fn).fnUpdater.setFromOwner(val != 0); return; } @@ -300,12 +323,46 @@ public void handleTractionControlReply(TractionControlReplyMessage msg, Connecti queryConsist(); return; } + if (msg.getCmd() == TractionControlReplyMessage.CMD_MGMT && + msg.getSubCmd() == TractionControlReplyMessage.SUBCMD_MGMT_HEARTBEAT) { + handleHeartbeatMessage(msg); + return; + } + } catch (ArrayIndexOutOfBoundsException e) { + // Invalid message. + logger.warning("Invalid traction message " +msg.toString()); + return; + } + logger.info("Unhandled traction response message " +msg.toString()); + } + + @Override + public void handleTractionControlRequest(TractionControlRequestMessage msg, Connection sender) { + if (trainNode == null) return; + if (!msg.getSourceNodeID().equals(trainNode.getNodeId())) return; + if (!msg.getDestNodeID().equals(iface.getNodeId())) return; + try { + if (msg.getCmd() == TractionControlRequestMessage.CMD_SET_SPEED) { + speedUpdater.setFromOwner(msg.getSpeed().getFloat()); + return; + } + if (msg.getCmd() == TractionControlRequestMessage.CMD_ESTOP) { + speedUpdater.setFromOwner(Float.NaN); + return; + } + if (msg.getCmd() == TractionControlRequestMessage.CMD_SET_FN) { + int fn = msg.getFnNumber(); + int val = msg.getFnVal(); + logger.fine("Function request: train function " + fn + " value " + val); + getFunctionInfo(fn).fnUpdater.setFromOwner(val != 0); + return; + } } catch (ArrayIndexOutOfBoundsException e) { // Invalid message. logger.warning("Invalid traction message " +msg.toString()); return; } - logger.info("Unhandled traction message " +msg.toString()); + logger.info("Unhandled traction request message (to listener) " +msg.toString()); } public String getStatus() { @@ -313,7 +370,7 @@ public String getStatus() { } private void setStatus(String status) { - logger.warning("Throttle status: " + status); + logger.fine("Throttle status: " + status); String oldStatus = this.status; this.status = status; firePropertyChange(UPDATE_PROP_STATUS, oldStatus, this.status); diff --git a/src/org/openlcb/messages/TractionControlRequestMessage.java b/src/org/openlcb/messages/TractionControlRequestMessage.java index 4b1c56f6..710fd507 100644 --- a/src/org/openlcb/messages/TractionControlRequestMessage.java +++ b/src/org/openlcb/messages/TractionControlRequestMessage.java @@ -44,11 +44,16 @@ public class TractionControlRequestMessage extends AddressedPayloadMessage { public final static int CONSIST_FLAG_FNN = 0x08; public final static int CONSIST_FLAG_HIDE = 0x80; + public final static int CONSIST_FLAG_LISTENERS = + CONSIST_FLAG_HIDE | CONSIST_FLAG_FN0 | CONSIST_FLAG_FNN; + public final static byte CMD_MGMT = 0x40; public final static byte SUBCMD_MGMT_RESERVE = 1; public final static byte SUBCMD_MGMT_RELEASE = 2; public final static byte SUBCMD_MGMT_NOOP = 3; + public final static byte CMD_LISTENER_FORWARD = (byte)0x80; + /// 1 scale mph in meters per second for the getspeed/setspeed commands public final static double MPH = 0.44704; /// 1 scale km/h in meters per second for the getspeed/setspeed commands @@ -168,7 +173,18 @@ public static TractionControlRequestMessage createNoop(NodeID source, NodeID des } public byte getCmd() throws ArrayIndexOutOfBoundsException { - return payload[0]; + int p = payload[0] & 0xff; + // Removes the consist listener forward flag. + return (byte)(p & 0x7F); + } + + /** + * Checks if this message is forwarded as part of the consist listener protocol. + * @return true if this message is a forwarded traciton request, false if this is an original + * (operator-initiated) command. + */ + public boolean isListenerMessage() { + return (payload[0] & CMD_LISTENER_FORWARD) != 0; } // Valid for messages that contain a subcommand. @@ -181,6 +197,26 @@ public Float16 getSpeed() throws ArrayIndexOutOfBoundsException { return new Float16((((int)payload[1]) << 8) | (payload[2] & 0xff)); } + // Valid only for get function request + public int getFnNumber() { + int retval = 0; + retval = payload[1] & 0xff; + retval <<= 8; + retval |= (payload[2] & 0xff); + retval <<= 8; + retval |= (payload[3] & 0xff); + return retval; + } + + // Valid only for get function request + public int getFnVal() { + int retval = 0; + retval = payload[4] & 0xff; + retval <<= 8; + retval |= (payload[5] & 0xff); + return retval; + } + @Override public void applyTo(MessageDecoder decoder, Connection sender) { decoder.handleTractionControlRequest(this, sender); @@ -234,6 +270,9 @@ public String toString() { p.append(" "); p.append(getEMTI().toString()); p.append(" "); + if (isListenerMessage()) { + p.append("[listener] "); + } try { switch (getCmd()) { case CMD_SET_SPEED: { diff --git a/test/org/openlcb/messages/TractionControlRequestMessageTest.java b/test/org/openlcb/messages/TractionControlRequestMessageTest.java index 1e74f293..f5ec3132 100644 --- a/test/org/openlcb/messages/TractionControlRequestMessageTest.java +++ b/test/org/openlcb/messages/TractionControlRequestMessageTest.java @@ -32,21 +32,49 @@ public void testCreateSetSpeed() throws Exception { Assert.assertEquals("00 45 D0", Utilities.toHexSpaceString(msg.getPayload())); Assert.assertEquals("06.05.05.04.04.03 - 02.02.02.04.04.04 TractionControlRequest " + "set speed F 13 mph", msg.toString()); + Assert.assertEquals(13 * MPH, msg.getSpeed().getFloat(), 1e-2); msg = TractionControlRequestMessage.createSetSpeed(src, dst, false, 13 * MPH); Assert.assertEquals("00 C5 D0", Utilities.toHexSpaceString(msg.getPayload())); Assert.assertEquals("06.05.05.04.04.03 - 02.02.02.04.04.04 TractionControlRequest " + "set speed R 13 mph", msg.toString()); + Assert.assertEquals(-13 * MPH, msg.getSpeed().getFloat(), 1e-2); msg = TractionControlRequestMessage.createSetSpeed(src, dst, true, 126 * MPH); Assert.assertEquals("00 53 0A", Utilities.toHexSpaceString(msg.getPayload())); Assert.assertEquals("06.05.05.04.04.03 - 02.02.02.04.04.04 TractionControlRequest " + "set speed F 126 mph", msg.toString()); + Assert.assertEquals(126 * MPH, msg.getSpeed().getFloat(), 1e-1); msg = TractionControlRequestMessage.createSetSpeed(src, dst, false, 126 * MPH); Assert.assertEquals("00 D3 0A", Utilities.toHexSpaceString(msg.getPayload())); Assert.assertEquals("06.05.05.04.04.03 - 02.02.02.04.04.04 TractionControlRequest " + "set speed R 126 mph", msg.toString()); + Assert.assertEquals(-126 * MPH, msg.getSpeed().getFloat(), 1e-1); + } + + @Test public void testListenerReply() throws Exception { + TractionControlRequestMessage msg = new TractionControlRequestMessage(src, dst, + Utilities.bytesFromHexString("80C5D0")); + Assert.assertEquals(TractionControlRequestMessage.CMD_SET_SPEED, msg.getCmd()); + Assert.assertEquals(-13 * MPH, msg.getSpeed().getFloat(), 1e-2); + Assert.assertEquals(true, msg.isListenerMessage()); + + msg = new TractionControlRequestMessage(src, dst, + Utilities.bytesFromHexString("81 BB AA 99 DD BA")); + Assert.assertEquals(TractionControlRequestMessage.CMD_SET_FN, msg.getCmd()); + Assert.assertEquals(12298905, msg.getFnNumber()); + Assert.assertEquals(56762, msg.getFnVal()); + Assert.assertEquals(true, msg.isListenerMessage()); + Assert.assertEquals("06.05.05.04.04.03 - 02.02.02.04.04.04 TractionControlRequest " + + "[listener] set fn 12298905 to 56762", msg.toString()); + + msg = new TractionControlRequestMessage(src, dst, + Utilities.bytesFromHexString("01 BB AA 99 DD BA")); + Assert.assertEquals(TractionControlRequestMessage.CMD_SET_FN, msg.getCmd()); + Assert.assertEquals(12298905, msg.getFnNumber()); + Assert.assertEquals(56762, msg.getFnVal()); + Assert.assertEquals(false, msg.isListenerMessage()); } @Test @@ -72,12 +100,16 @@ public void testCreateSetFn() throws Exception { Assert.assertEquals("01 00 00 0B 00 01", Utilities.toHexSpaceString(msg.getPayload())); Assert.assertEquals("06.05.05.04.04.03 - 02.02.02.04.04.04 TractionControlRequest " + "set fn 11 to 1", msg.toString()); + Assert.assertEquals(11, msg.getFnNumber()); + Assert.assertEquals(1, msg.getFnVal()); msg = TractionControlRequestMessage.createSetFn(src, dst, 12298905, 56762); Assert.assertEquals("01 BB AA 99 DD BA", Utilities.toHexSpaceString(msg.getPayload())); Assert.assertEquals("06.05.05.04.04.03 - 02.02.02.04.04.04 TractionControlRequest " + "set fn 12298905 to 56762", msg.toString()); + Assert.assertEquals(12298905, msg.getFnNumber()); + Assert.assertEquals(56762, msg.getFnVal()); } @Test