From f234d1fc680146d44e170bf8f2c6a1dcfd567ad2 Mon Sep 17 00:00:00 2001 From: Steve G Date: Tue, 21 May 2024 17:51:45 -0400 Subject: [PATCH] Set speed directly when distance, speed or profile values do not allow calculate a speed change plan. Add test. --- .../jmrit/dispatcher/AutoActiveTrain.java | 2 +- .../jmri/jmrit/roster/RosterSpeedProfile.java | 31 ++- .../jmrit/roster/RosterSpeedProfileTest.java | 250 +++++++++++++++++- 3 files changed, 278 insertions(+), 5 deletions(-) diff --git a/java/src/jmri/jmrit/dispatcher/AutoActiveTrain.java b/java/src/jmri/jmrit/dispatcher/AutoActiveTrain.java index c222abf1a04..46fc4933288 100644 --- a/java/src/jmri/jmrit/dispatcher/AutoActiveTrain.java +++ b/java/src/jmri/jmrit/dispatcher/AutoActiveTrain.java @@ -1957,7 +1957,7 @@ public void setHalt(boolean halt) { } public void setTargetSpeed(float distance, float speed) { - log.debug("Set Target Speed[{}] with distance{{}]",speed,distance); + log.debug("Set Target Speed[{}] with distance{{}] from speed[{}]",speed,distance,throttle.getSpeedSetting()); stopAllTimers(); if (rosterEntry != null) { rosterEntry.getSpeedProfile().setExtraInitialDelay(1500f); diff --git a/java/src/jmri/jmrit/roster/RosterSpeedProfile.java b/java/src/jmri/jmrit/roster/RosterSpeedProfile.java index 452ed53547c..aea2552369e 100644 --- a/java/src/jmri/jmrit/roster/RosterSpeedProfile.java +++ b/java/src/jmri/jmrit/roster/RosterSpeedProfile.java @@ -1,6 +1,8 @@ package jmri.jmrit.roster; +import java.util.ArrayList; import java.util.LinkedList; +import java.util.List; import java.util.Map.Entry; import java.util.TreeMap; import jmri.Block; @@ -8,6 +10,7 @@ import jmri.NamedBean; import jmri.Section; import jmri.implementation.SignalSpeedMap; + import org.jdom2.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,6 +72,24 @@ public boolean hasReverseSpeeds() { return _hasReverseSpeeds; } + /** + * place / remove SpeedProfile from test mode. + * reinitializes speedstep trace array + * @param value true/false + */ + protected void setTestMode(boolean value) { + profileInTestMode = value; + testSteps = new ArrayList(); + } + + /** + * Gets the speed step trace array. + * @return speedstep trace array + */ + protected List getSpeedStepTrace() { + return testSteps; + } + /* for speed conversions */ static public final float MMS_TO_MPH = 0.00223694f; static public final float MMS_TO_KPH = 0.0036f; @@ -697,6 +718,9 @@ public void changeLocoSpeed(DccThrottle t, float distance, float speed) { int extraTime = 0; + private List testSteps = new ArrayList(); + private boolean profileInTestMode = false; + void calculateStepDetails(float speedStep, float distance) { float stepIncrement = _throttle.getSpeedIncrement(); @@ -817,6 +841,9 @@ void calculateStepDetails(float speedStep, float distance) { SpeedSetting ss = new SpeedSetting(calculatingStep, timePerStep); synchronized (this) { stepQueue.addLast(ss); + if (profileInTestMode) { + testSteps.add(ss); + } } if (stopTimer == null) { //If this is the first time round then kick off the speed change setNextStep(); @@ -830,7 +857,7 @@ void calculateStepDetails(float speedStep, float distance) { calculatedDistance = 0; } if (calculatedDistance <= 0 && !calculated) { - log.error("distance remaining is now 0, but we have not reached desired speed setting {} v {}", desiredSpeedStep, calculatingStep); + log.warn("distance remaining is now 0, but we have not reached desired speed setting {} v {}", desiredSpeedStep, calculatingStep); ss = new SpeedSetting(desiredSpeedStep, 10); synchronized (this) { stepQueue.addLast(ss); @@ -891,7 +918,7 @@ synchronized void setNextStep() { } SpeedSetting ss = stepQueue.getFirst(); if (ss.getDuration() == 0) { - _throttle.setSpeedSetting(0); + _throttle.setSpeedSetting(desiredSpeedStep); finishChange(); return; } diff --git a/java/test/jmri/jmrit/roster/RosterSpeedProfileTest.java b/java/test/jmri/jmrit/roster/RosterSpeedProfileTest.java index 7a09665e7b9..c32f1a83cb1 100644 --- a/java/test/jmri/jmrit/roster/RosterSpeedProfileTest.java +++ b/java/test/jmri/jmrit/roster/RosterSpeedProfileTest.java @@ -1,6 +1,12 @@ package jmri.jmrit.roster; import jmri.util.JUnitUtil; +import jmri.DccThrottle; +import jmri.InstanceManager; +import jmri.LocoAddress; +import jmri.ThrottleListener; +import jmri.ThrottleManager; +import jmri.jmrit.roster.RosterSpeedProfile.SpeedSetting; import org.junit.Assert; import org.junit.jupiter.api.*; @@ -11,15 +17,257 @@ */ public class RosterSpeedProfileTest { + boolean throttleResult; + DccThrottle throttle; + protected class ThrottleListen implements ThrottleListener { + + @Override + public void notifyThrottleFound(DccThrottle t){ + throttleResult = true; + throttle = t; + } + + @Override + public void notifyFailedThrottleRequest(LocoAddress address, String reason){ + throttleResult = false; + } + + @Override + public void notifyDecisionRequired(LocoAddress address, DecisionType question) { + if ( question == DecisionType.STEAL ){ + throttleResult = false; + } + } + } + @Test public void testCTor() { RosterSpeedProfile t = new RosterSpeedProfile(new RosterEntry()); Assert.assertNotNull("exists",t); } + @Test + public void testSpeedProfileStopFromFiftyPercent() { + // statics for test objects + org.jdom2.Element f1 = null; + RosterEntry rF1 = null; + + // create Element + f1 = new org.jdom2.Element("locomotive") + .setAttribute("id", "id info") + .setAttribute("fileName", "file here") + .setAttribute("roadNumber", "431") + .setAttribute("roadName", "SP") + .setAttribute("mfg", "Athearn") + .setAttribute("dccAddress", "1234") + .addContent(new org.jdom2.Element("decoder") + .setAttribute("family", "91") + .setAttribute("model", "33") + ) + .addContent(new org.jdom2.Element("locoaddress") + .addContent(new org.jdom2.Element("number").addContent("1234")) + //As there is no throttle manager available all protocols default to dcc short + .addContent(new org.jdom2.Element("protocol").addContent("dcc_short")) + ) + .addContent(new org.jdom2.Element("speedprofile") + .addContent(new org.jdom2.Element("overRunTimeForward").addContent("0.0")) + .addContent(new org.jdom2.Element("overRunTimeReverse").addContent("0.0")) + .addContent(new org.jdom2.Element("speeds") + .addContent(new org.jdom2.Element("speed") + .addContent(new org.jdom2.Element("step").addContent("1000")) + .addContent(new org.jdom2.Element("forward").addContent("100.00")) + .addContent(new org.jdom2.Element("reverse").addContent("100.00")) + ) + ) + ); + rF1 = new RosterEntry(f1) { + @Override + protected void warnShortLong(String s) { + } + }; + ThrottleListener throtListen = new ThrottleListen(); + ThrottleManager tm = InstanceManager.getDefault(ThrottleManager.class); + boolean OK = tm.requestThrottle(rF1, throtListen, throttleResult); + Assert.assertTrue("Throttle request denied",OK); + JUnitUtil.waitFor(()-> (throttleResult), "Got No throttle"); + throttle.setIsForward(true); + throttle.setSpeedSetting(0.5f); + RosterSpeedProfile sp = rF1.getSpeedProfile(); + sp.setTestMode(true); + sp.changeLocoSpeed(throttle, 50.0f, 0.0f); + // Allow speed step table to be constructed + //JUnitUtil.waitFor(5000); + // Note it must be a perfect 0.0 + //Assert.assertEquals("Speed didnt get to a perfect zero", 0.0f, throttle.getSpeedSetting(), 0.0f); + JUnitUtil.waitFor(()->(throttle.getSpeedSetting() == 0.00f),"Failed to reach requested speed"); + float maxDelta = 1.0f/126.0f/2.0f; //half step + Assert.assertEquals("SpeedStep Table has lincorrect number of entries.", 7, sp.getSpeedStepTrace().size() ) ; + int[] correctDuration = {750, 750, 750, 750, 750, 519, 0} ; + int[] durations = new int[sp.getSpeedStepTrace().size()]; + int ix = 0; + for (SpeedSetting ss: sp.getSpeedStepTrace()) { + durations[ix]= ss.getDuration(); + ix++; + } + Assert.assertArrayEquals("Durations are wrong",correctDuration, durations); + + float[] correctSpeed = {0.30798f, 0.17571f, 0.09067f, 0.04558f, 0.02260f, 0.01466f, 0.0f} ; + float[] speed = new float[sp.getSpeedStepTrace().size()]; + ix=0; + for (SpeedSetting ss: sp.getSpeedStepTrace()) { + speed[ix]= ss.getSpeedStep(); + ix++; + } + Assert.assertArrayEquals("Speeds are wrong", correctSpeed, speed, maxDelta); + sp.cancelSpeedChange(); + } + + @Test + public void testSpeedProfileFromFiftyPercentToTwenty() { + // statics for test objects + org.jdom2.Element f1 = null; + RosterEntry rF1 = null; + + // create Element + f1 = new org.jdom2.Element("locomotive") + .setAttribute("id", "id info") + .setAttribute("fileName", "file here") + .setAttribute("roadNumber", "431") + .setAttribute("roadName", "SP") + .setAttribute("mfg", "Athearn") + .setAttribute("dccAddress", "1234") + .addContent(new org.jdom2.Element("decoder") + .setAttribute("family", "91") + .setAttribute("model", "33") + ) + .addContent(new org.jdom2.Element("locoaddress") + .addContent(new org.jdom2.Element("number").addContent("1234")) + //As there is no throttle manager available all protocols default to dcc short + .addContent(new org.jdom2.Element("protocol").addContent("dcc_short")) + ) + .addContent(new org.jdom2.Element("speedprofile") + .addContent(new org.jdom2.Element("overRunTimeForward").addContent("0.0")) + .addContent(new org.jdom2.Element("overRunTimeReverse").addContent("0.0")) + .addContent(new org.jdom2.Element("speeds") + .addContent(new org.jdom2.Element("speed") + .addContent(new org.jdom2.Element("step").addContent("1000")) + .addContent(new org.jdom2.Element("forward").addContent("200.00")) + .addContent(new org.jdom2.Element("reverse").addContent("200.00")) + ) + ) + ); + rF1 = new RosterEntry(f1) { + @Override + protected void warnShortLong(String s) { + } + }; + ThrottleListener throtListen = new ThrottleListen(); + ThrottleManager tm = InstanceManager.getDefault(ThrottleManager.class); + boolean OK = tm.requestThrottle(rF1, throtListen, throttleResult); + Assert.assertTrue("Throttle request denied",OK); + + JUnitUtil.waitFor(()-> (throttleResult), "Got No throttle"); + throttle.setIsForward(true); + throttle.setSpeedSetting(0.6f); + RosterSpeedProfile sp = rF1.getSpeedProfile(); + sp.setTestMode(true); + sp.changeLocoSpeed(throttle, 150.0f, 0.20f); + // Allow speed step table to be constructed + //JUnitUtil.waitFor(5000); + // Note it must be a perfect 0.20 + JUnitUtil.waitFor(()->(throttle.getSpeedSetting() == 0.20f),"Failed to reach requested speed"); + //Assert.assertEquals("Speed didnt get to a perfect 20", 0.20f, throttle.getSpeedSetting(), 0.00f); + float maxDelta = 1.0f/126.0f/2.0f; //half step + Assert.assertEquals("SpeedStep Table has lincorrect number of entries.", 4, sp.getSpeedStepTrace().size() ) ; + int[] correctDuration = {750, 750, 750, 540} ; + int[] durations = new int[sp.getSpeedStepTrace().size()]; + int ix = 0; + for (SpeedSetting ss: sp.getSpeedStepTrace()) { + durations[ix]= ss.getDuration(); + ix++; + } + Assert.assertArrayEquals("Durations are wrong",correctDuration, durations); + + float[] correctSpeed = {0.43912f, 0.30069f, 0.20311f, 0.2f} ; + float[] speed = new float[sp.getSpeedStepTrace().size()]; + ix=0; + for (SpeedSetting ss: sp.getSpeedStepTrace()) { + speed[ix]= ss.getSpeedStep(); + ix++; + } + Assert.assertArrayEquals("Speeds are wrong", correctSpeed, speed, maxDelta); + sp.cancelSpeedChange(); + } + + @Test + public void testSpeedProfileFromFiftyPercentToTwentyShortBlock() { + // statics for test objects + org.jdom2.Element f1 = null; + RosterEntry rF1 = null; + + // create Element + f1 = new org.jdom2.Element("locomotive") + .setAttribute("id", "id info") + .setAttribute("fileName", "file here") + .setAttribute("roadNumber", "431") + .setAttribute("roadName", "SP") + .setAttribute("mfg", "Athearn") + .setAttribute("dccAddress", "1234") + .addContent(new org.jdom2.Element("decoder") + .setAttribute("family", "91") + .setAttribute("model", "33") + ) + .addContent(new org.jdom2.Element("locoaddress") + .addContent(new org.jdom2.Element("number").addContent("1234")) + //As there is no throttle manager available all protocols default to dcc short + .addContent(new org.jdom2.Element("protocol").addContent("dcc_short")) + ) + .addContent(new org.jdom2.Element("speedprofile") + .addContent(new org.jdom2.Element("overRunTimeForward").addContent("0.0")) + .addContent(new org.jdom2.Element("overRunTimeReverse").addContent("0.0")) + .addContent(new org.jdom2.Element("speeds") + .addContent(new org.jdom2.Element("speed") + .addContent(new org.jdom2.Element("step").addContent("200")) + .addContent(new org.jdom2.Element("forward").addContent("40.00")) + .addContent(new org.jdom2.Element("reverse").addContent("40.00")) + ) + .addContent(new org.jdom2.Element("speed") + .addContent(new org.jdom2.Element("step").addContent("1000")) + .addContent(new org.jdom2.Element("forward").addContent("400.00")) + .addContent(new org.jdom2.Element("reverse").addContent("400.00")) + ) + ) + ); + rF1 = new RosterEntry(f1) { + @Override + protected void warnShortLong(String s) { + } + }; + ThrottleListener throtListen = new ThrottleListen(); + ThrottleManager tm = InstanceManager.getDefault(ThrottleManager.class); + boolean OK = tm.requestThrottle(rF1, throtListen, throttleResult); + Assert.assertTrue("Throttle request denied",OK); + JUnitUtil.waitFor(()-> (throttleResult), "Got No throttle"); + throttle.setIsForward(true); + throttle.setSpeedSetting(0.6f); + RosterSpeedProfile sp = rF1.getSpeedProfile(); + sp.setTestMode(true); + sp.setExtraInitialDelay(1500f); + sp.changeLocoSpeed(throttle, 152.0f, 0.20f); + // Allow speed step table to be constructed + //JUnitUtil.waitFor(3000); + // Note it must be a perfect 0.20 + //Assert.assertEquals("Speed didnt get to a perfect zero", 0.20f, throttle.getSpeedSetting(), 0.0f); + JUnitUtil.waitFor(()->(throttle.getSpeedSetting() == 0.20f),"Failed to reach requested speed"); + + // as the calc goes wrong we immediatly set speed to final speed. The entries are rubbish so dont bother checking + Assert.assertEquals("SpeedStep Table has lincorrect number of entries.", 1, sp.getSpeedStepTrace().size() ) ; + sp.cancelSpeedChange(); + } @BeforeEach public void setUp() { JUnitUtil.setUp(); + JUnitUtil.initDebugThrottleManager(); } @AfterEach @@ -27,6 +275,4 @@ public void tearDown() { JUnitUtil.tearDown(); } - // private final static Logger log = LoggerFactory.getLogger(RosterSpeedProfileTest.class); - }