From dec68728ad22b1bc25d470b870a6b1084f599d2c Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Fri, 27 Nov 2020 15:04:31 +0100 Subject: [PATCH] implemented pre-reservation of routes when autopilot is active --- pom.xml | 2 +- .../translations/Application.de.translation | 1 + .../de/srsoftware/web4rail/BaseClass.java | 19 ++++- .../de/srsoftware/web4rail/PathFinder.java | 2 +- .../java/de/srsoftware/web4rail/Plan.java | 18 ++-- .../java/de/srsoftware/web4rail/Route.java | 27 ++++-- .../srsoftware/web4rail/actions/Action.java | 3 +- .../web4rail/actions/PreserveRoute.java | 28 +++++++ .../web4rail/moving/Locomotive.java | 43 ++++++++-- .../de/srsoftware/web4rail/moving/Train.java | 82 ++++++++++++++----- .../de/srsoftware/web4rail/tiles/Block.java | 6 +- .../de/srsoftware/web4rail/tiles/Tile.java | 2 +- 12 files changed, 178 insertions(+), 55 deletions(-) create mode 100644 src/main/java/de/srsoftware/web4rail/actions/PreserveRoute.java diff --git a/pom.xml b/pom.xml index b8f0e71..519e71f 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 de.srsoftware web4rail - 1.0.18 + 1.1.1 Web4Rail jar Java Model Railway Control diff --git a/resources/translations/Application.de.translation b/resources/translations/Application.de.translation index 853917c..9c75ba7 100644 --- a/resources/translations/Application.de.translation +++ b/resources/translations/Application.de.translation @@ -108,6 +108,7 @@ other train properties : andere Zug-Eigenschaften Origin and destination : Start und Ziel Origin\: {} to {} : Start: {} nach {} Plan saved as "{}". : Plan als „{}“ gespeichert. +PreserveRoute : Anschlußroute vorwählen Properties : Eigenschaften Properties of {} : Eigenschaften von {} Properties of {} @ ({},{}) : Eigenschaften von {} @ ({},{}) diff --git a/src/main/java/de/srsoftware/web4rail/BaseClass.java b/src/main/java/de/srsoftware/web4rail/BaseClass.java index e69f77e..445c8af 100644 --- a/src/main/java/de/srsoftware/web4rail/BaseClass.java +++ b/src/main/java/de/srsoftware/web4rail/BaseClass.java @@ -1,5 +1,7 @@ package de.srsoftware.web4rail; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Map; import java.util.Random; @@ -13,7 +15,7 @@ public abstract class BaseClass implements Constants{ protected static Plan plan; // the track layout in use public static final Random random = new Random(); public static String speedUnit = DEFAULT_SPEED_UNIT; - + private static final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray(); public static Button contextButton(String context,String text) { String[] parts = context.split(":"); String realm = parts[0]; @@ -34,6 +36,21 @@ public abstract class BaseClass implements Constants{ return o != null; } + public static String md5sum(Object o) { + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + digest.update(o.toString().getBytes(UTF8)); + StringBuffer sb = new StringBuffer(); + for (byte b : digest.digest()) { + sb.append(HEX_CHARS[(b & 0xF0) >> 4]); + sb.append(HEX_CHARS[b & 0x0F]); + } + return sb.toString(); + } catch (NoSuchAlgorithmException e) { + return null; + } + } + public static HashMap merged(Map base, Map overlay) { HashMap merged = new HashMap<>(base); overlay.entrySet().stream().forEach(entry -> merged.put(entry.getKey(), entry.getValue())); diff --git a/src/main/java/de/srsoftware/web4rail/PathFinder.java b/src/main/java/de/srsoftware/web4rail/PathFinder.java index fd91d62..d46d7df 100644 --- a/src/main/java/de/srsoftware/web4rail/PathFinder.java +++ b/src/main/java/de/srsoftware/web4rail/PathFinder.java @@ -71,7 +71,7 @@ public class PathFinder extends BaseClass{ LOG.debug("{}- Candidate: {}",inset,routeCandidate.shortName()); Context forwardContext = new Context(train); forwardContext.block = routeCandidate.endBlock(); - forwardContext.direction = routeCandidate.endDirection.inverse(); + forwardContext.direction = routeCandidate.endDirection; forwardContext.route = null; visitedRoutes.add(routeCandidate); TreeMap> forwardRoutes = availableRoutes(forwardContext,visitedRoutes); diff --git a/src/main/java/de/srsoftware/web4rail/Plan.java b/src/main/java/de/srsoftware/web4rail/Plan.java index 8cc1ccf..5681802 100644 --- a/src/main/java/de/srsoftware/web4rail/Plan.java +++ b/src/main/java/de/srsoftware/web4rail/Plan.java @@ -244,7 +244,9 @@ public class Plan extends BaseClass{ private String analyze() { Vector routes = new Vector(); for (Block block : blocks) { - for (Connector con : block.startPoints()) routes.addAll(follow(new Route().begin(block,con.from.inverse()),con)); + for (Connector con : block.startPoints()) { + routes.addAll(follow(new Route().begin(block,con.from.inverse()),con)); + } } for (Tile tile : tiles.values()) tile.routes().clear(); for (Route route : routes) registerRoute(route.complete()); @@ -287,7 +289,7 @@ public class Plan extends BaseClass{ Tile tile = get(Tile.id(connector.x,connector.y),false); Vector results = new Vector<>(); if (tile == null) return results; - Tile addedTile = route.add(tile,connector.from); + Tile addedTile = route.add(tile,connector.from.inverse()); if (addedTile instanceof Block) { Map cons = addedTile.connections(connector.from); LOG.debug("Found {}, coming from {}.",addedTile,connector.from); @@ -296,7 +298,7 @@ public class Plan extends BaseClass{ Tile nextTile = get(Tile.id(con.x,con.y),false); if (nextTile instanceof Contact) { LOG.debug("{} is followed by {}",addedTile,nextTile); - route.add(nextTile, con.from); + route.add(nextTile, con.from.inverse()); } break; } @@ -622,13 +624,11 @@ public class Plan extends BaseClass{ * @return */ Route registerRoute(Route newRoute) { - for (Tile tile: newRoute.path()) { - if (isSet(tile)) tile.add(newRoute); - } - int routeId = newRoute.id(); - Route existingRoute = routes.get(routeId); + newRoute.path().stream().filter(Tile::isSet).forEach(tile -> tile.add(newRoute)); + int newRouteId = newRoute.id(); + Route existingRoute = routes.get(newRouteId); if (isSet(existingRoute)) newRoute.addPropertiesFrom(existingRoute); - routes.put(routeId, newRoute); + routes.put(newRouteId, newRoute); return newRoute; } diff --git a/src/main/java/de/srsoftware/web4rail/Route.java b/src/main/java/de/srsoftware/web4rail/Route.java index 4675920..bf916b9 100644 --- a/src/main/java/de/srsoftware/web4rail/Route.java +++ b/src/main/java/de/srsoftware/web4rail/Route.java @@ -25,6 +25,7 @@ import de.srsoftware.web4rail.actions.Action; import de.srsoftware.web4rail.actions.Action.Context; import de.srsoftware.web4rail.actions.ActionList; import de.srsoftware.web4rail.actions.FinishRoute; +import de.srsoftware.web4rail.actions.PreserveRoute; import de.srsoftware.web4rail.actions.SetSignal; import de.srsoftware.web4rail.actions.SetSpeed; import de.srsoftware.web4rail.conditions.Condition; @@ -157,7 +158,7 @@ public class Route extends BaseClass implements Comparable{ new Tag("h4").content(t("Origin and destination")).addTo(win); Tag list = new Tag("ul"); Plan.addLink(startBlock, t("Origin: {} to {}",startBlock.name,startDirection), list); - Plan.addLink(endBlock, t("Destination: {} from {}",endBlock.name,endDirection), list); + Plan.addLink(endBlock, t("Destination: {} from {}",endBlock.name,endDirection.inverse()), list); list.addTo(win); @@ -316,7 +317,8 @@ public class Route extends BaseClass implements Comparable{ if (contacts.size()>1) { // mindestens 2 Kontakte: erster Kontakt aktiviert Block, vorletzter Kontakt leitet Bremsung ein Contact nextToLastContact = contacts.get(contacts.size()-2); String trigger = nextToLastContact.trigger(); - add(trigger,new SetSpeed().to(30)); + add(trigger,new SetSpeed().to(50)); + add(trigger,new PreserveRoute()); for (Signal signal : signals) add(trigger,new SetSignal().set(signal).to(Signal.STOP)); } if (!contacts.isEmpty()) { @@ -385,12 +387,12 @@ public class Route extends BaseClass implements Comparable{ } if (isSet(train)) { train.set(endBlock); - train.heading(endDirection.inverse()); + train.heading(endDirection); if (endBlock == train.destination()) { train.destination(null).quitAutopilot(); plan.stream(t("{} reached it`s destination!",train)); } else { - train.setWaitTime(endBlock.getWaitTime(train)); + train.setWaitTime(endBlock.getWaitTime(train,train.direction())); } if (train.route == this) train.route = null; } @@ -408,13 +410,13 @@ public class Route extends BaseClass implements Comparable{ Tile tile = path.get(i); if (i>0) sb.append("-"); if (tile instanceof Block) { - sb.append(((Block)tile).name); + sb.append(" ").append(((Block)tile).name).append(" "); if (i>0) break; // Kontakt nach dem Ziel-Block nicht mitnehmen } else { - sb.append(tile.id()); + sb.append("("+tile.x+","+tile.y+")"); } } - return sb.toString(); + return sb.toString().trim(); } public int id() { @@ -551,9 +553,16 @@ public class Route extends BaseClass implements Comparable{ } public boolean lock() { + return lockIgnoring(null); + } + + public boolean lockIgnoring(Route ignoredRoute) { Vector alreadyLocked = new Vector(); + HashSet ignoredPath = new HashSet(); + if (isSet(ignoredRoute)) ignoredPath.addAll(ignoredRoute.path); boolean success = true; for (Tile tile : path) { + if (ignoredPath.contains(tile)) continue; try { tile.setRoute(this); } catch (IllegalStateException e) { @@ -583,7 +592,9 @@ public class Route extends BaseClass implements Comparable{ } public void name(String name) { - names.put(id(),name); + if (name.isEmpty()) { + names.remove(id()); + } else names.put(id(),name); } public Vector path() { diff --git a/src/main/java/de/srsoftware/web4rail/actions/Action.java b/src/main/java/de/srsoftware/web4rail/actions/Action.java index 51fd410..d27a3c8 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/Action.java +++ b/src/main/java/de/srsoftware/web4rail/actions/Action.java @@ -40,7 +40,7 @@ public abstract class Action extends BaseClass { public Block block = null; public Direction direction = null; - private Context(Contact c, Route r, Train t, Block b, Direction d) { + public Context(Contact c, Route r, Train t, Block b, Direction d) { contact = c; route = r; train = t; @@ -138,6 +138,7 @@ public abstract class Action extends BaseClass { DelayedAction.class, DetermineTrainInBlock.class, FinishRoute.class, + PreserveRoute.class, SendCommand.class, SetDisplayText.class, SetPower.class, diff --git a/src/main/java/de/srsoftware/web4rail/actions/PreserveRoute.java b/src/main/java/de/srsoftware/web4rail/actions/PreserveRoute.java new file mode 100644 index 0000000..f812911 --- /dev/null +++ b/src/main/java/de/srsoftware/web4rail/actions/PreserveRoute.java @@ -0,0 +1,28 @@ +package de.srsoftware.web4rail.actions; + +import de.srsoftware.web4rail.Range; +import de.srsoftware.web4rail.Route; +import de.srsoftware.web4rail.moving.Train; + +public class PreserveRoute extends Action { + + @Override + public boolean fire(Context context) { + Train train = context.train; + Route route = context.route; + // These are errors: + if (isNull(train)) return false; + if (isNull(route)) return false; + + Range waitTime = context.route.endBlock().getWaitTime(context.train,context.route.endDirection); + + // These are NOT errors: + if (!context.train.usesAutopilot()) return true; + if (waitTime.max > 0) return true; // train is expected to wait in next block. + if (train.destination() == route.endBlock()) return true; + + context.train.reserveNext(); + return true; + } + +} diff --git a/src/main/java/de/srsoftware/web4rail/moving/Locomotive.java b/src/main/java/de/srsoftware/web4rail/moving/Locomotive.java index 4ac2fe0..543bf10 100644 --- a/src/main/java/de/srsoftware/web4rail/moving/Locomotive.java +++ b/src/main/java/de/srsoftware/web4rail/moving/Locomotive.java @@ -13,15 +13,16 @@ import de.srsoftware.web4rail.Command; import de.srsoftware.web4rail.Constants; import de.srsoftware.web4rail.Device; import de.srsoftware.web4rail.Plan; +import de.srsoftware.web4rail.Protocol; import de.srsoftware.web4rail.Window; import de.srsoftware.web4rail.tags.Button; import de.srsoftware.web4rail.tags.Fieldset; -import de.srsoftware.web4rail.Protocol; import de.srsoftware.web4rail.tags.Form; import de.srsoftware.web4rail.tags.Input; import de.srsoftware.web4rail.tags.Label; import de.srsoftware.web4rail.tags.Radio; import de.srsoftware.web4rail.tags.Table; +import de.srsoftware.web4rail.tiles.Block; public class Locomotive extends Car implements Constants,Device{ @@ -76,16 +77,18 @@ public class Locomotive extends Car implements Constants,Device{ } public static Tag cockpit(Object locoOrTrain) { - String realm = null; int id = 0; int speed = 0; + String realm = null; + Train train = null; + Locomotive loco = null; if (locoOrTrain instanceof Locomotive) { - Locomotive loco = (Locomotive) locoOrTrain; + loco = (Locomotive) locoOrTrain; realm = REALM_LOCO; id = loco.id(); speed = loco.speed; } else if (locoOrTrain instanceof Train) { - Train train = (Train)locoOrTrain; + train = (Train)locoOrTrain; realm = REALM_TRAIN; id = train.id; speed = train.speed; @@ -95,7 +98,7 @@ public class Locomotive extends Car implements Constants,Device{ Fieldset fieldset = new Fieldset(t("Control")); - new Tag("span").content(t("Current velocity: {} {}",speed)).addTo(fieldset); + new Tag("span").content(t("Current velocity: {} {}",speed,speedUnit)).addTo(fieldset); Tag par = new Tag("p"); Map.of("Slower (10 steps)",ACTION_SLOWER10,"Faster (10 steps)",ACTION_FASTER10).entrySet().forEach(e -> { @@ -105,10 +108,30 @@ public class Locomotive extends Car implements Constants,Device{ par.addTo(fieldset); Tag direction = new Tag("p"); - Map.of("Turn",ACTION_TURN,"Stop",ACTION_STOP).entrySet().forEach(e -> { - params.put(ACTION, e.getValue()); - new Button(t(e.getKey()),params).clazz(e.getValue()).addTo(direction); - }); + if ((isSet(train) && train.speed > 0) || (isSet(loco) && loco.speed > 0)) { + params.put(ACTION, ACTION_STOP); + new Button(t("Stop"),params).clazz(ACTION_STOP).addTo(direction); + } + + params.put(ACTION, ACTION_TURN); + new Button(t("Turn"),params).clazz(ACTION_TURN).addTo(direction); + + if (isSet(train)) { + Block currentBlock = train.currentBlock(); + if (isSet(currentBlock)) { + if (isNull(train.route)) { + params.put(ACTION, ACTION_START); + new Button(t("start"),params).addTo(direction); + } + if (train.usesAutopilot()) { + params.put(ACTION, ACTION_QUIT); + new Button(t("quit autopilot"),params).addTo(direction); + } else { + params.put(ACTION, ACTION_AUTO); + new Button(t("auto"),params).addTo(direction); + } + } + } direction.addTo(fieldset); Tag functions = new Tag("p"); @@ -117,6 +140,8 @@ public class Locomotive extends Car implements Constants,Device{ new Button(t(e.getKey()),params).addTo(functions); }); functions.addTo(fieldset); + + return fieldset.clazz("cockpit"); } diff --git a/src/main/java/de/srsoftware/web4rail/moving/Train.java b/src/main/java/de/srsoftware/web4rail/moving/Train.java index ba47130..33d941e 100644 --- a/src/main/java/de/srsoftware/web4rail/moving/Train.java +++ b/src/main/java/de/srsoftware/web4rail/moving/Train.java @@ -80,6 +80,8 @@ public class Train extends BaseClass implements Comparable { private Block currentBlock,destination = null; LinkedList trace = new LinkedList(); + + private String brakeId = null; private class Autopilot extends Thread{ boolean stop = false; @@ -108,6 +110,8 @@ public class Train extends BaseClass implements Comparable { private Autopilot autopilot = null; private Plan plan; + + private Route nextRoute; public Train(Locomotive loco) { this(loco,null); @@ -147,7 +151,7 @@ public class Train extends BaseClass implements Comparable { case ACTION_MOVE: return train.setDestination(params); case ACTION_PROPS: - return train.props(); + return train.properties(); case ACTION_QUIT: return train.quitAutopilot(); case ACTION_SLOWER10: @@ -182,11 +186,8 @@ public class Train extends BaseClass implements Comparable { if (!params.containsKey(CAR_ID)) return t("No car id passed to Train.addCar!"); Car car = Car.get(params.get(CAR_ID)); if (isNull(car)) return t("No car with id \"{}\" known!",params.get(CAR_ID)); - if (car instanceof Locomotive) { - locos.add((Locomotive) car); - } else cars.add(car); - car.train(this); - return props(); + add(car); + return properties(); } public void add(Car car) { @@ -194,6 +195,7 @@ public class Train extends BaseClass implements Comparable { if (car instanceof Locomotive) { locos.add((Locomotive) car); } else cars.add(car); + brakeId = null; car.train(this); } @@ -205,6 +207,17 @@ public class Train extends BaseClass implements Comparable { return t("{} now in auto-mode",this); } + public String brakeId() { + if (isNull(brakeId)) { + TreeSet carIds = new TreeSet(); + locos.stream().map(loco -> loco.id()).forEach(carIds::add); + cars.stream().map(car -> car.id()).forEach(carIds::add); + brakeId = md5sum(carIds); + LOG.debug("generated new brake id for {}: {}",brakeId,this); + } + return brakeId; + } + private Tag carList() { Tag locoProp = new Tag("li").content(t("Cars:")); Tag locoList = new Tag("ul").clazz("carlist").content(""); @@ -291,10 +304,11 @@ public class Train extends BaseClass implements Comparable { } Locomotive loco = Locomotive.get(params.get(LOCO_ID)); if (isSet(loco)) { - locos.remove(loco); + locos.remove(loco); loco.train(null); } - return props(); + brakeId = null; + return properties(); } public void dropTrace() { @@ -303,7 +317,7 @@ public class Train extends BaseClass implements Comparable { private Tag faster(int steps) { setSpeed(speed+steps); - return props(); + return properties(); } public static Train get(int id) { @@ -502,7 +516,7 @@ public class Train extends BaseClass implements Comparable { return this; } - public Tag props() { + public Tag properties() { Window window = new Window("train-properties",t("Properties of {}",this)); Locomotive.cockpit(this).addTo(window); @@ -595,6 +609,22 @@ public class Train extends BaseClass implements Comparable { trace.remove(tile); } + public void reserveNext() { + Context context = new Context(null, route, this, route.endBlock(), route.endDirection); + Route nextRoute = PathFinder.chooseRoute(context); + + boolean error = !nextRoute.lockIgnoring(route); + error = error || !nextRoute.setTurnouts(); + error = error || !route.fireSetupActions(context); + + if (error) { + nextRoute.reset(); // may unlock tiles belonging to the current route. + route.lock(); // corrects unlocked tiles of nextRoute + } else { + this.nextRoute = nextRoute; + } + } + private void reverseTrace() { LinkedList reversed = new LinkedList(); LOG.debug("Trace: {}",trace); @@ -680,23 +710,28 @@ public class Train extends BaseClass implements Comparable { private Tag slower(int steps) { setSpeed(speed-steps); - return props(); + return properties(); } public String start() throws IOException { if (isNull(currentBlock)) return t("{} not in a block",this); if (isSet(route)) route.reset(); // reset route previously chosen - + Context context = new Context(this); - route = PathFinder.chooseRoute(context); - if (isNull(route)) return t("No free routes from {}",currentBlock); - if (!route.lock()) return t("Was not able to lock {}",route); - + String error = null; + if (isSet(nextRoute)) { + route = nextRoute; + if (!route.lock()) return t("Was not able to lock {}",route); + nextRoute = null; + } else { + route = PathFinder.chooseRoute(context); + if (isNull(route)) return t("No free routes from {}",currentBlock); + if (!route.lock()) return t("Was not able to lock {}",route); + if (!route.setTurnouts()) error = t("Was not able to set all turnouts!"); + if (isNull(error) && !route.fireSetupActions(context)) error = t("Was not able to fire all setup actions of route!"); + } if (direction != route.startDirection) turn(); - String error = null; - if (!route.setTurnouts()) error = t("Was not able to set all turnouts!"); - if (isNull(error) && !route.fireSetupActions(context)) error = t("Was not able to fire all setup actions of route!"); if (isNull(error) && !route.train(this)) error = t("Was not able to assign {} to {}!",this,route); if (isSet(error)) { route.reset(); @@ -746,11 +781,16 @@ public class Train extends BaseClass implements Comparable { public Object stopNow() { quitAutopilot(); setSpeed(0); + if (isSet(nextRoute)) { + nextRoute.reset(); + nextRoute = null; + } if (isSet(route)) { route.reset(); route = null; } - return props(); + + return properties(); } private static String t(String message, Object...fills) { @@ -778,7 +818,7 @@ public class Train extends BaseClass implements Comparable { reverseTrace(); if (isSet(currentBlock)) plan.place(currentBlock); } - return props(); + return properties(); } public Train update(HashMap params) { diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Block.java b/src/main/java/de/srsoftware/web4rail/tiles/Block.java index d90e600..afd3cb0 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/Block.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/Block.java @@ -122,7 +122,7 @@ public abstract class Block extends StretchableTile implements Comparable @Override public Object click() throws IOException { - if (isSet(train)) return train.props(); + if (isSet(train)) return train.properties(); return super.click(); } @@ -165,13 +165,13 @@ public abstract class Block extends StretchableTile implements Comparable return null; } - public Range getWaitTime(Train train) { + public Range getWaitTime(Train train,Direction dir) { for (WaitTime wt : waitTimes) { if (train.tags().contains(wt.tag)) { return wt.get(train.direction()); } } - return getWaitTime(NO_TAG).get(train.direction()); + return getWaitTime(NO_TAG).get(dir); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Tile.java b/src/main/java/de/srsoftware/web4rail/tiles/Tile.java index fa3ce18..99b0c29 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/Tile.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/Tile.java @@ -118,7 +118,7 @@ public abstract class Tile extends BaseClass{ public boolean isFreeFor(Train newTrain) { if (disabled) return false; - if (isSet(route)) return false; + if (isSet(route) && route.train != newTrain) return false; if (isSet(train) && train != newTrain) return false; return true; }