diff --git a/pom.xml b/pom.xml
index 02cce05..dcd0e72 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
4.0.0
de.srsoftware
web4rail
- 1.4.6
+ 1.4.7
Web4Rail
jar
Java Model Railway Control
diff --git a/resources/translations/Application.de.translation b/resources/translations/Application.de.translation
index 15e63fc..841bbda 100644
--- a/resources/translations/Application.de.translation
+++ b/resources/translations/Application.de.translation
@@ -326,6 +326,7 @@ Start actions : Start-Aktionen
Stock ID : Inventarnummer
Stop settings : Halte-Einstellungen
Start autopilot : Autopilot starten
+Start delay : Start-Verzögerung
Started {} : {} gestartet
starting delay : Anfahrverzögerung
State : Status
diff --git a/src/main/java/de/srsoftware/web4rail/Range.java b/src/main/java/de/srsoftware/web4rail/Range.java
index eb7bae6..100c6e8 100644
--- a/src/main/java/de/srsoftware/web4rail/Range.java
+++ b/src/main/java/de/srsoftware/web4rail/Range.java
@@ -13,8 +13,18 @@ public class Range extends BaseClass{
private static final String MAX = "max";
private static final String MIN = "min";
- public int min=0,max=10000;
+ public int min,max;
+ public Range(int min, int max) {
+ this.min = min;
+ this.max = max;
+ validate();
+ }
+
+ public Range() {
+ this(0,10_000);
+ }
+
public JSONObject json() {
return new JSONObject(Map.of(MIN,min,MAX,max));
}
diff --git a/src/main/java/de/srsoftware/web4rail/Route.java b/src/main/java/de/srsoftware/web4rail/Route.java
index 8fe00a2..376ad3b 100644
--- a/src/main/java/de/srsoftware/web4rail/Route.java
+++ b/src/main/java/de/srsoftware/web4rail/Route.java
@@ -22,7 +22,6 @@ import de.srsoftware.web4rail.Plan.Direction;
import de.srsoftware.web4rail.actions.Action;
import de.srsoftware.web4rail.actions.ActionList;
import de.srsoftware.web4rail.actions.BrakeStart;
-import de.srsoftware.web4rail.actions.DelayedAction;
import de.srsoftware.web4rail.actions.FinishRoute;
import de.srsoftware.web4rail.actions.PreserveRoute;
import de.srsoftware.web4rail.actions.SetSignal;
@@ -76,6 +75,12 @@ public class Route extends BaseClass {
private static final String ROUTE_SETUP = "route_setup";
+ private static final String MIN_START_DELAY = "min_start_delay";
+
+ private static final String MAX_START_DELAY = "max_start_delay";
+
+ private static final String STARt_DELAY = "start_delay";
+
private static HashMap names = new HashMap(); // maps id to name. needed to keep names during plan.analyze()
private HashMap brakeTimes = new HashMap();
@@ -85,17 +90,17 @@ public class Route extends BaseClass {
private boolean disabled = false;
private Block endBlock = null;
public Direction endDirection;
+ private Route nextPreparedRoute = null;
+ private RoutePrepper nextRoutePrepper = null;
private Vector path;
private Vector signals;
private HashMap triggeredActions = new HashMap();
private HashMap turnouts;
private Block startBlock = null;
+ private Range startDelay = null;
public Direction startDirection;
private HashSet triggeredContacts = new HashSet<>();
- private Route nextPreparedRoute;
-
- private RoutePrepper nextRoutePrepper;
public Route() {
conditions = new ConditionList();
@@ -303,12 +308,8 @@ public class Route extends BaseClass {
add(ROUTE_SETUP,new SetTurnout(this).setTurnout(turnout).setState(state));
}
for (Signal signal : signals) add(ROUTE_SETUP,new SetSignal(this).set(signal).to(Signal.GREEN));
- if (signals.isEmpty()) {
- add(ROUTE_START,new SetSpeed(this).to(999));
- } else {
- DelayedAction da = new DelayedAction(this).setMinDelay(1000).setMaxDelay(7500);
- add(ROUTE_START,da.add(new SetSpeed(this).to(999)));
- }
+ startDelay = signals.isEmpty() ? new Range(0,0) : new Range(1000,7500);
+ add(ROUTE_START,new SetSpeed(this).to(999));
return this;
}
@@ -485,6 +486,7 @@ public class Route extends BaseClass {
if (isSet(name)) json.put(NAME, name);
if (disabled) json.put(DISABLED, true);
+ if (isSet(startDelay)) json.put(STARt_DELAY, startDelay.json());
return json;
}
@@ -618,6 +620,7 @@ public class Route extends BaseClass {
JSONObject dummy = json.getJSONObject(BRAKE_TIMES);
dummy.keySet().forEach(key -> brakeTimes.put(key, dummy.getInt(key)));
}
+ if (json.has(STARt_DELAY)) startDelay = new Range().load(json.getJSONObject(STARt_DELAY));
return plan.registerRoute(this);
}
@@ -725,7 +728,12 @@ public class Route extends BaseClass {
formInputs.add(t("Name"),nameSpan);
Checkbox checkbox = new Checkbox(DISABLED, t("disabled"), disabled);
if (disabled) checkbox.clazz("disabled");
- formInputs.add(t("State"),checkbox);
+ formInputs.add(t("State"),checkbox);
+
+ Tag span = new Tag("span");
+ new Input(MIN_START_DELAY,isSet(startDelay) ? startDelay.min : 0).numeric().addTo(span).content(" ... ");
+ new Input(MAX_START_DELAY,isSet(startDelay) ? startDelay.max : 0).numeric().addTo(span).content(" ms");
+ formInputs.add(t("Start delay"),span);
postForm.add(basicProperties());
if (!turnouts.isEmpty()) postForm.add(turnouts());
@@ -846,9 +854,10 @@ public class Route extends BaseClass {
if (parts.length>1) name(parts[0].trim()+" - "+parts[parts.length-1].trim());
return this;
}
-
- public boolean start() {
- LOG.debug("{}.start()",this);
+
+ public boolean startNow() {
+ LOG.debug("{}.startNow()",this);
+
if (isNull(context) || context.invalidated()) {
LOG.debug("Invalid context: {}",context);
return false;
@@ -878,6 +887,11 @@ public class Route extends BaseClass {
return true;
}
+ public boolean start() {
+ if (isSet(startDelay)) sleep(startDelay.random());
+ return startNow();
+ }
+
public Block startBlock() {
return startBlock;
}
@@ -916,11 +930,35 @@ public class Route extends BaseClass {
disabled = "on".equals(params.get(DISABLED));
+ String delay = params.get(MIN_START_DELAY);
+ if (isSet(delay)) try {
+ int min = Integer.parseInt(delay);
+ if (isNull(startDelay)) {
+ startDelay = new Range(min, min);
+ } else {
+ startDelay.min = min;
+ startDelay.validate();
+ }
+ } catch (NumberFormatException e) {}
+
+ delay = params.get(MAX_START_DELAY);
+ if (isSet(delay)) try {
+ int max = Integer.parseInt(delay);
+ if (isNull(startDelay)) {
+ startDelay = new Range(max, max);
+ } else {
+ startDelay.max = max;
+ startDelay.validate();
+ }
+ } catch (NumberFormatException e) {}
+
+
Condition condition = Condition.create(params.get(REALM_CONDITION));
if (isSet(condition)) {
condition.parent(this);
conditions.add(condition);
}
+
super.update(params);
return properties();
}
diff --git a/src/main/java/de/srsoftware/web4rail/actions/SetTurnout.java b/src/main/java/de/srsoftware/web4rail/actions/SetTurnout.java
index dcdf36f..e72e6af 100644
--- a/src/main/java/de/srsoftware/web4rail/actions/SetTurnout.java
+++ b/src/main/java/de/srsoftware/web4rail/actions/SetTurnout.java
@@ -28,7 +28,7 @@ public class SetTurnout extends Action {
public boolean fire(Context context,Object cause) {
if (context.invalidated()) return false;
if (isNull(turnout)) return false;
- if (!turnout.state(state).succeeded()) return false;
+ if (!turnout.state(state,false).succeeded()) return false;
if (turnout.address() == 0) return true;
sleep(1000);
return true;
diff --git a/src/main/java/de/srsoftware/web4rail/moving/Train.java b/src/main/java/de/srsoftware/web4rail/moving/Train.java
index 198cd76..c7e3191 100644
--- a/src/main/java/de/srsoftware/web4rail/moving/Train.java
+++ b/src/main/java/de/srsoftware/web4rail/moving/Train.java
@@ -979,7 +979,7 @@ public class Train extends BaseClass implements Comparable {
autopilot |= auto;
if (isSet(nextPreparedRoute)) {
LOG.debug("starting nextPreparedRoute: {}",nextPreparedRoute);
- if (nextPreparedRoute.start()) {
+ if (nextPreparedRoute.startNow()) {
LOG.debug("dropped nextPreparedRoute (was {})",nextPreparedRoute);
nextPreparedRoute = null;
return null;
diff --git a/src/main/java/de/srsoftware/web4rail/threads/RoutePrepper.java b/src/main/java/de/srsoftware/web4rail/threads/RoutePrepper.java
index d63c2f0..87201f4 100644
--- a/src/main/java/de/srsoftware/web4rail/threads/RoutePrepper.java
+++ b/src/main/java/de/srsoftware/web4rail/threads/RoutePrepper.java
@@ -1,11 +1,11 @@
package de.srsoftware.web4rail.threads;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.TreeMap;
-import java.util.Vector;
import de.srsoftware.web4rail.Application;
import de.srsoftware.web4rail.BaseClass;
@@ -17,6 +17,23 @@ import de.srsoftware.web4rail.tiles.Block;
import de.srsoftware.web4rail.tiles.Tile;
public class RoutePrepper extends BaseClass implements Runnable{
+
+ private static class Candidate{
+
+ private int score;
+ private Route route;
+
+ public Candidate(Route r, int s) {
+ route = r;
+ score = s;
+ }
+
+ @Override
+ public String toString() {
+ return route+"(score: "+score+")";
+ }
+ }
+
private Context context;
private Route route;
private List failListeners = new LinkedList<>();
@@ -33,118 +50,113 @@ public class RoutePrepper extends BaseClass implements Runnable{
if (!errors.isEmpty()) throw new NullPointerException(String.join(", ", errors));
context = c;
}
-
- private static TreeMap> availableRoutes(Context context, HashSet visitedRoutes) {
- String inset = "";
- for (int i = 0; i < visitedRoutes.size(); i++) inset += " ";
- LOG.debug("{}{}.availableRoutes({})", inset, RoutePrepper.class.getSimpleName(), context);
-
- Block block = context.block();
- Train train = context.train();
- Direction startDirection = context.direction();
- Route currentRoute = context.route();
- TreeMap> availableRoutes = new TreeMap>();
-
+
+ private static TreeMap> availableRoutes(Context c){
boolean error = false;
- if (isNull(block) && (error = true))
- LOG.warn("{} → {}.availableRoutes called without context.block!", inset, Train.class.getSimpleName());
- if (isNull(train) && (error = true))
- LOG.warn("{}→ {}.availableRoutes called without context.train!", inset, Train.class.getSimpleName());
- if (error) return availableRoutes;
-
- if (isSet(startDirection)) {
- LOG.debug("{}- Looking for {}-bound routes from {}", inset, startDirection, block);
- } else {
- LOG.debug("{}- Looking for all routes from {}", inset, block);
- }
+
+ Block startBlock = c.block();
+ if (isNull(startBlock) && (error=true)) LOG.warn("RoutePrepper.findRoute(…) called without a startBlock!");
+
+ Train train = c.train();
+ if (isNull(train) && (error=true)) LOG.warn("RoutePrepper.findRoute(…) called without a startBlock!");
+
+ if (error) return new TreeMap<>();
Block destination = train.destination();
- if (isSet(destination) && visitedRoutes.isEmpty()) LOG.debug("{}- Destination: {}", inset, destination);
-
- for (Route routeCandidate : block.leavingRoutes()) {
- if (context.invalidated()) return availableRoutes;
- if (visitedRoutes.contains(routeCandidate)) {
- LOG.debug("{}→ Candidate {} would create loop, skipping", inset, routeCandidate.shortName());
- continue;
- }
-
- HashSet stuckTrace = train.stuckTrace(); // if train has been stopped in between two blocks lastly:
- // only allow starting routes that do not conflict with current train
- // position
- if (isSet(stuckTrace) && visitedRoutes.isEmpty() && !routeCandidate.path().containsAll(stuckTrace)) {
- LOG.debug("Stuck train occupies tiles ({}) outside of {} – not allowed.", stuckTrace, routeCandidate);
- continue;
- }
-
- if (!routeCandidate.allowed(context)) {
- if (routeCandidate.endBlock() != destination) { // allowance may be overridden by destination
- LOG.debug("{} not allowed for {}", routeCandidate, context);
- continue; // Zug darf auf Grund einer nicht erfüllten Bedingung nicht auf die Route
+
+ Direction startDirection = c.direction();
+ LOG.debug("RoutePrepper.findRoute({},{},{}), dest = {}",startBlock,startDirection,train,destination);
+
+ TreeMap> candidates = routesFrom(c);
+
+ if (isNull(destination)) {
+ LOG.debug("{} has no destination, returning {}",train,candidates);
+ return candidates;
+ }
+
+ LOG.debug("{} is heading for {}, starting breadth-first search…",train,destination);
+
+ HashMap predecessors = new HashMap<>();
+ TreeMap> routesToDest = new TreeMap<>();
+
+ int level = 0;
+
+ while (!candidates.isEmpty()) {
+ TreeMap> queue = new TreeMap<>();
+
+ while (!candidates.isEmpty()) {
+ Candidate candidate = pop(candidates);
+ LOG.debug(" - examining {}…",candidate);
+
+ Block endBlock = candidate.route.endBlock();
+ Direction endDir = candidate.route.endDirection;
+
+ if (endBlock == destination) {
+ LOG.debug(" - {} reaches destination!",candidate);
+ int score = candidate.score;
+
+ // The route we found leads to the destination block.
+ // However it might be the last route in a long path.
+ // Thus, we need to get the first route in this path:
+ while (predecessors.containsKey(candidate.route)) {
+ candidate = predecessors.get(candidate.route);
+ LOG.debug(" - predecessed by {}",candidate);
+ score += candidate.score;
+ }
+
+ LOG.debug(" → path starts with {} and has total score of {}",candidate.route,score);
+ LinkedList routesForScore = routesToDest.get(score);
+ if (isNull(routesForScore)) routesToDest.put(score, routesForScore = new LinkedList());
+ routesForScore.add(candidate.route);
+ continue;
}
- LOG.debug("{} not allowed for {} – overridden by selected destination", routeCandidate, context);
- }
-
- int priority = 0;
- if (isSet(startDirection) && routeCandidate.startDirection != startDirection) { // Route startet entgegen
- // der aktuellen
- // Fahrtrichtung des Zuges
- if (!train.pushPull) continue; // Zug kann nicht wenden
- if (!block.turnAllowed) continue; // Wenden im Block nicht gestattet
- priority -= 5;
- }
- if (routeCandidate == currentRoute) priority -= 10; // möglichst andere Route als zuvor wählen // TODO: den
- // Routen einen "last-used" Zeitstempel hinzufügen, und
- // diesen mit in die Priorisierung einbeziehen
-
- if (isSet(destination)) {
- if (routeCandidate.endBlock() == destination) { // route goes directly to destination
- LOG.debug("{}→ Candidate {} directly leads to {}", inset, routeCandidate.shortName(), destination);
- priority = 1_000_000;
- } else {
- LOG.debug("{}- Candidate: {}", inset, routeCandidate.shortName());
- Context forwardContext = new Context(train).block(routeCandidate.endBlock()).route(null)
- .direction(routeCandidate.endDirection);
- visitedRoutes.add(routeCandidate);
- TreeMap> forwardRoutes = availableRoutes(forwardContext, visitedRoutes);
- visitedRoutes.remove(routeCandidate);
- if (forwardRoutes.isEmpty()) continue; // the candidate does not lead to a block, from which routes
- // to the destination exist
- Entry> entry = forwardRoutes.lastEntry();
- LOG.debug("{}→ The following routes have connections to {}:", inset, destination);
- for (Route rt : entry.getValue()) LOG.debug("{} - {}", inset, rt.shortName());
- priority += entry.getKey() - 10;
+
+ LOG.debug(" - {} not reaching {}, adding ongoing routes to queue:",candidate,destination);
+ TreeMap> successors = routesFrom(c.clone().block(endBlock).direction(endDir));
+ while (!successors.isEmpty()) {
+ int score = successors.firstKey();
+ LinkedList best = successors.remove(score);
+ score -= 25; // Nachfolgeroute
+ for (Route route : best) {
+ LOG.debug(" - queueing {} with score {}",route,score);
+ if (predecessors.containsKey(route)) continue; // Route wurde bereits besucht
+ predecessors.put(route, candidate);
+
+ LinkedList list = queue.get(score);
+ if (isNull(list)) queue.put(score, list = new LinkedList<>());
+ list.add(route);
+ }
}
}
-
- List routeSet = availableRoutes.get(priority);
- if (isNull(routeSet)) {
- routeSet = new Vector();
- availableRoutes.put(priority, routeSet);
- }
- routeSet.add(routeCandidate);
- if (routeCandidate.endBlock() == destination) break; // direct connection to destination discovered, quit
- // search
- }
- if (!availableRoutes.isEmpty())
- LOG.debug("{}→ Routes from {}: {}", inset, block, availableRoutes.isEmpty() ? "none" : "");
- for (Entry> entry : availableRoutes.entrySet()) {
- LOG.debug("{} - Priority {}:", inset, entry.getKey());
- for (Route r : entry.getValue()) LOG.debug("{} - {}", inset, r.shortName());
+
+ if (!routesToDest.isEmpty()) return routesToDest;
+ LOG.debug("No routes to {} found with distance {}!",destination,level);
+ level ++;
+ candidates = queue;
}
- return availableRoutes;
+ LOG.debug("No more candidates for routes towards {}!",destination);
+
+ return new TreeMap<>();
+
}
private static Route chooseRoute(Context context) {
LOG.debug("{}.chooseRoute({})", RoutePrepper.class.getSimpleName(), context);
- TreeMap> availableRoutes = availableRoutes(context, new HashSet());
+ TreeMap> availableRoutes = availableRoutes(context);
+ LOG.debug("available routes: {}",availableRoutes);
while (!availableRoutes.isEmpty()) {
if (context.invalidated()) break;
LOG.debug("availableRoutes: {}", availableRoutes);
- Entry> entry = availableRoutes.lastEntry();
+ Entry> entry = availableRoutes.lastEntry();
List preferredRoutes = entry.getValue();
LOG.debug("preferredRoutes: {}", preferredRoutes);
Route selectedRoute = preferredRoutes.get(random.nextInt(preferredRoutes.size()));
- if (selectedRoute.isFreeFor(context)) {
+
+ HashSet stuckTrace = context.train().stuckTrace(); // if train has been stopped in between two blocks lastly:
+ // only allow starting routes that do not conflict with current train position
+ if (isSet(stuckTrace) && !selectedRoute.path().containsAll(stuckTrace)) {
+ LOG.debug("Stuck train occupies tiles ({}) outside of {} – not allowed.", stuckTrace, selectedRoute);
+ } else if (selectedRoute.isFreeFor(context)) {
LOG.debug("Chose \"{}\" with priority {}.", selectedRoute, entry.getKey());
return selectedRoute;
}
@@ -156,6 +168,11 @@ public class RoutePrepper extends BaseClass implements Runnable{
return null;
}
+ private boolean fail() {
+ notify(failListeners);
+ route = null;
+ return false;
+ }
private void notify(List listeners) {
for (EventListener listener: listeners) {
@@ -163,13 +180,6 @@ public class RoutePrepper extends BaseClass implements Runnable{
}
}
- private boolean fail() {
- notify(failListeners);
- // if (isSet(route) route.reset();
- route = null;
- return false;
- }
-
public void onFail(EventListener l) {
failListeners.add(l);
}
@@ -186,7 +196,20 @@ public class RoutePrepper extends BaseClass implements Runnable{
preparedListener = l;
}
-
+ private static Candidate pop(TreeMap> candidates) {
+ while (!candidates.isEmpty()) {
+ int score = candidates.firstKey();
+ LinkedList list = candidates.get(score);
+ if (isNull(list) || list.isEmpty()) {
+ candidates.remove(score);
+ } else {
+ Candidate candidate = new Candidate(list.removeFirst(),score);
+ if (list.isEmpty()) candidates.remove(score);
+ return candidate;
+ }
+ }
+ return null;
+ }
public boolean prepareRoute() {
if (isNull(context) || context.invalidated()) return fail();
@@ -206,6 +229,53 @@ public class RoutePrepper extends BaseClass implements Runnable{
return route;
}
+ private static TreeMap> routesFrom(Context c){
+ boolean error = false;
+
+ Block startBlock = c.block();
+ if (isNull(startBlock) && (error=true)) LOG.warn("RoutePrepper.routesFrom(…) called without a startBlock!");
+
+ Train train = c.train();
+ if (isNull(train) && (error=true)) LOG.warn("RoutePrepper.routesFrom(…) called without a startBlock!");
+
+ if (error) return null;
+
+ Block destination = train.destination();
+
+ Direction startDirection = c.direction();
+
+ LOG.debug("RoutePrepper.routesFrom({},{},{}), dest = {}",startBlock,startDirection,train,destination);
+
+ TreeMap> routes = new TreeMap<>();
+
+ for (Route route : startBlock.leavingRoutes()) {
+ LOG.debug(" - evaluating {}",route);
+
+ int score = 0;
+
+ if (!route.allowed(new Context(train).block(startBlock).direction(startDirection))) {
+ LOG.debug(" - {} not allowed for {}", route, train);
+ if (route.endBlock() != destination) continue;
+ LOG.debug(" …overridden by destination of train!", route, train);
+ }
+
+ if (route.endBlock() == destination) score = 100_000;
+
+ if (isSet(startDirection) && route.startDirection != startDirection) { // Route startet entgegen der aktuellen Fahrtrichtung des Zuges
+ if (!train.pushPull) continue; // Zug kann nicht wenden
+ if (!startBlock.turnAllowed) continue; // Wenden im Block nicht gestattet
+ score -= 5;
+ }
+
+ LinkedList routesForScore = routes.get(score);
+ if (isNull(routesForScore)) routes.put(score, routesForScore = new LinkedList());
+ LOG.debug(" → candidate!");
+ routesForScore.add(route);
+ }
+
+ return routes;
+ }
+
@Override
public void run() {
LOG.debug("{}.run()",this);
diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Contact.java b/src/main/java/de/srsoftware/web4rail/tiles/Contact.java
index 3ac8baa..0a62aa5 100644
--- a/src/main/java/de/srsoftware/web4rail/tiles/Contact.java
+++ b/src/main/java/de/srsoftware/web4rail/tiles/Contact.java
@@ -27,7 +27,6 @@ import de.srsoftware.web4rail.tags.Window;
import de.srsoftware.web4rail.threads.DelayedExecution;
public class Contact extends Tile{
- private static Logger LOG = LoggerFactory.getLogger(Contact.class);
private static final String ADDRESS = "address";
private static final HashMap contactsByAddr = new HashMap();
private boolean state = false;
diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Turnout.java b/src/main/java/de/srsoftware/web4rail/tiles/Turnout.java
index cdb75d1..a1dc98f 100644
--- a/src/main/java/de/srsoftware/web4rail/tiles/Turnout.java
+++ b/src/main/java/de/srsoftware/web4rail/tiles/Turnout.java
@@ -52,7 +52,7 @@ public abstract class Turnout extends Tile implements Device{
@Override
public Object click(boolean shift) throws IOException {
LOG.debug(getClass().getSimpleName()+".click()");
- if (!shift) init();
+ init();
return super.click(shift);
}
@@ -159,9 +159,13 @@ public abstract class Turnout extends Tile implements Device{
return state;
}
- public Reply state(State newState) {
+ public Reply state(State newState,boolean shift) {
Train lockingTrain = lockingTrain();
- if (isSet(lockingTrain) && newState != state) return new Reply(415, t("{} locked by {}!",this,lockingTrain));
+
+ if (isSet(lockingTrain)) {
+ if (newState != state && !shift) return new Reply(415, t("{} locked by {}!",this,lockingTrain));
+ // shift allows to switch locked turnouts...
+ } else if (shift) return new Reply(200,"OK"); // shift on a non-locked turnout skips the switch process
if (address == 0) {
sleep(300);
state = newState;
diff --git a/src/main/java/de/srsoftware/web4rail/tiles/TurnoutL.java b/src/main/java/de/srsoftware/web4rail/tiles/TurnoutL.java
index 2779e33..62917ea 100644
--- a/src/main/java/de/srsoftware/web4rail/tiles/TurnoutL.java
+++ b/src/main/java/de/srsoftware/web4rail/tiles/TurnoutL.java
@@ -15,7 +15,7 @@ public abstract class TurnoutL extends Turnout {
@Override
public Object click(boolean shift) throws IOException {
Object o = super.click(shift);
- if (!shift) state(state == State.STRAIGHT ? State.LEFT : State.STRAIGHT);
+ state(state == State.STRAIGHT ? State.LEFT : State.STRAIGHT,shift);
return o;
}
diff --git a/src/main/java/de/srsoftware/web4rail/tiles/TurnoutR.java b/src/main/java/de/srsoftware/web4rail/tiles/TurnoutR.java
index f639c71..20bdec0 100644
--- a/src/main/java/de/srsoftware/web4rail/tiles/TurnoutR.java
+++ b/src/main/java/de/srsoftware/web4rail/tiles/TurnoutR.java
@@ -15,13 +15,12 @@ public abstract class TurnoutR extends Turnout {
@Override
public Object click(boolean shift) throws IOException {
Object o = super.click(shift);
- if (!shift) state(state == State.STRAIGHT ? State.RIGHT : State.STRAIGHT);
+ state(state == State.STRAIGHT ? State.RIGHT : State.STRAIGHT,shift);
return o;
}
@Override
public String commandFor(State newState) {
-
switch (newState) {
case RIGHT:
return "SET {} GA "+address+" "+portB+" 1 "+delay;