diff --git a/ugs-core/src/com/willwinder/universalgcodesender/G2CoreController.java b/ugs-core/src/com/willwinder/universalgcodesender/G2CoreController.java index bdcbccfaa6..7f2ce6aa0c 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/G2CoreController.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/G2CoreController.java @@ -20,14 +20,15 @@ This file is part of Universal Gcode Sender (UGS). import com.google.gson.JsonObject; import com.willwinder.universalgcodesender.communicator.ICommunicator; +import com.willwinder.universalgcodesender.firmware.IOverrideManager; +import com.willwinder.universalgcodesender.firmware.g2core.G2CoreOverrideManager; import com.willwinder.universalgcodesender.listeners.ControllerState; import com.willwinder.universalgcodesender.listeners.ControllerStatus; import com.willwinder.universalgcodesender.listeners.ControllerStatusBuilder; import com.willwinder.universalgcodesender.listeners.MessageType; import com.willwinder.universalgcodesender.model.CommunicatorState; -import com.willwinder.universalgcodesender.model.PartialPosition; - import static com.willwinder.universalgcodesender.model.CommunicatorState.COMM_IDLE; +import com.willwinder.universalgcodesender.model.PartialPosition; /** * G2Core Control layer. @@ -41,13 +42,16 @@ public class G2CoreController extends TinyGController { * A temporary flag for emulating a JOG state when parsing the controller status */ private boolean isJogging = false; + private final IOverrideManager overrideManager; public G2CoreController() { super(); + overrideManager = new G2CoreOverrideManager(this, getCommunicator()); } public G2CoreController(ICommunicator communicator) { super(communicator); + overrideManager = new G2CoreOverrideManager(this, communicator); } @Override @@ -90,7 +94,7 @@ protected void handleReadyResponse(String response, JsonObject jo) { capabilities.addCapability(CapabilitiesConstants.CONTINUOUS_JOGGING); capabilities.addCapability(CapabilitiesConstants.HOMING); capabilities.addCapability(CapabilitiesConstants.FIRMWARE_SETTINGS); - capabilities.addCapability(CapabilitiesConstants.OVERRIDES); + capabilities.removeCapability(CapabilitiesConstants.OVERRIDES); capabilities.removeCapability(CapabilitiesConstants.SETUP_WIZARD); setCurrentState(COMM_IDLE); @@ -191,4 +195,9 @@ protected ControllerStatus parseControllerStatus(JsonObject jo) { return controllerStatus; } + + @Override + public IOverrideManager getOverrideManager() { + return overrideManager; + } } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/GrblController.java b/ugs-core/src/com/willwinder/universalgcodesender/GrblController.java index e381e1b364..6097105504 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/GrblController.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/GrblController.java @@ -1,5 +1,5 @@ /* - Copyright 2013-2023 Will Winder + Copyright 2013-2024 Will Winder This file is part of Universal Gcode Sender (UGS). @@ -33,7 +33,8 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.model.Alarm; import com.willwinder.universalgcodesender.model.Axis; import com.willwinder.universalgcodesender.model.CommunicatorState; -import com.willwinder.universalgcodesender.model.Overrides; +import static com.willwinder.universalgcodesender.model.CommunicatorState.COMM_CHECK; +import static com.willwinder.universalgcodesender.model.CommunicatorState.COMM_IDLE; import com.willwinder.universalgcodesender.model.PartialPosition; import com.willwinder.universalgcodesender.model.Position; import com.willwinder.universalgcodesender.model.UnitUtils.Units; @@ -42,6 +43,8 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.types.GrblSettingMessage; import com.willwinder.universalgcodesender.utils.ControllerUtils; import com.willwinder.universalgcodesender.utils.GrblLookups; +import com.willwinder.universalgcodesender.firmware.grbl.GrblOverrideManager; +import com.willwinder.universalgcodesender.firmware.IOverrideManager; import com.willwinder.universalgcodesender.utils.ThreadHelper; import org.apache.commons.lang3.StringUtils; @@ -49,9 +52,6 @@ This file is part of Universal Gcode Sender (UGS). import java.util.logging.Level; import java.util.logging.Logger; -import static com.willwinder.universalgcodesender.model.CommunicatorState.COMM_CHECK; -import static com.willwinder.universalgcodesender.model.CommunicatorState.COMM_IDLE; - /** * GRBL Control layer, coordinates all aspects of control. * @@ -63,10 +63,16 @@ public class GrblController extends AbstractController { private static final GrblLookups ERRORS = new GrblLookups("error_codes"); private final StatusPollTimer positionPollTimer; private final GrblFirmwareSettings firmwareSettings; + private final IOverrideManager overrideManager; private GrblControllerInitializer initializer; private Capabilities capabilities = new Capabilities(); // Polling state - private ControllerStatus controllerStatus = new ControllerStatus(ControllerState.DISCONNECTED, new Position(0, 0, 0, Units.MM), new Position(0, 0, 0, Units.MM)); + private ControllerStatus controllerStatus = ControllerStatusBuilder.newInstance() + .setState(ControllerState.DISCONNECTED) + .setWorkCoord(Position.ZERO) + .setMachineCoord(Position.ZERO) + .build(); + // Canceling state private Boolean isCanceling = false; // Set for the position polling thread. private int attemptsRemaining; @@ -88,6 +94,7 @@ public GrblController(ICommunicator communicator) { this.firmwareSettings = new GrblFirmwareSettings(this); this.comm.addListener(firmwareSettings); this.initializer = new GrblControllerInitializer(this); + this.overrideManager = new GrblOverrideManager(this, communicator); } public GrblController() { @@ -556,6 +563,11 @@ public ControllerStatus getControllerStatus() { return controllerStatus; } + @Override + public IOverrideManager getOverrideManager() { + return overrideManager; + } + // No longer a listener event private void handleStatusString(final String string) { if (this.capabilities == null) { @@ -641,15 +653,6 @@ protected void setControllerState(ControllerState controllerState) { dispatchStatusString(controllerStatus); } - @Override - public void sendOverrideCommand(Overrides command) throws Exception { - Byte realTimeCommand = GrblUtils.getOverrideForEnum(command, capabilities); - if (realTimeCommand != null) { - this.dispatchConsoleMessage(MessageType.INFO, String.format(">>> 0x%02x\n", realTimeCommand)); - this.comm.sendByteImmediately(realTimeCommand); - } - } - @Override public boolean getStatusUpdatesEnabled() { return positionPollTimer.isEnabled(); diff --git a/ugs-core/src/com/willwinder/universalgcodesender/GrblUtils.java b/ugs-core/src/com/willwinder/universalgcodesender/GrblUtils.java index 6091436d1a..278511c339 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/GrblUtils.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/GrblUtils.java @@ -1,5 +1,5 @@ /* - Copyright 2012-2023 Will Winder + Copyright 2012-2024 Will Winder This file is part of Universal Gcode Sender (UGS). @@ -21,22 +21,29 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.firmware.grbl.commands.GetStatusCommand; import com.willwinder.universalgcodesender.firmware.grbl.commands.GrblSystemCommand; +import com.willwinder.universalgcodesender.listeners.AccessoryStates; +import com.willwinder.universalgcodesender.listeners.AccessoryStatesBuilder; import com.willwinder.universalgcodesender.listeners.ControllerState; import com.willwinder.universalgcodesender.listeners.ControllerStatus; -import com.willwinder.universalgcodesender.listeners.ControllerStatus.AccessoryStates; -import com.willwinder.universalgcodesender.listeners.ControllerStatus.EnabledPins; -import com.willwinder.universalgcodesender.listeners.ControllerStatus.OverridePercents; +import com.willwinder.universalgcodesender.listeners.ControllerStatusBuilder; +import com.willwinder.universalgcodesender.listeners.EnabledPins; +import com.willwinder.universalgcodesender.listeners.EnabledPinsBuilder; import com.willwinder.universalgcodesender.listeners.MessageType; -import com.willwinder.universalgcodesender.model.*; +import com.willwinder.universalgcodesender.listeners.OverridePercents; +import com.willwinder.universalgcodesender.model.Alarm; +import com.willwinder.universalgcodesender.model.Axis; +import com.willwinder.universalgcodesender.model.Overrides; +import com.willwinder.universalgcodesender.model.PartialPosition; +import com.willwinder.universalgcodesender.model.Position; import com.willwinder.universalgcodesender.model.UnitUtils.Units; +import static com.willwinder.universalgcodesender.utils.ControllerUtils.sendAndWaitForCompletion; +import static com.willwinder.universalgcodesender.utils.ControllerUtils.sendAndWaitForCompletionWithRetry; import org.apache.commons.lang3.StringUtils; +import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static com.willwinder.universalgcodesender.utils.ControllerUtils.sendAndWaitForCompletion; -import static com.willwinder.universalgcodesender.utils.ControllerUtils.sendAndWaitForCompletionWithRetry; - /** * Collection of useful Grbl related utilities. * @@ -347,10 +354,11 @@ static protected ControllerStatus getStatusFromStatusString( public static ControllerStatus getStatusFromStatusStringLegacy(String status, Units reportingUnits) { String stateString = StringUtils.defaultString(getStateFromStatusString(status), "unknown"); ControllerState state = getControllerStateFromStateString(stateString); - return new ControllerStatus( - state, - getMachinePositionFromStatusString(status, reportingUnits), - getWorkPositionFromStatusString(status, reportingUnits)); + return ControllerStatusBuilder.newInstance() + .setState(state) + .setWorkCoord(getWorkPositionFromStatusString(status, reportingUnits)) + .setMachineCoord(getMachinePositionFromStatusString(status, reportingUnits)) + .build(); } /** @@ -404,13 +412,7 @@ else if (part.startsWith("WCO:")) { } else if (part.startsWith("Ov:")) { isOverrideReport = true; - String[] overrideParts = part.substring(3).trim().split(","); - if (overrideParts.length == 3) { - overrides = new OverridePercents( - Integer.parseInt(overrideParts[0]), - Integer.parseInt(overrideParts[1]), - Integer.parseInt(overrideParts[2])); - } + overrides = parseOverrides(part).orElse(OverridePercents.EMTPY_OVERRIDE_PERCENTS); } else if (part.startsWith("F:")) { feedSpeed = parseFeedSpeed(part); @@ -422,11 +424,11 @@ else if (part.startsWith("FS:")) { } else if (part.startsWith("Pn:")) { String value = part.substring(part.indexOf(':')+1); - pins = new EnabledPins(value); + pins = parseEnabledPins(value); } else if (part.startsWith("A:")) { String value = part.substring(part.indexOf(':')+1); - accessoryStates = new AccessoryStates(value); + accessoryStates = parseAccessoryStates(value); } } @@ -461,6 +463,48 @@ else if (part.startsWith("A:")) { return new ControllerStatus(state, subStateString, MPos, WPos, feedSpeed, reportingUnits, spindleSpeed, overrides, WCO, pins, accessoryStates); } + private static Optional parseOverrides(String value) { + String[] overrideParts = value.substring(3).trim().split(","); + if (overrideParts.length == 3) { + return Optional.of(new OverridePercents( + Integer.parseInt(overrideParts[0]), + Integer.parseInt(overrideParts[1]), + Integer.parseInt(overrideParts[2]))); + } + return Optional.empty(); + } + + private static EnabledPins parseEnabledPins(String value) { + String enabledUpper = value.toUpperCase(); + return new EnabledPinsBuilder() + .setX(enabledUpper.contains("X")) + .setY(enabledUpper.contains("Y")) + .setZ(enabledUpper.contains("Z")) + .setA(enabledUpper.contains("A")) + .setB(enabledUpper.contains("B")) + .setC(enabledUpper.contains("C")) + .setProbe(enabledUpper.contains("P")) + .setDoor(enabledUpper.contains("D")) + .setHold(enabledUpper.contains("H")) + .setSoftReset(enabledUpper.contains("R")) + .setCycleStart(enabledUpper.contains("S")) + .createEnabledPins(); + } + + /** + * Parses the accessory state string + * + * @param accessoryStates as a string + * @return the parsed accessory state + */ + private static AccessoryStates parseAccessoryStates(String accessoryStates) { + String enabledUpper = accessoryStates.toUpperCase(); + boolean spindleCW = enabledUpper.contains("S"); + boolean flood = enabledUpper.contains("F"); + boolean mist = enabledUpper.contains("M"); + return new AccessoryStatesBuilder().setSpindleCW(spindleCW).setFlood(flood).setMist(mist).createAccessoryStates(); + } + /** * Parses the feed speed from a status string starting with "F:". * The supported formats are F:1000.0 or F:3000.0,100.0,100.0 which are current feed rate, requested feed rate and override feed rate diff --git a/ugs-core/src/com/willwinder/universalgcodesender/IController.java b/ugs-core/src/com/willwinder/universalgcodesender/IController.java index 2da3143087..d773f6b166 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/IController.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/IController.java @@ -1,5 +1,5 @@ /* - Copyright 2015-2023 Will Winder + Copyright 2015-2024 Will Winder This file is part of Universal Gcode Sender (UGS). @@ -26,13 +26,13 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.listeners.ControllerListener; import com.willwinder.universalgcodesender.listeners.ControllerStatus; import com.willwinder.universalgcodesender.model.Axis; -import com.willwinder.universalgcodesender.model.Overrides; -import com.willwinder.universalgcodesender.model.PartialPosition; import com.willwinder.universalgcodesender.model.CommunicatorState; +import com.willwinder.universalgcodesender.model.PartialPosition; import com.willwinder.universalgcodesender.model.UnitUtils; import com.willwinder.universalgcodesender.services.MessageService; import com.willwinder.universalgcodesender.types.GcodeCommand; import com.willwinder.universalgcodesender.utils.IGcodeStreamReader; +import com.willwinder.universalgcodesender.firmware.IOverrideManager; import java.util.Optional; @@ -129,11 +129,6 @@ public interface IController { void probe(String axis, double feedRate, double distance, UnitUtils.Units units) throws Exception; void offsetTool(String axis, double offset, UnitUtils.Units units) throws Exception; - /* - Overrides - */ - void sendOverrideCommand(Overrides command) throws Exception; - /* Behavior */ @@ -246,4 +241,11 @@ public interface IController { * @return a command creator for this controller */ ICommandCreator getCommandCreator(); + + /** + * Gets the manager for handling overrides. + * + * @return the override manager. + */ + IOverrideManager getOverrideManager(); } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/TinyGController.java b/ugs-core/src/com/willwinder/universalgcodesender/TinyGController.java index c20ab53b0b..69184694e2 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/TinyGController.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/TinyGController.java @@ -1,5 +1,5 @@ /* - Copyright 2013-2023 Will Winder + Copyright 2013-2024 Will Winder This file is part of Universal Gcode Sender (UGS). @@ -23,27 +23,29 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.communicator.TinyGCommunicator; import com.willwinder.universalgcodesender.firmware.IFirmwareSettings; import com.willwinder.universalgcodesender.firmware.tinyg.TinyGFirmwareSettings; -import com.willwinder.universalgcodesender.gcode.ICommandCreator; import com.willwinder.universalgcodesender.firmware.tinyg.TinyGGcodeCommandCreator; +import com.willwinder.universalgcodesender.firmware.tinyg.commands.TinyGGcodeCommand; +import com.willwinder.universalgcodesender.gcode.ICommandCreator; import com.willwinder.universalgcodesender.gcode.util.GcodeUtils; import com.willwinder.universalgcodesender.i18n.Localization; import com.willwinder.universalgcodesender.listeners.ControllerState; import com.willwinder.universalgcodesender.listeners.ControllerStatus; import com.willwinder.universalgcodesender.listeners.ControllerStatusBuilder; import com.willwinder.universalgcodesender.listeners.MessageType; -import com.willwinder.universalgcodesender.model.*; +import com.willwinder.universalgcodesender.model.CommunicatorState; +import static com.willwinder.universalgcodesender.model.CommunicatorState.COMM_CHECK; +import static com.willwinder.universalgcodesender.model.CommunicatorState.COMM_IDLE; +import com.willwinder.universalgcodesender.model.PartialPosition; +import com.willwinder.universalgcodesender.model.UnitUtils; import com.willwinder.universalgcodesender.types.GcodeCommand; -import com.willwinder.universalgcodesender.firmware.tinyg.commands.TinyGGcodeCommand; import com.willwinder.universalgcodesender.utils.ControllerUtils; +import com.willwinder.universalgcodesender.firmware.DefaultOverrideManager; +import com.willwinder.universalgcodesender.firmware.IOverrideManager; import java.util.List; -import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; -import static com.willwinder.universalgcodesender.model.CommunicatorState.COMM_CHECK; -import static com.willwinder.universalgcodesender.model.CommunicatorState.COMM_IDLE; - /** * TinyG Control layer, coordinates all aspects of control. * @@ -74,7 +76,7 @@ public TinyGController(ICommunicator communicator) { firmwareSettings = new TinyGFirmwareSettings(this); communicator.addListener(firmwareSettings); - controllerStatus = new ControllerStatus(ControllerState.DISCONNECTED, new Position(0, 0, 0, UnitUtils.Units.MM), new Position(0, 0, 0, UnitUtils.Units.MM)); + controllerStatus = ControllerStatus.EMPTY_CONTROLLER_STATUS; firmwareVersion = "TinyG unknown version"; } @@ -404,15 +406,6 @@ public void setStatusUpdateRate(int statusUpdateRate) { comm.queueCommand(getCommandCreator().createCommand("{si:" + getStatusUpdateRate() + "}")); } - @Override - public void sendOverrideCommand(Overrides command) throws Exception { - ControllerStatus.OverridePercents currentOverrides = controllerStatus.getOverrides(); - Optional gcodeCommand = TinyGUtils.createOverrideCommand(getCommandCreator(), currentOverrides, command); - if (gcodeCommand.isPresent()) { - sendCommandImmediately(gcodeCommand.get()); - } - } - @Override public String getFirmwareVersion() { return firmwareVersion; @@ -423,6 +416,11 @@ public ControllerStatus getControllerStatus() { return controllerStatus; } + @Override + public IOverrideManager getOverrideManager() { + return new DefaultOverrideManager(); + } + @Override public CommunicatorState getCommunicatorState() { return getControlState(getControllerStatus().getState()); diff --git a/ugs-core/src/com/willwinder/universalgcodesender/TinyGUtils.java b/ugs-core/src/com/willwinder/universalgcodesender/TinyGUtils.java index 3c73efab82..5c6a76285a 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/TinyGUtils.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/TinyGUtils.java @@ -23,8 +23,12 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.gcode.GcodeState; import com.willwinder.universalgcodesender.gcode.ICommandCreator; import com.willwinder.universalgcodesender.gcode.util.Code; +import com.willwinder.universalgcodesender.listeners.AccessoryStates; import com.willwinder.universalgcodesender.listeners.ControllerState; import com.willwinder.universalgcodesender.listeners.ControllerStatus; +import com.willwinder.universalgcodesender.listeners.ControllerStatusBuilder; +import com.willwinder.universalgcodesender.listeners.EnabledPins; +import com.willwinder.universalgcodesender.listeners.OverridePercents; import com.willwinder.universalgcodesender.model.Axis; import com.willwinder.universalgcodesender.model.Overrides; import com.willwinder.universalgcodesender.model.PartialPosition; @@ -212,9 +216,9 @@ public static ControllerStatus updateControllerStatus(final ControllerStatus las int overrideRapid = 100; int overrideSpindle = 100; if (lastControllerStatus.getOverrides() != null) { - overrideFeed = lastControllerStatus.getOverrides().feed; - overrideRapid = lastControllerStatus.getOverrides().rapid; - overrideSpindle = lastControllerStatus.getOverrides().spindle; + overrideFeed = lastControllerStatus.getOverrides().feed(); + overrideRapid = lastControllerStatus.getOverrides().rapid(); + overrideSpindle = lastControllerStatus.getOverrides().spindle(); } if (hasNumericField(statusResultObject, FIELD_STATUS_REPORT_MFO)) { @@ -244,11 +248,22 @@ public static ControllerStatus updateControllerStatus(final ControllerStatus las Double spindleSpeed = lastControllerStatus.getSpindleSpeed(); Position workCoordinateOffset = lastControllerStatus.getWorkCoordinateOffset(); - ControllerStatus.EnabledPins enabledPins = lastControllerStatus.getEnabledPins(); - ControllerStatus.AccessoryStates accessoryStates = lastControllerStatus.getAccessoryStates(); - - ControllerStatus.OverridePercents overrides = new ControllerStatus.OverridePercents(overrideFeed, overrideRapid, overrideSpindle); - return new ControllerStatus(state, machineCoord, workCoord, feedSpeed, feedSpeedUnits, spindleSpeed, overrides, workCoordinateOffset, enabledPins, accessoryStates); + EnabledPins enabledPins = lastControllerStatus.getEnabledPins(); + AccessoryStates accessoryStates = lastControllerStatus.getAccessoryStates(); + + OverridePercents overrides = new OverridePercents(overrideFeed, overrideRapid, overrideSpindle); + return ControllerStatusBuilder.newInstance() + .setState(state) + .setMachineCoord(machineCoord) + .setWorkCoord(workCoord) + .setFeedSpeed(feedSpeed) + .setFeedSpeedUnits(feedSpeedUnits) + .setSpindleSpeed(spindleSpeed) + .setOverrides(overrides) + .setWorkCoordinateOffset(workCoordinateOffset) + .setPins(enabledPins) + .setStates(accessoryStates) + .build(); } return lastControllerStatus; @@ -413,18 +428,17 @@ private static boolean hasNumericField(JsonObject statusResultObject, String fie /** * Creates an override gcode command based on the current override state. * - * * @param commandCreator * @param currentOverrides the current override state * @param command the command which we want to build a gcode command from * @return the gcode command */ - public static Optional createOverrideCommand(ICommandCreator commandCreator, ControllerStatus.OverridePercents currentOverrides, Overrides command) { + public static Optional createOverrideCommand(ICommandCreator commandCreator, OverridePercents currentOverrides, Overrides command) { double feedOverride = OVERRIDE_DEFAULT; double spindleOverride = OVERRIDE_DEFAULT; if (currentOverrides != null) { - feedOverride = ((double) currentOverrides.feed) / 100.0; - spindleOverride = ((double) currentOverrides.spindle) / 100.0; + feedOverride = ((double) currentOverrides.feed()) / 100.0; + spindleOverride = ((double) currentOverrides.spindle()) / 100.0; } GcodeCommand result = null; diff --git a/ugs-core/src/com/willwinder/universalgcodesender/Utils.java b/ugs-core/src/com/willwinder/universalgcodesender/Utils.java index fbc4ca07bf..90ad663918 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/Utils.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/Utils.java @@ -21,8 +21,13 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.i18n.Localization; import com.willwinder.universalgcodesender.listeners.ControllerState; +import com.willwinder.universalgcodesender.listeners.ControllerStatus; +import com.willwinder.universalgcodesender.listeners.UGSEventListener; +import com.willwinder.universalgcodesender.model.BackendAPI; +import com.willwinder.universalgcodesender.model.events.ControllerStatusEvent; import com.willwinder.universalgcodesender.uielements.helpers.ThemeColors; import com.willwinder.universalgcodesender.utils.Settings; +import com.willwinder.universalgcodesender.utils.ThreadHelper; import com.willwinder.universalgcodesender.utils.Version; import javax.swing.JCheckBox; @@ -32,6 +37,12 @@ This file is part of Universal Gcode Sender (UGS). import java.awt.EventQueue; import java.text.DecimalFormat; import java.text.NumberFormat; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import java.util.logging.Logger; /** * A collection of utilities that don't relate to anything in particular. @@ -39,8 +50,9 @@ This file is part of Universal Gcode Sender (UGS). * @author wwinder */ public class Utils { - public static final NumberFormat formatter = new DecimalFormat("#.###", Localization.dfs); + private static final Logger LOGGER = Logger.getLogger(Utils.class.getSimpleName()); + private static final int MAX_WAIT_TIME_FOR_STATUS_REPORT = 1000; public static String formattedMillis(long millis) { String format = String.format("%%0%dd", 2); @@ -141,4 +153,58 @@ public static void checkNightlyBuild(Settings settings) { } } + /** + * Rounds to the closest step value within a min-max range + * + * @param value the value to round + * @param min the minimum allowed value + * @param max the maximum allowed value + * @param stepValue the step range + * @return the rounded value + */ + public static double roundToNearestStepValue(double value, double min, double max, double stepValue) { + return Math.round(Math.max(min, Math.min(max, value)) / stepValue) * stepValue; + } + + /** + * Creates a temporary listener and waits (for a maximum time) on a new status report + * + * @param backend the backend to listen to + * @return the optional status report + */ + public static Optional waitForStatusReport(BackendAPI backend) { + AtomicReference controllerStatus = new AtomicReference<>(); + + try { + ThreadHelper.waitUntil(() -> { + UGSEventListener ugsEventListener = evt -> { + if (evt instanceof ControllerStatusEvent controllerStatusEvent) { + controllerStatus.set(controllerStatusEvent.getStatus()); + } + }; + + try { + backend.addUGSEventListener(ugsEventListener); + backend.getController().requestStatusReport(); + while (controllerStatus.get() == null) { + try { + Thread.sleep(10); + } catch (InterruptedException ignored) { + // Never mind + } + } + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Could not fetch a status report", e); + } finally { + backend.removeUGSEventListener(ugsEventListener); + } + return true; + }, MAX_WAIT_TIME_FOR_STATUS_REPORT, TimeUnit.MILLISECONDS); + } catch (TimeoutException ex) { + LOGGER.warning("Could not get a status report within " + MAX_WAIT_TIME_FOR_STATUS_REPORT + " ms"); + } + + return Optional.ofNullable(controllerStatus.get()); + } + } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/firmware/AbstractOverrideManager.java b/ugs-core/src/com/willwinder/universalgcodesender/firmware/AbstractOverrideManager.java new file mode 100644 index 0000000000..db0e2adcac --- /dev/null +++ b/ugs-core/src/com/willwinder/universalgcodesender/firmware/AbstractOverrideManager.java @@ -0,0 +1,178 @@ +/* + Copyright 2024 Will Winder + + This file is part of Universal Gcode Sender (UGS). + + UGS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UGS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UGS. If not, see . + */ +package com.willwinder.universalgcodesender.firmware; + +import com.willwinder.universalgcodesender.IController; +import static com.willwinder.universalgcodesender.Utils.roundToNearestStepValue; +import com.willwinder.universalgcodesender.communicator.ICommunicator; +import com.willwinder.universalgcodesender.listeners.ControllerState; +import com.willwinder.universalgcodesender.listeners.ControllerStatus; +import com.willwinder.universalgcodesender.listeners.DefaultControllerListener; +import com.willwinder.universalgcodesender.listeners.OverridePercents; +import com.willwinder.universalgcodesender.listeners.OverrideType; +import com.willwinder.universalgcodesender.model.Overrides; + +/** + * An abstract override manager with some default implementation of common functions. + * + * @author Joacim Breiler + */ +public abstract class AbstractOverrideManager implements IOverrideManager { + public static final int SETTLE_TIME_MS = 50; + + protected final IController controller; + protected final ICommunicator communicator; + + protected int targetFeedSpeed = 100; + protected int targetSpindleSpeed = 100; + + private boolean isRunning = false; + private long lastSentCommand = 0; + + protected AbstractOverrideManager(IController controller, ICommunicator communicator) { + this.controller = controller; + this.communicator = communicator; + this.controller.addListener(new DefaultControllerListener() { + @Override + public void statusStringListener(ControllerStatus status) { + onControllerStatus(status); + } + }); + } + + public void onControllerStatus(ControllerStatus controllerStatus) { + if (!isRunning) { + targetFeedSpeed = controllerStatus.getOverrides().feed(); + targetSpindleSpeed = controllerStatus.getOverrides().spindle(); + return; + } + + // Wait for the override to settle + if (lastSentCommand + SETTLE_TIME_MS > System.currentTimeMillis()) { + return; + } + lastSentCommand = System.currentTimeMillis(); + + OverridePercents currentOverridePercents = controllerStatus.getOverrides(); + adjustFeedOverride(currentOverridePercents); + adjustSpindleOverride(currentOverridePercents); + + if (hasSettled()) { + stop(); + } + } + + protected void adjustFeedOverride(OverridePercents currentOverridePercents) { + if (currentOverridePercents.feed() == targetFeedSpeed) { + return; + } + + float currentFeed = currentOverridePercents.feed(); + int majorSteps = (int) ((targetFeedSpeed - currentFeed) / getSpeedMajorStep(OverrideType.FEED_SPEED)); + int minorSteps = (int) ((targetFeedSpeed - currentFeed) / getSpeedMinorStep(OverrideType.FEED_SPEED)); + + try { + if (majorSteps < 0) { + sendOverrideCommand(Overrides.CMD_FEED_OVR_COARSE_MINUS); + } else if (majorSteps > 0) { + sendOverrideCommand(Overrides.CMD_FEED_OVR_COARSE_PLUS); + } else if (minorSteps < 0) { + sendOverrideCommand(Overrides.CMD_FEED_OVR_FINE_MINUS); + } else if (minorSteps > 0) { + sendOverrideCommand(Overrides.CMD_FEED_OVR_FINE_PLUS); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + protected void adjustSpindleOverride(OverridePercents currentOverridePercents) { + if (currentOverridePercents.spindle() == targetSpindleSpeed) { + return; + } + + float currentSpindle = currentOverridePercents.spindle(); + int majorSteps = (int) ((targetSpindleSpeed - currentSpindle) / getSpeedMajorStep(OverrideType.SPINDLE_SPEED)); + int minorSteps = (int) ((targetSpindleSpeed - currentSpindle) / getSpeedMinorStep(OverrideType.SPINDLE_SPEED)); + + try { + if (majorSteps < 0) { + sendOverrideCommand(Overrides.CMD_SPINDLE_OVR_COARSE_MINUS); + } else if (majorSteps > 0) { + sendOverrideCommand(Overrides.CMD_SPINDLE_OVR_COARSE_PLUS); + } else if (minorSteps < 0) { + sendOverrideCommand(Overrides.CMD_SPINDLE_OVR_FINE_MINUS); + } else if (minorSteps > 0) { + sendOverrideCommand(Overrides.CMD_SPINDLE_OVR_FINE_PLUS); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + protected abstract int getSpeedMinorStep(OverrideType overrideType); + + protected abstract int getSpeedMajorStep(OverrideType overrideType); + + public boolean isAvailable() { + if (controller == null || controller.getControllerStatus() == null || + controller.getControllerStatus().getState() == null || + controller.getCapabilities() == null) { + return false; + } + + ControllerState state = controller.getControllerStatus().getState(); + return controller.getCapabilities().hasOverrides() && (state == ControllerState.HOLD || state == ControllerState.IDLE || state == ControllerState.RUN); + } + + /** + * Starts sending continuous override commands to achieve the target speeds + */ + protected void start() { + if (!isRunning) { + isRunning = true; + onControllerStatus(controller.getControllerStatus()); + } + } + + /** + * Stops sending override commands + */ + private void stop() { + isRunning = false; + } + + @Override + public boolean hasSettled() { + OverridePercents overrides = controller.getControllerStatus().getOverrides(); + return overrides.spindle() == targetSpindleSpeed && overrides.feed() == targetFeedSpeed; + } + + @Override + public void setSpeedTarget(OverrideType type, int percent) { + percent = (int) Math.round(roundToNearestStepValue(percent, getSpeedMin(type), getSpeedMax(type), getSpeedStep(type))); + if (type == OverrideType.FEED_SPEED) { + targetFeedSpeed = percent; + } else if (type == OverrideType.SPINDLE_SPEED) { + targetSpindleSpeed = percent; + } + + start(); + } +} diff --git a/ugs-core/src/com/willwinder/universalgcodesender/firmware/DefaultOverrideManager.java b/ugs-core/src/com/willwinder/universalgcodesender/firmware/DefaultOverrideManager.java new file mode 100644 index 0000000000..16785ff722 --- /dev/null +++ b/ugs-core/src/com/willwinder/universalgcodesender/firmware/DefaultOverrideManager.java @@ -0,0 +1,97 @@ +/* + Copyright 2024 Will Winder + + This file is part of Universal Gcode Sender (UGS). + + UGS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UGS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UGS. If not, see . + */ +package com.willwinder.universalgcodesender.firmware; + +import com.willwinder.universalgcodesender.listeners.OverrideType; +import com.willwinder.universalgcodesender.model.Overrides; + +import java.util.List; + +/** + * A default empty implementation of an override manager which can be used if the + * controller does not support overrides. + * + * @author Joacim Breiler + */ +public class DefaultOverrideManager implements IOverrideManager { + @Override + public void setSpeedTarget(OverrideType type, int value) { + // Not implemented + } + + @Override + public boolean hasSettled() { + return true; + } + + @Override + public int getSpeedMax(OverrideType type) { + return 0; + } + + @Override + public int getSpeedMin(OverrideType type) { + return 0; + } + + @Override + public int getSpeedStep(OverrideType type) { + return 0; + } + + @Override + public void sendOverrideCommand(Overrides command) { + // Not implemented + } + + @Override + public int getSpeedDefault(OverrideType overrideType) { + return 0; + } + + @Override + public int getSpeedTargetValue(OverrideType type) { + return 0; + } + + @Override + public List getSpeedTypes() { + return List.of(); + } + + @Override + public List getToggleTypes() { + return List.of(); + } + + @Override + public boolean isAvailable() { + return false; + } + + @Override + public void toggle(OverrideType overrideType) { + // Not implemented + } + + @Override + public boolean isToggled(OverrideType overrideType) { + return false; + } +} diff --git a/ugs-core/src/com/willwinder/universalgcodesender/firmware/IOverrideManager.java b/ugs-core/src/com/willwinder/universalgcodesender/firmware/IOverrideManager.java new file mode 100644 index 0000000000..6cc2a7fcc2 --- /dev/null +++ b/ugs-core/src/com/willwinder/universalgcodesender/firmware/IOverrideManager.java @@ -0,0 +1,104 @@ +/* + Copyright 2024 Will Winder + + This file is part of Universal Gcode Sender (UGS). + + UGS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UGS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UGS. If not, see . + */ +package com.willwinder.universalgcodesender.firmware; + +import com.willwinder.universalgcodesender.listeners.OverrideType; +import com.willwinder.universalgcodesender.model.Overrides; + +import java.util.List; + +/** + * The override manager is used to apply overrides to the controller + * + * @author Joacim Breiler + */ +public interface IOverrideManager { + /** + * Sets the new override percents for the given type. If the controller doesn't support given override value + * the nearest will be used instead. If the override type does not support setting a speed step it is ignored. + * Which {@link OverrideType} that can be used with this function is determined with the {@link #getSpeedTypes}. + * + * @param type the override value to set + * @param value new controller override value in percent + */ + void setSpeedTarget(OverrideType type, int value); + + int getSpeedMax(OverrideType type); + + int getSpeedMin(OverrideType type); + + int getSpeedStep(OverrideType type); + + void sendOverrideCommand(Overrides command); + + int getSpeedDefault(OverrideType type); + + /** + * Get the target speed for the given override type. Which {@link OverrideType} that can be used with this + * function is determined with the {@link #getSpeedTypes}. + * + * @param type the override type to get the target speed for. + * @return the target speed in percent + */ + int getSpeedTargetValue(OverrideType type); + + /** + * Returns true if the changes to be made with the override manager has settled and are done. + * + * @return true if the changes are done + */ + boolean hasSettled(); + + /** + * Returns the override types that can be set with speed settings + * + * @return a list of override speed types + */ + List getSpeedTypes(); + + /** + * Returns the override types that can be toggled + * + * @return a list of toggleable override types + */ + List getToggleTypes(); + + /** + * Returns true when override functions are available + * + * @return true when it is available and can be used to override behaviour on the controller + */ + boolean isAvailable(); + + /** + * Toggles the given override. Which {@link OverrideType} that can be used with this function is determined + * with the {@link #getToggleTypes()}. + * + * @param type the + */ + void toggle(OverrideType type); + + /** + * Returns if the given override is currently toggled. + * + * @param type the override type + * @return true if the override is active + */ + boolean isToggled(OverrideType type); +} diff --git a/ugs-core/src/com/willwinder/universalgcodesender/firmware/OverrideException.java b/ugs-core/src/com/willwinder/universalgcodesender/firmware/OverrideException.java new file mode 100644 index 0000000000..df550f88ec --- /dev/null +++ b/ugs-core/src/com/willwinder/universalgcodesender/firmware/OverrideException.java @@ -0,0 +1,25 @@ +/* + Copyright 2024 Will Winder + + This file is part of Universal Gcode Sender (UGS). + + UGS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UGS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UGS. If not, see . + */ +package com.willwinder.universalgcodesender.firmware; + +public class OverrideException extends RuntimeException { + public OverrideException(String message, Throwable throwable) { + super(message, throwable); + } +} diff --git a/ugs-core/src/com/willwinder/universalgcodesender/firmware/fluidnc/FluidNCController.java b/ugs-core/src/com/willwinder/universalgcodesender/firmware/fluidnc/FluidNCController.java index 3d639e50f0..c3a4e9793c 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/firmware/fluidnc/FluidNCController.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/firmware/fluidnc/FluidNCController.java @@ -34,6 +34,7 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.connection.ConnectionException; import com.willwinder.universalgcodesender.firmware.FirmwareSettingsException; import com.willwinder.universalgcodesender.firmware.IFirmwareSettings; +import com.willwinder.universalgcodesender.firmware.IOverrideManager; import static com.willwinder.universalgcodesender.firmware.fluidnc.FluidNCUtils.DISABLE_ECHO_COMMAND; import static com.willwinder.universalgcodesender.firmware.fluidnc.FluidNCUtils.GRBL_COMPABILITY_VERSION; import com.willwinder.universalgcodesender.firmware.fluidnc.commands.DetectEchoCommand; @@ -45,6 +46,7 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.firmware.fluidnc.commands.GetStartupMessagesCommand; import com.willwinder.universalgcodesender.firmware.fluidnc.commands.GetStatusCommand; import com.willwinder.universalgcodesender.firmware.fluidnc.commands.SystemCommand; +import com.willwinder.universalgcodesender.firmware.grbl.GrblOverrideManager; import com.willwinder.universalgcodesender.gcode.GcodeParser; import com.willwinder.universalgcodesender.gcode.GcodeState; import com.willwinder.universalgcodesender.gcode.ICommandCreator; @@ -56,7 +58,6 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.listeners.MessageType; import com.willwinder.universalgcodesender.model.Axis; import com.willwinder.universalgcodesender.model.CommunicatorState; -import com.willwinder.universalgcodesender.model.Overrides; import com.willwinder.universalgcodesender.model.PartialPosition; import com.willwinder.universalgcodesender.model.Position; import com.willwinder.universalgcodesender.model.UnitUtils; @@ -99,6 +100,7 @@ public class FluidNCController implements IController, ICommunicatorListener { private final StopWatch streamStopWatch = new StopWatch(); private final IFileService fileService; private final ICommandCreator commandCreator; + private final IOverrideManager overrideManager; private MessageService messageService = new MessageService(); private ControllerStatus controllerStatus; private SemanticVersion semanticVersion = new SemanticVersion(); @@ -120,6 +122,7 @@ public FluidNCController(ICommunicator communicator) { this.communicator.addListener(this); this.fileService = new FluidNCFileService(this, positionPollTimer); this.commandCreator = new FluidNCCommandCreator(); + this.overrideManager = new GrblOverrideManager(this, communicator); } @Override @@ -285,15 +288,6 @@ public void offsetTool(String axis, double offset, UnitUtils.Units units) throws restoreParserModalState(); } - @Override - public void sendOverrideCommand(Overrides command) throws Exception { - Byte realTimeCommand = GrblUtils.getOverrideForEnum(command, capabilities); - if (realTimeCommand != null) { - messageService.dispatchMessage(MessageType.INFO, String.format("> 0x%02x\n", realTimeCommand)); - communicator.sendByteImmediately(realTimeCommand); - } - } - @Override public boolean getSingleStepMode() { return true; @@ -846,4 +840,9 @@ public IFileService getFileService() { public ICommandCreator getCommandCreator() { return commandCreator; } + + @Override + public IOverrideManager getOverrideManager() { + return overrideManager; + } } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/firmware/fluidnc/FluidNCUtils.java b/ugs-core/src/com/willwinder/universalgcodesender/firmware/fluidnc/FluidNCUtils.java index 3b4dd82806..d97f4f4111 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/firmware/fluidnc/FluidNCUtils.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/firmware/fluidnc/FluidNCUtils.java @@ -16,6 +16,8 @@ import com.willwinder.universalgcodesender.model.Position; import com.willwinder.universalgcodesender.model.UnitUtils; import com.willwinder.universalgcodesender.services.MessageService; +import static com.willwinder.universalgcodesender.utils.ControllerUtils.sendAndWaitForCompletion; +import static com.willwinder.universalgcodesender.utils.ControllerUtils.sendAndWaitForCompletionWithRetry; import com.willwinder.universalgcodesender.utils.SemanticVersion; import org.apache.commons.lang3.StringUtils; @@ -24,9 +26,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import static com.willwinder.universalgcodesender.utils.ControllerUtils.sendAndWaitForCompletion; -import static com.willwinder.universalgcodesender.utils.ControllerUtils.sendAndWaitForCompletionWithRetry; - public class FluidNCUtils { public static final double GRBL_COMPABILITY_VERSION = 1.1d; public static final SemanticVersion MINIMUM_VERSION = new SemanticVersion(3, 3, 0); @@ -38,16 +37,13 @@ public class FluidNCUtils { private static final Pattern PROBE_PATTERN = Pattern.compile(PROBE_REGEX); private static final String WELCOME_REGEX = "(?.*)\\s(?[0-9a-z.]*)\\s\\[((?[a-zA-Z]*)?\\s(v(?[0-9.]*))?)+.*]"; private static final Pattern WELCOME_PATTERN = Pattern.compile(WELCOME_REGEX, Pattern.CASE_INSENSITIVE); - private static final Pattern MACHINE_PATTERN = Pattern.compile("(?<=MPos:)(-?\\d*\\.?\\d*),(-?\\d*\\.?\\d*),(-?\\d*\\.?\\d*)(?:,(-?\\d*\\.?\\d+))?(?:,(-?\\d*\\.?\\d+))?(?:,(-?\\d*\\.?\\d+))?"); private static final Pattern PROBE_POSITION_PATTERN = Pattern.compile("\\[PRB:(-?\\d*\\.\\d*),(-?\\d*\\.\\d*),(-?\\d*\\.\\d*)(?:,(-?\\d*\\.?\\d+))?(?:,(-?\\d*\\.?\\d+))?(?:,(-?\\d*\\.?\\d+))?:\\d?]"); - private static final Pattern WORK_PATTERN = Pattern.compile("(?<=WPos:)(-?\\d*\\.?\\d*),(-?\\d*\\.?\\d*),(-?\\d*\\.?\\d*)(?:,(-?\\d*\\.?\\d+))?(?:,(-?\\d*\\.?\\d+))?(?:,(-?\\d*\\.?\\d+))?"); - private static final Pattern WCO_PATTERN = Pattern.compile("(?<=WCO:)(-?\\d*\\.?\\d*),(-?\\d*\\.?\\d*),(-?\\d*\\.?\\d*)(?:,(-?\\d*\\.?\\d+))?(?:,(-?\\d*\\.?\\d+))?(?:,(-?\\d*\\.?\\d+))?"); public static boolean isMessageResponse(String response) { return MESSAGE_PATTERN.matcher(response).find(); } - static protected Optional parseMessageResponse(final String response) { + protected static Optional parseMessageResponse(final String response) { if (!isMessageResponse(response)) { return Optional.empty(); } @@ -59,7 +55,7 @@ public static boolean isProbeMessage(String response) { return PROBE_PATTERN.matcher(response).find(); } - static protected Position parseProbePosition(final String response, final UnitUtils.Units units) { + protected static Position parseProbePosition(final String response, final UnitUtils.Units units) { // Don't parse failed probe response. if (response.endsWith(":0]")) { return Position.INVALID; @@ -91,7 +87,7 @@ public static ControllerStatus getStatusFromStatusResponse(ControllerStatus last } public static GetStatusCommand queryForStatusReport(IController controller, MessageService messageService) throws InterruptedException { - return sendAndWaitForCompletionWithRetry(GetStatusCommand::new, controller, 4000, 10, (executionNumber) -> { + return sendAndWaitForCompletionWithRetry(GetStatusCommand::new, controller, 4000, 10, executionNumber -> { if (executionNumber == 1) { messageService.dispatchMessage(MessageType.INFO, "*** Fetching device status\n"); } else { diff --git a/ugs-core/src/com/willwinder/universalgcodesender/firmware/g2core/G2CoreOverrideManager.java b/ugs-core/src/com/willwinder/universalgcodesender/firmware/g2core/G2CoreOverrideManager.java new file mode 100644 index 0000000000..e2edc10f89 --- /dev/null +++ b/ugs-core/src/com/willwinder/universalgcodesender/firmware/g2core/G2CoreOverrideManager.java @@ -0,0 +1,148 @@ +/* + Copyright 2024 Will Winder + + This file is part of Universal Gcode Sender (UGS). + + UGS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UGS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UGS. If not, see . + */ +package com.willwinder.universalgcodesender.firmware.g2core; + +import com.willwinder.universalgcodesender.IController; +import com.willwinder.universalgcodesender.TinyGUtils; +import com.willwinder.universalgcodesender.communicator.ICommunicator; +import com.willwinder.universalgcodesender.firmware.AbstractOverrideManager; +import com.willwinder.universalgcodesender.firmware.IOverrideManager; +import com.willwinder.universalgcodesender.listeners.OverrideType; +import com.willwinder.universalgcodesender.model.Overrides; +import com.willwinder.universalgcodesender.types.GcodeCommand; + +import java.util.List; +import java.util.Optional; +import java.util.logging.Logger; + +public class G2CoreOverrideManager extends AbstractOverrideManager implements IOverrideManager { + private static final Logger LOGGER = Logger.getLogger(G2CoreOverrideManager.class.getSimpleName()); + private static final int MINOR_STEP = 1; + private static final int MAJOR_STEP = 10; + private static final int FEED_MIN = 10; + private static final int FEED_MAX = 200; + private static final int FEED_DEFAULT = 100; + private static final int SPINDLE_MIN = 10; + private static final int SPINDLE_MAX = 200; + private static final int SPINDLE_DEFAULT = 100; + + public G2CoreOverrideManager(IController controller, ICommunicator communicator) { + super(controller, communicator); + } + + @Override + protected int getSpeedMinorStep(OverrideType overrideType) { + return MINOR_STEP; + } + + @Override + protected int getSpeedMajorStep(OverrideType overrideType) { + return MAJOR_STEP; + } + + public void sendOverrideCommand(Overrides command) { + Optional gcodeCommand = TinyGUtils.createOverrideCommand(controller.getCommandCreator(), controller.getControllerStatus().getOverrides(), command); + if (gcodeCommand.isEmpty()) { + return; + } + + try { + controller.sendCommandImmediately(gcodeCommand.get()); + } catch (Exception e) { + LOGGER.info("Could not send override command " + command); + } + } + + @Override + public int getSpeedDefault(OverrideType type) { + return switch (type) { + case FEED_SPEED -> FEED_DEFAULT; + case SPINDLE_SPEED -> SPINDLE_DEFAULT; + default -> 0; + }; + } + + @Override + public int getSpeedTargetValue(OverrideType type) { + return switch (type) { + case FEED_SPEED -> targetFeedSpeed; + case SPINDLE_SPEED -> targetSpindleSpeed; + default -> 0; + }; + } + + @Override + public List getSpeedTypes() { + return List.of(OverrideType.FEED_SPEED, OverrideType.SPINDLE_SPEED); + } + + @Override + public List getToggleTypes() { + return List.of(OverrideType.SPINDLE_TOGGLE, OverrideType.MIST_TOGGLE, OverrideType.FLOOD_TOGGLE); + } + + @Override + public void toggle(OverrideType type) { + switch (type) { + case SPINDLE_TOGGLE -> sendOverrideCommand(Overrides.CMD_TOGGLE_SPINDLE); + case MIST_TOGGLE -> sendOverrideCommand(Overrides.CMD_TOGGLE_MIST_COOLANT); + case FLOOD_TOGGLE -> sendOverrideCommand(Overrides.CMD_TOGGLE_FLOOD_COOLANT); + default -> throw new IllegalStateException("Unexpected value: " + type); + } + } + + @Override + public boolean isToggled(OverrideType overrideType) { + return switch (overrideType) { + case MIST_TOGGLE -> controller.getControllerStatus().getAccessoryStates().mist(); + case FLOOD_TOGGLE -> controller.getControllerStatus().getAccessoryStates().flood(); + case SPINDLE_TOGGLE -> (controller.getControllerStatus().getSpindleSpeed() > 0); + default -> false; + }; + } + + @Override + public int getSpeedMax(OverrideType type) { + return switch (type) { + case FEED_SPEED -> FEED_MAX; + case SPINDLE_SPEED -> SPINDLE_MAX; + case RAPID_SPEED -> 100; + default -> 0; + }; + } + + @Override + public int getSpeedMin(OverrideType type) { + return switch (type) { + case FEED_SPEED -> FEED_MIN; + case SPINDLE_SPEED -> SPINDLE_MIN; + case RAPID_SPEED -> 25; + default -> 0; + }; + } + + @Override + public int getSpeedStep(OverrideType type) { + return switch (type) { + case FEED_SPEED, SPINDLE_SPEED -> MINOR_STEP; + default -> 0; + }; + } + +} diff --git a/ugs-core/src/com/willwinder/universalgcodesender/firmware/grbl/GrblOverrideManager.java b/ugs-core/src/com/willwinder/universalgcodesender/firmware/grbl/GrblOverrideManager.java new file mode 100644 index 0000000000..ddefd79805 --- /dev/null +++ b/ugs-core/src/com/willwinder/universalgcodesender/firmware/grbl/GrblOverrideManager.java @@ -0,0 +1,141 @@ +/* + Copyright 2024 Will Winder + + This file is part of Universal Gcode Sender (UGS). + + UGS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UGS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UGS. If not, see . + */ +package com.willwinder.universalgcodesender.firmware.grbl; + +import com.willwinder.universalgcodesender.GrblUtils; +import com.willwinder.universalgcodesender.IController; +import com.willwinder.universalgcodesender.communicator.ICommunicator; +import com.willwinder.universalgcodesender.firmware.AbstractOverrideManager; +import com.willwinder.universalgcodesender.firmware.IOverrideManager; +import com.willwinder.universalgcodesender.firmware.OverrideException; +import com.willwinder.universalgcodesender.listeners.OverrideType; +import com.willwinder.universalgcodesender.model.Overrides; + +import java.util.List; + +public class GrblOverrideManager extends AbstractOverrideManager implements IOverrideManager { + private static final int MINOR_STEP = 1; + private static final int MAJOR_STEP = 10; + private static final int FEED_MIN = 10; + private static final int FEED_MAX = 200; + private static final int FEED_DEFAULT = 100; + private static final int SPINDLE_MIN = 10; + private static final int SPINDLE_MAX = 200; + private static final int SPINDLE_DEFAULT = 100; + + public GrblOverrideManager(IController controller, ICommunicator communicator) { + super(controller, communicator); + } + + @Override + protected int getSpeedMinorStep(OverrideType overrideType) { + return MINOR_STEP; + } + + @Override + protected int getSpeedMajorStep(OverrideType overrideType) { + return MAJOR_STEP; + } + + public void sendOverrideCommand(Overrides command) { + Byte realTimeCommand = GrblUtils.getOverrideForEnum(command, controller.getCapabilities()); + if (realTimeCommand != null) { + try { + communicator.sendByteImmediately(realTimeCommand); + } catch (Exception e) { + throw new OverrideException("Could not send override command", e); + } + } + } + + @Override + public int getSpeedDefault(OverrideType type) { + return switch (type) { + case FEED_SPEED -> FEED_DEFAULT; + case SPINDLE_SPEED -> SPINDLE_DEFAULT; + default -> 0; + }; + } + + @Override + public int getSpeedTargetValue(OverrideType type) { + return switch (type) { + case FEED_SPEED -> targetFeedSpeed; + case SPINDLE_SPEED -> targetSpindleSpeed; + default -> 0; + }; + } + + @Override + public List getSpeedTypes() { + return List.of(OverrideType.FEED_SPEED, OverrideType.SPINDLE_SPEED); + } + + @Override + public List getToggleTypes() { + return List.of(OverrideType.SPINDLE_TOGGLE, OverrideType.MIST_TOGGLE, OverrideType.FLOOD_TOGGLE); + } + + @Override + public void toggle(OverrideType type) { + switch (type) { + case SPINDLE_TOGGLE -> sendOverrideCommand(Overrides.CMD_TOGGLE_SPINDLE); + case MIST_TOGGLE -> sendOverrideCommand(Overrides.CMD_TOGGLE_MIST_COOLANT); + case FLOOD_TOGGLE -> sendOverrideCommand(Overrides.CMD_TOGGLE_FLOOD_COOLANT); + default -> throw new IllegalStateException("Unexpected value: " + type); + } + } + + @Override + public boolean isToggled(OverrideType overrideType) { + return switch (overrideType) { + case MIST_TOGGLE -> controller.getControllerStatus().getAccessoryStates().mist(); + case FLOOD_TOGGLE -> controller.getControllerStatus().getAccessoryStates().flood(); + case SPINDLE_TOGGLE -> (controller.getControllerStatus().getSpindleSpeed() > 0); + default -> false; + }; + } + + @Override + public int getSpeedMax(OverrideType type) { + return switch (type) { + case FEED_SPEED -> FEED_MAX; + case SPINDLE_SPEED -> SPINDLE_MAX; + default -> 0; + }; + } + + @Override + public int getSpeedMin(OverrideType type) { + return switch (type) { + case FEED_SPEED -> FEED_MIN; + case SPINDLE_SPEED -> SPINDLE_MIN; + default -> 0; + }; + } + + @Override + public int getSpeedStep(OverrideType type) { + return switch (type) { + case FEED_SPEED, SPINDLE_SPEED -> MINOR_STEP; + default -> 0; + }; + } + +} diff --git a/ugs-core/src/com/willwinder/universalgcodesender/firmware/grbl/commands/GetStatusCommand.java b/ugs-core/src/com/willwinder/universalgcodesender/firmware/grbl/commands/GetStatusCommand.java index 084e3d6de7..118ea0ec8b 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/firmware/grbl/commands/GetStatusCommand.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/firmware/grbl/commands/GetStatusCommand.java @@ -1,5 +1,5 @@ /* - Copyright 2023 Will Winder + Copyright 2023-2024 Will Winder This file is part of Universal Gcode Sender (UGS). @@ -27,8 +27,8 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.types.GcodeCommand; public class GetStatusCommand extends GcodeCommand { - public static final ControllerStatus EMPTY_STATUS = new ControllerStatus(ControllerState.DISCONNECTED, Position.ZERO, Position.ZERO); - private ControllerStatus controllerStatus = ControllerStatusBuilder.newInstance().build(); + public static final ControllerStatus EMPTY_STATUS = new ControllerStatus(ControllerState.UNKNOWN, Position.ZERO, Position.ZERO); + private ControllerStatus controllerStatus = ControllerStatusBuilder.newInstance().setState(ControllerState.UNKNOWN).build(); public GetStatusCommand() { super("?"); diff --git a/ugs-core/src/com/willwinder/universalgcodesender/firmware/smoothie/SmoothieController.java b/ugs-core/src/com/willwinder/universalgcodesender/firmware/smoothie/SmoothieController.java index 66fe20696b..dbde1defdd 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/firmware/smoothie/SmoothieController.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/firmware/smoothie/SmoothieController.java @@ -33,21 +33,21 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.listeners.ControllerStatusBuilder; import com.willwinder.universalgcodesender.listeners.MessageType; import com.willwinder.universalgcodesender.model.CommunicatorState; -import com.willwinder.universalgcodesender.model.Overrides; +import static com.willwinder.universalgcodesender.model.CommunicatorState.COMM_CHECK; +import static com.willwinder.universalgcodesender.model.CommunicatorState.COMM_DISCONNECTED; +import static com.willwinder.universalgcodesender.model.CommunicatorState.COMM_IDLE; import com.willwinder.universalgcodesender.model.PartialPosition; import com.willwinder.universalgcodesender.model.Position; import com.willwinder.universalgcodesender.model.UnitUtils; import com.willwinder.universalgcodesender.types.GcodeCommand; import com.willwinder.universalgcodesender.utils.ControllerUtils; +import com.willwinder.universalgcodesender.firmware.DefaultOverrideManager; +import com.willwinder.universalgcodesender.firmware.IOverrideManager; import com.willwinder.universalgcodesender.utils.ThreadHelper; import org.apache.commons.lang3.StringUtils; import java.util.Optional; -import static com.willwinder.universalgcodesender.model.CommunicatorState.COMM_CHECK; -import static com.willwinder.universalgcodesender.model.CommunicatorState.COMM_DISCONNECTED; -import static com.willwinder.universalgcodesender.model.CommunicatorState.COMM_IDLE; - /** * Controller implementation for Smoothieware * @@ -221,11 +221,6 @@ private void handleStatusResponse(String response) { dispatchStatusString(controllerStatus); } - @Override - public void sendOverrideCommand(Overrides command) { - - } - @Override public boolean getStatusUpdatesEnabled() { return statusPollTimer.isEnabled(); @@ -277,6 +272,11 @@ public ControllerStatus getControllerStatus() { return controllerStatus; } + @Override + public IOverrideManager getOverrideManager() { + return new DefaultOverrideManager(); + } + @Override public void setWorkPosition(PartialPosition axisPosition) throws Exception { String command = SmoothieUtils.generateSetWorkPositionCommand(controllerStatus, getCurrentGcodeState(), axisPosition); diff --git a/ugs-core/src/com/willwinder/universalgcodesender/listeners/AccessoryStates.java b/ugs-core/src/com/willwinder/universalgcodesender/listeners/AccessoryStates.java new file mode 100644 index 0000000000..d977b18b04 --- /dev/null +++ b/ugs-core/src/com/willwinder/universalgcodesender/listeners/AccessoryStates.java @@ -0,0 +1,26 @@ +/* + Copyright 2013-2024 Will Winder + + This file is part of Universal Gcode Sender (UGS). + + UGS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UGS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UGS. If not, see . + */ +package com.willwinder.universalgcodesender.listeners; + +/** + * A class for storing the accessory states + */ +public record AccessoryStates(boolean spindleCW, boolean flood, boolean mist) { + public static final AccessoryStates EMPTY_ACCESSORY_STATE = new AccessoryStatesBuilder().createAccessoryStates(); +} diff --git a/ugs-core/src/com/willwinder/universalgcodesender/listeners/AccessoryStatesBuilder.java b/ugs-core/src/com/willwinder/universalgcodesender/listeners/AccessoryStatesBuilder.java new file mode 100644 index 0000000000..343a214b8d --- /dev/null +++ b/ugs-core/src/com/willwinder/universalgcodesender/listeners/AccessoryStatesBuilder.java @@ -0,0 +1,47 @@ +/* + Copyright 2013-2024 Will Winder + + This file is part of Universal Gcode Sender (UGS). + + UGS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UGS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UGS. If not, see . + */ +package com.willwinder.universalgcodesender.listeners; + +/** + * A class for storing the accessory states + */ +public class AccessoryStatesBuilder { + private boolean spindleCW = true; + private boolean flood; + private boolean mist; + + public AccessoryStatesBuilder setSpindleCW(boolean spindleCW) { + this.spindleCW = spindleCW; + return this; + } + + public AccessoryStatesBuilder setFlood(boolean flood) { + this.flood = flood; + return this; + } + + public AccessoryStatesBuilder setMist(boolean mist) { + this.mist = mist; + return this; + } + + public AccessoryStates createAccessoryStates() { + return new AccessoryStates(spindleCW, flood, mist); + } +} \ No newline at end of file diff --git a/ugs-core/src/com/willwinder/universalgcodesender/listeners/ControllerStatus.java b/ugs-core/src/com/willwinder/universalgcodesender/listeners/ControllerStatus.java index e782e2753a..6a469e5cc2 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/listeners/ControllerStatus.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/listeners/ControllerStatus.java @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 Will Winder + Copyright 2016-2024 Will Winder This file is part of Universal Gcode Sender (UGS). @@ -27,6 +27,7 @@ This file is part of Universal Gcode Sender (UGS). * @author wwinder */ public class ControllerStatus { + public static final ControllerStatus EMPTY_CONTROLLER_STATUS = ControllerStatusBuilder.newInstance().build(); private final Position machineCoord; private final Position workCoord; private final Position workCoordinateOffset; @@ -48,17 +49,7 @@ public class ControllerStatus { * @param workCoord controller work coordinates */ public ControllerStatus(ControllerState state, Position machineCoord, Position workCoord) { - this(state, machineCoord, workCoord, 0d, UnitUtils.Units.MM, 0d, null, null, null, null); - } - - /** - * Additional parameters - */ - public ControllerStatus(ControllerState state, Position machineCoord, - Position workCoord, Double feedSpeed, UnitUtils.Units feedSpeedUnits, Double spindleSpeed, - OverridePercents overrides, Position workCoordinateOffset, - EnabledPins pins, AccessoryStates states) { - this(state, "", machineCoord, workCoord, feedSpeed, feedSpeedUnits, spindleSpeed, overrides, workCoordinateOffset, pins, states); + this(state, "", machineCoord, workCoord, 0d, UnitUtils.Units.MM, 0d, null, null, null, null); } /** @@ -81,10 +72,6 @@ public ControllerStatus(ControllerState state, String subState, Position machine this.accessoryStates = states; } - public ControllerStatus() { - this(ControllerState.DISCONNECTED, Position.ZERO, Position.ZERO); - } - public ControllerState getState() { return state; } @@ -139,92 +126,4 @@ public String getSubState() { return subState; } - public static class EnabledPins { - public static final EnabledPins EMPTY_PINS = new EnabledPins(""); - - final public boolean X; - final public boolean Y; - final public boolean Z; - final public boolean A; - final public boolean B; - final public boolean C; - final public boolean Probe; - final public boolean Door; - final public boolean Hold; - final public boolean SoftReset; - final public boolean CycleStart; - - public EnabledPins(String enabled) { - String enabledUpper = enabled.toUpperCase(); - X = enabledUpper.contains("X"); - Y = enabledUpper.contains("Y"); - Z = enabledUpper.contains("Z"); - A = enabledUpper.contains("A"); - B = enabledUpper.contains("B"); - C = enabledUpper.contains("C"); - Probe = enabledUpper.contains("P"); - Door = enabledUpper.contains("D"); - Hold = enabledUpper.contains("H"); - SoftReset = enabledUpper.contains("R"); - CycleStart = enabledUpper.contains("S"); - } - - @Override - public boolean equals(Object o) { - return EqualsBuilder.reflectionEquals(this, o); - } - - @Override - public int hashCode() { - return HashCodeBuilder.reflectionHashCode(this); - } - } - - public static class AccessoryStates { - public static final AccessoryStates EMPTY_ACCESSORY_STATE = new AccessoryStates(""); - final public boolean SpindleCW; - final public boolean SpindleCCW; - final public boolean Flood; - final public boolean Mist; - - public AccessoryStates(String enabled) { - String enabledUpper = enabled.toUpperCase(); - SpindleCW = enabledUpper.contains("S"); - SpindleCCW = enabledUpper.contains("C"); - Flood = enabledUpper.contains("F"); - Mist = enabledUpper.contains("M"); - } - - @Override - public boolean equals(Object o) { - return EqualsBuilder.reflectionEquals(this, o); - } - - @Override - public int hashCode() { - return HashCodeBuilder.reflectionHashCode(this); - } - } - - public static class OverridePercents { - final public int feed; - final public int rapid; - final public int spindle; - - public OverridePercents(int feed, int rapid, int spindle) { - this.feed = feed; - this.rapid = rapid; - this.spindle = spindle; - } - - @Override - public boolean equals(Object o) { - return EqualsBuilder.reflectionEquals(this, o); - } - - @Override - public int hashCode() { - return HashCodeBuilder.reflectionHashCode(this); - } - } } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/listeners/ControllerStatusBuilder.java b/ugs-core/src/com/willwinder/universalgcodesender/listeners/ControllerStatusBuilder.java index 48a78156e5..4904c95218 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/listeners/ControllerStatusBuilder.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/listeners/ControllerStatusBuilder.java @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Will Winder + Copyright 2016-2024 Will Winder This file is part of Universal Gcode Sender (UGS). @@ -27,16 +27,16 @@ This file is part of Universal Gcode Sender (UGS). * @author Joacim Breiler */ public class ControllerStatusBuilder { - private ControllerState state = ControllerState.UNKNOWN; + private ControllerState state = ControllerState.DISCONNECTED; private Position machineCoord = Position.ZERO; private Position workCoord = Position.ZERO; private Double feedSpeed = 0d; private UnitUtils.Units feedSpeedUnits = UnitUtils.Units.MM; private Double spindleSpeed = 0d; - private ControllerStatus.OverridePercents overrides = null; + private OverridePercents overrides = OverridePercents.EMTPY_OVERRIDE_PERCENTS; private Position workCoordinateOffset = Position.ZERO; - private ControllerStatus.EnabledPins pins = null; - private ControllerStatus.AccessoryStates states = null; + private EnabledPins pins = EnabledPins.EMPTY_PINS; + private AccessoryStates states = AccessoryStates.EMPTY_ACCESSORY_STATE; private String subState = ""; public static ControllerStatusBuilder newInstance() { @@ -97,7 +97,7 @@ public ControllerStatusBuilder setSpindleSpeed(Double spindleSpeed) { return this; } - public ControllerStatusBuilder setOverrides(ControllerStatus.OverridePercents overrides) { + public ControllerStatusBuilder setOverrides(OverridePercents overrides) { this.overrides = overrides; return this; } @@ -107,12 +107,12 @@ public ControllerStatusBuilder setWorkCoordinateOffset(Position workCoordinateOf return this; } - public ControllerStatusBuilder setPins(ControllerStatus.EnabledPins pins) { + public ControllerStatusBuilder setPins(EnabledPins pins) { this.pins = pins; return this; } - public ControllerStatusBuilder setStates(ControllerStatus.AccessoryStates states) { + public ControllerStatusBuilder setStates(AccessoryStates states) { this.states = states; return this; } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/listeners/DefaultControllerListener.java b/ugs-core/src/com/willwinder/universalgcodesender/listeners/DefaultControllerListener.java new file mode 100644 index 0000000000..8a5d34c118 --- /dev/null +++ b/ugs-core/src/com/willwinder/universalgcodesender/listeners/DefaultControllerListener.java @@ -0,0 +1,86 @@ +/* + Copyright 2024 Will Winder + + This file is part of Universal Gcode Sender (UGS). + + UGS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UGS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UGS. If not, see . + */ +package com.willwinder.universalgcodesender.listeners; + +import com.willwinder.universalgcodesender.model.Alarm; +import com.willwinder.universalgcodesender.model.Position; +import com.willwinder.universalgcodesender.types.GcodeCommand; + +/** + * A controller listener that has empty default implementations of the interface. + * Override only the things that you need. + * + * @author Joacim Breiler + */ +public class DefaultControllerListener implements ControllerListener { + @Override + public void streamCanceled() { + // Not implemented + } + + @Override + public void streamStarted() { + // Not implemented + } + + @Override + public void streamPaused() { + // Not implemented + } + + @Override + public void streamResumed() { + // Not implemented + } + + @Override + public void streamComplete() { + // Not implemented + } + + @Override + public void receivedAlarm(Alarm alarm) { + // Not implemented + } + + @Override + public void commandSkipped(GcodeCommand command) { + // Not implemented + } + + @Override + public void commandSent(GcodeCommand command) { + // Not implemented + } + + @Override + public void commandComplete(GcodeCommand command) { + // Not implemented + } + + @Override + public void probeCoordinates(Position p) { + // Not implemented + } + + @Override + public void statusStringListener(ControllerStatus status) { + // Not implemented + } +} diff --git a/ugs-core/src/com/willwinder/universalgcodesender/listeners/EnabledPins.java b/ugs-core/src/com/willwinder/universalgcodesender/listeners/EnabledPins.java new file mode 100644 index 0000000000..91e02cb592 --- /dev/null +++ b/ugs-core/src/com/willwinder/universalgcodesender/listeners/EnabledPins.java @@ -0,0 +1,24 @@ +/* + Copyright 2013-2024 Will Winder + + This file is part of Universal Gcode Sender (UGS). + + UGS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UGS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UGS. If not, see . + */ +package com.willwinder.universalgcodesender.listeners; + +public record EnabledPins(boolean x, boolean y, boolean z, boolean a, boolean b, boolean c, boolean probe, boolean door, + boolean hold, boolean softReset, boolean cycleStart) { + public static final EnabledPins EMPTY_PINS = new EnabledPinsBuilder().createEnabledPins(); +} diff --git a/ugs-core/src/com/willwinder/universalgcodesender/listeners/EnabledPinsBuilder.java b/ugs-core/src/com/willwinder/universalgcodesender/listeners/EnabledPinsBuilder.java new file mode 100644 index 0000000000..82d28a5046 --- /dev/null +++ b/ugs-core/src/com/willwinder/universalgcodesender/listeners/EnabledPinsBuilder.java @@ -0,0 +1,92 @@ +/* + Copyright 2013-2024 Will Winder + + This file is part of Universal Gcode Sender (UGS). + + UGS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UGS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UGS. If not, see . + */ +package com.willwinder.universalgcodesender.listeners; + +public class EnabledPinsBuilder { + private boolean x = false; + private boolean y = false; + private boolean z = false; + private boolean a = false; + private boolean b = false; + private boolean c = false; + private boolean probe = false; + private boolean door = false; + private boolean hold = false; + private boolean softReset = false; + private boolean cycleStart = false; + + public EnabledPinsBuilder setX(boolean x) { + this.x = x; + return this; + } + + public EnabledPinsBuilder setY(boolean y) { + this.y = y; + return this; + } + + public EnabledPinsBuilder setZ(boolean z) { + this.z = z; + return this; + } + + public EnabledPinsBuilder setA(boolean a) { + this.a = a; + return this; + } + + public EnabledPinsBuilder setB(boolean b) { + this.b = b; + return this; + } + + public EnabledPinsBuilder setC(boolean c) { + this.c = c; + return this; + } + + public EnabledPinsBuilder setProbe(boolean probe) { + this.probe = probe; + return this; + } + + public EnabledPinsBuilder setDoor(boolean door) { + this.door = door; + return this; + } + + public EnabledPinsBuilder setHold(boolean hold) { + this.hold = hold; + return this; + } + + public EnabledPinsBuilder setSoftReset(boolean softReset) { + this.softReset = softReset; + return this; + } + + public EnabledPinsBuilder setCycleStart(boolean cycleStart) { + this.cycleStart = cycleStart; + return this; + } + + public EnabledPins createEnabledPins() { + return new EnabledPins(x, y, z, a, b, c, probe, door, hold, softReset, cycleStart); + } +} \ No newline at end of file diff --git a/ugs-core/src/com/willwinder/universalgcodesender/listeners/OverridePercents.java b/ugs-core/src/com/willwinder/universalgcodesender/listeners/OverridePercents.java new file mode 100644 index 0000000000..217923ff7d --- /dev/null +++ b/ugs-core/src/com/willwinder/universalgcodesender/listeners/OverridePercents.java @@ -0,0 +1,32 @@ +/* + Copyright 2013-2024 Will Winder + + This file is part of Universal Gcode Sender (UGS). + + UGS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UGS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UGS. If not, see . + */ +package com.willwinder.universalgcodesender.listeners; + +/** + * Overrides given as percents where the value 100 is the normal speed to 100%. + * The value range is dependant on the controller but can normally be given as a + * value between 10 - 200 % + * + * @param feed the feed speed in percent + * @param rapid the rapid speed in percent + * @param spindle the spindle speed in percent + */ +public record OverridePercents(int feed, int rapid, int spindle) { + public static final OverridePercents EMTPY_OVERRIDE_PERCENTS = new OverridePercents(100, 100, 100); +} diff --git a/ugs-core/src/com/willwinder/universalgcodesender/listeners/OverrideType.java b/ugs-core/src/com/willwinder/universalgcodesender/listeners/OverrideType.java new file mode 100644 index 0000000000..1254335ade --- /dev/null +++ b/ugs-core/src/com/willwinder/universalgcodesender/listeners/OverrideType.java @@ -0,0 +1,40 @@ +/* + Copyright 2024 Will Winder + + This file is part of Universal Gcode Sender (UGS). + + UGS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UGS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UGS. If not, see . + */ +package com.willwinder.universalgcodesender.listeners; + +import com.willwinder.universalgcodesender.uielements.panels.OverrideLabels; + +public enum OverrideType { + SPINDLE_SPEED(OverrideLabels.SPINDLE_SHORT), + FEED_SPEED(OverrideLabels.FEED_SHORT), + MIST_TOGGLE(OverrideLabels.MIST), + SPINDLE_TOGGLE(OverrideLabels.SPINDLE_SHORT), + FLOOD_TOGGLE(OverrideLabels.FLOOD), + RAPID_SPEED(OverrideLabels.RAPID_SHORT); + + private final String label; + + OverrideType(String label) { + this.label = label; + } + + public String getLabel() { + return label; + } +} diff --git a/ugs-core/src/com/willwinder/universalgcodesender/model/BackendAPI.java b/ugs-core/src/com/willwinder/universalgcodesender/model/BackendAPI.java index aa4964b209..d2da0a0a6f 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/model/BackendAPI.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/model/BackendAPI.java @@ -166,10 +166,6 @@ public interface BackendAPI extends BackendAPIReadOnly { void issueSoftReset() throws Exception; void requestParserState() throws Exception; - // Programatically call an override. - void sendOverrideCommand(Overrides override) throws Exception; - - // Shouldn't be needed often. IController getController(); /** diff --git a/ugs-core/src/com/willwinder/universalgcodesender/model/GUIBackend.java b/ugs-core/src/com/willwinder/universalgcodesender/model/GUIBackend.java index 5c597327d4..2f5d250297 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/model/GUIBackend.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/model/GUIBackend.java @@ -794,9 +794,4 @@ private void initializeProcessedLines(boolean forceReprocess, File startFile, Gc logger.info("Took " + (end - start) + "ms to preprocess"); } } - - @Override - public void sendOverrideCommand(Overrides override) throws Exception { - this.controller.sendOverrideCommand(override); - } } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/model/Overrides.java b/ugs-core/src/com/willwinder/universalgcodesender/model/Overrides.java index 643d6a7312..afd5d58a6d 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/model/Overrides.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/model/Overrides.java @@ -19,25 +19,32 @@ This file is part of Universal Gcode Sender (UGS). package com.willwinder.universalgcodesender.model; /** - * * @author wwinder */ public enum Overrides { - //CMD_DEBUG_REPORT, // 0x85 // Only when DEBUG enabled, sends debug report in '{}' braces. - CMD_FEED_OVR_RESET, // 0x90 // Restores feed override value to 100%. - CMD_FEED_OVR_COARSE_PLUS, // 0x91 - CMD_FEED_OVR_COARSE_MINUS, // 0x92 - CMD_FEED_OVR_FINE_PLUS , // 0x93 - CMD_FEED_OVR_FINE_MINUS , // 0x94 - CMD_RAPID_OVR_RESET, // 0x95 // Restores rapid override value to 100%. - CMD_RAPID_OVR_MEDIUM, // 0x96 - CMD_RAPID_OVR_LOW, // 0x97 - CMD_SPINDLE_OVR_RESET, // 0x99 // Restores spindle override value to 100%. - CMD_SPINDLE_OVR_COARSE_PLUS, // 0x9A - CMD_SPINDLE_OVR_COARSE_MINUS, // 0x9B - CMD_SPINDLE_OVR_FINE_PLUS, // 0x9C - CMD_SPINDLE_OVR_FINE_MINUS, // 0x9D - CMD_TOGGLE_SPINDLE, // 0x9E - CMD_TOGGLE_FLOOD_COOLANT, // 0xA0 - CMD_TOGGLE_MIST_COOLANT, // 0xA1 + /** + * Restores feed override value to 100%. + */ + CMD_FEED_OVR_RESET, + CMD_FEED_OVR_COARSE_PLUS, + CMD_FEED_OVR_COARSE_MINUS, + CMD_FEED_OVR_FINE_PLUS, + CMD_FEED_OVR_FINE_MINUS, + /** + * Restores rapid override value to 100%. + */ + CMD_RAPID_OVR_RESET, + CMD_RAPID_OVR_MEDIUM, + CMD_RAPID_OVR_LOW, + /** + * Restores spindle override value to 100%. + */ + CMD_SPINDLE_OVR_RESET, + CMD_SPINDLE_OVR_COARSE_PLUS, + CMD_SPINDLE_OVR_COARSE_MINUS, + CMD_SPINDLE_OVR_FINE_PLUS, + CMD_SPINDLE_OVR_FINE_MINUS, + CMD_TOGGLE_SPINDLE, + CMD_TOGGLE_FLOOD_COOLANT, + CMD_TOGGLE_MIST_COOLANT; } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/model/UGSEventDispatcher.java b/ugs-core/src/com/willwinder/universalgcodesender/model/UGSEventDispatcher.java index f422dab060..74b8e88165 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/model/UGSEventDispatcher.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/model/UGSEventDispatcher.java @@ -57,7 +57,7 @@ public class UGSEventDispatcher implements ControllerListener, IFirmwareSettings /** * A cached instance of the controller status for preventing duplicate status events to be dispatched */ - private ControllerStatus controllerStatus = new ControllerStatus(); + private ControllerStatus controllerStatus = ControllerStatus.EMPTY_CONTROLLER_STATUS; public void sendUGSEvent(UGSEvent event) { LOGGER.log(Level.FINEST, "Sending event {0}.", event.getClass().getSimpleName()); @@ -73,14 +73,14 @@ public void sendUGSEvent(UGSEvent event) { public void addListener(UGSEventListener listener) { if (!listeners.contains(listener)) { - LOGGER.log(Level.INFO, "Adding UGSEvent listener: {0}", listener.getClass().getSimpleName()); + LOGGER.log(Level.FINE, "Adding UGSEvent listener: {0}", listener.getClass().getSimpleName()); listeners.add(listener); } } public void removeListener(UGSEventListener listener) { if (listeners.contains(listener)) { - LOGGER.log(Level.INFO, "Removing UGSEvent listener: {0}", listener.getClass().getSimpleName()); + LOGGER.log(Level.FINE, "Removing UGSEvent listener: {0}", listener.getClass().getSimpleName()); listeners.remove(listener); } } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/uielements/panels/OverrideLabels.java b/ugs-core/src/com/willwinder/universalgcodesender/uielements/panels/OverrideLabels.java new file mode 100644 index 0000000000..fbca4151e5 --- /dev/null +++ b/ugs-core/src/com/willwinder/universalgcodesender/uielements/panels/OverrideLabels.java @@ -0,0 +1,40 @@ +/* + Copyright 2024 Will Winder + + This file is part of Universal Gcode Sender (UGS). + + UGS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UGS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UGS. If not, see . + */ +package com.willwinder.universalgcodesender.uielements.panels; + +import com.willwinder.universalgcodesender.i18n.Localization; + +public class OverrideLabels { + public static final String FEED_SHORT = Localization.getString("overrides.feed.short"); + public static final String SPINDLE_SHORT = Localization.getString("overrides.spindle.short"); + public static final String RAPID_SHORT = Localization.getString("overrides.rapid.short"); + public static final String TOGGLE_SHORT = Localization.getString("overrides.toggle.short"); + public static final String RESET_SPINDLE = Localization.getString("overrides.spindle.reset"); + public static final String RESET_FEED = Localization.getString("overrides.feed.reset"); + public static final String MINUS_COARSE = "--"; + public static final String MINUS_FINE = "-"; + public static final String PLUS_COARSE = "++"; + public static final String PLUS_FINE = "+"; + public static final String RAPID_LOW = Localization.getString("overrides.rapid.low"); + public static final String RAPID_MEDIUM = Localization.getString("overrides.rapid.medium"); + public static final String RAPID_FULL = Localization.getString("overrides.rapid.full"); + public static final String MIST = Localization.getString("overrides.mist"); + public static final String FLOOD = Localization.getString("overrides.flood"); + public static final String NOT_SUPPORTED = Localization.getString("overrides.not.supported"); +} diff --git a/ugs-core/src/com/willwinder/universalgcodesender/uielements/panels/OverridesPanel.java b/ugs-core/src/com/willwinder/universalgcodesender/uielements/panels/OverridesPanel.java index 809cc4e860..8718a9aaca 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/uielements/panels/OverridesPanel.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/uielements/panels/OverridesPanel.java @@ -18,23 +18,32 @@ This file is part of Universal Gcode Sender (UGS). */ package com.willwinder.universalgcodesender.uielements.panels; -import com.willwinder.universalgcodesender.i18n.Localization; +import com.willwinder.universalgcodesender.firmware.IOverrideManager; +import com.willwinder.universalgcodesender.listeners.ControllerState; import com.willwinder.universalgcodesender.listeners.ControllerStatus; -import com.willwinder.universalgcodesender.listeners.ControllerStatus.AccessoryStates; +import com.willwinder.universalgcodesender.listeners.OverrideType; import com.willwinder.universalgcodesender.listeners.UGSEventListener; import com.willwinder.universalgcodesender.model.BackendAPI; -import com.willwinder.universalgcodesender.model.Overrides; import com.willwinder.universalgcodesender.model.UGSEvent; import com.willwinder.universalgcodesender.model.events.ControllerStateEvent; import com.willwinder.universalgcodesender.model.events.ControllerStatusEvent; +import static com.willwinder.universalgcodesender.uielements.panels.OverrideLabels.TOGGLE_SHORT; +import com.willwinder.universalgcodesender.utils.ThreadHelper; import net.miginfocom.swing.MigLayout; -import javax.swing.*; -import java.awt.*; +import javax.swing.AbstractAction; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.JToggleButton; +import javax.swing.SwingConstants; import java.awt.event.ActionEvent; -import java.util.ArrayList; -import java.util.logging.Level; -import java.util.logging.Logger; +import java.util.Arrays; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * Send speed override commands to the backend. @@ -42,47 +51,14 @@ This file is part of Universal Gcode Sender (UGS). * @author wwinder */ public final class OverridesPanel extends JPanel implements UGSEventListener { - private final BackendAPI backend; - private final ArrayList components = new ArrayList<>(); + private final transient BackendAPI backend; + private final JPanel overridesControlsPanel = new JPanel(new MigLayout("fillx")); + private final JLabel notConnectedLabel = new JLabel("Not connected", SwingConstants.CENTER); + private final JLabel notSupportedLabel = new JLabel("" + OverrideLabels.NOT_SUPPORTED + "", SwingConstants.CENTER); + private final Map speedSliders = new ConcurrentHashMap<>(); + private final Map toggleButtons = new ConcurrentHashMap<>(); - private final JLabel feedSpeed = new JLabel("100%"); - private final JRadioButton feedRadio = new JRadioButton(FEED_SHORT); - - private final JLabel spindleSpeed = new JLabel("100%"); - private final JRadioButton spindleRadio = new JRadioButton(SPINDLE_SHORT); - - private final JLabel rapidSpeed = new JLabel("100%"); - private final JRadioButton rapidRadio = new JRadioButton(RAPID_SHORT); - - private final JButton adjust1 = new JButton(""); - private final JButton adjust2 = new JButton(""); - private final JButton adjust3 = new JButton(""); - private final JButton adjust4 = new JButton(""); - private final JButton adjust5 = new JButton(""); - - private final JButton toggleSpindle = new JButton(SPINDLE_SHORT); - private final JButton toggleFloodCoolant = new JButton(FLOOD); - private final JButton toggleMistCoolant = new JButton(MIST); - - private final ArrayList rapidActions = new ArrayList<>(); - private final ArrayList spindleActions = new ArrayList<>(); - private final ArrayList feedActions = new ArrayList<>(); - - public static final String FEED_SHORT = Localization.getString("overrides.feed.short"); - public static final String SPINDLE_SHORT = Localization.getString("overrides.spindle.short"); - public static final String RAPID_SHORT = Localization.getString("overrides.rapid.short"); - public static final String TOGGLE_SHORT = Localization.getString("overrides.toggle.short"); - public static final String RESET_SPINDLE = Localization.getString("overrides.spindle.reset"); - public static final String RESET_FEED = Localization.getString("overrides.feed.reset"); - public static final String MINUS_COARSE = "--"; - public static final String MINUS_FINE = "-"; - public static final String PLUS_COARSE = "++"; - public static final String PLUS_FINE = "+"; - public static final String RAPID_LOW = Localization.getString("overrides.rapid.low"); - public static final String RAPID_MEDIUM = Localization.getString("overrides.rapid.medium"); - public static final String RAPID_FULL = Localization.getString("overrides.rapid.full"); - public static final String MIST = Localization.getString("overrides.mist"); - public static final String FLOOD = Localization.getString("overrides.flood"); + private boolean overridesPanelInitiated = false; public OverridesPanel(BackendAPI backend) { this.backend = backend; @@ -90,194 +66,123 @@ public OverridesPanel(BackendAPI backend) { backend.addUGSEventListener(this); } - initComponents(); + setLayout(new MigLayout("fillx, hidemode 3")); + add(overridesControlsPanel, "grow"); + add(notConnectedLabel, "spanx, growx, gaptop 16, wrap"); + add(notSupportedLabel, "spanx, growx, gaptop 16, wrap"); updateControls(); } public void updateControls() { - boolean enabled = backend.isConnected() && - backend.getController().getCapabilities().hasOverrides(); - - this.setEnabled(enabled); - for (Component c : components) { - c.setEnabled(enabled); + if (backend.getControllerState() == ControllerState.DISCONNECTED || backend.getControllerState() == ControllerState.CONNECTING) { + removeComponents(); + return; + } else if (!backend.getController().getCapabilities().hasOverrides() || (backend.getController().getOverrideManager().getSpeedTypes().isEmpty() && backend.getController().getOverrideManager().getToggleTypes().isEmpty())) { + showNotSupportedPanel(); + return; + } else if (!overridesPanelInitiated) { + initAndShowOverridesPanel(); } - if (enabled) { - radioSelected(); - } else { - toggleSpindle.setBackground(null); - toggleMistCoolant.setBackground(null); - toggleFloodCoolant.setBackground(null); - } + setEnabled(backend.getController().getOverrideManager().isAvailable()); + } + + private void showNotSupportedPanel() { + notSupportedLabel.setVisible(true); + notConnectedLabel.setVisible(false); + overridesControlsPanel.setVisible(false); + revalidate(); + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + Arrays.stream(getComponents()).forEach(c -> c.setEnabled(enabled)); } @Override public void UGSEvent(UGSEvent evt) { if (evt instanceof ControllerStateEvent) { updateControls(); - } else if (evt instanceof ControllerStatusEvent) { - ControllerStatus status = ((ControllerStatusEvent) evt).getStatus(); + } else if (evt instanceof ControllerStatusEvent controllerStatusEvent) { + ControllerStatus status = controllerStatusEvent.getStatus(); if (status.getOverrides() != null) { - this.feedSpeed.setText(status.getOverrides().feed + "%"); - this.spindleSpeed.setText(status.getOverrides().spindle + "%"); - this.rapidSpeed.setText(status.getOverrides().rapid + "%"); - } - if (status.getAccessoryStates() != null) { - AccessoryStates states = status.getAccessoryStates(); - - toggleSpindle.setBackground((states.SpindleCW || states.SpindleCCW) ? Color.GREEN : Color.RED); - toggleFloodCoolant.setBackground(states.Flood ? Color.GREEN : Color.RED); - toggleMistCoolant.setBackground(states.Mist ? Color.GREEN : Color.RED); - - toggleSpindle.setOpaque(true); - toggleFloodCoolant.setOpaque(true); - toggleMistCoolant.setOpaque(true); + speedSliders.keySet().forEach(type -> speedSliders.get(type).setValue(backend.getController().getOverrideManager().getSpeedTargetValue(type))); + toggleButtons.keySet().forEach(type -> toggleButtons.get(type).setSelected(backend.getController().getOverrideManager().isToggled(type))); } } } - public void add(Component comp, String str) { - super.add(comp, str); - if (comp instanceof JButton || comp instanceof JRadioButton) - components.add(comp); - } - - public Component add(Component comp) { - Component ret = super.add(comp); - if (comp instanceof JButton || comp instanceof JRadioButton) - components.add(comp); - return ret; + private void removeComponents() { + overridesControlsPanel.removeAll(); + speedSliders.clear(); + toggleButtons.clear(); + overridesControlsPanel.setVisible(false); + notConnectedLabel.setVisible(true); + notSupportedLabel.setVisible(false); + overridesPanelInitiated = false; + revalidate(); } - private void radioSelected() { - if (rapidRadio.isSelected()) { - adjust2.setEnabled(false); - adjust5.setEnabled(false); - - adjust1.setText(RAPID_LOW); - adjust2.setText(""); - adjust3.setText(RAPID_MEDIUM); - adjust4.setText(RAPID_FULL); - adjust5.setText(""); - - adjust1.setAction(rapidActions.get(0)); - adjust3.setAction(rapidActions.get(1)); - adjust4.setAction(rapidActions.get(2)); - } else { - adjust2.setEnabled(true); - adjust5.setEnabled(true); - - adjust1.setText(MINUS_COARSE); - adjust2.setText(MINUS_FINE); - adjust4.setText(PLUS_FINE); - adjust5.setText(PLUS_COARSE); + private void initAndShowOverridesPanel() { + overridesPanelInitiated = true; + overridesControlsPanel.setVisible(true); + notSupportedLabel.setVisible(false); + notConnectedLabel.setVisible(false); + + overridesControlsPanel.removeAll(); + IOverrideManager overrideManager = backend.getController().getOverrideManager(); + if (!overrideManager.getToggleTypes().isEmpty()) { + overridesControlsPanel.add(new JLabel(TOGGLE_SHORT), "spanx, grow, wrap, gaptop 10"); + overrideManager.getToggleTypes().forEach(this::createAndAddToggleButtons); + } - ArrayList actions = null; - if (feedRadio.isSelected()) { - adjust3.setText(RESET_FEED); - actions = feedActions; - } else if (spindleRadio.isSelected()) { - adjust3.setText(RESET_SPINDLE); - actions = spindleActions; - } + overrideManager.getSpeedTypes().forEach(this::createAndAddSpeedSlider); + revalidate(); + } - if (actions != null) { - adjust1.setAction(actions.get(0)); - adjust2.setAction(actions.get(1)); - adjust3.setAction(actions.get(2)); - adjust4.setAction(actions.get(3)); - adjust5.setAction(actions.get(4)); + private void createAndAddToggleButtons(OverrideType overrideType) { + IOverrideManager overrideManager = backend.getController().getOverrideManager(); + JToggleButton toggleSpindle = new JToggleButton(overrideType.name()); + toggleSpindle.setAction(new AbstractAction(overrideType.getLabel()) { + @Override + public void actionPerformed(ActionEvent e) { + overrideManager.toggle(overrideType); + toggleSpindle.setSelected(!overrideManager.isToggled(overrideType)); + ThreadHelper.invokeLater(() -> toggleSpindle.setSelected(overrideManager.isToggled(overrideType)), 200); } - } + }); + overridesControlsPanel.add(toggleSpindle, "growx"); + toggleButtons.put(overrideType, toggleSpindle); } - private void initComponents() { - rapidActions.add(new RealTimeAction(RAPID_LOW, Overrides.CMD_RAPID_OVR_LOW, backend)); - rapidActions.add(new RealTimeAction(RAPID_MEDIUM, Overrides.CMD_RAPID_OVR_MEDIUM, backend)); - rapidActions.add(new RealTimeAction(RAPID_FULL, Overrides.CMD_RAPID_OVR_RESET, backend)); - - spindleActions.add(new RealTimeAction(MINUS_COARSE, Overrides.CMD_SPINDLE_OVR_COARSE_MINUS, backend)); - spindleActions.add(new RealTimeAction(MINUS_FINE, Overrides.CMD_SPINDLE_OVR_FINE_MINUS, backend)); - spindleActions.add(new RealTimeAction(RESET_SPINDLE, Overrides.CMD_SPINDLE_OVR_RESET, backend)); - spindleActions.add(new RealTimeAction(PLUS_FINE, Overrides.CMD_SPINDLE_OVR_FINE_PLUS, backend)); - spindleActions.add(new RealTimeAction(PLUS_COARSE, Overrides.CMD_SPINDLE_OVR_COARSE_PLUS, backend)); - - feedActions.add(new RealTimeAction(MINUS_COARSE, Overrides.CMD_FEED_OVR_COARSE_MINUS, backend)); - feedActions.add(new RealTimeAction(MINUS_FINE, Overrides.CMD_FEED_OVR_FINE_MINUS, backend)); - feedActions.add(new RealTimeAction(RESET_FEED, Overrides.CMD_FEED_OVR_RESET, backend)); - feedActions.add(new RealTimeAction(PLUS_FINE, Overrides.CMD_FEED_OVR_FINE_PLUS, backend)); - feedActions.add(new RealTimeAction(PLUS_COARSE, Overrides.CMD_FEED_OVR_COARSE_PLUS, backend)); + private void createAndAddSpeedSlider(OverrideType type) { + IOverrideManager overrideManager = backend.getController().getOverrideManager(); - adjust1.setEnabled(false); - adjust2.setEnabled(false); - adjust3.setEnabled(false); - adjust4.setEnabled(false); - adjust5.setEnabled(false); + JSlider speedSlider = new JSlider(0, overrideManager.getSpeedMax(type), overrideManager.getSpeedDefault(type)); + speedSlider.setMinorTickSpacing(0); + speedSlider.setMajorTickSpacing(10); + speedSliders.put(type, speedSlider); - ButtonGroup group = new ButtonGroup(); - group.add(feedRadio); - group.add(spindleRadio); - group.add(rapidRadio); - - // Initialize callbacks - // Radio buttons - feedRadio.addActionListener((ActionEvent ae) -> radioSelected()); - spindleRadio.addActionListener((ActionEvent ae) -> radioSelected()); - rapidRadio.addActionListener((ActionEvent ae) -> radioSelected()); - // Toggle actions - toggleSpindle.setAction(new RealTimeAction("spindle", Overrides.CMD_TOGGLE_SPINDLE, backend)); - toggleSpindle.setBackground(Color.RED); - toggleFloodCoolant.setAction(new RealTimeAction("flood", Overrides.CMD_TOGGLE_FLOOD_COOLANT, backend)); - toggleFloodCoolant.setBackground(Color.RED); - toggleMistCoolant.setAction(new RealTimeAction("mist", Overrides.CMD_TOGGLE_MIST_COOLANT, backend)); - toggleMistCoolant.setBackground(Color.RED); - - // Layout components - this.setLayout(new MigLayout("wrap 4")); - - this.add(feedRadio); - this.add(spindleRadio); - this.add(rapidRadio, "wrap"); - - this.add(new JLabel(FEED_SHORT + ":")); - this.add(feedSpeed); - this.add(adjust1); - this.add(adjust2); - - this.add(new JLabel(SPINDLE_SHORT + ":")); - this.add(spindleSpeed); - this.add(adjust3, "span 2"); - - this.add(new JLabel("Rapid:")); - this.add(rapidSpeed); - this.add(adjust4); - this.add(adjust5, "wrap"); + Dictionary dict = new Hashtable<>(); + for (int i = 0; i <= overrideManager.getSpeedMax(type); i += 100) { + dict.put(i, new JLabel(i + "%")); + } - this.add(new JLabel(TOGGLE_SHORT + ":")); - this.add(toggleSpindle); - this.add(toggleFloodCoolant); - this.add(toggleMistCoolant); + speedSlider.setLabelTable(dict); + speedSlider.setPaintLabels(true); + speedSlider.setPaintTicks(true); + speedSlider.setPaintTrack(true); + speedSlider.addChangeListener(l -> updateSpeed(type, speedSlider)); + overridesControlsPanel.add(new JLabel(type.getLabel()), "spanx, grow, newline, wrap, gaptop 10"); + overridesControlsPanel.add(speedSlider, "spanx, grow, wrap"); } - private static class RealTimeAction extends AbstractAction { - private final Overrides command; - private final BackendAPI backend; - public RealTimeAction(String name, Overrides override, BackendAPI backend) { - this.putValue(Action.NAME, name); - this.command = override; - this.backend = backend; + private void updateSpeed(OverrideType type, JSlider slider) { + if (backend.getController() == null || !backend.isConnected()) { + return; } - @Override - public void actionPerformed(ActionEvent e) { - if (isEnabled()) { - try { - backend.sendOverrideCommand(command); - } catch (Exception ex) { - Logger.getLogger(OverridesPanel.class.getName()).log(Level.SEVERE, null, ex); - } - } - } + backend.getController().getOverrideManager().setSpeedTarget(type, slider.getValue()); } } diff --git a/ugs-core/src/resources/MessagesBundle_en_US.properties b/ugs-core/src/resources/MessagesBundle_en_US.properties index ac5308454b..e56f274826 100644 --- a/ugs-core/src/resources/MessagesBundle_en_US.properties +++ b/ugs-core/src/resources/MessagesBundle_en_US.properties @@ -457,6 +457,7 @@ overrides.rapid.full = Full overrides.mist = Mist Coolant overrides.flood = Flood Coolant overrides.toggle.short = Toggle +overrides.not.supported = Overrides are not supported for this controller restart = Restart is needed platform.window.restart.changed.settings = Click here to restart the platform and apply your settings. mainWindow.ui.connect = Connect @@ -704,6 +705,8 @@ platform.plugin.joystick.paddle3 = Paddle 3 platform.plugin.joystick.paddle4 = Paddle 4 platform.plugin.joystick.touchpad = Touchpad platform.plugin.joystick.action.continuousJogging = Analog jog +platform.plugin.joystick.action.analogFeed = Analog feed override +platform.plugin.joystick.action.analogSpindle = Analog spindle override platform.plugin.joystick.reverseAxis = Reverse platform.plugin.joystick.axisThreshold = Zero threshold (%) platform.plugin.cloud.s3Id = AWS Access Key ID diff --git a/ugs-core/test/com/willwinder/universalgcodesender/GrblUtilsTest.java b/ugs-core/test/com/willwinder/universalgcodesender/GrblUtilsTest.java index db910a8d88..291648a025 100644 --- a/ugs-core/test/com/willwinder/universalgcodesender/GrblUtilsTest.java +++ b/ugs-core/test/com/willwinder/universalgcodesender/GrblUtilsTest.java @@ -1,5 +1,5 @@ /* - Copyright 2013-2023 Will Winder + Copyright 2013-2024 Will Winder This file is part of Universal Gcode Sender (UGS). @@ -341,15 +341,15 @@ public void getStatusFromStatusStringV1ShouldReturnPinStatus() { String status = ""; ControllerStatus controllerStatus = GrblUtils.getStatusFromStatusStringV1(null, status, MM); - assertFalse(controllerStatus.getEnabledPins().CycleStart); + assertFalse(controllerStatus.getEnabledPins().cycleStart()); status = ""; controllerStatus = GrblUtils.getStatusFromStatusStringV1(null, status, MM); - assertTrue(controllerStatus.getEnabledPins().CycleStart); + assertTrue(controllerStatus.getEnabledPins().cycleStart()); status = ""; controllerStatus = GrblUtils.getStatusFromStatusStringV1(controllerStatus, status, MM); - assertFalse(controllerStatus.getEnabledPins().CycleStart); + assertFalse(controllerStatus.getEnabledPins().cycleStart()); } @Test @@ -359,23 +359,23 @@ public void getStatusFromStatusStringV1ShouldReturnAccessoryStates() { String status = ""; ControllerStatus controllerStatus = GrblUtils.getStatusFromStatusStringV1(null, status, MM); - assertFalse(controllerStatus.getAccessoryStates().Flood); + assertFalse(controllerStatus.getAccessoryStates().flood()); status = ""; controllerStatus = GrblUtils.getStatusFromStatusStringV1(controllerStatus, status, MM); - assertTrue(controllerStatus.getAccessoryStates().Flood); + assertTrue(controllerStatus.getAccessoryStates().flood()); status = ""; controllerStatus = GrblUtils.getStatusFromStatusStringV1(controllerStatus, status, MM); - assertTrue("The accessory states should be retained even if it isn't included in the report", controllerStatus.getAccessoryStates().Flood); + assertTrue("The accessory states should be retained even if it isn't included in the report", controllerStatus.getAccessoryStates().flood()); status = ""; controllerStatus = GrblUtils.getStatusFromStatusStringV1(controllerStatus, status, MM); - assertFalse("The accessory states should be set to disabled if not included in overrides report", controllerStatus.getAccessoryStates().Flood); + assertFalse("The accessory states should be set to disabled if not included in overrides report", controllerStatus.getAccessoryStates().flood()); status = ""; controllerStatus = GrblUtils.getStatusFromStatusStringV1(controllerStatus, status, MM); - assertTrue("The accessory state should be set even if not a overrides report", controllerStatus.getAccessoryStates().Flood); + assertTrue("The accessory state should be set even if not a overrides report", controllerStatus.getAccessoryStates().flood()); } @Test @@ -502,26 +502,25 @@ public void getStatusFromStringVersion1WithCompleteStatusString() { assertEquals(new Position(4.4, 5.5, 6.6, MM), controllerStatus.getWorkCoord()); assertEquals(new Position(7.7, 8.8, 9.9, MM), controllerStatus.getWorkCoordinateOffset()); - assertEquals(1, controllerStatus.getOverrides().feed); - assertEquals(2, controllerStatus.getOverrides().rapid); - assertEquals(3, controllerStatus.getOverrides().spindle); + assertEquals(1, controllerStatus.getOverrides().feed()); + assertEquals(2, controllerStatus.getOverrides().rapid()); + assertEquals(3, controllerStatus.getOverrides().spindle()); assertEquals(Double.valueOf(12345.7), controllerStatus.getFeedSpeed()); assertEquals(Double.valueOf(65432.1), controllerStatus.getSpindleSpeed()); - assertTrue(controllerStatus.getEnabledPins().CycleStart); - assertTrue(controllerStatus.getEnabledPins().Door); - assertTrue(controllerStatus.getEnabledPins().Hold); - assertTrue(controllerStatus.getEnabledPins().SoftReset); - assertTrue(controllerStatus.getEnabledPins().Probe); - assertTrue(controllerStatus.getEnabledPins().X); - assertTrue(controllerStatus.getEnabledPins().Y); - assertTrue(controllerStatus.getEnabledPins().Z); + assertTrue(controllerStatus.getEnabledPins().cycleStart()); + assertTrue(controllerStatus.getEnabledPins().door()); + assertTrue(controllerStatus.getEnabledPins().hold()); + assertTrue(controllerStatus.getEnabledPins().softReset()); + assertTrue(controllerStatus.getEnabledPins().probe()); + assertTrue(controllerStatus.getEnabledPins().x()); + assertTrue(controllerStatus.getEnabledPins().y()); + assertTrue(controllerStatus.getEnabledPins().z()); - assertTrue(controllerStatus.getAccessoryStates().Flood); - assertTrue(controllerStatus.getAccessoryStates().Mist); - assertTrue(controllerStatus.getAccessoryStates().SpindleCCW); - assertTrue(controllerStatus.getAccessoryStates().SpindleCW); + assertTrue(controllerStatus.getAccessoryStates().flood()); + assertTrue(controllerStatus.getAccessoryStates().mist()); + assertTrue(controllerStatus.getAccessoryStates().spindleCW()); } @Test @@ -605,14 +604,14 @@ public void getStatusFromStringVersion1WithoutPinsStatusString() { ControllerStatus controllerStatus = GrblUtils.getStatusFromStatusString(null, status, version, MM); - assertFalse(controllerStatus.getEnabledPins().CycleStart); - assertFalse(controllerStatus.getEnabledPins().Door); - assertFalse(controllerStatus.getEnabledPins().Hold); - assertFalse(controllerStatus.getEnabledPins().SoftReset); - assertFalse(controllerStatus.getEnabledPins().Probe); - assertFalse(controllerStatus.getEnabledPins().X); - assertFalse(controllerStatus.getEnabledPins().Y); - assertFalse(controllerStatus.getEnabledPins().Z); + assertFalse(controllerStatus.getEnabledPins().cycleStart()); + assertFalse(controllerStatus.getEnabledPins().door()); + assertFalse(controllerStatus.getEnabledPins().hold()); + assertFalse(controllerStatus.getEnabledPins().softReset()); + assertFalse(controllerStatus.getEnabledPins().probe()); + assertFalse(controllerStatus.getEnabledPins().x()); + assertFalse(controllerStatus.getEnabledPins().y()); + assertFalse(controllerStatus.getEnabledPins().z()); } @Test @@ -623,10 +622,9 @@ public void getStatusFromStringVersion1WithoutAccessoryStatusString() { ControllerStatus controllerStatus = GrblUtils.getStatusFromStatusString(null, status, version, MM); - assertFalse(controllerStatus.getAccessoryStates().Flood); - assertFalse(controllerStatus.getAccessoryStates().Mist); - assertFalse(controllerStatus.getAccessoryStates().SpindleCCW); - assertFalse(controllerStatus.getAccessoryStates().SpindleCW); + assertFalse(controllerStatus.getAccessoryStates().flood()); + assertFalse(controllerStatus.getAccessoryStates().mist()); + assertTrue(controllerStatus.getAccessoryStates().spindleCW()); } @Test @@ -638,10 +636,9 @@ public void getStatusFromStatusStringShouldBeAbleToProcessEmptyAccessoryState() assertEquals(ControllerState.IDLE, controllerStatus.getState()); assertNotNull(controllerStatus.getAccessoryStates()); - assertFalse(controllerStatus.getAccessoryStates().Flood); - assertFalse(controllerStatus.getAccessoryStates().Mist); - assertFalse(controllerStatus.getAccessoryStates().SpindleCCW); - assertFalse(controllerStatus.getAccessoryStates().SpindleCW); + assertFalse(controllerStatus.getAccessoryStates().flood()); + assertFalse(controllerStatus.getAccessoryStates().mist()); + assertFalse(controllerStatus.getAccessoryStates().spindleCW()); } @Test @@ -658,9 +655,9 @@ public void get6AxesCoordinates() { assertEquals(new Position(7.7, 8.8, 9.9, 10.10, 11.11, 12.12, MM), controllerStatus.getWorkCoord()); assertEquals(new Position(13.13, 14.14, 15.15, 16.16, 17.17, 18.18, MM), controllerStatus.getWorkCoordinateOffset()); - assertTrue(controllerStatus.getEnabledPins().A); - assertTrue(controllerStatus.getEnabledPins().B); - assertTrue(controllerStatus.getEnabledPins().C); + assertTrue(controllerStatus.getEnabledPins().a()); + assertTrue(controllerStatus.getEnabledPins().b()); + assertTrue(controllerStatus.getEnabledPins().c()); } @Test @@ -676,9 +673,9 @@ public void get5AxesCoordinates() { assertEquals(new Position(1.1, 2.2, 3.3, 4.4, 5.5, Double.NaN, MM), controllerStatus.getMachineCoord()); assertEquals(new Position(7.7, 8.8, 9.9, 10.10, 11.11, Double.NaN, MM), controllerStatus.getWorkCoord()); assertEquals(new Position(13.13, 14.14, 15.15, 16.16, 17.17, Double.NaN, MM), controllerStatus.getWorkCoordinateOffset()); - assertTrue(controllerStatus.getEnabledPins().A); - assertTrue(controllerStatus.getEnabledPins().B); - assertFalse(controllerStatus.getEnabledPins().C); + assertTrue(controllerStatus.getEnabledPins().a()); + assertTrue(controllerStatus.getEnabledPins().b()); + assertFalse(controllerStatus.getEnabledPins().c()); } @Test @@ -695,9 +692,9 @@ public void get4AxesCoordinates() { assertEquals(new Position(7.7, 8.8, 9.9, 10.10, Double.NaN, Double.NaN, MM), controllerStatus.getWorkCoord()); assertEquals(new Position(13.13, 14.14, 15.15, 16.16, Double.NaN, Double.NaN, MM), controllerStatus.getWorkCoordinateOffset()); - assertTrue(controllerStatus.getEnabledPins().A); - assertFalse(controllerStatus.getEnabledPins().B); - assertFalse(controllerStatus.getEnabledPins().C); + assertTrue(controllerStatus.getEnabledPins().a()); + assertFalse(controllerStatus.getEnabledPins().b()); + assertFalse(controllerStatus.getEnabledPins().c()); } @Test diff --git a/ugs-core/test/com/willwinder/universalgcodesender/TinyGUtilsTest.java b/ugs-core/test/com/willwinder/universalgcodesender/TinyGUtilsTest.java index 0fb2edaccd..d1b957200d 100644 --- a/ugs-core/test/com/willwinder/universalgcodesender/TinyGUtilsTest.java +++ b/ugs-core/test/com/willwinder/universalgcodesender/TinyGUtilsTest.java @@ -24,16 +24,20 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.gcode.util.Code; import com.willwinder.universalgcodesender.listeners.ControllerState; import com.willwinder.universalgcodesender.listeners.ControllerStatus; -import com.willwinder.universalgcodesender.model.*; +import com.willwinder.universalgcodesender.listeners.OverridePercents; +import com.willwinder.universalgcodesender.model.Axis; +import com.willwinder.universalgcodesender.model.Overrides; +import com.willwinder.universalgcodesender.model.PartialPosition; +import com.willwinder.universalgcodesender.model.Position; +import com.willwinder.universalgcodesender.model.UnitUtils; import com.willwinder.universalgcodesender.types.GcodeCommand; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import org.junit.Test; import java.util.List; import java.util.Optional; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - /** * Tests for the TinyGUtils class * @@ -204,7 +208,7 @@ public void convertStatusReportShouldHandleArcDistanceMode() { @Test public void createOverrideCommandForFeedOverride() { - ControllerStatus.OverridePercents overridePercents = new ControllerStatus.OverridePercents(100, 150, 175); + OverridePercents overridePercents = new OverridePercents(100, 150, 175); Optional overrideCommand = TinyGUtils.createOverrideCommand(new DefaultCommandCreator(), overridePercents, Overrides.CMD_FEED_OVR_COARSE_PLUS); assertEquals("{mfo:1.1}", overrideCommand.get().getCommandString()); @@ -220,7 +224,7 @@ public void createOverrideCommandForFeedOverride() { @Test public void createOverrideCommandForSpindleOverride() { - ControllerStatus.OverridePercents overridePercents = new ControllerStatus.OverridePercents(150, 175, 100); + OverridePercents overridePercents = new OverridePercents(150, 175, 100); Optional overrideCommand = TinyGUtils.createOverrideCommand(new DefaultCommandCreator(), overridePercents, Overrides.CMD_SPINDLE_OVR_COARSE_PLUS); assertEquals("{sso:1.1}", overrideCommand.get().getCommandString()); @@ -240,11 +244,11 @@ public void updateControllerStatusShouldHandleFeedOverrides() { JsonObject response = TinyGUtils.jsonToObject("{sr:{mfo:1.4}}"); ControllerStatus controllerStatus = TinyGUtils.updateControllerStatus(lastControllerStatus, response); - assertEquals(140, controllerStatus.getOverrides().feed); + assertEquals(140, controllerStatus.getOverrides().feed()); response = TinyGUtils.jsonToObject("{sr:{mfo:0.8}}"); controllerStatus = TinyGUtils.updateControllerStatus(lastControllerStatus, response); - assertEquals(80, controllerStatus.getOverrides().feed); + assertEquals(80, controllerStatus.getOverrides().feed()); } @Test @@ -253,10 +257,10 @@ public void updateControllerStatusShouldHandleSpindleverrides() { JsonObject response = TinyGUtils.jsonToObject("{sr:{sso:1.4}}"); ControllerStatus controllerStatus = TinyGUtils.updateControllerStatus(lastControllerStatus, response); - assertEquals(140, controllerStatus.getOverrides().spindle); + assertEquals(140, controllerStatus.getOverrides().spindle()); response = TinyGUtils.jsonToObject("{sr:{sso:0.8}}"); controllerStatus = TinyGUtils.updateControllerStatus(lastControllerStatus, response); - assertEquals(80, controllerStatus.getOverrides().spindle); + assertEquals(80, controllerStatus.getOverrides().spindle()); } } diff --git a/ugs-core/test/com/willwinder/universalgcodesender/UtilsTest.java b/ugs-core/test/com/willwinder/universalgcodesender/UtilsTest.java index c29e1663d9..14d11cb28f 100644 --- a/ugs-core/test/com/willwinder/universalgcodesender/UtilsTest.java +++ b/ugs-core/test/com/willwinder/universalgcodesender/UtilsTest.java @@ -86,4 +86,34 @@ public void testProcessFile() throws Exception { fail("The test case is a prototype."); */ } + + @Test + public void roundToNearestStepValueWithOnePercentIncrements() { + assertEquals("Should round to lowest value", 0.01f, Utils.roundToNearestStepValue(0.001f, 0.01f, 2.00f, 0.01f), 0.0001f); + assertEquals(0.01f, Utils.roundToNearestStepValue(0.014f, 0.01f, 2.00f, 0.01f), 0.0001f); + assertEquals(0.02f, Utils.roundToNearestStepValue(0.015f, 0.01f, 2.00f, 0.01f), 0.0001f); + assertEquals(1.00f, Utils.roundToNearestStepValue(1.001f, 0.01f, 2.00f, 0.01f), 0.0001f); + assertEquals(2.00f, Utils.roundToNearestStepValue(2.001f, 0.01f, 2.00f, 0.01f), 0.0001f); + assertEquals(2.00f, Utils.roundToNearestStepValue(1.995f, 0.01f, 2.00f, 0.01f), 0.0001f); + } + + @Test + public void roundToNearestStepValueWithOnePercentIncrementsAsIntegerValues() { + assertEquals("Should round to lowest value", 1, Utils.roundToNearestStepValue(0.1, 1, 200, 1), 0.01); + assertEquals(1d, Utils.roundToNearestStepValue(1.4, 1, 200, 1), 0.01); + assertEquals(2d, Utils.roundToNearestStepValue(1.5, 1, 200, 1), 0.01); + assertEquals(100d, Utils.roundToNearestStepValue(100.1, 1, 200, 1), 0.01); + assertEquals(200d, Utils.roundToNearestStepValue(200.1, 1, 200, 1), 0.01); + assertEquals(200d, Utils.roundToNearestStepValue(199.5, 1, 200, 1), 0.01); + } + + @Test + public void roundToNearestStepValueWith25PercentIncrements() { + assertEquals(0.25f, Utils.roundToNearestStepValue(0.001f, 0.25f, 1.00f, 0.25f), 0.0001f); + assertEquals(0.25f, Utils.roundToNearestStepValue(0.24f, 0.25f, 1.00f, 0.25f), 0.0001f); + assertEquals(0.25f, Utils.roundToNearestStepValue(0.30f, 0.25f, 1.00f, 0.25f), 0.0001f); + assertEquals(0.50f, Utils.roundToNearestStepValue(0.375f, 0.25f, 1.00f, 0.25f), 0.0001f); + assertEquals(0.75f, Utils.roundToNearestStepValue(0.76f, 0.25f, 1.00f, 0.25f), 0.0001f); + assertEquals(1f, Utils.roundToNearestStepValue(2f, 0.25f, 1.00f, 0.25f), 0.0001f); + } } diff --git a/ugs-core/test/com/willwinder/universalgcodesender/firmware/grbl/GrblOverrideManagerTest.java b/ugs-core/test/com/willwinder/universalgcodesender/firmware/grbl/GrblOverrideManagerTest.java new file mode 100644 index 0000000000..dc65a50d24 --- /dev/null +++ b/ugs-core/test/com/willwinder/universalgcodesender/firmware/grbl/GrblOverrideManagerTest.java @@ -0,0 +1,165 @@ +package com.willwinder.universalgcodesender.firmware.grbl; + +import com.willwinder.universalgcodesender.Capabilities; +import com.willwinder.universalgcodesender.CapabilitiesConstants; +import com.willwinder.universalgcodesender.IController; +import com.willwinder.universalgcodesender.communicator.ICommunicator; +import com.willwinder.universalgcodesender.listeners.ControllerState; +import com.willwinder.universalgcodesender.listeners.ControllerStatus; +import com.willwinder.universalgcodesender.listeners.ControllerStatusBuilder; +import com.willwinder.universalgcodesender.listeners.OverridePercents; +import com.willwinder.universalgcodesender.listeners.OverrideType; +import com.willwinder.universalgcodesender.model.Overrides; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; +import static org.mockito.ArgumentMatchers.anyByte; +import org.mockito.Mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +public class GrblOverrideManagerTest { + + @Mock + private IController controller; + + @Mock + private ICommunicator communicator; + private GrblOverrideManager overrideManager; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + overrideManager = new GrblOverrideManager(controller, communicator); + } + + @Test + public void isAvailableShouldReturnFalseIfControllerStatusIsNull() { + when(controller.getControllerStatus()).thenReturn(null); + assertFalse(overrideManager.isAvailable()); + } + + @Test + public void isAvailableShouldReturnFalseIfControllerCapabilitiesIsNull() { + when(controller.getControllerStatus()).thenReturn(null); + assertFalse(overrideManager.isAvailable()); + } + + @Test + public void isAvailableShouldReturnTrueIfControllerIsInHoldState() { + mockOverrideCapabilities(); + mockControllerStatus(ControllerState.HOLD); + + assertTrue(overrideManager.isAvailable()); + } + + @Test + public void isAvailableShouldReturnTrueIfControllerIsInIdleState() { + mockOverrideCapabilities(); + mockControllerStatus(ControllerState.IDLE); + + assertTrue(overrideManager.isAvailable()); + } + + @Test + public void isAvailableShouldReturnTrueIfControllerIsInRunState() { + mockOverrideCapabilities(); + mockControllerStatus(ControllerState.RUN); + + assertTrue(overrideManager.isAvailable()); + } + + @Test + public void sendOverrideCommandShouldSendCommandByteToCommunicator() throws Exception { + mockOverrideCapabilities(); + mockControllerStatus(ControllerState.RUN); + + overrideManager.sendOverrideCommand(Overrides.CMD_TOGGLE_SPINDLE); + + verify(communicator, times(1)).sendByteImmediately(anyByte()); + } + + @Test + public void getSpeedValuesShouldReturnBasedOnOverrideType() { + mockOverrideCapabilities(); + mockControllerStatus(ControllerState.RUN); + + assertEquals(100, overrideManager.getSpeedDefault(OverrideType.SPINDLE_SPEED)); + assertEquals(100, overrideManager.getSpeedDefault(OverrideType.FEED_SPEED)); + assertEquals(0, overrideManager.getSpeedDefault(OverrideType.RAPID_SPEED)); + assertEquals(0, overrideManager.getSpeedDefault(OverrideType.MIST_TOGGLE)); + + assertEquals(200, overrideManager.getSpeedMax(OverrideType.SPINDLE_SPEED)); + assertEquals(200, overrideManager.getSpeedMax(OverrideType.FEED_SPEED)); + assertEquals(0, overrideManager.getSpeedMax(OverrideType.RAPID_SPEED)); + assertEquals(0, overrideManager.getSpeedMax(OverrideType.MIST_TOGGLE)); + + assertEquals(10, overrideManager.getSpeedMin(OverrideType.SPINDLE_SPEED)); + assertEquals(10, overrideManager.getSpeedMin(OverrideType.FEED_SPEED)); + assertEquals(0, overrideManager.getSpeedMin(OverrideType.RAPID_SPEED)); + assertEquals(0, overrideManager.getSpeedMin(OverrideType.MIST_TOGGLE)); + + assertEquals(100, overrideManager.getSpeedTargetValue(OverrideType.SPINDLE_SPEED)); + assertEquals(100, overrideManager.getSpeedTargetValue(OverrideType.FEED_SPEED)); + assertEquals(0, overrideManager.getSpeedTargetValue(OverrideType.RAPID_SPEED)); + assertEquals(0, overrideManager.getSpeedTargetValue(OverrideType.MIST_TOGGLE)); + + assertEquals(1, overrideManager.getSpeedStep(OverrideType.SPINDLE_SPEED)); + assertEquals(1, overrideManager.getSpeedStep(OverrideType.FEED_SPEED)); + assertEquals(0, overrideManager.getSpeedStep(OverrideType.RAPID_SPEED)); + assertEquals(0, overrideManager.getSpeedStep(OverrideType.MIST_TOGGLE)); + + assertEquals(10, overrideManager.getSpeedMajorStep(OverrideType.SPINDLE_SPEED)); + assertEquals(10, overrideManager.getSpeedMajorStep(OverrideType.FEED_SPEED)); + + assertEquals(1, overrideManager.getSpeedMinorStep(OverrideType.SPINDLE_SPEED)); + assertEquals(1, overrideManager.getSpeedMinorStep(OverrideType.FEED_SPEED)); + + assertEquals(List.of(OverrideType.FEED_SPEED, OverrideType.SPINDLE_SPEED), overrideManager.getSpeedTypes()); + } + + @Test + public void setSpeedTargetShouldSendCommand() throws Exception { + mockControllerStatus(ControllerState.IDLE); + mockOverrideCapabilities(); + + overrideManager.setSpeedTarget(OverrideType.FEED_SPEED, 10); + verify(communicator, times(1)).sendByteImmediately(anyByte()); + assertFalse(overrideManager.hasSettled()); + + + ControllerStatus controllerStatus = ControllerStatusBuilder.newInstance().setState(ControllerState.RUN).setOverrides(new OverridePercents(10, 0, 0)).build(); + when(controller.getControllerStatus()).thenReturn(controllerStatus); + assertFalse(overrideManager.hasSettled()); + } + + @Test + public void hasSettledShouldReturnTrueWhenOverridePercentReachesTarget() { + mockControllerStatus(ControllerState.IDLE); + mockOverrideCapabilities(); + + overrideManager.setSpeedTarget(OverrideType.FEED_SPEED, 10); + ControllerStatus controllerStatus = ControllerStatusBuilder.newInstance().setState(ControllerState.RUN).setOverrides(new OverridePercents(10, 100, 100)).build(); + when(controller.getControllerStatus()).thenReturn(controllerStatus); + overrideManager.onControllerStatus(controllerStatus); + + assertTrue(overrideManager.hasSettled()); + } + + private void mockControllerStatus(ControllerState controllerState) { + ControllerStatus controllerStatus = ControllerStatusBuilder.newInstance().setOverrides(new OverridePercents(100, 100, 100)).setState(controllerState).build(); + when(controller.getControllerStatus()).thenReturn(controllerStatus); + } + + private void mockOverrideCapabilities() { + Capabilities capabilites = new Capabilities(); + capabilites.addCapability(CapabilitiesConstants.OVERRIDES); + when(controller.getCapabilities()).thenReturn(capabilites); + } +} \ No newline at end of file diff --git a/ugs-platform/ugs-platform-plugin-dro/src/main/java/com/willwinder/ugs/nbp/dro/panels/MachineStatusPanel.java b/ugs-platform/ugs-platform-plugin-dro/src/main/java/com/willwinder/ugs/nbp/dro/panels/MachineStatusPanel.java index ce7130ef60..f3af2f1d57 100644 --- a/ugs-platform/ugs-platform-plugin-dro/src/main/java/com/willwinder/ugs/nbp/dro/panels/MachineStatusPanel.java +++ b/ugs-platform/ugs-platform-plugin-dro/src/main/java/com/willwinder/ugs/nbp/dro/panels/MachineStatusPanel.java @@ -25,9 +25,13 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.i18n.Localization; import com.willwinder.universalgcodesender.listeners.ControllerState; import com.willwinder.universalgcodesender.listeners.ControllerStatus; -import com.willwinder.universalgcodesender.listeners.ControllerStatus.EnabledPins; +import com.willwinder.universalgcodesender.listeners.EnabledPins; import com.willwinder.universalgcodesender.listeners.UGSEventListener; -import com.willwinder.universalgcodesender.model.*; +import com.willwinder.universalgcodesender.model.Axis; +import com.willwinder.universalgcodesender.model.BackendAPI; +import com.willwinder.universalgcodesender.model.Position; +import com.willwinder.universalgcodesender.model.UGSEvent; +import com.willwinder.universalgcodesender.model.UnitUtils; import com.willwinder.universalgcodesender.model.UnitUtils.Units; import com.willwinder.universalgcodesender.model.events.ControllerStateEvent; import com.willwinder.universalgcodesender.model.events.ControllerStatusEvent; @@ -39,11 +43,19 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.utils.Settings; import net.miginfocom.swing.MigLayout; -import javax.swing.*; -import java.awt.*; +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.GraphicsEnvironment; import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumMap; import java.util.List; -import java.util.*; +import java.util.Map; import java.util.logging.Logger; import java.util.stream.Stream; @@ -53,19 +65,7 @@ This file is part of Universal Gcode Sender (UGS). public class MachineStatusPanel extends JPanel implements UGSEventListener, AxisPanelListener { private static final Logger LOGGER = Logger.getLogger(MachineStatusPanel.class.getName()); private static final int COMMON_RADIUS = 7; - - private final static String OFFLINE = Localization.getString("mainWindow.status.offline").toUpperCase(); - private final static String PIN_X = Localization.getString("machineStatus.pin.x").toUpperCase(); - private final static String PIN_Y = Localization.getString("machineStatus.pin.y").toUpperCase(); - private final static String PIN_Z = Localization.getString("machineStatus.pin.z").toUpperCase(); - private final static String PIN_A = Localization.getString("machineStatus.pin.a").toUpperCase(); - private final static String PIN_B = Localization.getString("machineStatus.pin.b").toUpperCase(); - private final static String PIN_C = Localization.getString("machineStatus.pin.c").toUpperCase(); - private final static String PIN_PROBE = Localization.getString("machineStatus.pin.probe").toUpperCase(); - private final static String PIN_DOOR = Localization.getString("machineStatus.pin.door").toUpperCase(); - private final static String PIN_HOLD = Localization.getString("machineStatus.pin.hold").toUpperCase(); - private final static String PIN_SOFT_RESET = Localization.getString("machineStatus.pin.softReset").toUpperCase(); - private final static String PIN_CYCLE_STARY = Localization.getString("machineStatus.pin.cycleStart").toUpperCase(); + public static final String PANEL_CONSTRAINTS = "growx"; private final RoundedPanel activeStatePanel = new RoundedPanel(COMMON_RADIUS); private final JLabel activeStateValueLabel = new JLabel(" "); @@ -78,13 +78,13 @@ public class MachineStatusPanel extends JPanel implements UGSEventListener, Axis private final RoundedPanel pinStatePanel = new RoundedPanel(COMMON_RADIUS); private final JLabel pinStatesLabel = new JLabel(); - private final FontManager fontManager = new FontManager(); + private final transient FontManager fontManager = new FontManager(); - private final BackendAPI backend; + private final transient BackendAPI backend; private final JPanel axisPanel = new JPanel(); private Units units; - private final Map axisPanels = new HashMap<>(); + private final Map axisPanels = new EnumMap<>(Axis.class); private final DecimalFormat decimalFormatter = new DecimalFormat("0.000"); @@ -126,19 +126,19 @@ private void initComponents() { setLayout(new MigLayout(debug + "fillx, wrap 1, inset 5", "grow")); activeStateValueLabel.setForeground(ThemeColors.VERY_DARK_GREY); - activeStateValueLabel.setText(OFFLINE); + activeStateValueLabel.setText(Translations.OFFLINE); activeStatePanel.setLayout(new MigLayout(debug + "fill, inset 0 5 0 5")); activeStatePanel.setBackground(Color.BLACK); activeStatePanel.setForeground(ThemeColors.VERY_DARK_GREY); activeStatePanel.add(activeStateValueLabel, "al center"); activeStateValueLabel.setBorder(BorderFactory.createEmptyBorder()); - add(activeStatePanel, "growx"); + add(activeStatePanel, PANEL_CONSTRAINTS); // Default to showing X, Y, Z axisPanel.setLayout(new MigLayout(debug + "fillx, wrap 1, hidemode 3, inset 0 0 0 0", "grow")); Stream.of(Axis.values()).forEach(this::initializeAxisPanel); - add(axisPanel, "growx"); + add(axisPanel, PANEL_CONSTRAINTS); JPanel speedPanel = new JPanel(new MigLayout(debug + "fillx, wrap 2, inset 0", "[al right][]")); speedPanel.setOpaque(false); @@ -148,7 +148,7 @@ private void initComponents() { JLabel spindleSpeedLabel = new JLabel(Localization.getString("overrides.spindle.short")); speedPanel.add(spindleSpeedLabel); speedPanel.add(spindleSpeedValue, "pad 2 0 0 0"); - add(speedPanel, "growx"); + add(speedPanel, PANEL_CONSTRAINTS); add(gStatesLabel, "align center"); @@ -171,12 +171,12 @@ private void initializeAxisPanel(Axis axis) { panel.setVisible(axis.isLinear()); panel.setEnabled(false); axisPanels.put(axis, panel); - axisPanel.add(panel, "growx"); + axisPanel.add(panel, PANEL_CONSTRAINTS); panel.addListener(this); } private void resetStatePinComponents() { - pinStatesLabel.setText(new String()); + pinStatesLabel.setText(""); pinStatesLabel.setForeground(ThemeColors.GREY); pinStatePanel.setForeground(ThemeColors.GREY); } @@ -187,7 +187,7 @@ private void setAllCaps(JLabel... labels) { @Override public void setEnabled(boolean enabled) { - + // Disable this functionality as the styling will get messed up otherwise } private void setUnits(Units u) { @@ -292,27 +292,29 @@ private void onControllerStatusReceived(ControllerStatus status) { } private void updatePinStates(ControllerStatus status) { - if (status.getEnabledPins() != null) { - EnabledPins ep = status.getEnabledPins(); - - List enabled = new ArrayList<>(); - if (ep.X) enabled.add(PIN_X); - if (ep.Y) enabled.add(PIN_Y); - if (ep.Z) enabled.add(PIN_Z); - if (ep.A) enabled.add(PIN_A); - if (ep.B) enabled.add(PIN_B); - if (ep.C) enabled.add(PIN_C); - if (ep.Probe) enabled.add(PIN_PROBE); - if (ep.Door) enabled.add(PIN_DOOR); - if (ep.Hold) enabled.add(PIN_HOLD); - if (ep.SoftReset) enabled.add(PIN_SOFT_RESET); - if (ep.CycleStart) enabled.add(PIN_CYCLE_STARY); - - if (!enabled.isEmpty()) { - pinStatesLabel.setText(String.join(" ", enabled)); - pinStatesLabel.setForeground(ThemeColors.RED); - pinStatePanel.setForeground(ThemeColors.RED); - } + if (status.getEnabledPins() == null) { + return; + } + + EnabledPins ep = status.getEnabledPins(); + + List enabled = new ArrayList<>(); + if (ep.x()) enabled.add(Translations.PIN_X); + if (ep.y()) enabled.add(Translations.PIN_Y); + if (ep.z()) enabled.add(Translations.PIN_Z); + if (ep.a()) enabled.add(Translations.PIN_A); + if (ep.b()) enabled.add(Translations.PIN_B); + if (ep.c()) enabled.add(Translations.PIN_C); + if (ep.probe()) enabled.add(Translations.PIN_PROBE); + if (ep.door()) enabled.add(Translations.PIN_DOOR); + if (ep.hold()) enabled.add(Translations.PIN_HOLD); + if (ep.softReset()) enabled.add(Translations.PIN_SOFT_RESET); + if (ep.cycleStart()) enabled.add(Translations.PIN_CYCLE_STARY); + + if (!enabled.isEmpty()) { + pinStatesLabel.setText(String.join(" ", enabled)); + pinStatesLabel.setForeground(ThemeColors.RED); + pinStatePanel.setForeground(ThemeColors.RED); } } @@ -338,7 +340,7 @@ public void onWorkPositionClick(JComponent component, Axis axis) { String text = decimalFormatter.format(backend.getWorkPosition().getPositionIn(units).get(axis)); PopupEditor popupEditor = new PopupEditor(component, "Set " + axis + " work position", text); popupEditor.setVisible(true); - popupEditor.addPopupListener((value) -> { + popupEditor.addPopupListener(value -> { try { backend.setWorkPositionUsingExpression(axis, value); } catch (Exception e) { diff --git a/ugs-platform/ugs-platform-plugin-dro/src/main/java/com/willwinder/ugs/nbp/dro/panels/Translations.java b/ugs-platform/ugs-platform-plugin-dro/src/main/java/com/willwinder/ugs/nbp/dro/panels/Translations.java new file mode 100644 index 0000000000..f2e4112921 --- /dev/null +++ b/ugs-platform/ugs-platform-plugin-dro/src/main/java/com/willwinder/ugs/nbp/dro/panels/Translations.java @@ -0,0 +1,20 @@ +package com.willwinder.ugs.nbp.dro.panels; + +import com.willwinder.universalgcodesender.i18n.Localization; + +class Translations { + static final String OFFLINE = Localization.getString("mainWindow.status.offline").toUpperCase(); + static final String PIN_X = Localization.getString("machineStatus.pin.x").toUpperCase(); + static final String PIN_Y = Localization.getString("machineStatus.pin.y").toUpperCase(); + static final String PIN_Z = Localization.getString("machineStatus.pin.z").toUpperCase(); + static final String PIN_A = Localization.getString("machineStatus.pin.a").toUpperCase(); + static final String PIN_B = Localization.getString("machineStatus.pin.b").toUpperCase(); + static final String PIN_C = Localization.getString("machineStatus.pin.c").toUpperCase(); + static final String PIN_PROBE = Localization.getString("machineStatus.pin.probe").toUpperCase(); + static final String PIN_DOOR = Localization.getString("machineStatus.pin.door").toUpperCase(); + static final String PIN_HOLD = Localization.getString("machineStatus.pin.hold").toUpperCase(); + static final String PIN_SOFT_RESET = Localization.getString("machineStatus.pin.softReset").toUpperCase(); + static final String PIN_CYCLE_STARY = Localization.getString("machineStatus.pin.cycleStart").toUpperCase(); + private Translations() { + } +} diff --git a/ugs-platform/ugs-platform-plugin-joystick/src/main/java/com/willwinder/ugs/nbp/joystick/action/AnalogFeedOverrideAction.java b/ugs-platform/ugs-platform-plugin-joystick/src/main/java/com/willwinder/ugs/nbp/joystick/action/AnalogFeedOverrideAction.java new file mode 100644 index 0000000000..dca4bacd9b --- /dev/null +++ b/ugs-platform/ugs-platform-plugin-joystick/src/main/java/com/willwinder/ugs/nbp/joystick/action/AnalogFeedOverrideAction.java @@ -0,0 +1,35 @@ +/* + Copyright 2024 Will Winder + + This file is part of Universal Gcode Sender (UGS). + + UGS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UGS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UGS. If not, see . + */ +package com.willwinder.ugs.nbp.joystick.action; + +import com.willwinder.universalgcodesender.i18n.Localization; +import com.willwinder.universalgcodesender.listeners.OverrideType; + +/** + * An action for setting the analog feed override + * + * @author Joacim Breiler + */ +public class AnalogFeedOverrideAction extends AnalogOverrideAction { + + public AnalogFeedOverrideAction() { + super(OverrideType.FEED_SPEED); + this.putValue(NAME, Localization.getString("platform.plugin.joystick.action.analogFeed")); + } +} diff --git a/ugs-platform/ugs-platform-plugin-joystick/src/main/java/com/willwinder/ugs/nbp/joystick/action/AnalogOverrideAction.java b/ugs-platform/ugs-platform-plugin-joystick/src/main/java/com/willwinder/ugs/nbp/joystick/action/AnalogOverrideAction.java new file mode 100644 index 0000000000..bb86bb6055 --- /dev/null +++ b/ugs-platform/ugs-platform-plugin-joystick/src/main/java/com/willwinder/ugs/nbp/joystick/action/AnalogOverrideAction.java @@ -0,0 +1,73 @@ +/* + Copyright 2024 Will Winder + + This file is part of Universal Gcode Sender (UGS). + + UGS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UGS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UGS. If not, see . + */ +package com.willwinder.ugs.nbp.joystick.action; + +import com.willwinder.ugs.nbp.lib.lookup.CentralLookup; +import com.willwinder.universalgcodesender.listeners.ControllerState; +import com.willwinder.universalgcodesender.listeners.OverrideType; +import com.willwinder.universalgcodesender.listeners.UGSEventListener; +import com.willwinder.universalgcodesender.model.BackendAPI; +import com.willwinder.universalgcodesender.model.UGSEvent; +import com.willwinder.universalgcodesender.model.events.ControllerStateEvent; +import com.willwinder.universalgcodesender.firmware.IOverrideManager; + +import javax.swing.AbstractAction; +import java.awt.event.ActionEvent; + +/** + * Makes it possible to do an analog override + * + * @author Joacim Breiler + */ +public abstract class AnalogOverrideAction extends AbstractAction implements AnalogAction, UGSEventListener { + private final transient BackendAPI backend; + private final OverrideType overrideType; + private float value; + + protected AnalogOverrideAction(OverrideType overrideType) { + this.overrideType = overrideType; + setEnabled(false); + + backend = CentralLookup.getDefault().lookup(BackendAPI.class); + backend.addUGSEventListener(this); + } + + @Override + public void UGSEvent(UGSEvent evt) { + if (evt instanceof ControllerStateEvent controllerStateEvent) { + ControllerState state = controllerStateEvent.getState(); + setEnabled(state == ControllerState.IDLE || state == ControllerState.RUN); + } + } + + @Override + public void setValue(float value) { + this.value = value; + } + + @Override + public void actionPerformed(ActionEvent e) { + if (!isEnabled() || backend.getController() == null || backend.getController().getOverrideManager() == null) { + return; + } + + IOverrideManager overrideManager = backend.getController().getOverrideManager(); + overrideManager.setSpeedTarget(overrideType, Math.round(value * overrideManager.getSpeedMax(overrideType) + overrideManager.getSpeedDefault(overrideType))); + } +} diff --git a/ugs-platform/ugs-platform-plugin-joystick/src/main/java/com/willwinder/ugs/nbp/joystick/action/AnalogSpindleOverrideAction.java b/ugs-platform/ugs-platform-plugin-joystick/src/main/java/com/willwinder/ugs/nbp/joystick/action/AnalogSpindleOverrideAction.java new file mode 100644 index 0000000000..082d9c5e14 --- /dev/null +++ b/ugs-platform/ugs-platform-plugin-joystick/src/main/java/com/willwinder/ugs/nbp/joystick/action/AnalogSpindleOverrideAction.java @@ -0,0 +1,34 @@ +/* + Copyright 2024 Will Winder + + This file is part of Universal Gcode Sender (UGS). + + UGS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UGS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UGS. If not, see . + */ +package com.willwinder.ugs.nbp.joystick.action; + +import com.willwinder.universalgcodesender.i18n.Localization; +import com.willwinder.universalgcodesender.listeners.OverrideType; + +/** + * An action for setting the analog spindle override + * + * @author Joacim Breiler + */ +public class AnalogSpindleOverrideAction extends AnalogOverrideAction { + public AnalogSpindleOverrideAction() { + super(OverrideType.SPINDLE_SPEED); + this.putValue(NAME, Localization.getString("platform.plugin.joystick.action.analogSpindle")); + } +} diff --git a/ugs-platform/ugs-platform-plugin-joystick/src/main/java/com/willwinder/ugs/nbp/joystick/service/JoystickServiceImpl.java b/ugs-platform/ugs-platform-plugin-joystick/src/main/java/com/willwinder/ugs/nbp/joystick/service/JoystickServiceImpl.java index 112d2344a0..6c62416f5e 100644 --- a/ugs-platform/ugs-platform-plugin-joystick/src/main/java/com/willwinder/ugs/nbp/joystick/service/JoystickServiceImpl.java +++ b/ugs-platform/ugs-platform-plugin-joystick/src/main/java/com/willwinder/ugs/nbp/joystick/service/JoystickServiceImpl.java @@ -34,7 +34,9 @@ This file is part of Universal Gcode Sender (UGS). import static com.willwinder.ugs.nbp.joystick.Utils.ACTION_Z_UP; import com.willwinder.ugs.nbp.joystick.action.ActionDispatcher; import com.willwinder.ugs.nbp.joystick.action.ActionManager; +import com.willwinder.ugs.nbp.joystick.action.AnalogFeedOverrideAction; import com.willwinder.ugs.nbp.joystick.action.AnalogJogAction; +import com.willwinder.ugs.nbp.joystick.action.AnalogSpindleOverrideAction; import com.willwinder.ugs.nbp.joystick.driver.JamepadJoystickDriver; import com.willwinder.ugs.nbp.joystick.driver.JoystickDriver; import com.willwinder.ugs.nbp.joystick.driver.JoystickDriverListener; @@ -61,6 +63,8 @@ This file is part of Universal Gcode Sender (UGS). */ public class JoystickServiceImpl implements JoystickService, JoystickDriverListener { public static final String CONTINUOUS_JOG_ACTION_CATEGORY = "Actions/Machine"; + public static final String CONTINUOUS_OVERRIDE_ACTION_CATEGORY = "Actions/Overrides"; + private static final Logger LOGGER = Logger.getLogger(JoystickServiceImpl.class.getSimpleName()); /** @@ -90,6 +94,8 @@ public JoystickServiceImpl() { actionManager.registerAction("continuousJogAAction", CONTINUOUS_JOG_ACTION_CATEGORY, new AnalogJogAction(continuousJogWorker, Axis.A)); actionManager.registerAction("continuousJogBAction", CONTINUOUS_JOG_ACTION_CATEGORY, new AnalogJogAction(continuousJogWorker, Axis.B)); actionManager.registerAction("continuousJogCAction", CONTINUOUS_JOG_ACTION_CATEGORY, new AnalogJogAction(continuousJogWorker, Axis.C)); + actionManager.registerAction("analogFeedOverrideAction", CONTINUOUS_OVERRIDE_ACTION_CATEGORY, new AnalogFeedOverrideAction()); + actionManager.registerAction("analogSpindleOverrideAction", CONTINUOUS_OVERRIDE_ACTION_CATEGORY, new AnalogSpindleOverrideAction()); joystickActionDispatcher = new ActionDispatcher(actionManager, continuousJogWorker); addListener(joystickActionDispatcher); diff --git a/ugs-platform/ugs-platform-plugin-setup-wizard/src/main/java/com/willwinder/ugs/nbp/setupwizard/panels/WizardPanelHardLimits.java b/ugs-platform/ugs-platform-plugin-setup-wizard/src/main/java/com/willwinder/ugs/nbp/setupwizard/panels/WizardPanelHardLimits.java index 37c8504803..bfb8a03753 100644 --- a/ugs-platform/ugs-platform-plugin-setup-wizard/src/main/java/com/willwinder/ugs/nbp/setupwizard/panels/WizardPanelHardLimits.java +++ b/ugs-platform/ugs-platform-plugin-setup-wizard/src/main/java/com/willwinder/ugs/nbp/setupwizard/panels/WizardPanelHardLimits.java @@ -22,7 +22,6 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.firmware.FirmwareSettingsException; import com.willwinder.universalgcodesender.firmware.IFirmwareSettings; import com.willwinder.universalgcodesender.i18n.Localization; -import com.willwinder.universalgcodesender.listeners.ControllerStatus; import com.willwinder.universalgcodesender.listeners.UGSEventListener; import com.willwinder.universalgcodesender.model.Alarm; import com.willwinder.universalgcodesender.model.BackendAPI; @@ -38,8 +37,12 @@ This file is part of Universal Gcode Sender (UGS). import org.openide.NotifyDescriptor; import org.openide.util.ImageUtilities; -import javax.swing.*; -import java.awt.*; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import java.awt.Color; +import java.awt.Font; /** * A wizard step panel for configuring hard limits on a controller. @@ -94,7 +97,7 @@ private void initComponents() { checkboxEnableHardLimits = new JCheckBox("Enable limit switches"); checkboxEnableHardLimits.addActionListener(event -> onHardLimitsClicked()); - labelHardLimitsNotSupported = new JLabel("" + Localization.getString("platform.plugin.setupwizard.limit-switches.not-available") + "", ImageUtilities.loadImageIcon("icons/information24.png", false), JLabel.LEFT); + labelHardLimitsNotSupported = new JLabel("" + Localization.getString("platform.plugin.setupwizard.limit-switches.not-available") + "", ImageUtilities.loadImageIcon("icons/information24.png", false), SwingConstants.LEFT); labelHardLimitsNotSupported.setVisible(false); labelLimitX = createLimitLabel("X"); @@ -118,7 +121,7 @@ private void initComponents() { } private JLabel createLimitLabel(String text) { - JLabel label = new JLabel(text, JLabel.CENTER); + JLabel label = new JLabel(text, SwingConstants.CENTER); label.setFont(label.getFont().deriveFont(Font.BOLD)); label.setBorder(new RoundedBorder(8)); label.setForeground(Color.WHITE); @@ -157,14 +160,13 @@ public boolean isEnabled() { public void UGSEvent(UGSEvent evt) { if (evt instanceof FirmwareSettingEvent) { refreshComponents(); - } else if (evt instanceof ControllerStatusEvent) { + } else if (evt instanceof ControllerStatusEvent controllerStatus) { ThreadHelper.invokeLater(() -> { - ControllerStatus controllerStatus = ((ControllerStatusEvent) evt).getStatus(); - labelLimitX.setBackground(controllerStatus.getEnabledPins().X ? ThemeColors.RED : ThemeColors.LIGHT_GREEN); - labelLimitY.setBackground(controllerStatus.getEnabledPins().Y ? ThemeColors.RED : ThemeColors.LIGHT_GREEN); - labelLimitZ.setBackground(controllerStatus.getEnabledPins().Z ? ThemeColors.RED : ThemeColors.LIGHT_GREEN); + labelLimitX.setBackground(controllerStatus.getStatus().getEnabledPins().x() ? ThemeColors.RED : ThemeColors.LIGHT_GREEN); + labelLimitY.setBackground(controllerStatus.getStatus().getEnabledPins().y() ? ThemeColors.RED : ThemeColors.LIGHT_GREEN); + labelLimitZ.setBackground(controllerStatus.getStatus().getEnabledPins().z() ? ThemeColors.RED : ThemeColors.LIGHT_GREEN); }); - } else if (evt instanceof AlarmEvent && ((AlarmEvent) evt).getAlarm() == Alarm.HARD_LIMIT) { + } else if (evt instanceof AlarmEvent alarmEvent && alarmEvent.getAlarm() == Alarm.HARD_LIMIT) { ThreadHelper.invokeLater(() -> { try { getBackend().issueSoftReset(); diff --git a/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/services/OverrideActionService.java b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/services/OverrideActionService.java index b1623ae348..3934fe0919 100644 --- a/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/services/OverrideActionService.java +++ b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/services/OverrideActionService.java @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Will Winder + Copyright 2016-2024 Will Winder This file is part of Universal Gcode Sender (UGS). @@ -23,7 +23,17 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.i18n.Localization; import com.willwinder.universalgcodesender.model.BackendAPI; import com.willwinder.universalgcodesender.model.Overrides; -import com.willwinder.universalgcodesender.uielements.panels.OverridesPanel; +import static com.willwinder.universalgcodesender.uielements.panels.OverrideLabels.FLOOD; +import static com.willwinder.universalgcodesender.uielements.panels.OverrideLabels.MINUS_COARSE; +import static com.willwinder.universalgcodesender.uielements.panels.OverrideLabels.MINUS_FINE; +import static com.willwinder.universalgcodesender.uielements.panels.OverrideLabels.MIST; +import static com.willwinder.universalgcodesender.uielements.panels.OverrideLabels.PLUS_COARSE; +import static com.willwinder.universalgcodesender.uielements.panels.OverrideLabels.PLUS_FINE; +import static com.willwinder.universalgcodesender.uielements.panels.OverrideLabels.RAPID_FULL; +import static com.willwinder.universalgcodesender.uielements.panels.OverrideLabels.RAPID_LOW; +import static com.willwinder.universalgcodesender.uielements.panels.OverrideLabels.RAPID_MEDIUM; +import static com.willwinder.universalgcodesender.uielements.panels.OverrideLabels.RESET_FEED; +import static com.willwinder.universalgcodesender.uielements.panels.OverrideLabels.SPINDLE_SHORT; import com.willwinder.universalgcodesender.utils.GUIHelpers; import org.openide.util.Exceptions; import org.openide.util.Lookup; @@ -38,7 +48,12 @@ This file is part of Universal Gcode Sender (UGS). */ @ServiceProvider(service = OverrideActionService.class) public class OverrideActionService { - private BackendAPI backend; + public static final String MENU_OVERRIDE_LABEL = Localization.getString("platform.menu.overrides"); + public static final String MENU_MACHINE_LABEL = Localization.getString("platform.menu.machine"); + public static final String MENU_TOGGLE_LABEL = Localization.getString("overrides.toggle.short"); + public static final String MENU_PATH = "Menu/Machine/Overrides"; + public static final String MENU_OVERRIDES_TOGGLE_PATH = "Menu/Machine/Overrides/Toggles"; + private final BackendAPI backend; public OverrideActionService() { backend = CentralLookup.getDefault().lookup(BackendAPI.class); @@ -48,7 +63,7 @@ public OverrideActionService() { public void runAction(Overrides action) { if (canRunAction()) { try { - backend.sendOverrideCommand(action); + backend.getController().getOverrideManager().sendOverrideCommand(action); } catch (Exception ex) { GUIHelpers.displayErrorDialog(ex.getMessage()); } @@ -59,104 +74,99 @@ public boolean canRunAction() { return backend.isConnected() && backend.getController().getCapabilities().hasOverrides(); } - final public void initActions() { + public final void initActions() { ActionRegistrationService ars = Lookup.getDefault().lookup(ActionRegistrationService.class); try { // Feed Overrides String category = "Overrides"; - String localizedCategory = Localization.getString("platform.menu.overrides"); - String menuPath = "Menu/Machine/Overrides"; String localized = String.format("Menu/%s/%s", - Localization.getString("platform.menu.machine"), - Localization.getString("platform.menu.overrides")); + MENU_MACHINE_LABEL, + MENU_OVERRIDE_LABEL); String pattern = Localization.getString("overrides.feed") + " (%s)"; - ars.registerAction(OverrideAction.class.getCanonicalName() + ".feedOvrCoarseMinus", String.format(pattern, OverridesPanel.MINUS_COARSE), - category, null, menuPath, 0, localized, + ars.registerAction(OverrideAction.class.getCanonicalName() + ".feedOvrCoarseMinus", String.format(pattern, MINUS_COARSE), + category, null, MENU_PATH, 0, localized, new OverrideAction(Overrides.CMD_FEED_OVR_COARSE_MINUS)); - ars.registerAction(OverrideAction.class.getCanonicalName() + ".feedOvrFineMinus", String.format(pattern, OverridesPanel.MINUS_FINE), - category, null, menuPath, 0, localized, + ars.registerAction(OverrideAction.class.getCanonicalName() + ".feedOvrFineMinus", String.format(pattern, MINUS_FINE), + category, null, MENU_PATH, 0, localized, new OverrideAction(Overrides.CMD_FEED_OVR_FINE_MINUS)); - ars.registerAction(OverrideAction.class.getCanonicalName() + ".feedOvrFinePlus", String.format(pattern, OverridesPanel.PLUS_FINE), - category, null, menuPath, 0, localized, + ars.registerAction(OverrideAction.class.getCanonicalName() + ".feedOvrFinePlus", String.format(pattern, PLUS_FINE), + category, null, MENU_PATH, 0, localized, new OverrideAction(Overrides.CMD_FEED_OVR_FINE_PLUS)); - ars.registerAction(OverrideAction.class.getCanonicalName() + ".feedOvrCoarsePlus", String.format(pattern, OverridesPanel.PLUS_COARSE), - category, null, menuPath, 0, localized, + ars.registerAction(OverrideAction.class.getCanonicalName() + ".feedOvrCoarsePlus", String.format(pattern, PLUS_COARSE), + category, null, MENU_PATH, 0, localized, new OverrideAction(Overrides.CMD_FEED_OVR_COARSE_PLUS)); - ars.registerAction(OverrideAction.class.getCanonicalName() + ".feedOvrReset", String.format(pattern, OverridesPanel.RESET_FEED), - category, null, menuPath, 0, localized, + ars.registerAction(OverrideAction.class.getCanonicalName() + ".feedOvrReset", String.format(pattern, RESET_FEED), + category, null, MENU_PATH, 0, localized, new OverrideAction(Overrides.CMD_FEED_OVR_RESET)); // Spindle Overrides - menuPath = "Menu/Machine/Overrides"; localized = String.format("Menu/%s/%s", - Localization.getString("platform.menu.machine"), - Localization.getString("platform.menu.overrides")); + MENU_MACHINE_LABEL, + MENU_OVERRIDE_LABEL); pattern = Localization.getString("overrides.spindle") + " (%s)"; - ars.registerAction(OverrideAction.class.getCanonicalName() + ".spindleOvrCoarseMinus", String.format(pattern, OverridesPanel.MINUS_COARSE), - category, null, menuPath, 0, localized, + ars.registerAction(OverrideAction.class.getCanonicalName() + ".spindleOvrCoarseMinus", String.format(pattern, MINUS_COARSE), + category, null, MENU_PATH, 0, localized, new OverrideAction(Overrides.CMD_SPINDLE_OVR_COARSE_MINUS)); - ars.registerAction(OverrideAction.class.getCanonicalName() + ".spindleOvrFineMinus", String.format(pattern, OverridesPanel.MINUS_FINE), - category, null, menuPath, 0, localized, + ars.registerAction(OverrideAction.class.getCanonicalName() + ".spindleOvrFineMinus", String.format(pattern, MINUS_FINE), + category, null, MENU_PATH, 0, localized, new OverrideAction(Overrides.CMD_SPINDLE_OVR_FINE_MINUS)); - ars.registerAction(OverrideAction.class.getCanonicalName() + ".spindleOvrFinePlus", String.format(pattern, OverridesPanel.PLUS_FINE), - category, null, menuPath, 0, localized, + ars.registerAction(OverrideAction.class.getCanonicalName() + ".spindleOvrFinePlus", String.format(pattern, PLUS_FINE), + category, null, MENU_PATH, 0, localized, new OverrideAction(Overrides.CMD_SPINDLE_OVR_FINE_PLUS)); - ars.registerAction(OverrideAction.class.getCanonicalName() + ".spindleOvrCoarsePlus", String.format(pattern, OverridesPanel.PLUS_COARSE), - category, null, menuPath, 0, localized, + ars.registerAction(OverrideAction.class.getCanonicalName() + ".spindleOvrCoarsePlus", String.format(pattern, PLUS_COARSE), + category, null, MENU_PATH, 0, localized, new OverrideAction(Overrides.CMD_SPINDLE_OVR_COARSE_PLUS)); ars.registerAction(OverrideAction.class.getCanonicalName() + ".spindleOvrReset", String.format(pattern, Localization.getString("mainWindow.swing.reset")), - category, null, menuPath, 0, localized, + category, null, MENU_PATH, 0, localized, new OverrideAction(Overrides.CMD_SPINDLE_OVR_RESET)); // Rapid Overrides - menuPath = "Menu/Machine/Overrides"; localized = String.format("Menu/%s/%s", - Localization.getString("platform.menu.machine"), - Localization.getString("platform.menu.overrides")); + MENU_MACHINE_LABEL, + MENU_OVERRIDE_LABEL); pattern = Localization.getString("overrides.rapid") + " (%s)"; - ars.registerAction(OverrideAction.class.getCanonicalName() + ".rapidOvrLow", String.format(pattern, OverridesPanel.RAPID_LOW), - category, null, menuPath, 0, localized, + ars.registerAction(OverrideAction.class.getCanonicalName() + ".rapidOvrLow", String.format(pattern, RAPID_LOW), + category, null, MENU_PATH, 0, localized, new OverrideAction(Overrides.CMD_RAPID_OVR_LOW)); - ars.registerAction(OverrideAction.class.getCanonicalName() + ".rapidOvrMedium", String.format(pattern, OverridesPanel.RAPID_MEDIUM), - category, null, menuPath, 0, localized, + ars.registerAction(OverrideAction.class.getCanonicalName() + ".rapidOvrMedium", String.format(pattern, RAPID_MEDIUM), + category, null, MENU_PATH, 0, localized, new OverrideAction(Overrides.CMD_RAPID_OVR_MEDIUM)); - ars.registerAction(OverrideAction.class.getCanonicalName() + ".rapidOvrReset", String.format(pattern, OverridesPanel.RAPID_FULL), - category, null, menuPath, 0, localized, + ars.registerAction(OverrideAction.class.getCanonicalName() + ".rapidOvrReset", String.format(pattern, RAPID_FULL), + category, null, MENU_PATH, 0, localized, new OverrideAction(Overrides.CMD_RAPID_OVR_RESET)); // Toggles - menuPath = "Menu/Machine/Overrides/Toggles"; localized = String.format("Menu/%s/%s/%s", - Localization.getString("platform.menu.machine"), - Localization.getString("platform.menu.overrides"), - Localization.getString("overrides.toggle.short")); + MENU_MACHINE_LABEL, + MENU_OVERRIDE_LABEL, + MENU_TOGGLE_LABEL); - ars.registerAction(OverrideAction.class.getCanonicalName() + ".toggleSpindle", OverridesPanel.SPINDLE_SHORT, - category, null, menuPath, 0, localized, + ars.registerAction(OverrideAction.class.getCanonicalName() + ".toggleSpindle", SPINDLE_SHORT, + category, null, MENU_OVERRIDES_TOGGLE_PATH, 0, localized, new OverrideAction(Overrides.CMD_TOGGLE_SPINDLE)); - ars.registerAction(OverrideAction.class.getCanonicalName() + ".toogleFloodCoolant", OverridesPanel.FLOOD, - category, null, menuPath, 0, localized, + ars.registerAction(OverrideAction.class.getCanonicalName() + ".toogleFloodCoolant", FLOOD, + category, null, MENU_OVERRIDES_TOGGLE_PATH, 0, localized, new OverrideAction(Overrides.CMD_TOGGLE_FLOOD_COOLANT)); - ars.registerAction(OverrideAction.class.getCanonicalName() + ".toggleMistCoolant", OverridesPanel.MIST, - category, null, menuPath, 0, localized, + ars.registerAction(OverrideAction.class.getCanonicalName() + ".toggleMistCoolant", MIST, + category, null, MENU_OVERRIDES_TOGGLE_PATH, 0, localized, new OverrideAction(Overrides.CMD_TOGGLE_MIST_COOLANT)); } catch (IOException ex) { Exceptions.printStackTrace(ex);