diff --git a/pom.xml b/pom.xml index cb041d8..c10e0fd 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 de.srsoftware web4rail - 1.3.2 + 1.3.3 Web4Rail jar Java Model Railway Control diff --git a/resources/css/style.css b/resources/css/style.css index 58ff6ce..8f23dae 100644 --- a/resources/css/style.css +++ b/resources/css/style.css @@ -261,11 +261,15 @@ h4,ul{ } .window .disabled{ - background: red; + background: orange; padding: 3px; display: table; } +.window tr.disabled { + display: table-row; +} + svg.disabled circle, svg.disabled line, svg.disabled polygon, @@ -309,6 +313,10 @@ table tr:hover td{ background: #cadbdb; } +table tr.disabled:hover td { + background: yellow; +} + table.brake-times tr > *{ border-style: solid; border-color: black; diff --git a/resources/translations/Application.de.translation b/resources/translations/Application.de.translation index 4fdf712..fdb91ab 100644 --- a/resources/translations/Application.de.translation +++ b/resources/translations/Application.de.translation @@ -1,4 +1,5 @@ abort : abbrechen +Accessory : Zubehör Actions : Aktionen Actions\: : Aktionen: Actions and contacts : Aktionen und Kontakte @@ -26,6 +27,7 @@ Address\: : Adresse: analyze : analysieren Analyze : analysieren Analyze may overwrite these routes! : Durch die Analyse können diese Fahrstraßen überschrieben werden! +Analyzing plan... : Plan wird analysiert... and : und AndCondition : Und-Bedingung Apply : Übernehmen @@ -83,7 +85,6 @@ Current location\: {} : Aufenthaltsort: {} Current velocity\: {} {} : Aktuelle Geschwindigkeit: {} {} custom fields : benutzerdefinierte Felder Decoder address : Decoder-Adresse -Delay : Verzögerung DelayedAction : verzögerte Aktion delete : entfernen delete route : Route löschen @@ -152,8 +153,10 @@ Lower speed limit : Minimale Geschwindigkeit Manage cars : Waggons verwalten Manage locos : Lokomotiven verwalten Manage trains : Züge verwalten +Maximum delay : maximale Verzögerung Maximum Speed : Höchstgeschwindigkeit Maximum train length : maximale Zug-Länge +Minimum delay : minimale Verzögerung Minimum and maximum times (in Miliseconds) trains with the respective tag have to wait in this block. : Minamle und maximale Block-Haltezeit (in Millisekunden) für Züge mit der entsprchender Markierung. Move tiles : Kacheln verschieben name\: : Name: @@ -194,9 +197,9 @@ quit autopilot : Autopilot beenden {} reached it`s destination! : {} ist am Ziel angekommen! ReactivateContact : Kontakt reaktivieren Relay : Relais -Relays and Turnouts : Relais und Weichen -Relay/Turnout : Relais/Weiche +Relay/Signal/Turnout : Relais/Signal/Weiche Remove tag "{}" from train : Markierung "{}" von Zug entfernen +Removed {} : {} gelöscht Report Issue : Problem melden reverse : wenden Reversed {}. : {} umgedreht. diff --git a/src/main/java/de/srsoftware/web4rail/Device.java b/src/main/java/de/srsoftware/web4rail/Device.java index b2fdab5..978b206 100644 --- a/src/main/java/de/srsoftware/web4rail/Device.java +++ b/src/main/java/de/srsoftware/web4rail/Device.java @@ -1,9 +1,13 @@ package de.srsoftware.web4rail; +import de.srsoftware.tools.Tag; + public interface Device { public static final String ADDRESS = "address"; public static final String PORT = "port"; public static final String PROTOCOL = "proto"; public int address(); + + public Tag link(String...args); } diff --git a/src/main/java/de/srsoftware/web4rail/Plan.java b/src/main/java/de/srsoftware/web4rail/Plan.java index 8bdd0ec..bd6bae5 100644 --- a/src/main/java/de/srsoftware/web4rail/Plan.java +++ b/src/main/java/de/srsoftware/web4rail/Plan.java @@ -9,7 +9,7 @@ import java.lang.reflect.InvocationTargetException; import java.nio.file.Files; import java.security.InvalidParameterException; import java.util.Collection; -import java.util.Comparator; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -650,7 +650,7 @@ public class Plan extends BaseClass{ return actions.addTo(actionMenu); } - private Window properties(HashMap params) { + public Window properties(HashMap params) { if (params.containsKey(ID)) { Tile tile = get(Id.from(params), true); if (isSet(tile)) return tile.properties(); @@ -689,18 +689,41 @@ public class Plan extends BaseClass{ } private Fieldset relayProperties() { - Fieldset fieldset = new Fieldset(t("Relays and Turnouts")); + Fieldset fieldset = new Fieldset(t("Accessory")); Table table = new Table(); - table.addHead(t("Address"),t("Relay/Turnout")); + table.addHead(t("Address"),t("Relay/Signal/Turnout")); + List devices = BaseClass.listElements(Tile.class) .stream() .filter(tile -> tile instanceof Device ) .map(tile -> (Device) tile) - .sorted(Comparator.comparing(Device::address)) .collect(Collectors.toList()); + + for (Signal signal : BaseClass.listElements(Signal.class)) { + for (int addr : signal.addresses()) { + devices.add(new Device() { + @Override + public int address() { + return addr; + } + + @Override + public Tag link(String... args) { + return signal.link(args); + } + + @Override + public String toString() { + return signal.toString(); + } + }); + } + } + + Collections.sort(devices, (d1,d2) -> d1.address() - d2.address()); + for (Device device : devices) { - Tile tile = (Tile) device; - table.addRow(device.address(),tile.link(tile.toString())); + table.addRow(device.address(),device.link(device.toString())); if (device.address() % 4 == 1) table.children().lastElement().clazz("group"); } @@ -719,8 +742,17 @@ public class Plan extends BaseClass{ Table table = new Table(); table.addHead(t("Name"),t("Start"),t("End"),t("Actions")); List routes = BaseClass.listElements(Route.class); + Collections.sort(routes, (r1,r2) -> r1.name().compareTo(r2.name())); for (Route route : routes) { - table.addRow(route.link("span",route.name()),route.link("span", route.startBlock()),route.link("span", route.endBlock()),plan.button(t("simplify name"), Map.of(ACTION,ACTION_AUTO,ROUTE,route.id().toString()))); + Tag actions = new Tag("div"); + plan.button(t("simplify name"), Map.of(ACTION,ACTION_AUTO,ROUTE,route.id().toString())).addTo(actions); + route.button(t("delete"), Map.of(ACTION,ACTION_DROP)).addTo(actions); + Tag row = table.addRow( + route.link("span",route.name()), + route.link("span", route.startBlock()), + route.link("span", route.endBlock()), + actions); + if (route.isDisabled()) row.clazz("disabled"); } table.clazz("turnouts").addTo(fieldset); return fieldset; diff --git a/src/main/java/de/srsoftware/web4rail/Route.java b/src/main/java/de/srsoftware/web4rail/Route.java index e736d89..b786fca 100644 --- a/src/main/java/de/srsoftware/web4rail/Route.java +++ b/src/main/java/de/srsoftware/web4rail/Route.java @@ -24,7 +24,7 @@ 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.BrakeStop; +import de.srsoftware.web4rail.actions.DelayedAction; import de.srsoftware.web4rail.actions.FinishRoute; import de.srsoftware.web4rail.actions.PreserveRoute; import de.srsoftware.web4rail.actions.SetSignal; @@ -177,7 +177,8 @@ public class Route extends BaseClass { switch (params.get(ACTION)) { case ACTION_DROP: route.remove(); - return t("Removed {}.",route); + plan.stream(t("Removed {}.",route)); + return plan.properties(new HashMap()); case ACTION_PROPS: return route.properties(); case ACTION_UPDATE: @@ -319,11 +320,6 @@ public class Route extends BaseClass { brakeProcessor = new BrakeProcessor(this,train); } - public void brakeStop() { - train.setSpeed(0); - if (isSet(brakeProcessor)) brakeProcessor.finish(); - } - protected Route clone() { Route clone = new Route(); clone.startBlock = startBlock; @@ -349,18 +345,19 @@ public class Route extends BaseClass { trigger = secondContact.trigger(); for (Signal signal : signals) add(trigger,new SetSignal(this).set(signal).to(Signal.RED)); } - if (!contacts.isEmpty()) { - Contact lastContact = contacts.lastElement(); - add(lastContact.trigger(), new BrakeStop(this)); - add(lastContact.trigger(), new FinishRoute(this)); - } + if (!contacts.isEmpty()) add(contacts.lastElement().trigger(), new FinishRoute(this)); for (Entry entry : turnouts.entrySet()) { Turnout turnout = entry.getKey(); Turnout.State state = entry.getValue(); add(ROUTE_SETUP,new SetTurnout(this).setTurnout(turnout).setState(state)); } - for (Signal signal : signals) add(ROUTE_START,new SetSignal(this).set(signal).to(Signal.GREEN)); - add(ROUTE_START,new SetSpeed(this).to(999)); + 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))); + } return this; } @@ -427,11 +424,21 @@ public class Route extends BaseClass { } public void finish() { + if (isSet(train)) { + if (train.nextRoutePrepared()) { + if (isSet(brakeProcessor)) brakeProcessor.abort(); + } else { + train.setSpeed(0); + if (isSet(brakeProcessor)) brakeProcessor.finish(); + } + } + context.clear(); // prevent delayed actions from firing after route has finished setSignals(Signal.RED); - for (Tile tile : path) try { + for (Tile tile : path) try { // remove route from tiles on path tile.unset(this); } catch (IllegalArgumentException e) {} + Tile lastTile = path.lastElement(); if (lastTile instanceof Contact) { lastTile.setTrain(null); @@ -447,7 +454,7 @@ public class Route extends BaseClass { train.setWaitTime(endBlock.getWaitTime(train,train.direction())); } if (train.route == this) train.route = null; - if (!train.onTrace(startBlock) && startBlock.train() == train) startBlock.setTrain(null); + if (startBlock.train() == train && !train.onTrace(startBlock)) startBlock.setTrain(null); // withdraw train from start block only if trace does not go back there } train = null; } diff --git a/src/main/java/de/srsoftware/web4rail/actions/Action.java b/src/main/java/de/srsoftware/web4rail/actions/Action.java index bd730ab..76ada8b 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/Action.java +++ b/src/main/java/de/srsoftware/web4rail/actions/Action.java @@ -44,7 +44,6 @@ public abstract class Action extends BaseClass { AddRemoveTag.class, BrakeCancel.class, BrakeStart.class, - BrakeStop.class, ConditionalAction.class, DelayedAction.class, DetermineTrainInBlock.class, diff --git a/src/main/java/de/srsoftware/web4rail/actions/ActionList.java b/src/main/java/de/srsoftware/web4rail/actions/ActionList.java index 98546e0..c8e2f80 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/ActionList.java +++ b/src/main/java/de/srsoftware/web4rail/actions/ActionList.java @@ -61,6 +61,20 @@ public class ActionList extends Action implements Iterable{ public void clear() { while (!actions.isEmpty()) actions.firstElement().remove(); } + + @Override + public boolean correspondsTo(Action other) { + if (other instanceof ActionList) { + ActionList otherAL = (ActionList) other; + if (actions.size() != otherAL.actions.size()) return false; + for (int i=0; i{ if (o instanceof JSONObject) { JSONObject jsonObject = (JSONObject) o; Action action = Action.create(jsonObject.getString(TYPE),this); - if (action != null) add(action.load(jsonObject)); + if (isSet(action)) add(action.load(jsonObject)); } } } diff --git a/src/main/java/de/srsoftware/web4rail/actions/BrakeStop.java b/src/main/java/de/srsoftware/web4rail/actions/BrakeStop.java deleted file mode 100644 index e3d94c6..0000000 --- a/src/main/java/de/srsoftware/web4rail/actions/BrakeStop.java +++ /dev/null @@ -1,17 +0,0 @@ -package de.srsoftware.web4rail.actions; - -import de.srsoftware.web4rail.BaseClass; - -public class BrakeStop extends Action { - - public BrakeStop(BaseClass parent) { - super(parent); - } - - @Override - public boolean fire(Context context) { - if (isNull(context.route()) || isNull(context.route().train())) return false; - context.route().brakeStop(); - return true; - } -} diff --git a/src/main/java/de/srsoftware/web4rail/actions/DelayedAction.java b/src/main/java/de/srsoftware/web4rail/actions/DelayedAction.java index a111870..22d9af1 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/DelayedAction.java +++ b/src/main/java/de/srsoftware/web4rail/actions/DelayedAction.java @@ -15,15 +15,18 @@ import de.srsoftware.web4rail.tags.Input; public class DelayedAction extends ActionList { public static final String DELAY = "delay"; + public static final String MIN_DELAY = "min_delay"; + public static final String MAX_DELAY = "max_delay"; private static final int DEFAULT_DELAY = 1000; - private int delay = DEFAULT_DELAY; + private int min_delay = DEFAULT_DELAY; + private int max_delay = DEFAULT_DELAY; public DelayedAction(BaseClass parent) { super(parent); } - + public boolean equals(DelayedAction other) { - return (delay+":"+actions).equals(other.delay+":"+other.actions); + return (min_delay+":"+max_delay+":"+actions).equals(other.min_delay+":"+other.max_delay+":"+other.actions); } @Override @@ -31,8 +34,8 @@ public class DelayedAction extends ActionList { Application.threadPool.execute(new Thread() { public void run() { try { - Thread.sleep(delay); - LOG.debug("{} ms passed by, firing actions:",delay); + Thread.sleep(min_delay + (min_delay < max_delay ? random.nextInt(max_delay - min_delay) : 0)); + LOG.debug("{} ms passed by, firing actions:",min_delay); } catch (InterruptedException e) { LOG.warn("Interrupted Exception thrown while waiting:",e); } @@ -44,38 +47,69 @@ public class DelayedAction extends ActionList { @Override public JSONObject json() { - return super.json().put(DELAY, delay); + return super.json().put(MIN_DELAY, min_delay).put(MAX_DELAY, max_delay); } public DelayedAction load(JSONObject json) { super.load(json); - delay = json.getInt(DELAY); + if (json.has(DELAY)) { + min_delay = json.getInt(DELAY); + max_delay = json.getInt(DELAY); + } + if (json.has(MIN_DELAY)) min_delay = json.getInt(MIN_DELAY); + if (json.has(MAX_DELAY)) max_delay = json.getInt(MAX_DELAY); return this; } @Override protected Window properties(List
preForm, FormInput formInputs, List
postForm) { - formInputs.add(t("Delay"),new Input(DELAY,delay).numeric().addTo(new Tag("span")).content(NBSP+"ms")); + formInputs.add(t("Minimum delay"),new Input(MIN_DELAY,min_delay).numeric().addTo(new Tag("span")).content(NBSP+"ms")); + formInputs.add(t("Maximum delay"),new Input(MAX_DELAY,max_delay).numeric().addTo(new Tag("span")).content(NBSP+"ms")); return super.properties(preForm, formInputs, postForm); } + + public DelayedAction setMaxDelay(int max_delay) { + this.max_delay = max_delay; + return this; + } + + public DelayedAction setMinDelay(int min_delay) { + this.min_delay = min_delay; + return this; + } @Override - public String toString() { - return t("Wait {} ms, then:",delay); + public String toString() { + return t("Wait {} ms, then:",min_delay < max_delay ? min_delay+"…"+max_delay : min_delay); } @Override protected Object update(HashMap params) { - String d = params.get(DELAY); - if (d != null) try { + String d = params.get(MIN_DELAY); + if (isSet(d)) try { + int ms = Integer.parseInt(d); + if (ms < 0) throw new NumberFormatException(t("Delay must not be less than zero!")); + min_delay = ms; + } catch (NumberFormatException nfe) { + Window props = properties(); + props.children().insertElementAt(new Tag("div").content(nfe.getMessage()), 2); + return props; + } + d = params.get(MAX_DELAY); + if (isSet(d)) try { int ms = Integer.parseInt(d); if (ms < 0) throw new NumberFormatException(t("Delay must not be less than zero!")); - delay = ms; + max_delay = ms; } catch (NumberFormatException nfe) { Window props = properties(); props.children().insertElementAt(new Tag("div").content(nfe.getMessage()), 2); return props; } + if (min_delay > max_delay) { + int dummy = min_delay; + min_delay = max_delay; + max_delay = dummy; + } return super.update(params); } } diff --git a/src/main/java/de/srsoftware/web4rail/actions/PreserveRoute.java b/src/main/java/de/srsoftware/web4rail/actions/PreserveRoute.java index ecf8135..d7f19ee 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/PreserveRoute.java +++ b/src/main/java/de/srsoftware/web4rail/actions/PreserveRoute.java @@ -19,10 +19,9 @@ public class PreserveRoute extends Action { if (isNull(train)) return false; if (isNull(route)) return false; - // These are NOT errors: - if (!train.usesAutopilot()) return true; - if (train.destination() == route.endBlock()) return true; + if (!train.usesAutopilot()) return true; // do not reserve routes, when not in auto-mode + if (train.destination() == route.endBlock()) return true; // do not reserve routes, when destination has been reached Range waitTime = route.endBlock().getWaitTime(train,route.endDirection); if (waitTime.max > 0) { diff --git a/src/main/java/de/srsoftware/web4rail/actions/SetSignal.java b/src/main/java/de/srsoftware/web4rail/actions/SetSignal.java index 34584cd..b9a9b21 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/SetSignal.java +++ b/src/main/java/de/srsoftware/web4rail/actions/SetSignal.java @@ -23,6 +23,15 @@ public class SetSignal extends Action { private Signal signal = null; private String state = Signal.RED; + @Override + public boolean correspondsTo(Action other) { + if (other instanceof SetSignal) { + SetSignal otherSS = (SetSignal) other; + return otherSS.signal == this.signal; + } + return false; + } + @Override public boolean fire(Context context) { if (isNull(signal)) return false; diff --git a/src/main/java/de/srsoftware/web4rail/conditions/BlockFree.java b/src/main/java/de/srsoftware/web4rail/conditions/BlockFree.java index 18de176..b6bca3b 100644 --- a/src/main/java/de/srsoftware/web4rail/conditions/BlockFree.java +++ b/src/main/java/de/srsoftware/web4rail/conditions/BlockFree.java @@ -50,7 +50,7 @@ public class BlockFree extends Condition { @Override public String toString() { - if (block == null) return t("[Click here to select block!]"); + if (block == null) return "["+t("Click here to select block!")+"]"; return t(inverted ? "Block {} is occupied":"Block {} is free",block); } diff --git a/src/main/java/de/srsoftware/web4rail/tags/Table.java b/src/main/java/de/srsoftware/web4rail/tags/Table.java index 711d5d8..33a95eb 100644 --- a/src/main/java/de/srsoftware/web4rail/tags/Table.java +++ b/src/main/java/de/srsoftware/web4rail/tags/Table.java @@ -10,7 +10,7 @@ public class Table extends Tag{ super("table"); } - public Table addRow(Object...cols) { + public Tag addRow(Object...cols) { Tag row = new Tag("tr"); for (Object column : cols) { Tag col = null; @@ -22,12 +22,13 @@ public class Table extends Tag{ col.addTo(row); } row.addTo(this); - return this; + return row; } public Table addHead(Object...cols) { Object[] tags = new Tag[cols.length]; for (int i=0; i(); Select select = new Select(Block.class.getSimpleName()); new Tag("option").attr("value","0").content(t("unset")).addTo(select); - for (Block block : BaseClass.listElements(Block.class)) { + List blocks = BaseClass.listElements(Block.class); + Collections.sort(blocks, (b1,b2) -> b1.name.compareTo(b2.name)); + for (Block block : blocks) { if (exclude.contains(block)) continue; Tag opt = select.addOption(block.id(), block); if (block == preselected) opt.attr("selected", "selected"); diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Signal.java b/src/main/java/de/srsoftware/web4rail/tiles/Signal.java index a86c433..fa1736e 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/Signal.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/Signal.java @@ -44,6 +44,14 @@ public abstract class Signal extends Tile { super(); } + public HashSet addresses(){ + HashSet list = new HashSet(); + for (HashSet commands : aspects.values()) { + for (int[] data : commands) list.add(data[0]); + } + return list; + } + @Override protected Vector classes() { Vector classes = super.classes();