Java tutorial
/* Copyright 2013-2018 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 <http://www.gnu.org/licenses/>. */ package com.willwinder.universalgcodesender; import com.google.gson.JsonObject; import com.willwinder.universalgcodesender.firmware.IFirmwareSettings; import com.willwinder.universalgcodesender.firmware.tinyg.TinyGFirmwareSettings; import com.willwinder.universalgcodesender.gcode.TinyGGcodeCommandCreator; 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.MessageType; import com.willwinder.universalgcodesender.model.Axis; import com.willwinder.universalgcodesender.model.Overrides; import com.willwinder.universalgcodesender.model.Position; import com.willwinder.universalgcodesender.model.UGSEvent; import com.willwinder.universalgcodesender.model.UnitUtils; import com.willwinder.universalgcodesender.types.GcodeCommand; import com.willwinder.universalgcodesender.types.TinyGGcodeCommand; import org.apache.commons.lang3.StringUtils; import java.io.File; import java.util.List; import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; import static com.willwinder.universalgcodesender.model.UGSEvent.ControlState.COMM_CHECK; import static com.willwinder.universalgcodesender.model.UGSEvent.ControlState.COMM_IDLE; import static com.willwinder.universalgcodesender.model.UGSEvent.ControlState.COMM_SENDING; import static com.willwinder.universalgcodesender.model.UGSEvent.ControlState.COMM_SENDING_PAUSED; /** * TinyG Control layer, coordinates all aspects of control. * * @author wwinder * @author Joacim Breiler */ public class TinyGController extends AbstractController { private static final Logger LOGGER = Logger.getLogger(TinyGController.class.getSimpleName()); private static final String NOT_SUPPORTED_YET = "Not supported yet."; private final TinyGFirmwareSettings firmwareSettings; private final Capabilities capabilities; private ControllerStatus controllerStatus; private String firmwareVersion; public TinyGController() { this(new TinyGCommunicator()); } public TinyGController(AbstractCommunicator abstractCommunicator) { super(abstractCommunicator); capabilities = new Capabilities(); commandCreator = new TinyGGcodeCommandCreator(); firmwareSettings = new TinyGFirmwareSettings(this); abstractCommunicator.setListenAll(firmwareSettings); controllerStatus = new ControllerStatus(StringUtils.EMPTY, ControllerState.UNKNOWN, new Position(0, 0, 0, UnitUtils.Units.MM), new Position(0, 0, 0, UnitUtils.Units.MM)); firmwareVersion = "TinyG unknown version"; } @Override public Boolean handlesAllStateChangeEvents() { return false; } @Override public Capabilities getCapabilities() { return capabilities; } @Override public IFirmwareSettings getFirmwareSettings() { return firmwareSettings; } @Override public long getJobLengthEstimate(File gcodeFile) { return 0; } @Override protected void closeCommBeforeEvent() { // Not needed yet } @Override protected void closeCommAfterEvent() { // Not needed yet } @Override protected void openCommAfterEvent() throws Exception { // Not needed yet } @Override protected void cancelSendBeforeEvent() throws Exception { pauseStreaming(); } @Override public void jogMachine(int dirX, int dirY, int dirZ, double stepSize, double feedRate, UnitUtils.Units units) throws Exception { UnitUtils.Units targetUnits = UnitUtils.Units.getUnits(getCurrentGcodeState().units); double scale = UnitUtils.scaleUnits(units, targetUnits); String commandString = GcodeUtils.generateMoveCommand("G91G1", stepSize * scale, feedRate * scale, dirX, dirY, dirZ); GcodeCommand command = createCommand(commandString); command.setTemporaryParserModalChange(true); sendCommandImmediately(command); restoreParserModalState(); } @Override protected void cancelSendAfterEvent() throws Exception { // Canceling the job on the controller (which will also flush the buffer) comm.sendByteImmediately(TinyGUtils.COMMAND_KILL_JOB); // Work around for clearing the sent buffer size comm.softReset(); // We will end up in an alarm state, clear the alarm killAlarmLock(); } @Override protected void pauseStreamingEvent() throws Exception { comm.sendByteImmediately(TinyGUtils.COMMAND_PAUSE); } @Override protected void resumeStreamingEvent() throws Exception { comm.sendByteImmediately(TinyGUtils.COMMAND_RESUME); } @Override protected Boolean isIdleEvent() { return getControlState() == COMM_IDLE || getControlState() == COMM_CHECK; } @Override protected void rawResponseHandler(String response) { JsonObject jo; try { jo = TinyGUtils.jsonToObject(response); } catch (Exception ignored) { // Some TinyG responses aren't JSON, those will end up here. //this.messageForConsole(response + "\n"); return; } if (TinyGUtils.isRestartingResponse(jo)) { this.dispatchConsoleMessage(MessageType.INFO, "[restarting] " + response + "\n"); } else if (TinyGUtils.isReadyResponse(jo)) { if (TinyGUtils.isTinyGVersion(jo)) { firmwareVersion = "TinyG " + TinyGUtils.getVersion(jo); } capabilities.addCapability(CapabilitiesConstants.JOGGING); capabilities.addCapability(CapabilitiesConstants.CONTINUOUS_JOGGING); capabilities.addCapability(CapabilitiesConstants.HOMING); capabilities.addCapability(CapabilitiesConstants.FIRMWARE_SETTINGS); capabilities.addCapability(CapabilitiesConstants.OVERRIDES); capabilities.removeCapability(CapabilitiesConstants.SETUP_WIZARD); setCurrentState(COMM_IDLE); dispatchConsoleMessage(MessageType.INFO, "[ready] " + response + "\n"); try { comm.sendByteImmediately(TinyGUtils.COMMAND_ENQUIRE_STATUS); } catch (Exception e) { e.printStackTrace(); } } else if (jo.has("ack")) { // TODO what do we do with ack=false, or if we don't get any response at all? dispatchConsoleMessage(MessageType.INFO, "[ack] " + response + "\n"); sendInitCommands(); } else if (TinyGUtils.isStatusResponse(jo)) { updateControllerStatus(jo); dispatchConsoleMessage(MessageType.INFO, response + "\n"); checkStreamFinished(); } else if (TinyGGcodeCommand.isOkErrorResponse(response)) { if (jo.get("r").getAsJsonObject().has(TinyGUtils.FIELD_STATUS_REPORT)) { updateControllerStatus(jo.get("r").getAsJsonObject()); checkStreamFinished(); } else if (rowsRemaining() > 0) { try { commandComplete(response); } catch (Exception e) { this.dispatchConsoleMessage(MessageType.ERROR, Localization.getString("controller.error.response") + " <" + response + ">: " + e.getMessage()); } } this.dispatchConsoleMessage(MessageType.INFO, response + "\n"); } else if (TinyGGcodeCommand.isQueueReportResponse(response)) { LOGGER.log(Level.FINE, "Queue buffer usage: " + jo.get("qr").getAsString()); } else { // Display any unhandled messages this.dispatchConsoleMessage(MessageType.INFO, "[unhandled message] " + response + "\n"); } } private void updateControllerStatus(JsonObject jo) { // Save the old state ControllerState previousState = controllerStatus.getState(); UGSEvent.ControlState previousControlState = getControlState(previousState); // Update the internal state List<String> gcodeList = TinyGUtils.convertStatusReportToGcode(jo); gcodeList.forEach(gcode -> updateParserModalState(new GcodeCommand(gcode))); // Notify our listeners about the new status controllerStatus = TinyGUtils.updateControllerStatus(controllerStatus, jo); dispatchStatusString(controllerStatus); // Notify state change to our listeners UGSEvent.ControlState newControlState = getControlState(controllerStatus.getState()); if (!previousControlState.equals(newControlState)) { LOGGER.log(Level.FINE, "Changing state from " + previousControlState + " to " + newControlState); setCurrentState(newControlState); } } private void sendInitCommands() { // Enable JSON mode // 0=text mode, 1=JSON mode comm.queueStringForComm("{ej:1}"); // Configure status reports comm.queueStringForComm( "{sr:{posx:t, posy:t, posz:t, mpox:t, mpoy:t, mpoz:t, plan:t, vel:t, unit:t, stat:t, dist:t, admo:t, frmo:t, coor:t, mfo:t, sso:t, mto:t}}"); // JSON verbosity // 0=silent, 1=footer, 2=messages, 3=configs, 4=linenum, 5=verbose comm.queueStringForComm("{jv:4}"); // Queue report verbosity // 0=off, 1=filtered, 2=verbose comm.queueStringForComm("{qv:0}"); // Status report verbosity // 0=off, 1=filtered, 2=verbose comm.queueStringForComm("{sv:1}"); // Request firmware settings comm.queueStringForComm("$$"); // Request initial status report comm.queueStringForComm("{sr:n}"); // Enable feed overrides comm.queueStringForComm("{mfoe:1}"); comm.queueStringForComm("{mtoe:1}"); comm.queueStringForComm("{ssoe:1}"); comm.streamCommands(); // Refresh the status update setStatusUpdateRate(getStatusUpdateRate()); } @Override public void updateParserModalState(GcodeCommand command) { // Prevent internal TinyG commands to update the parser modal state if (!command.getCommandString().startsWith("{")) { super.updateParserModalState(command); } } @Override public void performHomingCycle() throws Exception { sendCommandImmediately(new GcodeCommand("G28.2 Z0 X0 Y0")); } @Override public void resetCoordinatesToZero() throws Exception { String command = TinyGUtils.generateResetCoordinatesToZeroCommand(controllerStatus, getCurrentGcodeState()); sendCommandImmediately(new GcodeCommand(command)); } @Override public void returnToHome() throws Exception { if (controllerStatus.getWorkCoord().getZ() < 0) { sendCommandImmediately(new GcodeCommand("G90 G0 Z0")); } sendCommandImmediately(new GcodeCommand("G90 G0 X0 Y0")); sendCommandImmediately(new GcodeCommand("G90 G0 Z0")); } @Override public void killAlarmLock() throws Exception { sendCommandImmediately(new GcodeCommand(TinyGUtils.COMMAND_KILL_ALARM_LOCK)); } @Override public void toggleCheckMode() throws Exception { throw new UnsupportedOperationException(NOT_SUPPORTED_YET); } @Override public void viewParserState() throws Exception { if (this.isCommOpen()) { sendCommandImmediately(new GcodeCommand(TinyGUtils.COMMAND_STATUS_REPORT)); } } @Override public void softReset() throws Exception { // TODO This doesn't work, it will disconnect from the host //this.comm.sendByteImmediately(TinyGUtils.COMMAND_RESET); this.comm.sendByteImmediately(TinyGUtils.COMMAND_KILL_JOB); this.comm.sendByteImmediately(TinyGUtils.COMMAND_QUEUE_FLUSH); this.comm.sendByteImmediately((byte) '\n'); sendInitCommands(); } @Override public void setWorkPosition(Axis axis, double position) throws Exception { String command = TinyGUtils.generateSetWorkPositionCommand(controllerStatus, getCurrentGcodeState(), axis, position); sendCommandImmediately(new GcodeCommand(command)); } @Override protected void isReadyToStreamCommandsEvent() throws Exception { // Not needed yet } @Override protected void isReadyToSendCommandsEvent() throws Exception { // Not needed yet } @Override protected void statusUpdatesEnabledValueChanged(boolean enabled) { // We don't care about this } @Override protected void statusUpdatesRateValueChanged(int rate) { // Status report interval in milliseconds (50ms minimum interval) comm.queueStringForComm("{si:" + rate + "}"); } @Override public void sendOverrideCommand(Overrides command) throws Exception { ControllerStatus.OverridePercents currentOverrides = controllerStatus.getOverrides(); Optional<GcodeCommand> gcodeCommand = TinyGUtils.createOverrideCommand(currentOverrides, command); if (gcodeCommand.isPresent()) { sendCommandImmediately(gcodeCommand.get()); } } @Override public String getFirmwareVersion() { return firmwareVersion; } @Override public ControllerStatus getControllerStatus() { return controllerStatus; } @Override public UGSEvent.ControlState getControlState() { return getControlState(getControllerStatus().getState()); } protected UGSEvent.ControlState getControlState(ControllerState controllerState) { switch (controllerState) { case JOG: case RUN: return COMM_SENDING; case HOLD: case DOOR: return COMM_SENDING_PAUSED; case IDLE: if (isStreaming()) { return COMM_SENDING_PAUSED; } else { return COMM_IDLE; } case ALARM: return COMM_IDLE; case CHECK: if (isStreaming() && comm.isPaused()) { return COMM_SENDING_PAUSED; } else if (isStreaming() && !comm.isPaused()) { return COMM_SENDING; } else { return COMM_CHECK; } default: return COMM_IDLE; } } }