rolled back usage of CompletableFuture in favour of callbacks
This commit is contained in:
@@ -1,21 +1,103 @@
|
||||
package de.srsoftware.web4rail;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.Date;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class Command extends CompletableFuture<ControlUnit.Reply> {
|
||||
public class Command {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Command.class);
|
||||
private String command;
|
||||
private Reply reply = null;
|
||||
|
||||
public static class Reply{
|
||||
private long secs;
|
||||
private int milis;
|
||||
private int code;
|
||||
private String message;
|
||||
|
||||
public Reply(Scanner scanner) {
|
||||
String word = scanner.next();
|
||||
secs = Long.parseLong(word.substring(0, word.length()-4));
|
||||
milis = Integer.parseInt(word.substring(word.length()-3));
|
||||
code = scanner.nextInt();
|
||||
message = scanner.nextLine().trim();
|
||||
LOG.info("recv {}.{} {} {}.",secs,milis,code,message);
|
||||
}
|
||||
|
||||
public Reply(int code, String message) {
|
||||
secs = new Date().getTime();
|
||||
milis = (int) (secs % 1000);
|
||||
secs /= 1000;
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public boolean is(int code) {
|
||||
return code == this.code;
|
||||
}
|
||||
|
||||
public boolean succeeded() {
|
||||
return (code > 199 && code < 300);
|
||||
}
|
||||
|
||||
public String message() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Reply("+secs+"."+milis+" / "+code+" / "+message+")";
|
||||
}
|
||||
}
|
||||
|
||||
public Command(String command) {
|
||||
this.command = command;
|
||||
this.orTimeout(500, TimeUnit.MILLISECONDS);
|
||||
LOG.debug("Created new Command({}).",command);
|
||||
}
|
||||
|
||||
protected void onFailure(Reply reply) {
|
||||
LOG.warn("onFailure({})",command);
|
||||
}
|
||||
|
||||
public void onResponse(Reply reply) {
|
||||
this.reply = reply;
|
||||
if (reply.succeeded()) {
|
||||
onSuccess();
|
||||
} else onFailure(reply);
|
||||
}
|
||||
|
||||
public void onSuccess(){
|
||||
LOG.debug("onSuccess({})",command);
|
||||
}
|
||||
|
||||
public void readReplyFrom(Scanner scanner) {
|
||||
onResponse(new Reply(scanner));
|
||||
}
|
||||
|
||||
public Reply reply() throws TimeoutException {
|
||||
return reply(100);
|
||||
}
|
||||
|
||||
public Reply reply(int timeout) throws TimeoutException {
|
||||
int counter = 0;
|
||||
while (reply == null) try {
|
||||
if (counter++ > timeout) timeout();
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warn("wait() interrupted!",e);
|
||||
}
|
||||
return reply;
|
||||
}
|
||||
|
||||
private void timeout() throws TimeoutException {
|
||||
String msg = command;
|
||||
command = null;
|
||||
throw new TimeoutException("\""+msg+"\" timed out!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
@@ -26,44 +27,10 @@ public class ControlUnit extends Thread implements Constants{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ControlUnit.class);
|
||||
private static final String DEFAULT_HOST = "localhost";
|
||||
private static final int DEFAULT_PORT = 4303;
|
||||
private static final int OK_PROTO = 201;
|
||||
private static final int OK_MODE = 202;
|
||||
private static final int OK = 200;
|
||||
private static final String HOST = "host";
|
||||
private static final String PORT = "port";
|
||||
private static final String BUS = "bus";
|
||||
|
||||
public class Reply{
|
||||
private long secs;
|
||||
private int milis;
|
||||
private int code;
|
||||
private String message;
|
||||
|
||||
public Reply(Scanner scanner) {
|
||||
String word = scanner.next();
|
||||
secs = Long.parseLong(word.substring(0, word.length()-4));
|
||||
milis = Integer.parseInt(word.substring(word.length()-3));
|
||||
code = scanner.nextInt();
|
||||
message = scanner.nextLine().trim();
|
||||
LOG.info("recv {}.{} {} {}.",secs,milis,code,message);
|
||||
}
|
||||
|
||||
public String message() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Reply("+secs+"."+milis+" / "+code+" / "+message+")";
|
||||
}
|
||||
|
||||
public boolean is(int code) {
|
||||
return code == this.code;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private String host = DEFAULT_HOST;
|
||||
private int port = DEFAULT_PORT;
|
||||
private int bus = 0;
|
||||
@@ -72,6 +39,11 @@ public class ControlUnit extends Thread implements Constants{
|
||||
private Socket socket;
|
||||
private Scanner scanner;
|
||||
private boolean power = false;
|
||||
private Plan plan;
|
||||
|
||||
public ControlUnit(Plan plan) {
|
||||
this.plan = plan;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return stops the loop at the next interval
|
||||
@@ -81,7 +53,7 @@ public class ControlUnit extends Thread implements Constants{
|
||||
return this;
|
||||
}
|
||||
|
||||
private void handshake() throws IOException {
|
||||
private void handshake() throws TimeoutException, IOException {
|
||||
String proto = null;
|
||||
if (scanner.hasNext()) {
|
||||
String line = scanner.nextLine();
|
||||
@@ -92,18 +64,19 @@ public class ControlUnit extends Thread implements Constants{
|
||||
}
|
||||
if (proto == null) throw new IOException("Handshake failed: "+line);
|
||||
if (!proto.startsWith("0.8.")) throw new IOException("Unsupported protocol: "+proto);
|
||||
writeln("SET PROTOCOL SRCP "+proto);
|
||||
} else throw new IOException("Handshake expected.");
|
||||
|
||||
Command command = new Command("SET PROTOCOL SRCP "+proto);
|
||||
send(command);
|
||||
if (!command.reply().succeeded()) throw new IOException("Handshake failed: "+command.reply());
|
||||
|
||||
Reply reply = new Reply(scanner);
|
||||
if (reply.code != OK_PROTO) throw new IOException("Handshake failed: "+reply);
|
||||
command = new Command("SET CONNECTIONMODE SRCP COMMAND"); // preset following mode: COMMAND MODE
|
||||
send(command);
|
||||
if (!command.reply().succeeded()) throw new IOException("Handshake failed: "+command.reply());
|
||||
|
||||
writeln("SET CONNECTIONMODE SRCP COMMAND"); // preset following mode: COMMAND MODE
|
||||
reply = new Reply(scanner);
|
||||
if (reply.code != OK_MODE) throw new IOException("Handshake failed: "+reply);
|
||||
writeln("GO"); // switch mode
|
||||
reply = new Reply(scanner);
|
||||
if (reply.code != OK) throw new IOException("Handshake failed: "+reply);
|
||||
command = new Command("GO"); // switch mode
|
||||
send(command);
|
||||
if (!command.reply().succeeded()) throw new IOException("Handshake failed: "+command.reply());
|
||||
}
|
||||
|
||||
private JSONObject json() {
|
||||
@@ -124,10 +97,21 @@ public class ControlUnit extends Thread implements Constants{
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
ControlUnit cu = new ControlUnit().setEndpoint("Modellbahn", DEFAULT_PORT).setBus(1).restart();
|
||||
ControlUnit cu = new ControlUnit(null).setEndpoint("Modellbahn", DEFAULT_PORT).setBus(1).restart();
|
||||
Thread.sleep(1000);
|
||||
cu.queue("SET {} POWER ON");
|
||||
cu.queue("SET {} GL 1 0 10 128");
|
||||
cu.queue(new Command("SET {} POWER ON") {
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
LOG.debug("Power on");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Reply reply) {
|
||||
LOG.debug("Was not able to turn power on: {}",reply.message());
|
||||
}
|
||||
|
||||
});
|
||||
Thread.sleep(1000);
|
||||
cu.end();
|
||||
}
|
||||
@@ -166,10 +150,9 @@ public class ControlUnit extends Thread implements Constants{
|
||||
return win;
|
||||
}
|
||||
|
||||
public Command queue(String command) {
|
||||
Command promise = new Command(command);
|
||||
queue.add(promise);
|
||||
return promise;
|
||||
public Command queue(Command command) {
|
||||
queue.add(command);
|
||||
return command;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,7 +173,7 @@ public class ControlUnit extends Thread implements Constants{
|
||||
Thread.sleep(10);
|
||||
} else {
|
||||
Command command = queue.pop();
|
||||
command.complete(send(command));
|
||||
send(command);
|
||||
}
|
||||
} catch (InterruptedException | IOException e) {
|
||||
e.printStackTrace();
|
||||
@@ -215,10 +198,12 @@ public class ControlUnit extends Thread implements Constants{
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private Reply send(Command command) throws IOException {
|
||||
if (command == null) return null;
|
||||
writeln(command.toString());
|
||||
return new Reply(scanner);
|
||||
private void send(Command command) throws IOException {
|
||||
if (command == null || command.toString() == null) return;
|
||||
String data = command.toString().replace("{}", ""+bus);
|
||||
socket.getOutputStream().write((data+"\n").getBytes(StandardCharsets.US_ASCII));
|
||||
LOG.info("sent {}.",data);
|
||||
command.readReplyFrom(scanner);
|
||||
}
|
||||
|
||||
private ControlUnit setBus(int bus) {
|
||||
@@ -239,7 +224,7 @@ public class ControlUnit extends Thread implements Constants{
|
||||
scanner = new Scanner(socket.getInputStream());
|
||||
handshake();
|
||||
stopped = false;
|
||||
} catch (IOException e) {
|
||||
} catch (IOException | TimeoutException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
super.start();
|
||||
@@ -249,11 +234,23 @@ public class ControlUnit extends Thread implements Constants{
|
||||
return Translation.get(Application.class, text, fills);
|
||||
}
|
||||
|
||||
private Object togglePower() {
|
||||
private Command togglePower() {
|
||||
power = !power;
|
||||
String PW = power?"ON":"OFF";
|
||||
queue("SET {} POWER "+PW);
|
||||
return t("Turned power {}.",PW);
|
||||
Command command = new Command("SET {} POWER "+PW) {
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
plan.stream(t("Turned power {}.",PW));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Reply reply) {
|
||||
plan.stream(t("Was not able to turn power {}!",PW));
|
||||
}
|
||||
|
||||
};
|
||||
return queue(command);
|
||||
}
|
||||
|
||||
|
||||
@@ -263,10 +260,4 @@ public class ControlUnit extends Thread implements Constants{
|
||||
if (params.containsKey(BUS)) bus = Integer.parseInt(params.get(BUS));
|
||||
return t("Updated control unit settings");
|
||||
}
|
||||
|
||||
private void writeln(String data) throws IOException {
|
||||
data = data.replace("{}", ""+bus);
|
||||
socket.getOutputStream().write((data+"\n").getBytes(StandardCharsets.US_ASCII));
|
||||
LOG.info("sent {}.",data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Stack;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -100,7 +99,7 @@ public class Plan implements Constants{
|
||||
public HashMap<String,Tile> tiles = new HashMap<String,Tile>();
|
||||
private HashSet<Block> blocks = new HashSet<Block>();
|
||||
private HashMap<Integer, Route> routes = new HashMap<Integer, Route>();
|
||||
private ControlUnit controlUnit = new ControlUnit();
|
||||
private ControlUnit controlUnit = new ControlUnit(this);
|
||||
|
||||
public Plan() {
|
||||
new Heartbeat().start();
|
||||
@@ -520,7 +519,7 @@ public class Plan implements Constants{
|
||||
stream(t("Warning: {}",t("Ghost train @ {}",contact)));
|
||||
}
|
||||
|
||||
public CompletableFuture<ControlUnit.Reply> queue(String command) {
|
||||
public Command queue(Command command) {
|
||||
return controlUnit.queue(command);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@ import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
@@ -22,7 +22,6 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import de.keawe.tools.translations.Translation;
|
||||
import de.srsoftware.tools.Tag;
|
||||
import de.srsoftware.web4rail.ControlUnit.Reply;
|
||||
import de.srsoftware.web4rail.Plan.Direction;
|
||||
import de.srsoftware.web4rail.actions.Action;
|
||||
import de.srsoftware.web4rail.actions.ActivateRoute;
|
||||
@@ -292,34 +291,20 @@ public class Route implements Constants{
|
||||
file.close();
|
||||
}
|
||||
|
||||
public CompletableFuture<Reply> lock(Train train) throws IOException {
|
||||
Vector<Tile> locked = new Vector<Tile>();
|
||||
try {
|
||||
for (Tile tile : path) locked.add(tile.lock(this)); // try to lock all tiles along the path
|
||||
} catch (Exception e) { // if something fails: unlock all tiles locked so far
|
||||
for (Tile tile : locked) try {
|
||||
tile.unlock();
|
||||
} catch (IOException ex) {
|
||||
LOG.warn("Problem while unlocking {}",ex);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
CompletableFuture<Reply> promise = null;
|
||||
for (Entry<Turnout, State> entry : turnouts.entrySet()) {// try to switch all turnouts of this route
|
||||
CompletableFuture<Reply> reply = entry.getKey().state(entry.getValue()); // switching a turnout is an asynchronous process, so it returns a CompletableFuture here
|
||||
promise = promise == null ? reply : promise.thenCombine(reply, (a,b) -> a);
|
||||
}
|
||||
if (promise == null) promise = CompletableFuture.completedFuture(null);
|
||||
promise.exceptionally(ex -> {
|
||||
for (Tile tile : locked) try {
|
||||
tile.unlock();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Problem while unlocking {}",e);
|
||||
}
|
||||
throw new RuntimeException(ex);
|
||||
}).thenRun(() -> this.train = train);
|
||||
public boolean lock() {
|
||||
|
||||
return promise;
|
||||
ArrayList<Tile> lockedTiles = new ArrayList<Tile>();
|
||||
try {
|
||||
for (Tile tile : path) lockedTiles.add(tile.lock(this));
|
||||
} catch (IOException e) {
|
||||
for (Tile tile: lockedTiles) try {
|
||||
tile.unlock();
|
||||
} catch (IOException inner) {
|
||||
LOG.warn("Was not able to unlock {}!",tile,inner);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<Route> multiply(int size) {
|
||||
@@ -413,9 +398,27 @@ public class Route implements Constants{
|
||||
if (lastTile instanceof Turnout) addTurnout((Turnout) lastTile,state);
|
||||
}
|
||||
|
||||
public Route setSignals(String state) throws IOException {
|
||||
for (Signal signal : signals) signal.state(state == null ? "go" : state);
|
||||
return this;
|
||||
public boolean setSignals(String state) throws IOException {
|
||||
for (Signal signal : signals) {
|
||||
if (!signal.state(state == null ? Signal.GO : state)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean setTurnouts() {
|
||||
Turnout turnout = null;
|
||||
for (Entry<Turnout, State> entry : turnouts.entrySet()) try {
|
||||
turnout = entry.getKey();
|
||||
State targetVal = entry.getValue();
|
||||
if (!turnout.state(targetVal).succeeded()) return false;
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {}
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Was not able to switch turnout {}!",turnout,e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Route start(Block block,Direction from) {
|
||||
@@ -443,6 +446,12 @@ public class Route implements Constants{
|
||||
return getClass().getSimpleName()+"("+name()+")";
|
||||
}
|
||||
|
||||
public boolean train(Train train) {
|
||||
if (this.train != null && this.train != train) return false;
|
||||
this.train = train;
|
||||
return true;
|
||||
}
|
||||
|
||||
public Route unlock() throws IOException {
|
||||
setSignals(Signal.STOP);
|
||||
for (Tile tile : path) tile.unlock();
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.Vector;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import de.srsoftware.tools.Tag;
|
||||
import de.srsoftware.web4rail.Command;
|
||||
import de.srsoftware.web4rail.Constants;
|
||||
import de.srsoftware.web4rail.Device;
|
||||
import de.srsoftware.web4rail.Plan;
|
||||
@@ -146,7 +147,20 @@ public class Locomotive extends Car implements Constants,Device{
|
||||
case ZIMO:
|
||||
proto = "Z"; break;
|
||||
}
|
||||
plan.queue("INIT {} GL "+address+" "+proto);
|
||||
plan.queue(new Command("INIT {} GL "+address+" "+proto) {
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
super.onSuccess();
|
||||
plan.stream(t("{} initialized.",this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Reply r) {
|
||||
super.onFailure(r);
|
||||
plan.stream(t("Was not able to initialize {}!",this));
|
||||
}
|
||||
});
|
||||
init = true;
|
||||
}
|
||||
|
||||
@@ -226,7 +240,14 @@ public class Locomotive extends Car implements Constants,Device{
|
||||
}
|
||||
|
||||
private void queue() {
|
||||
plan.queue("SET {} GL "+address+" "+(reverse?1:0)+" "+speed+" "+VMAX+" "+(f1?1:0)+" "+(f2?1:0)+" "+(f3?1:0)+" "+(f4?1:0));
|
||||
plan.queue(new Command("SET {} GL "+address+" "+(reverse?1:0)+" "+speed+" "+VMAX+" "+(f1?1:0)+" "+(f2?1:0)+" "+(f3?1:0)+" "+(f4?1:0)) {
|
||||
|
||||
@Override
|
||||
public void onFailure(Reply reply) {
|
||||
super.onFailure(reply);
|
||||
plan.stream(t("Failed to send command to {}: {}",this,reply.message()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public String setSpeed(int newSpeed) {
|
||||
|
||||
@@ -23,6 +23,7 @@ import de.srsoftware.web4rail.Application;
|
||||
import de.srsoftware.web4rail.Constants;
|
||||
import de.srsoftware.web4rail.Plan;
|
||||
import de.srsoftware.web4rail.Plan.Direction;
|
||||
import de.srsoftware.web4rail.actions.SetSignalsToStop;
|
||||
import de.srsoftware.web4rail.Route;
|
||||
import de.srsoftware.web4rail.Window;
|
||||
import de.srsoftware.web4rail.tags.Button;
|
||||
@@ -366,22 +367,18 @@ public class Train implements Constants {
|
||||
if (availableRoutes.isEmpty()) return t("No free routes from {}",block);
|
||||
route = availableRoutes.get(rand.nextInt(availableRoutes.size()));
|
||||
|
||||
route.lock(this).thenApply(reply -> {
|
||||
try {
|
||||
route.setSignals(null);
|
||||
if (direction != route.startDirection) turn();
|
||||
setSpeed(100);
|
||||
return t("started {}",this);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}).thenAccept(message -> plan.stream(message))
|
||||
.exceptionally(ex -> {
|
||||
plan.stream(ex.getMessage());
|
||||
throw new RuntimeException(ex);
|
||||
});
|
||||
|
||||
return t("Trying to start {}",this);
|
||||
if (!route.lock()) return t("Was not able to lock {}",route);
|
||||
String error = null;
|
||||
if (!route.setTurnouts()) error = t("Was not able to set all turnouts!");
|
||||
if (error == null && !route.setSignals(null)) error = t("Was not able to set all signals!");
|
||||
if (error == null && !route.train(this)) error = t("Was not able to assign {} to {}!",this,route);
|
||||
if (error == null) {
|
||||
setSpeed(128);
|
||||
return t("Started {}",this);
|
||||
}
|
||||
route.unlock();
|
||||
this.block.train(this); // re-set train on previous block
|
||||
return error;
|
||||
}
|
||||
|
||||
private Object stop() {
|
||||
|
||||
@@ -10,6 +10,7 @@ import de.srsoftware.web4rail.Plan.Direction;
|
||||
public abstract class Signal extends Tile{
|
||||
|
||||
public static final String STOP = "stop";
|
||||
public static final String GO = "go";
|
||||
private String state = STOP;
|
||||
|
||||
public Signal() {
|
||||
@@ -25,9 +26,10 @@ public abstract class Signal extends Tile{
|
||||
|
||||
public abstract boolean isAffectedFrom(Direction dir);
|
||||
|
||||
public void state(String state) throws IOException {
|
||||
public boolean state(String state) throws IOException {
|
||||
this.state = state;
|
||||
plan.stream("place "+tag(null));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,12 +3,13 @@ package de.srsoftware.web4rail.tiles;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import de.srsoftware.tools.Tag;
|
||||
import de.srsoftware.web4rail.ControlUnit.Reply;
|
||||
import de.srsoftware.web4rail.Command;
|
||||
import de.srsoftware.web4rail.Command.Reply;
|
||||
import de.srsoftware.web4rail.Device;
|
||||
import de.srsoftware.web4rail.Protocol;
|
||||
import de.srsoftware.web4rail.tags.Fieldset;
|
||||
@@ -41,6 +42,8 @@ public abstract class Turnout extends Tile implements Device{
|
||||
init();
|
||||
return super.click();
|
||||
}
|
||||
|
||||
protected abstract String commandFor(State newState);
|
||||
|
||||
public void error(Reply reply) {
|
||||
this.error = true;
|
||||
@@ -52,11 +55,30 @@ public abstract class Turnout extends Tile implements Device{
|
||||
throw new RuntimeException(reply.message());
|
||||
}
|
||||
|
||||
protected void init() {
|
||||
protected Reply init() {
|
||||
if (!initialized) {
|
||||
plan.queue("INIT {} GA "+address+" "+proto());
|
||||
initialized = true;
|
||||
Command command = new Command("INIT {} GA "+address+" "+proto()) {
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
super.onSuccess();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Reply r) {
|
||||
super.onSuccess();
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
};
|
||||
try {
|
||||
return plan.queue(command).reply();
|
||||
} catch (TimeoutException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
}
|
||||
return new Reply(200, "OK");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -111,7 +133,37 @@ public abstract class Turnout extends Tile implements Device{
|
||||
return state;
|
||||
}
|
||||
|
||||
public abstract CompletableFuture<Reply> state(State newState) throws IOException;
|
||||
public Reply state(State newState) throws IOException {
|
||||
Reply reply = init();
|
||||
if (reply != null && !reply.succeeded()) return reply;
|
||||
LOG.debug("Setting {} to {}",this,newState);
|
||||
try {
|
||||
String cmd = commandFor(newState);
|
||||
return plan.queue(new Command(cmd) {
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
super.onSuccess();
|
||||
try {
|
||||
Turnout.this.state = newState;
|
||||
plan.place(Turnout.this);
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFailure(Reply reply) {
|
||||
super.onFailure(reply);
|
||||
plan.stream(t("Unable to switch {}: {}",this,reply.message()));
|
||||
}
|
||||
|
||||
}).reply();
|
||||
} catch (TimeoutException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
return new Reply(417,t("Timeout while trying to switch {}.",this));
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void success() {
|
||||
this.error = false;
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
package de.srsoftware.web4rail.tiles;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import de.srsoftware.web4rail.Connector;
|
||||
import de.srsoftware.web4rail.ControlUnit.Reply;
|
||||
import de.srsoftware.web4rail.Plan.Direction;
|
||||
|
||||
public class Turnout3E extends Turnout{
|
||||
|
||||
@Override
|
||||
public String commandFor(State newState) {
|
||||
LOG.warn("Turnout3E.state({}) not implemented, yet!",newState);
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Connector, State> connections(Direction from) {
|
||||
switch (from) {
|
||||
@@ -29,11 +32,4 @@ public class Turnout3E extends Turnout{
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Reply> state(State newState) throws IOException {
|
||||
// TODO Auto-generated method stub
|
||||
LOG.warn("Turnout3E.state({}) not implemented, yet!",newState);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,8 @@ package de.srsoftware.web4rail.tiles;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import de.srsoftware.tools.Tag;
|
||||
import de.srsoftware.web4rail.ControlUnit.Reply;
|
||||
import de.srsoftware.web4rail.tags.Fieldset;
|
||||
import de.srsoftware.web4rail.tags.Input;
|
||||
import de.srsoftware.web4rail.tags.Label;
|
||||
@@ -19,16 +17,22 @@ public class TurnoutL extends Turnout {
|
||||
Object o = super.click();
|
||||
if (route != null) {
|
||||
plan.stream(t("{} is locked by {}!",this,route));
|
||||
} else {
|
||||
CompletableFuture<Reply> promise = state(state == State.STRAIGHT ? State.LEFT : State.STRAIGHT);
|
||||
promise.exceptionally(ex -> {
|
||||
LOG.warn("Failed to toggle turnout: ",ex);
|
||||
throw new RuntimeException(ex);
|
||||
}).thenAccept(reply -> LOG.debug("Success: {}",reply));
|
||||
}
|
||||
} else state(state == State.STRAIGHT ? State.LEFT : State.STRAIGHT);
|
||||
return o;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String commandFor(State newState) {
|
||||
switch (newState) {
|
||||
case LEFT:
|
||||
return "SET {} GA "+address+" "+portB+" 1 "+delay;
|
||||
case STRAIGHT:
|
||||
return "SET {} GA "+address+" "+portA+" 1 "+delay;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tag propForm() {
|
||||
Tag form = super.propForm();
|
||||
@@ -43,31 +47,7 @@ public class TurnoutL extends Turnout {
|
||||
new Input(LEFT, portB).numeric().addTo(new Label(t("Left port"))).addTo(fieldset);
|
||||
return form;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Reply> state(State newState) throws IOException {
|
||||
init();
|
||||
LOG.debug("Requesting to set {} to {}",this,newState);
|
||||
CompletableFuture<Reply> result;
|
||||
switch (newState) {
|
||||
case LEFT:
|
||||
result = plan.queue("SET {} GA "+address+" "+portB+" 1 "+delay);
|
||||
break;
|
||||
case STRAIGHT:
|
||||
result = plan.queue("SET {} GA "+address+" "+portA+" 1 "+delay);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return result.thenApply(reply -> {
|
||||
LOG.debug("{} received {}",getClass().getSimpleName(),reply);
|
||||
if (!reply.is(200)) error(reply);
|
||||
state = newState;
|
||||
success();
|
||||
return reply;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Tile update(HashMap<String, String> params) throws IOException {
|
||||
if (params.containsKey(STRAIGHT)) portA = Integer.parseInt(params.get(STRAIGHT));
|
||||
|
||||
@@ -2,10 +2,8 @@ package de.srsoftware.web4rail.tiles;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import de.srsoftware.tools.Tag;
|
||||
import de.srsoftware.web4rail.ControlUnit.Reply;
|
||||
import de.srsoftware.web4rail.tags.Fieldset;
|
||||
import de.srsoftware.web4rail.tags.Input;
|
||||
import de.srsoftware.web4rail.tags.Label;
|
||||
@@ -23,6 +21,19 @@ public class TurnoutR extends Turnout {
|
||||
return o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String commandFor(State newState) {
|
||||
|
||||
switch (newState) {
|
||||
case RIGHT:
|
||||
return "SET {} GA "+address+" "+portB+" 1 "+delay;
|
||||
case STRAIGHT:
|
||||
return "SET {} GA "+address+" "+portA+" 1 "+delay;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tag propForm() {
|
||||
Tag form = super.propForm();
|
||||
@@ -44,28 +55,4 @@ public class TurnoutR extends Turnout {
|
||||
if (params.containsKey(RIGHT)) portB = Integer.parseInt(params.get(RIGHT));
|
||||
return super.update(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Reply> state(State newState) throws IOException {
|
||||
init();
|
||||
LOG.debug("Setting {} to {}",this,newState);
|
||||
CompletableFuture<Reply> result;
|
||||
switch (newState) {
|
||||
case RIGHT:
|
||||
result = plan.queue("SET {} GA "+address+" "+portB+" 1 "+delay);
|
||||
break;
|
||||
case STRAIGHT:
|
||||
result = plan.queue("SET {} GA "+address+" "+portA+" 1 "+delay);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return result.thenApply(reply -> {
|
||||
LOG.debug("{} received {}",getClass().getSimpleName(),reply);
|
||||
if (!reply.is(200)) error(reply);
|
||||
state = newState;
|
||||
success();
|
||||
return reply;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user