package org.openlcb.implementations.throttle;
import org.openlcb.Connection;
import org.openlcb.Message;
import org.openlcb.MessageDecoder;
import org.openlcb.OlcbInterface;
import org.openlcb.implementations.VersionedValue;
import org.openlcb.implementations.VersionedValueListener;
import org.openlcb.messages.TractionControlReplyMessage;
import org.openlcb.messages.TractionControlRequestMessage;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
/**
* Traction protocol based implementation of the throttle. This differs from {@ref
* ThrottleImplementation} in that it uses the {@ref TractionControlRequest} messages for talking
* to the train nodes, including proper allocation and deallocation of throttles.
*
* Created by bracz on 12/30/15.
*/
public class TractionThrottle extends MessageDecoder {
public static final String UPDATE_PROP_ENABLED = "updateEnabled";
public static final String UPDATE_PROP_STATUS = "updateStatus";
private static Logger logger = Logger.getLogger(new Object() {
}.getClass().getSuperclass()
.getName());
private final OlcbInterface iface;
RemoteTrainNode trainNode;
boolean assigned = false;
boolean enabled = false;
String status;
java.beans.PropertyChangeSupport pcs = new java.beans.PropertyChangeSupport(this);
private VersionedValue speed = new VersionedValue<>(0.0f);
private VersionedValueListener speedUpdater = new VersionedValueListener(speed) {
@Override
public void update(Float t) {
if (!enabled) return;
Message m = TractionControlRequestMessage.createSetSpeed(iface.getNodeId(),
trainNode.getNodeId(), t >= 0, t);
iface.getOutputConnection().put(m, TractionThrottle.this);
}
};
private Map functions = new HashMap<>();
private boolean pendingAssign = false;
public TractionThrottle(OlcbInterface iface) {
this.iface = iface;
}
public void start(RemoteTrainNode trainNode) {
if (assigned && !trainNode.getNodeId().equals(trainNode.getNodeId())) {
release();
}
this.trainNode = trainNode;
assign();
}
/**
* Releases the throttle from the assigned train node.
*/
public void release() {
if (!assigned) return;
Message m = TractionControlRequestMessage.createReleaseController(iface.getNodeId(),
trainNode.getNodeId());
iface.getOutputConnection().put(m, this);
assigned = false;
setEnabled(false);
iface.unRegisterMessageListener(this);
setStatus("Released node.");
}
private void assign() {
setStatus("Assigning node...");
iface.registerMessageListener(this);
pendingAssign = true;
Message m = TractionControlRequestMessage.createAssignController(iface.getNodeId(),
trainNode.getNodeId());
iface.getOutputConnection().put(m, this);
}
private void assignComplete() {
assigned = true;
setStatus("Enabled.");
setEnabled(true);
querySpeed();
// Refreshes functions after getting the definite promise from the node.
for (FunctionInfo f : functions.values()) {
queryFunction(f.fn);
}
}
/**
* Initiates fetching the current speed from the OpenLCB node. When the speed value arrives,
* the property change listener will be called on the speed object.
*/
public void querySpeed() {
Message m = TractionControlRequestMessage.createGetSpeed(iface.getNodeId(), trainNode
.getNodeId());
iface.getOutputConnection().put(m, this);
}
/**
* Initiates fetching the current value of a given function from the OpenLCB node. When the
* function value arrives, the property change listener will be called on the function object.
*
* @param fn the number of the function (address in OLCB land)
*/
public void queryFunction(int fn) {
Message m = TractionControlRequestMessage.createGetFn(iface.getNodeId(), trainNode
.getNodeId(), fn);
iface.getOutputConnection().put(m, this);
}
public VersionedValue getFunction(int fn) {
return getFunctionInfo(fn).shared;
}
/**
* Creates or returns the FunctionInfo structure for a given function number.
*/
private synchronized FunctionInfo getFunctionInfo(int fn) {
FunctionInfo v = functions.get(fn);
if (v == null) {
logger.warning("Creating function " + fn);
v = new FunctionInfo(fn);
functions.put(fn, v);
if (!pendingAssign) {
queryFunction(fn);
}
}
return v;
}
public VersionedValue getSpeed() {
return speed;
}
@Override
public void handleTractionControlReply(TractionControlReplyMessage 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() == TractionControlReplyMessage.CMD_CONTROLLER &&
msg.getSubCmd() == TractionControlReplyMessage.SUBCMD_CONTROLLER_ASSIGN) {
byte result = msg.getAssignControllerReply();
pendingAssign = false;
if (result == 0) {
assignComplete();
} else if ((result & 1) != 0) {
setStatus("Assigning controller failed: controller refused.");
} else if ((result & 2) != 0) {
setStatus("Assigning controller failed: train node refused.");
}
return;
}
if (msg.getCmd() == TractionControlReplyMessage.CMD_GET_SPEED) {
speedUpdater.setFromOwner(msg.getSetSpeed().getFloat());
return;
}
if (msg.getCmd() == TractionControlReplyMessage.CMD_GET_FN) {
int fn = msg.getFnNumber();
int val = msg.getFnVal();
logger.warning("Function response: 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());
}
public String getStatus() {
return status;
}
private void setStatus(String status) {
logger.warning("Throttle status: " + status);
String oldStatus = this.status;
this.status = status;
firePropertyChange(UPDATE_PROP_STATUS, oldStatus, this.status);
}
public boolean getEnabled() {
return enabled;
}
private void setEnabled(boolean enabled_) {
boolean old = enabled;
enabled = enabled_;
firePropertyChange(UPDATE_PROP_ENABLED, old, enabled);
}
public synchronized void addPropertyChangeListener(java.beans.PropertyChangeListener l) {
pcs.addPropertyChangeListener(l);
}
public synchronized void removePropertyChangeListener(java.beans.PropertyChangeListener l) {
pcs.removePropertyChangeListener(l);
}
protected void firePropertyChange(String p, Object old, Object n) {
pcs.firePropertyChange(p, old, n);
}
private class FunctionInfo {
int fn;
VersionedValue shared = new VersionedValue<>(false);
VersionedValueListener fnUpdater = new VersionedValueListener(shared) {
@Override
public void update(Boolean aBoolean) {
if (!enabled) return;
Message m = TractionControlRequestMessage.createSetFn(iface.getNodeId(),
trainNode.getNodeId(), fn, aBoolean.booleanValue() ? 1 : 0);
iface.getOutputConnection().put(m, TractionThrottle.this);
}
};
public FunctionInfo(int num) {
fn = num;
}
}
}