diff --git a/services/scan-server/.classpath b/services/scan-server/.classpath index 9bddbe1d5b..32fcc0978c 100644 --- a/services/scan-server/.classpath +++ b/services/scan-server/.classpath @@ -1,17 +1,17 @@ + - - + - + diff --git a/services/scan-server/src/main/java/org/csstudio/scan/server/command/LoopCommandImpl.java b/services/scan-server/src/main/java/org/csstudio/scan/server/command/LoopCommandImpl.java index a1c9c1748b..dd711a58a7 100644 --- a/services/scan-server/src/main/java/org/csstudio/scan/server/command/LoopCommandImpl.java +++ b/services/scan-server/src/main/java/org/csstudio/scan/server/command/LoopCommandImpl.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2011-2018 Oak Ridge National Laboratory. + * Copyright (c) 2011-2026 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -111,15 +111,20 @@ public String[] getDeviceNames(final MacroContext macros) throws Exception return device_names.toArray(new String[device_names.size()]); } - private double getLoopStart() + /** @return Lower loop limit */ + double getLoopStart() { return Math.min(command.getStart(), command.getEnd()); } - private double getLoopEnd() + + /** @return Upper loop limit */ + double getLoopEnd() { return Math.max(command.getStart(), command.getEnd()); } - private double getLoopStep() + + /** @return Loop step, might toggle direction */ + double getLoopStep() { final double step = direction * command.getStepSize(); // Revert direction for next iteration of the complete loop? @@ -128,10 +133,27 @@ private double getLoopStep() return step; } - public int getNumSteps() { + /** @return Number of loop steps */ + int getNumSteps() + { return (int)Math.ceil(Math.abs(((command.getEnd() - command.getStart()) / command.getStepSize()))) + 1; } + /** @param start Start value, might be upper or lower limit + * @param step Loop step value, positive or negative + * @param i Loop index + * @return Loop value + */ + double computeStep(double start, double step, int i) + { + // Compute loop value, but limit to upper or lower + // end depending on step direction + if (step >= 0) + return Math.min(start + i * step, getLoopEnd()); + else + return Math.max(start + i * step, getLoopStart()); + } + /** {@inheritDoc} */ @Override public void simulate(final SimulationContext context) throws Exception @@ -143,7 +165,7 @@ public void simulate(final SimulationContext context) throws Exception double start = step < 0 ? getLoopEnd() : getLoopStart(); int num_steps = getNumSteps(); for (int i = 0; i < num_steps; i++) - simulateStep(context, device, start + i * step); + simulateStep(context, device, computeStep(start, step, i)); } /** Simulate one step in the loop iteration @@ -211,7 +233,7 @@ public void execute(final ScanContext context) throws Exception int num_steps = getNumSteps(); for (int i = 0; i < num_steps; i++) - executeStep(context, device, condition, readback, start + i * step); + executeStep(context, device, condition, readback, computeStep(start, step, i)); } /** Execute one step of the loop diff --git a/services/scan-server/src/test/java/org/csstudio/scan/server/command/LoopTest.java b/services/scan-server/src/test/java/org/csstudio/scan/server/command/LoopTest.java new file mode 100644 index 0000000000..fd1301de4d --- /dev/null +++ b/services/scan-server/src/test/java/org/csstudio/scan/server/command/LoopTest.java @@ -0,0 +1,122 @@ +/******************************************************************************* + * Copyright (c) 2026 Oak Ridge National Laboratory. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The scan engine idea is based on the "ScanEngine" developed + * by the Software Services Group (SSG), Advanced Photon Source, + * Argonne National Laboratory, + * Copyright (c) 2011 , UChicago Argonne, LLC. + * + * This implementation, however, contains no SSG "ScanEngine" source code + * and is not endorsed by the SSG authors. + ******************************************************************************/ +package org.csstudio.scan.server.command; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.csstudio.scan.command.LoopCommand; +import org.junit.jupiter.api.Test; + +/** JUnit test for loop */ +public class LoopTest +{ + @Test + void testNormalLoop() throws Exception + { + LoopCommand cmd = new LoopCommand("loc://x(0)", 1.0, 10.0, 0.5, List.of()); + LoopCommandImpl impl = new LoopCommandImpl(cmd); + + System.out.println(cmd); + + double step = impl.getLoopStep(); + double start = step < 0 ? impl.getLoopEnd() : impl.getLoopStart(); + int num_steps = impl.getNumSteps(); + + double last = Double.NaN; + for (int i = 0; i < num_steps; i++) + { + last = impl.computeStep(start, step, i); + System.out.println(last); + } + assertEquals(10.0, last); + } + + @Test + void testUpwardsLoop() throws Exception + { + LoopCommand cmd = new LoopCommand("loc://x(0)", 1.0, 1.1, 5.0, List.of()); + LoopCommandImpl impl = new LoopCommandImpl(cmd); + + System.out.println(cmd); + + double step = impl.getLoopStep(); + double start = step < 0 ? impl.getLoopEnd() : impl.getLoopStart(); + int num_steps = impl.getNumSteps(); + + double last = Double.NaN; + for (int i = 0; i < num_steps; i++) + { + last = impl.computeStep(start, step, i); + System.out.println(last); + } + assertEquals(1.1, last); + } + + @Test + void testDownwardsLoop() throws Exception + { + LoopCommand cmd = new LoopCommand("loc://x(0)", 1.1, 1.0, -5.0, List.of()); + LoopCommandImpl impl = new LoopCommandImpl(cmd); + + System.out.println(cmd); + + double step = impl.getLoopStep(); + double start = step < 0 ? impl.getLoopEnd() : impl.getLoopStart(); + int num_steps = impl.getNumSteps(); + + double last = Double.NaN; + for (int i = 0; i < num_steps; i++) + { + last = impl.computeStep(start, step, i); + System.out.println(last); + } + assertEquals(1.0, last); + } + + @Test + void testTogglingLoop() throws Exception + { + LoopCommand cmd = new LoopCommand("loc://x(0)", 1.0, 1.1, -5.0, List.of()); + LoopCommandImpl impl = new LoopCommandImpl(cmd); + + System.out.println(cmd); + + double step = impl.getLoopStep(); + double start = step < 0 ? impl.getLoopEnd() : impl.getLoopStart(); + int num_steps = impl.getNumSteps(); + + double last = Double.NaN; + for (int i = 0; i < num_steps; i++) + { + last = impl.computeStep(start, step, i); + System.out.println(last); + } + assertEquals(1.0, last); + + // Run loop again, expect toggled direction + step = impl.getLoopStep(); + start = step < 0 ? impl.getLoopEnd() : impl.getLoopStart(); + num_steps = impl.getNumSteps(); + for (int i = 0; i < num_steps; i++) + { + last = impl.computeStep(start, step, i); + System.out.println(last); + } + assertEquals(1.1, last); + } +}