From ba2be67f8f806374eaf87e17cf8a48e3480fc942 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Fri, 27 Nov 2020 23:51:45 +0100 Subject: [PATCH] implemented automatic brake time learning --- pom.xml | 2 +- .../translations/Application.de.translation | 23 +++-- .../java/de/srsoftware/web4rail/Route.java | 93 ++++++++++++++++++- .../srsoftware/web4rail/actions/Action.java | 4 + .../web4rail/actions/BrakeCancel.java | 12 +++ .../web4rail/actions/BrakeStart.java | 13 +++ .../web4rail/actions/BrakeStop.java | 12 +++ .../web4rail/actions/SetContextTrain.java | 77 +++++++++++++++ .../web4rail/moving/Locomotive.java | 2 +- .../de/srsoftware/web4rail/moving/Train.java | 5 +- .../de/srsoftware/web4rail/tiles/Block.java | 2 +- 11 files changed, 230 insertions(+), 15 deletions(-) create mode 100644 src/main/java/de/srsoftware/web4rail/actions/BrakeCancel.java create mode 100644 src/main/java/de/srsoftware/web4rail/actions/BrakeStart.java create mode 100644 src/main/java/de/srsoftware/web4rail/actions/BrakeStop.java create mode 100644 src/main/java/de/srsoftware/web4rail/actions/SetContextTrain.java diff --git a/pom.xml b/pom.xml index 185527a..3c89c61 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 de.srsoftware web4rail - 1.1.5 + 1.1.6 Web4Rail jar Java Model Railway Control diff --git a/resources/translations/Application.de.translation b/resources/translations/Application.de.translation index ec7512e..0aefd35 100644 --- a/resources/translations/Application.de.translation +++ b/resources/translations/Application.de.translation @@ -26,23 +26,27 @@ Block {} is free : Block {} ist frei Block {} is occupied : Block {} ist belegt Block properties : Block-Eigenschaften {}bound : nach {} +BrakeCancel : Bremsvorgang abbrechen +BrakeStart : Bremsvorgang starten +BrakeStop : Zug anhalten Car manager : Waggon-Verwaltung Cars\: : Waggons: -Click on a name to edit the entry. : Klicke auf einen Namen, um einen Eintrag zu bearbeiten. -ConditionalAction : bedingte Aktion -Conditions : Bedingungen -Condition type\: : Bedingungs-Typ: -Control : Steuerung -Control unit : Zentrale -Current location\: : Aktueller Ort: +Click here to select train! : HIer klicken, um Zug auszuwählen! [Click here to select block!] : [Hier klicken, um Block auszuwählen!] [Click here to add condition] : [Hier klicken, um Bedingung hinzuzufügen] [Click here to select display!] : [Hier klicken, um Anzeige auszuwählen!] [Click here to select train!] : [Hier klicken, um Zug auszuwählen!] click here to setup contact : Hier klicken, um Kontakt auszuwählen click here to setup relay : Hier klicken, um Relais einzurichten +Click on a name to edit the entry. : Klicke auf einen Namen, um einen Eintrag zu bearbeiten. Command to send to control unit\: : Kommando, welches zur Zentrale gesendet werden soll: +ConditionalAction : bedingte Aktion +Conditions : Bedingungen +Condition type\: : Bedingungs-Typ: +Control : Steuerung +Control unit : Zentrale Create action : Aktion erzeugen +Current location\: : Aktueller Ort: Current location : Aufenthaltsort Current location\: {} : Aufenthaltsort: {} Current velocity\: {} {} : Aktuelle Geschwindigkeit: {} {} @@ -64,6 +68,7 @@ Dropped destination of {}. : Ziel von {} verworfen. EAST : Osten editable train properties : veränderliche Zug-Eigenschaften Emergency : Notfall +Faster (10 {}) : 10 {} schneller Firing {} : starte {} FinishRoute : Route abschließen Found {} routes. : {} Routen gefunden. @@ -101,6 +106,7 @@ No : keine No free routes from {} : keine Route von {} frei NORTH : Norden {} now heading for {} : {} ist nun unterweg nach {} +{} now in auto-mode : {} ist nun im Automatikmodus Occupied area\: : Belegte Abschnitte: Off : Aus On : An @@ -132,6 +138,8 @@ Select relay\: : Relais auswählen: Select train\: : Zug auswählen: SendCommand : Kommando senden Send command "{}" to control unit : Kommando „{}“ an Zentrale senden +Set {} as context : {} als Kontext setzen +SetContextTrain : Zug für Folgeaktionen festlegen SetDisplayText : Anzeige-Text setzen SetRelay : Relais schalten SetSignal : Signal stellen @@ -146,6 +154,7 @@ Setup actions : Vorbereitung-Aktionen ShowText : Text anzeigen Signals : Signale Simulating movement of {}... : Simuliere Fahrt von {}... +Slower (10 {}) : 10 {} langsamer SOUTH : Süden Start actions : Start-Aktionen Stock ID : Inventarnummer diff --git a/src/main/java/de/srsoftware/web4rail/Route.java b/src/main/java/de/srsoftware/web4rail/Route.java index bf916b9..fd0511a 100644 --- a/src/main/java/de/srsoftware/web4rail/Route.java +++ b/src/main/java/de/srsoftware/web4rail/Route.java @@ -5,6 +5,7 @@ import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.util.Collection; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -24,6 +25,8 @@ import de.srsoftware.web4rail.Plan.Direction; import de.srsoftware.web4rail.actions.Action; import de.srsoftware.web4rail.actions.Action.Context; import de.srsoftware.web4rail.actions.ActionList; +import de.srsoftware.web4rail.actions.BrakeStart; +import de.srsoftware.web4rail.actions.BrakeStop; import de.srsoftware.web4rail.actions.FinishRoute; import de.srsoftware.web4rail.actions.PreserveRoute; import de.srsoftware.web4rail.actions.SetSignal; @@ -54,6 +57,7 @@ public class Route extends BaseClass implements Comparable{ private static final String ACTIONS = "actions"; private static final String ACTION_LISTS = "action_lists"; + private static final String BRAKE_TIMES = "brake_times"; private static final String CONDITIONS = "conditions"; private static final String DROP_CONDITION = "drop_condition"; private static final String END_DIRECTION = "direction_end"; @@ -66,9 +70,68 @@ public class Route extends BaseClass implements Comparable{ static final String PATH = "path"; static final String SIGNALS = "signals"; static final String TURNOUTS = "turnouts"; - + private static HashMap names = new HashMap(); // maps id to name. needed to keep names during plan.analyze() + + private class BrakeProcessor extends Thread { + private int startSpeed; + private long timestamp; + private int timeStep; + private Route route; + private Train train; + private boolean aborted = false; + private static final int ENDSPEED = 5; + + public BrakeProcessor(Route route, Train train, Integer timestep) { + startSpeed = train.speed; + this.timeStep = isNull(timestep) ? 100 : timestep; + this.route = route; + this.train = train; + start(); + } + public void abort() { + aborted = true; + train.setSpeed(startSpeed); + } + + public void finish() { + long timestamp2 = new Date().getTime(); + //int remainingSpeed = train.speed; + train.setSpeed(0); + if (aborted) return; + long runtime = timestamp2 - timestamp; + int newTimeStep = 5*(int) runtime/(startSpeed - ENDSPEED); + + int diff = newTimeStep - timeStep; + int absDiff = diff < 0 ? -diff : diff; + if (absDiff > timeStep/4) absDiff=timeStep/4; + newTimeStep = diff < 0 ? timeStep - absDiff : timeStep + absDiff; + + if (newTimeStep != timeStep) { + route.brakeTimes.put(train.brakeId(),newTimeStep); + LOG.debug("Corrected brake timestep for {} @ {} from {} to {} ms.",train,route,timeStep,newTimeStep); + } + } + + @Override + public void run() { + timestamp = new Date().getTime(); + if (train.speed == 0) aborted = true; + while (train.speed > ENDSPEED) { + if (aborted) break; + train.setSpeed(train.speed - 5); + try { + sleep(timeStep); + } catch (InterruptedException e) { + LOG.warn("BrakeProcessor interrupted!", e); + } + } + } + } + + private BrakeProcessor brakeProcessor = null; + private HashMap brakeTimes = new HashMap(); private Vector conditions = new Vector(); private Vector contacts; private boolean disabled = false; @@ -248,6 +311,7 @@ public class Route extends BaseClass implements Comparable{ newActionList.addActionsFrom(existingActionList); } } + brakeTimes = new HashMap(existingRoute.brakeTimes); } void addSignal(Signal signal) { @@ -295,6 +359,20 @@ public class Route extends BaseClass implements Comparable{ return this; } + public void brakeCancel() { + if (isSet(brakeProcessor)) brakeProcessor.abort(); + } + + public void brakeStart() { + if (isNull(train)) return; + Integer brakeTime = brakeTimes.get(train.brakeId()); + brakeProcessor = new BrakeProcessor(this,train,brakeTime); + } + + public void brakeStop() { + if (isSet(brakeProcessor)) brakeProcessor.finish(); + } + protected Route clone() { Route clone = new Route(); clone.startBlock = startBlock; @@ -305,6 +383,7 @@ public class Route extends BaseClass implements Comparable{ clone.signals = new Vector(signals); clone.turnouts = new HashMap<>(turnouts); clone.path = new Vector<>(path); + clone.brakeTimes = new HashMap(brakeTimes); return clone; } @@ -317,17 +396,17 @@ 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(50)); + add(trigger,new BrakeStart()); add(trigger,new PreserveRoute()); for (Signal signal : signals) add(trigger,new SetSignal().set(signal).to(Signal.STOP)); } if (!contacts.isEmpty()) { Contact lastContact = contacts.lastElement(); - add(lastContact.trigger(), new SetSpeed()); + add(lastContact.trigger(), new BrakeStop()); add(lastContact.trigger(), new FinishRoute()); } for (Signal signal : signals) setupActions.add(new SetSignal().set(signal).to(Signal.GO)); - startActions.add(new SetSpeed().to(100)); + startActions.add(new SetSpeed().to(999)); return this; } @@ -456,6 +535,8 @@ public class Route extends BaseClass implements Comparable{ json.put(START_DIRECTION, startDirection); json.put(END_DIRECTION, endDirection); + json.put(BRAKE_TIMES, brakeTimes); + JSONArray jConditions = new JSONArray(); for (Condition condition : conditions) jConditions.put(condition.json()); if (!jConditions.isEmpty()) json.put(CONDITIONS, jConditions); @@ -521,6 +602,10 @@ public class Route extends BaseClass implements Comparable{ if (json.has(SETUP_ACTIONS)) setupActions = ActionList.load(json.getJSONArray(SETUP_ACTIONS)); if (json.has(START_ACTIONS)) startActions = ActionList.load(json.getJSONArray(START_ACTIONS)); if (json.has(DISABLED)) disabled = json.getBoolean(DISABLED); + if (json.has(BRAKE_TIMES)) { + JSONObject dummy = json.getJSONObject(BRAKE_TIMES); + dummy.keySet().forEach(key -> brakeTimes.put(key, dummy.getInt(key))); + } return plan.registerRoute(this); } diff --git a/src/main/java/de/srsoftware/web4rail/actions/Action.java b/src/main/java/de/srsoftware/web4rail/actions/Action.java index d27a3c8..313e01a 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/Action.java +++ b/src/main/java/de/srsoftware/web4rail/actions/Action.java @@ -134,12 +134,16 @@ public abstract class Action extends BaseClass { public static List> list() { return List.of( + BrakeStart.class, + BrakeStop.class, + BrakeCancel.class, ConditionalAction.class, DelayedAction.class, DetermineTrainInBlock.class, FinishRoute.class, PreserveRoute.class, SendCommand.class, + SetContextTrain.class, SetDisplayText.class, SetPower.class, SetRelay.class, diff --git a/src/main/java/de/srsoftware/web4rail/actions/BrakeCancel.java b/src/main/java/de/srsoftware/web4rail/actions/BrakeCancel.java new file mode 100644 index 0000000..a3f40d5 --- /dev/null +++ b/src/main/java/de/srsoftware/web4rail/actions/BrakeCancel.java @@ -0,0 +1,12 @@ +package de.srsoftware.web4rail.actions; + +public class BrakeCancel extends Action { + + @Override + public boolean fire(Context context) { + if (isNull(context.route)) return false; + context.route.brakeCancel(); + return true; + } + +} diff --git a/src/main/java/de/srsoftware/web4rail/actions/BrakeStart.java b/src/main/java/de/srsoftware/web4rail/actions/BrakeStart.java new file mode 100644 index 0000000..e779f26 --- /dev/null +++ b/src/main/java/de/srsoftware/web4rail/actions/BrakeStart.java @@ -0,0 +1,13 @@ +package de.srsoftware.web4rail.actions; + +public class BrakeStart extends Action { + + @Override + public boolean fire(Context context) { + if (isNull(context.route)) return false; + context.route.brakeStart(); + LOG.debug("Started brake process..."); + return true; + } + +} diff --git a/src/main/java/de/srsoftware/web4rail/actions/BrakeStop.java b/src/main/java/de/srsoftware/web4rail/actions/BrakeStop.java new file mode 100644 index 0000000..81d5b2a --- /dev/null +++ b/src/main/java/de/srsoftware/web4rail/actions/BrakeStop.java @@ -0,0 +1,12 @@ +package de.srsoftware.web4rail.actions; + +public class BrakeStop extends Action { + + @Override + public boolean fire(Context context) { + if (isNull(context.route)) return false; + context.route.brakeStop(); + return true; + } + +} diff --git a/src/main/java/de/srsoftware/web4rail/actions/SetContextTrain.java b/src/main/java/de/srsoftware/web4rail/actions/SetContextTrain.java new file mode 100644 index 0000000..789d974 --- /dev/null +++ b/src/main/java/de/srsoftware/web4rail/actions/SetContextTrain.java @@ -0,0 +1,77 @@ +package de.srsoftware.web4rail.actions; + +import java.util.HashMap; + +import org.json.JSONObject; + +import de.srsoftware.web4rail.Window; +import de.srsoftware.web4rail.moving.Train; +import de.srsoftware.web4rail.tags.Button; +import de.srsoftware.web4rail.tags.Form; +import de.srsoftware.web4rail.tags.Input; +import de.srsoftware.web4rail.tags.Label; +import de.srsoftware.web4rail.tags.Select; + +public class SetContextTrain extends Action { + + private Train train = null; + + @Override + public boolean fire(Context context) { + context.train = train; + return true; + } + + @Override + public JSONObject json() { + JSONObject json = super.json(); + if (isSet(train)) json.put(REALM_TRAIN, train.id); + return json; + } + + @Override + public Action load(JSONObject json) { + super.load(json); + if (json.has(REALM_TRAIN)) { + new Thread() { // load asynchronously, as referred tile may not be available,yet + public void run() { + try { + sleep(1000); + int trainId = json.getInt(REALM_TRAIN); + if (isSet(trainId)) train = Train.get(trainId); + } catch (InterruptedException e) {} + }; + }.start(); + } + return this; + } + + @Override + public Window properties(HashMap params) { + Window win = super.properties(params); + Form form = new Form("action-prop-form-"+id); + new Input(REALM,REALM_ACTIONS).hideIn(form); + new Input(ID,params.get(ID)).hideIn(form); + new Input(ACTION,ACTION_UPDATE).hideIn(form); + new Input(CONTEXT,params.get(CONTEXT)).hideIn(form); + + Select select = Train.selector(train, null); + select.addTo(new Label(t("Select train:")+NBSP)).addTo(form); + + new Button(t("Apply"),form).addTo(form).addTo(win); + return win; + } + + public String toString() { + return isSet(train) ? t("Set {} as context",train) : "["+t("Click here to select train!")+"]"; + }; + + @Override + protected Object update(HashMap params) { + LOG.debug("update: {}",params); + String trainId = params.get(Train.class.getSimpleName()); + if (isSet(trainId)) train = Train.get(Integer.parseInt(trainId)); + return properties(params); + } + +} diff --git a/src/main/java/de/srsoftware/web4rail/moving/Locomotive.java b/src/main/java/de/srsoftware/web4rail/moving/Locomotive.java index e7ed328..04d83d4 100644 --- a/src/main/java/de/srsoftware/web4rail/moving/Locomotive.java +++ b/src/main/java/de/srsoftware/web4rail/moving/Locomotive.java @@ -101,7 +101,7 @@ public class Locomotive extends Car implements Constants,Device{ 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 -> { + Map.of(t("Slower (10 {})",speedUnit),ACTION_SLOWER10,t("Faster (10 {})",speedUnit),ACTION_FASTER10).entrySet().forEach(e -> { params.put(ACTION, e.getValue()); new Button(t(e.getKey()),params).addTo(par); }); diff --git a/src/main/java/de/srsoftware/web4rail/moving/Train.java b/src/main/java/de/srsoftware/web4rail/moving/Train.java index e7d0b36..387ee89 100644 --- a/src/main/java/de/srsoftware/web4rail/moving/Train.java +++ b/src/main/java/de/srsoftware/web4rail/moving/Train.java @@ -214,7 +214,7 @@ public class Train extends BaseClass implements Comparable { 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); + brakeId = md5sum(carIds+":"+direction); LOG.debug("generated new brake id for {}: {}",brakeId,this); } return brakeId; @@ -598,6 +598,7 @@ public class Train extends BaseClass implements Comparable { public void reserveNext() { Context context = new Context(null, route, this, route.endBlock(), route.endDirection); Route nextRoute = PathFinder.chooseRoute(context); + if (isNull(nextRoute)) return; boolean error = !nextRoute.lockIgnoring(route); error = error || !nextRoute.setTurnouts(); @@ -608,6 +609,7 @@ public class Train extends BaseClass implements Comparable { route.lock(); // corrects unlocked tiles of nextRoute } else { this.nextRoute = nextRoute; + this.route.brakeCancel(); } } @@ -775,6 +777,7 @@ public class Train extends BaseClass implements Comparable { } if (isSet(route)) { route.reset(); + route.brakeCancel(); route = null; } diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Block.java b/src/main/java/de/srsoftware/web4rail/tiles/Block.java index afd3cb0..6a78430 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.properties(); + if (isSet(train) && train.currentBlock() == this) return train.properties(); return super.click(); }