Skip to content

Commit

Permalink
New GRBL initialization protocol (#2194)
Browse files Browse the repository at this point in the history
  • Loading branch information
breiler committed Apr 7, 2023
1 parent 18588b1 commit 94b3158
Show file tree
Hide file tree
Showing 18 changed files with 1,007 additions and 608 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
<miglayout.version>3.7.4</miglayout.version>
<guava.version>28.1-jre</guava.version>
<jssc.version>2.8.0</jssc.version>
<jserialcomm.version>2.9.2</jserialcomm.version>
<jserialcomm.version>2.9.3</jserialcomm.version>
<commons-lang3.version>3.12.0</commons-lang3.version>
<commons-io.version>2.11.0</commons-io.version>
<commons-csv.version>1.9.0</commons-csv.version>
Expand Down
174 changes: 74 additions & 100 deletions ugs-core/src/com/willwinder/universalgcodesender/GrblController.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package com.willwinder.universalgcodesender;

import com.willwinder.universalgcodesender.firmware.grbl.GrblVersion;
import com.willwinder.universalgcodesender.firmware.grbl.commands.GetBuildInfoCommand;
import com.willwinder.universalgcodesender.firmware.grbl.commands.GetParserStateCommand;
import com.willwinder.universalgcodesender.firmware.grbl.commands.GetSettingsCommand;
import com.willwinder.universalgcodesender.listeners.ControllerState;
import com.willwinder.universalgcodesender.listeners.MessageType;
import com.willwinder.universalgcodesender.services.MessageService;

import java.util.concurrent.atomic.AtomicBoolean;

import static com.willwinder.universalgcodesender.utils.ControllerUtils.sendAndWaitForCompletion;

/**
* A class that implements an initialization protocol for GRBL and keeps an internal state of the
* connection process. The query process will not require the controller to be reset which is needed
* for controllers such as grblHAL or GRBL_ESP32.
* <p/>
* 1. It will first to query the machine for a status report 10 times, if the status is HOLD or ALARM
* a blank line will be sent to see if the controller is responsive
* 2. Fetch the build info for the controller
* 3. Fetch the parser state
* 4. Start the status poller
*
* @author Joacim Breiler
*/
public class GrblControllerInitializer implements IControllerInitializer {
private final AtomicBoolean isInitializing = new AtomicBoolean(false);
private final AtomicBoolean isInitialized = new AtomicBoolean(false);
private final MessageService messageService;
private final GrblController controller;
private GrblVersion version = GrblVersion.NO_VERSION;

public GrblControllerInitializer(GrblController controller, MessageService messageService) {
this.controller = controller;
this.messageService = messageService;
}

@Override
public boolean initialize() {
// Only allow one initialization at a time
if (isInitializing.get() || isInitialized.get()) {
return false;
}

controller.resetBuffers();

controller.setControllerState(ControllerState.CONNECTING);
isInitializing.set(true);
try {
Thread.sleep(2000);
if (!GrblUtils.isControllerResponsive(controller, messageService)) {
isInitializing.set(false);
messageService.dispatchMessage(MessageType.INFO, "*** Device is in a holding or alarm state and needs to be reset\n");
controller.issueSoftReset();
return false;
}

// Toggle the state to force UI update
controller.setControllerState(ControllerState.CONNECTING);
controller.setControllerState(ControllerState.IDLE);

fetchControllerState();

messageService.dispatchMessage(MessageType.INFO, String.format("*** Connected to %s\n", version.toString()));
controller.requestStatusReport();
isInitialized.set(true);
isInitializing.set(false);
return true;
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}

private void fetchControllerState() throws Exception {
// Send commands to get the state of the controller
GetBuildInfoCommand getBuildInfoCommand = sendAndWaitForCompletion(controller, new GetBuildInfoCommand());
version = getBuildInfoCommand.getVersion().orElse(GrblVersion.NO_VERSION);

sendAndWaitForCompletion(controller, new GetSettingsCommand());
sendAndWaitForCompletion(controller, new GetParserStateCommand());
}

@Override
public void reset() {
isInitializing.set(false);
isInitialized.set(false);
version = GrblVersion.NO_VERSION;
}

@Override
public boolean isInitialized() {
return isInitialized.get();
}

public GrblVersion getVersion() {
return version;
}
}
120 changes: 72 additions & 48 deletions ugs-core/src/com/willwinder/universalgcodesender/GrblUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,33 @@ This file is part of Universal Gcode Sender (UGS).

package com.willwinder.universalgcodesender;

import com.willwinder.universalgcodesender.firmware.grbl.commands.GetStatusCommand;
import com.willwinder.universalgcodesender.firmware.grbl.commands.GrblSystemCommand;
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.MessageType;
import com.willwinder.universalgcodesender.model.*;
import com.willwinder.universalgcodesender.model.UnitUtils.Units;
import com.willwinder.universalgcodesender.services.MessageService;
import com.willwinder.universalgcodesender.types.GcodeCommand;
import org.apache.commons.lang3.StringUtils;

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.
*
* @author wwinder
*/
public class GrblUtils {

// Note: The Grbl RX buffer is not consumed by real-time commands
public static final int GRBL_RX_BUFFER_SIZE= 128;

Expand All @@ -57,7 +64,8 @@ public class GrblUtils {
public static final String GRBL_TOGGLE_CHECK_MODE_COMMAND = "$C";
public static final String GRBL_VIEW_PARSER_STATE_COMMAND = "$G";
public static final String GRBL_VIEW_SETTINGS_COMMAND = "$$";

public static final String GRBL_BUILD_INFO_COMMAND = "$I";

/**
* Gcode Commands
*/
Expand All @@ -82,35 +90,35 @@ public static Boolean isGrblVersionString(final String response) {
boolean version = response.startsWith("Grbl ") || response.startsWith("CarbideMotion ") || response.startsWith("GrblHAL ") || response.startsWith("gCarvin ");
return version && (getVersionDouble(response) != -1);
}
/**

/**
* Parses the version double out of the version response string.
*/
final static String VERSION_DOUBLE_REGEX = "[0-9]*\\.[0-9]*";
final static Pattern VERSION_DOUBLE_PATTERN = Pattern.compile(VERSION_DOUBLE_REGEX);
public static double getVersionDouble(final String response) {
double retValue = -1;

// Search for a version.
Matcher matcher = VERSION_DOUBLE_PATTERN.matcher(response);
if (matcher.find()) {
retValue = Double.parseDouble(matcher.group(0));
}

return retValue;
}

final static String VERSION_LETTER_REGEX = "(?<=[0-9]\\.[0-9])[a-zA-Z]";
final static Pattern VERSION_LETTER_PATTERN = Pattern.compile(VERSION_LETTER_REGEX);
public static Character getVersionLetter(final String response) {
Character retValue = null;

// Search for a version.
Matcher matcher = VERSION_LETTER_PATTERN.matcher(response);
if (matcher.find()) {
retValue = matcher.group(0).charAt(0);
}

return retValue;
}

Expand Down Expand Up @@ -192,7 +200,7 @@ static protected String getKillAlarmLockCommand(final double version, final Char
return "";
}
}

static protected String getToggleCheckModeCommand(final double version, final Character letter) {
if ((version >= 0.8 && (letter != null) && letter >= 'c')
|| version >= 0.9) {
Expand All @@ -202,7 +210,7 @@ static protected String getToggleCheckModeCommand(final double version, final Ch
return "";
}
}

static protected String getViewParserStateCommand(final double version, final Character letter) {
if ((version >= 0.8 && (letter != null) && letter >= 'c')
|| version >= 0.9) {
Expand All @@ -212,7 +220,7 @@ static protected String getViewParserStateCommand(final double version, final Ch
return "";
}
}

/**
* Determines version of GRBL position capability.
*/
Expand Down Expand Up @@ -261,7 +269,7 @@ static protected Position parseProbePosition(final String response, final Units

return GrblUtils.getPositionFromStatusString(response, PROBE_POSITION_PATTERN, units);
}

/**
* Check if a string contains a GRBL position string.
*/
Expand Down Expand Up @@ -308,7 +316,7 @@ public static String parseFeedbackMessageV1(final String response) {
static protected Boolean isGrblSettingMessage(final String response) {
return SETTING_PATTERN.matcher(response).find();
}

/**
* Parses a GRBL status string in the legacy format or v1.x format:
* legacy: <status,WPos:1,2,3,MPos:1,2,3>
Expand All @@ -325,7 +333,7 @@ static protected ControllerStatus getStatusFromStatusString(
final Capabilities version, Units reportingUnits) {
// Legacy status.
if (!version.hasCapability(GrblCapabilitiesConstants.V1_FORMAT)) {
return getStatusFromStatusStringLegacy(status, version, reportingUnits);
return getStatusFromStatusStringLegacy(status, reportingUnits);
} else {
return getStatusFromStatusStringV1(lastStatus, status, reportingUnits);
}
Expand All @@ -335,17 +343,16 @@ static protected ControllerStatus getStatusFromStatusString(
* Parses a GRBL status string in the legacy format:
* legacy: <status,WPos:1,2,3,MPos:1,2,3>
* @param status the raw status string
* @param version capabilities flags
* @param reportingUnits units
* @return the parsed controller status
*/
private static ControllerStatus getStatusFromStatusStringLegacy(String status, Capabilities version, Units reportingUnits) {
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, version, reportingUnits),
getWorkPositionFromStatusString(status, version, reportingUnits));
getMachinePositionFromStatusString(status, reportingUnits),
getWorkPositionFromStatusString(status, reportingUnits));
}

/**
Expand Down Expand Up @@ -499,6 +506,13 @@ protected static String getStateFromStatusString(final String status) {
return retValue;
}

private final static String STATUS_VERSION_1_REGEX = "^<[a-zA-Z]+[|]+.*>$";
private final static Pattern STATUS_VERSION_1_PATTERN = Pattern.compile(STATUS_VERSION_1_REGEX);

public static boolean isGrblStatusStringV1(String response) {
return STATUS_VERSION_1_PATTERN.matcher(response).matches();
}

public static ControllerState getControllerStateFromStateString(String stateString) {
switch (stateString.toLowerCase()) {
case "jog":
Expand Down Expand Up @@ -529,22 +543,14 @@ public static ControllerState getControllerStateFromStateString(String stateStri
static Pattern machinePattern = Pattern.compile("(?<=MPos:)(-?\\d*\\.?\\d*),(-?\\d*\\.?\\d*),(-?\\d*\\.?\\d*)(?:,(-?\\d*\\.?\\d+))?(?:,(-?\\d*\\.?\\d+))?(?:,(-?\\d*\\.?\\d+))?");
static Pattern workPattern = Pattern.compile("(?<=WPos:)(-?\\d*\\.?\\d*),(-?\\d*\\.?\\d*),(-?\\d*\\.?\\d*)(?:,(-?\\d*\\.?\\d+))?(?:,(-?\\d*\\.?\\d+))?(?:,(-?\\d*\\.?\\d+))?");
static Pattern wcoPattern = Pattern.compile("(?<=WCO:)(-?\\d*\\.?\\d*),(-?\\d*\\.?\\d*),(-?\\d*\\.?\\d*)(?:,(-?\\d*\\.?\\d+))?(?:,(-?\\d*\\.?\\d+))?(?:,(-?\\d*\\.?\\d+))?");
static protected Position getMachinePositionFromStatusString(final String status, final Capabilities version, Units reportingUnits) {
if (version.hasCapability(GrblCapabilitiesConstants.REAL_TIME)) {
return GrblUtils.getPositionFromStatusString(status, machinePattern, reportingUnits);
} else {
return null;
}
static protected Position getMachinePositionFromStatusString(final String status, Units reportingUnits) {
return GrblUtils.getPositionFromStatusString(status, machinePattern, reportingUnits);
}

static protected Position getWorkPositionFromStatusString(final String status, final Capabilities version, Units reportingUnits) {
if (version.hasCapability(GrblCapabilitiesConstants.REAL_TIME)) {
return GrblUtils.getPositionFromStatusString(status, workPattern, reportingUnits);
} else {
return null;
}

static protected Position getWorkPositionFromStatusString(final String status, Units reportingUnits) {
return GrblUtils.getPositionFromStatusString(status, workPattern, reportingUnits);
}

public static Position getPositionFromStatusString(final String status, final Pattern pattern, Units reportingUnits) {
Matcher matcher = pattern.matcher(status);
if (matcher.find()) {
Expand All @@ -566,7 +572,7 @@ public static Position getPositionFromStatusString(final String status, final Pa

return result;
}

return null;
}

Expand Down Expand Up @@ -640,21 +646,39 @@ public static Alarm parseAlarmResponse(String response) {
}
}

public static void updateGcodeCommandFromResponse(GcodeCommand gcodeCommand, String response) {
gcodeCommand.setResponse(response);

// No response? Set it to false or else update it's responses
if (StringUtils.isEmpty(response)) {
gcodeCommand.setOk(false);
gcodeCommand.setError(false);
} else if (GrblUtils.isOkResponse(response)) {
gcodeCommand.setOk(true);
gcodeCommand.setError(false);
} else if (GrblUtils.isErrorResponse(response) || GrblUtils.isAlarmResponse(response)) {
gcodeCommand.setOk(false);
gcodeCommand.setError(true);
/**
* Checks if the controller is responsive and not in a locked alarm state.
*
* @return true if responsive
* @throws Exception if we couldn't query for status
*/
public static boolean isControllerResponsive(GrblController controller, MessageService messageService) throws Exception {
GetStatusCommand statusCommand = GrblUtils.queryForStatusReport(controller, messageService);
if (!statusCommand.isDone() || statusCommand.isError()) {
controller.closeCommPort();
throw new IllegalStateException("Could not query the device status");
}

// The controller is not up and running properly
if (statusCommand.getControllerStatus().getState() == ControllerState.HOLD || statusCommand.getControllerStatus().getState() == ControllerState.ALARM) {
try {
// Figure out if it is still responsive even if it is in HOLD or ALARM state
sendAndWaitForCompletion(controller, new GrblSystemCommand(""));
} catch (Exception e) {
return false;
}
}

gcodeCommand.setDone(true);
return true;
}

private static GetStatusCommand queryForStatusReport(GrblController controller, MessageService messageService) throws InterruptedException {
return sendAndWaitForCompletionWithRetry(GetStatusCommand::new, controller, 1000, 3, (executionNumber) -> {
if (executionNumber == 1) {
messageService.dispatchMessage(MessageType.INFO, "*** Fetching device status\n");
} else {
messageService.dispatchMessage(MessageType.INFO, "*** Fetching device status (" + executionNumber + " of 3)...\n");
}
});
}
}

0 comments on commit 94b3158

Please sign in to comment.