/* * * Goko * Copyright (C) 2013, 2016 PsyKo * * This program 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. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. * */ package org.goko.controller.grbl.v08; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Scanner; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.LinkedBlockingQueue; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.core.runtime.preferences.InstanceScope; import org.goko.common.preferences.ScopedPreferenceStore; import org.goko.controller.grbl.v08.bean.GrblExecutionError; import org.goko.controller.grbl.v08.bean.StatusReport; import org.goko.controller.grbl.v08.configuration.GrblConfiguration; import org.goko.controller.grbl.v08.configuration.GrblSetting; import org.goko.controller.grbl.v08.topic.GrblExecutionErrorTopic; import org.goko.core.common.GkUtils; import org.goko.core.common.applicative.logging.IApplicativeLogService; import org.goko.core.common.event.EventBrokerUtils; import org.goko.core.common.event.EventDispatcher; import org.goko.core.common.event.EventListener; import org.goko.core.common.event.ObservableDelegate; import org.goko.core.common.exception.GkException; import org.goko.core.common.exception.GkFunctionalException; import org.goko.core.common.exception.GkTechnicalException; import org.goko.core.common.measure.Units; import org.goko.core.common.measure.quantity.Length; import org.goko.core.common.measure.quantity.LengthUnit; import org.goko.core.common.measure.quantity.Speed; import org.goko.core.common.measure.quantity.SpeedUnit; import org.goko.core.common.measure.units.Unit; import org.goko.core.config.GokoPreference; import org.goko.core.connection.IConnectionService; import org.goko.core.controller.action.IGkControllerAction; import org.goko.core.controller.bean.EnumControllerAxis; import org.goko.core.controller.bean.MachineValue; import org.goko.core.controller.bean.MachineValueDefinition; import org.goko.core.controller.event.IGCodeContextListener; import org.goko.core.controller.event.MachineValueUpdateEvent; import org.goko.core.gcode.element.GCodeLine; import org.goko.core.gcode.element.ICoordinateSystem; import org.goko.core.gcode.element.IGCodeProvider; import org.goko.core.gcode.execution.ExecutionQueueType; import org.goko.core.gcode.execution.ExecutionState; import org.goko.core.gcode.execution.ExecutionToken; import org.goko.core.gcode.execution.ExecutionTokenState; import org.goko.core.gcode.rs274ngcv3.IRS274NGCService; import org.goko.core.gcode.rs274ngcv3.context.CoordinateSystem; import org.goko.core.gcode.rs274ngcv3.context.CoordinateSystemFactory; import org.goko.core.gcode.rs274ngcv3.context.GCodeContext; import org.goko.core.gcode.rs274ngcv3.context.GCodeContextObservable; import org.goko.core.gcode.rs274ngcv3.element.InstructionProvider; import org.goko.core.gcode.service.IExecutionService; import org.goko.core.log.GkLog; import org.goko.core.math.Tuple6b; import org.osgi.service.event.Event; import org.osgi.service.event.EventAdmin; /** * GRBL v0.8 Controller implementation * * @author PsyKo * */ public class GrblControllerService extends EventDispatcher implements IGrblControllerService { /** Service ID */ public static final String SERVICE_ID = "Grbl v0.8 Controller"; /** Log */ private static final GkLog LOG = GkLog.getLogger(GrblControllerService.class); private static final String VALUE_STORE_ID = "org.goko.controller.grbl.v08.GrblControllerService"; private static final String PERSISTED_FEED = "org.goko.controller.grbl.v08.GrblControllerService.feed"; private static final String PERSISTED_STEP = "org.goko.controller.grbl.v08.GrblControllerService.step"; /** GCode service*/ private IRS274NGCService gcodeService; /** Status polling */ private Timer statusPollingTimer; /** Controller action factory*/ private GrblActionFactory grblActionFactory; /** Grbl configuration */ private GrblConfiguration configuration; /** Applicative log service */ private IApplicativeLogService applicativeLogService; /** Grbl state object */ private GrblState grblState; /** Grbl communicator */ private GrblCommunicator communicator; /** The monitor service */ private IExecutionService<ExecutionTokenState, ExecutionToken<ExecutionTokenState>> executionService; /** Event admin object to send topic to UI*/ private EventAdmin eventAdmin; /** Jog related fields - Feedrate */ private Speed feed; /** Jog related fields - Step */ private Length step; /** Preference store */ private ScopedPreferenceStore preferenceStore; /** The Grbl Executor */ private GrblExecutor grblExecutor; /** The history of used buffer for the last sent command s*/ private LinkedBlockingQueue<Integer> usedBufferStack; /** GCode context listener delegate */ private ObservableDelegate<IGCodeContextListener<GCodeContext>> gcodeContextListener; /** Jogging utility */ private GrblJogging grblJogging; /** * Constructor * @throws GkException GkException */ public GrblControllerService() throws GkException { communicator = new GrblCommunicator(this); preferenceStore = new ScopedPreferenceStore(InstanceScope.INSTANCE, VALUE_STORE_ID); usedBufferStack = new LinkedBlockingQueue<Integer>(); grblExecutor = new GrblExecutor(this, gcodeService); gcodeContextListener = new GCodeContextObservable(); initPersistedValues(); } /** (inheritDoc) * @see org.goko.core.common.service.IGokoService#getServiceId() */ @Override public String getServiceId() throws GkException { return SERVICE_ID; } /** (inheritDoc) * @see org.goko.core.common.service.IGokoService#start() */ @Override public void start() throws GkException { LOG.info("Starting " + SERVICE_ID); grblActionFactory = new GrblActionFactory(this); configuration = new GrblConfiguration(); grblState = new GrblState(); grblState.addListener(this); grblJogging = new GrblJogging(this, communicator); LOG.info("Successfully started " + SERVICE_ID); } /** (inheritDoc) * @see org.goko.core.common.event.IObservable#addObserver(java.lang.Object) */ @Override public void addObserver(IGCodeContextListener<GCodeContext> observer) { gcodeContextListener.addObserver(observer); } /** (inheritDoc) * @see org.goko.core.common.event.IObservable#removeObserver(java.lang.Object) */ @Override public boolean removeObserver(IGCodeContextListener<GCodeContext> observer) { return gcodeContextListener.removeObserver(observer); } protected void stopStatusPolling(){ statusPollingTimer.cancel(); } public void startStatusPolling() { statusPollingTimer = new Timer(); TimerTask task = new TimerTask(){ @Override public void run() { try { refreshStatus(); } catch (GkException e) { LOG.error(e); } } }; statusPollingTimer.scheduleAtFixedRate(task, new Date(), 100); } /** * @param evt */ @EventListener(MachineValueUpdateEvent.class) public void onMachineValueUpdate(MachineValueUpdateEvent evt){ notifyListeners(evt); } /** (inheritDoc) * @see org.goko.core.common.service.IGokoService#stop() */ @Override public void stop() throws GkException { persistValues(); } /** (inheritDoc) * @see org.goko.controller.grbl.v08.IGrblControllerService#send(org.goko.core.gcode.element.GCodeLine) */ @Override public void send(GCodeLine gCodeLine) throws GkException{ String cmd = gcodeService.render(gCodeLine); List<Byte> byteCommand = GkUtils.toBytesList(cmd); int usedBufferCount = CollectionUtils.size(byteCommand); communicator.send( byteCommand ); incrementUsedBufferCount(usedBufferCount + 2); // Dirty hack for end of line chars } /** * Register a quantity of space buffer being used for the last sent data * @param amount the amount of used space * @throws GkException GkException */ private void incrementUsedBufferCount(int amount) throws GkException{ usedBufferStack.add(amount); setUsedGrblBuffer(getUsedGrblBuffer() + amount); } /** * Decrement the used serial buffer by depiling the size of the send data, in reverse order * @throws GkException GkException */ private void decrementUsedBufferCount() throws GkException{ if(CollectionUtils.isNotEmpty(usedBufferStack)){ Integer amount = usedBufferStack.poll(); setUsedGrblBuffer(getUsedGrblBuffer() - amount); } } /** (inheritDoc) * @see org.goko.core.controller.IControllerService#getPosition() */ @Override public Tuple6b getPosition() throws GkException { return grblState.getWorkPosition(); } /** (inheritDoc) * @see org.goko.core.controller.IThreeAxisControllerAdapter#getX() */ @Override public Length getX() throws GkException { Length xPos = grblState.getWorkPosition().getX(); return xPos; } /** (inheritDoc) * @see org.goko.core.controller.IThreeAxisControllerAdapter#getY() */ @Override public Length getY() throws GkException { Length yPos = grblState.getWorkPosition().getY(); return yPos; } /** (inheritDoc) * @throws GkException * @see org.goko.core.controller.IThreeAxisControllerAdapter#getZ() */ @Override public Length getZ() throws GkException { Length zPos = grblState.getWorkPosition().getZ(); return zPos; } /** (inheritDoc) * @see org.goko.core.controller.IControllerService#isReadyForFileStreaming() */ @Override public boolean isReadyForFileStreaming() throws GkException { return GrblMachineState.READY.equals(getState()) || GrblMachineState.CHECK.equals(getState()); } /** (inheritDoc) * @see org.goko.core.controller.IControllerService#getControllerAction(java.lang.String) */ @Override public IGkControllerAction getControllerAction(String actionId) throws GkException { IGkControllerAction action = grblActionFactory.findAction(actionId); if(action == null){ throw new GkFunctionalException("Action '"+actionId+"' is not supported by this controller ("+getServiceId()+")"); } return action; } /** (inheritDoc) * @see org.goko.core.controller.IControllerService#isControllerAction(java.lang.String) */ @Override public boolean isControllerAction(String actionId) throws GkException { return grblActionFactory.findAction(actionId) != null; } /** (inheritDoc) * @see org.goko.core.controller.IControllerService#getMachineValue(java.lang.String, java.lang.Class) */ @Override public <T> MachineValue<T> getMachineValue(String name, Class<T> clazz) throws GkException { return getGrblState().getValue(name, clazz); } /** (inheritDoc) * @see org.goko.core.controller.IControllerService#getMachineValueType(java.lang.String) */ @Override public Class<?> getMachineValueType(String name) throws GkException { return getGrblState().getControllerValueType(name); } /** (inheritDoc) * @see org.goko.core.controller.IControllerService#getMachineValueDefinition() */ @Override public List<MachineValueDefinition> getMachineValueDefinition() throws GkException { return getGrblState().getMachineValueDefinition(); } /** (inheritDoc) * @see org.goko.core.controller.IControllerService#findMachineValueDefinition(java.lang.String) */ @Override public MachineValueDefinition findMachineValueDefinition(String id) throws GkException { return getGrblState().findMachineValueDefinition(id); } /** (inheritDoc) * @see org.goko.core.controller.IControllerService#getMachineValueDefinition(java.lang.String) */ @Override public MachineValueDefinition getMachineValueDefinition(String id) throws GkException { return getGrblState().getMachineValueDefinition(id); } /** (inheritDoc) * @see org.goko.core.controller.IControllerService#cancelFileSending() */ @Override public void cancelFileSending() throws GkException { stopMotion(); } /** * @param connectionService the connectionService to set * @throws GkException GkException */ public void setConnectionService(IConnectionService connectionService) throws GkException { this.communicator.setConnectionService(connectionService); } /** * Refresh the status of the remote Grbl controller * @throws GkException GkException */ public void refreshStatus() throws GkException{ if(isActivePollingEnabled()){ communicator.sendWithoutEndLineCharacter( GkUtils.toBytesList(Grbl.CURRENT_STATUS) ); } } public void refreshSpaceCoordinates() throws GkException{ communicator.send( GkUtils.toBytesList(Grbl.VIEW_PARAMETERS) ); } public void refreshParserState() throws GkException{ communicator.send( GkUtils.toBytesList(Grbl.PARSER_STATE) ); } public void refreshConfiguration() throws GkException{ communicator.send( GkUtils.toBytesList(Grbl.CONFIGURATION) ); } protected void handleConfigurationReading(String cofigurationMessage) throws GkException{ String identifier = StringUtils.substringBefore(cofigurationMessage, "=").trim(); String value = StringUtils.substringBetween(cofigurationMessage, "=","(").trim(); configuration.setValue(identifier, value); LOG.info("Updating setting '"+identifier+"' with value '"+value+"'"); } protected void receiveParserState(String parserState) throws GkException { String[] commands = StringUtils.split(parserState," "); GCodeContext context = getGCodeContext(); if(commands != null){ for (String strCommand : commands) { IGCodeProvider provider = gcodeService.parse(strCommand); InstructionProvider instructions = gcodeService.getInstructions(context, provider); context = gcodeService.update(context, instructions); } } grblState.setCurrentContext(context); gcodeContextListener.getEventDispatcher().onGCodeContextEvent(context); } protected void handleError(String errorMessage) throws GkException{ decrementUsedBufferCount(); if(executionService.getExecutionState() == ExecutionState.RUNNING || executionService.getExecutionState() == ExecutionState.PAUSED || executionService.getExecutionState() == ExecutionState.ERROR ){ GCodeLine line = grblExecutor.markNextLineAsError(); logError(errorMessage, line); } } /** * Log the given error on the given gcode line (line can be null) * @param errorMessage the error message * @param line the line (optionnal) * @throws GkException GkException */ protected void logError(String errorMessage, GCodeLine line) throws GkException{ String formattedErrorMessage = StringUtils.EMPTY; if(line != null){ String lineStr = gcodeService.render(line); formattedErrorMessage = "Error with command '"+lineStr+"' : "+ StringUtils.substringAfter(errorMessage, "error: "); }else{ formattedErrorMessage = "Grbl Error : "+ StringUtils.substringAfter(errorMessage, "error: "); } LOG.error(formattedErrorMessage); getApplicativeLogService().error(formattedErrorMessage, SERVICE_ID); // If not in check mode, let's pause the execution (disabled in check mode because check mode can't handle paused state and buffer would be flooded with commands) if(!ObjectUtils.equals(GrblMachineState.CHECK, getState())){ pauseMotion(); EventBrokerUtils.send(eventAdmin, new GrblExecutionErrorTopic(), new GrblExecutionError("Error reported durring execution", "Execution was paused after Grbl reported an error. You can resume, or stop the execution at your own risk.", formattedErrorMessage)); } } protected void handleOkResponse() throws GkException{ decrementUsedBufferCount(); if(executionService.getExecutionState() == ExecutionState.RUNNING || executionService.getExecutionState() == ExecutionState.PAUSED || executionService.getExecutionState() == ExecutionState.ERROR ){ grblExecutor.confirmNextLineExecution(); } } protected void initialiseConnectedState() throws GkException{ setUsedGrblBuffer(0); refreshConfiguration(); refreshSpaceCoordinates(); refreshParserState(); } protected void handleStatusReport(StatusReport statusReport) throws GkException{ GrblMachineState previousState = getState(); grblState.setState(statusReport.getState()); grblState.setMachinePosition(statusReport.getMachinePosition(), getConfiguration().getReportUnit()); grblState.setWorkPosition(statusReport.getWorkPosition(), getConfiguration().getReportUnit()); if(!ObjectUtils.equals(previousState, statusReport.getState())){ eventAdmin.sendEvent(new Event(CONTROLLER_TOPIC_STATE_UPDATE, (Map<String, ?>)null)); } gcodeContextListener.getEventDispatcher().onGCodeContextEvent(getGCodeContext()); } @Override public GrblMachineState getState() throws GkException{ return grblState.getState(); } public void setState(GrblMachineState state) throws GkException{ grblState.setState(state); eventAdmin.sendEvent(new Event(CONTROLLER_TOPIC_STATE_UPDATE, (Map<String, ?>)null)); } protected GrblMachineState getGrblStateFromString(String code){ switch(code){ case "Alarm": return GrblMachineState.ALARM; case "Idle" : return GrblMachineState.READY; case "Queue" : return GrblMachineState.MOTION_HOLDING; case "Run" : return GrblMachineState.MOTION_RUNNING; case "Home" : return GrblMachineState.HOMING; case "Check" : return GrblMachineState.CHECK; default: return GrblMachineState.UNDEFINED; } } /* * Action related methods */ public void startHomingSequence() throws GkException{ List<Byte> homeCommand = new ArrayList<Byte>(); homeCommand.addAll(GkUtils.toBytesList(Grbl.HOME_COMMAND)); communicator.send( homeCommand ); } /** * Pause the motion by sending a pause character to Grbl * If the execution queue is not empty, it is also paused * @throws GkException GkException */ public void pauseMotion() throws GkException{ List<Byte> pauseCommand = new ArrayList<Byte>(); pauseCommand.add(Grbl.PAUSE_COMMAND); communicator.sendImmediately( pauseCommand ); executionService.pauseQueueExecution(); } /** * Stop the motion by sending a pause and a flush character to Grbl * If the execution queue is not empty, it is also stopped and emptied * @throws GkException GkException */ public void stopMotion() throws GkException{ List<Byte> stopCommand = new ArrayList<Byte>(); stopCommand.add(Grbl.PAUSE_COMMAND); stopCommand.add(Grbl.RESET_COMMAND); // TODO : it seems that resetting while in motion causes the GRBL to go back to alarm state. Wait motion to be complete before resetting communicator.sendImmediately(stopCommand); executionService.stopQueueExecution(); setUsedGrblBuffer(0); usedBufferStack.clear(); } /** * Start the motion by sending a resume character to Grbl * If the execution queue is paused, it is also resumed * @throws GkException GkException */ public void startMotion() throws GkException{ List<Byte> startResumeCommand = new ArrayList<Byte>(); startResumeCommand.add(Grbl.RESUME_COMMAND); communicator.sendWithoutEndLineCharacter( startResumeCommand ); if(executionService.getExecutionState() == ExecutionState.PAUSED){ executionService.resumeQueueExecution(); }else{ executionService.beginQueueExecution(ExecutionQueueType.DEFAULT); } } public void resumeMotion() throws GkException{ List<Byte> startResumeCommand = new ArrayList<Byte>(); startResumeCommand.add(Grbl.RESUME_COMMAND); communicator.sendWithoutEndLineCharacter( startResumeCommand ); executionService.resumeQueueExecution(); } // /** (inheritDoc) // * @see org.goko.core.controller.IJogService#startJog(org.goko.core.controller.bean.EnumControllerAxis, java.math.BigDecimal) // */ // @Override // public void startJog(EnumControllerAxis axis, BigDecimal feedrate) throws GkException { // String oldDistanceMode = "G90"; // if(grblState.getDistanceMode() == EnumGCodeCommandDistanceMode.RELATIVE){ // oldDistanceMode = "G91"; // } // String command = "G91G1"+axis.getAxisCode(); // if(axis.isNegative()){ // command+="-"; // } // command += jogStep.to(getCurrentGCodeContext().getUnit().getUnit()).value(); // if(feedrate != null){ // command += "F"+feedrate; // } // List<Byte> lstBytes = GkUtils.toBytesList(command); // communicator.send(lstBytes); // List<Byte> distanceModeBackup = GkUtils.toBytesList(oldDistanceMode); // communicator.send(distanceModeBackup); // } /** (inheritDoc) * @see org.goko.core.controller.IStepJogService#stopJog() */ @Override public void stopJog() throws GkException { grblJogging.stopJog(); } public void resetZero(List<String> axes) throws GkException{ List<Byte> lstBytes = GkUtils.toBytesList("G92"); if(CollectionUtils.isNotEmpty(axes)){ for (String axe : axes) { lstBytes.addAll(GkUtils.toBytesList(axe+"0")); } }else{ lstBytes.addAll( GkUtils.toBytesList("X0Y0Z0")); } communicator.send(lstBytes); } public void killAlarm() throws GkException{ List<Byte> lstBytes = GkUtils.toBytesList("$X"); communicator.send(lstBytes); } /** * @return the usedGrblBuffer * @throws GkException GkException */ @Override public int getUsedGrblBuffer() throws GkException { return grblState.getUsedGrblBuffer(); } /** * @param usedGrblBuffer the usedGrblBuffer to set * @throws GkException GkException */ public void setUsedGrblBuffer(int usedGrblBuffer) throws GkException { grblState.setUsedGrblBuffer(usedGrblBuffer); } /** * @return the configuration */ @Override public GrblConfiguration getConfiguration() { return configuration; } /** * @param configuration the configuration to set * @throws GkException GkException */ @Override public void setConfiguration(GrblConfiguration configuration) throws GkException { this.configuration = configuration; if(CollectionUtils.isNotEmpty( configuration.getLstGrblSetting() )){ List<GrblSetting<?>> lstSetting = configuration.getLstGrblSetting(); List<Byte> cfgCommand = new ArrayList<Byte>(); for (GrblSetting<?> grblSetting : lstSetting) { cfgCommand.addAll(GkUtils.toBytesList(grblSetting.getIdentifier()+"="+grblSetting.getValueAsString() )); communicator.send( cfgCommand ); cfgCommand.clear(); // Start of dirty hack to avoid flooding Grbl RX buffer. Need to work on a proper solution try { Thread.sleep(10); } catch (InterruptedException e) { LOG.error(e); } // End of dirty hack to avoid flooding Grbl RX buffer. Need to work on a proper solution } } } /** (inheritDoc) * @see org.goko.core.controller.IControllerService#moveToAbsolutePosition(org.goko.core.math.Tuple6b) */ @Override public void moveToAbsolutePosition(Tuple6b position) throws GkException { // TODO Auto-generated method stub } /** * @return the gcodeService */ public IRS274NGCService getGCodeService() { return gcodeService; } /** * @param gcodeService the gcodeService to set */ public void setGCodeService(IRS274NGCService gcodeService) { this.gcodeService = gcodeService; } /** * @return the applicativeLogService */ public IApplicativeLogService getApplicativeLogService() { return applicativeLogService; } /** * @param applicativeLogService the applicativeLogService to set */ public void setApplicativeLogService(IApplicativeLogService applicativeLogService) { this.applicativeLogService = applicativeLogService; } /** (inheritDoc) * @see org.goko.controller.grbl.v08.IGrblControllerService#getGrblState() */ @Override public GrblState getGrblState() { return grblState; } protected void setCoordinateSystemOffset(CoordinateSystem coordinateSystem, Tuple6b value) throws GkException{ getGrblState().setOffset(coordinateSystem, value); gcodeContextListener.getEventDispatcher().onGCodeContextEvent(getGCodeContext()); } /** (inheritDoc) * @see org.goko.core.controller.IControllerService#getGCodeContext() */ @Override public GCodeContext getGCodeContext() throws GkException { return grblState.getCurrentContext(); } /** (inheritDoc) * @see org.goko.core.controller.ICoordinateSystemAdapter#getCoordinateSystemOffset(org.goko.core.gcode.element.ICoordinateSystem) */ @Override public Tuple6b getCoordinateSystemOffset(ICoordinateSystem cs) throws GkException { return grblState.getOffset(cs); } /** (inheritDoc) * @see org.goko.core.controller.ICoordinateSystemAdapter#getCoordinateSystem() */ @Override public List<ICoordinateSystem> getCoordinateSystem() throws GkException { List<ICoordinateSystem> lstCoordinateSystem = new ArrayList<>(); lstCoordinateSystem.addAll(new CoordinateSystemFactory().get()); return lstCoordinateSystem; } /** (inheritDoc) * @see org.goko.core.controller.ICoordinateSystemAdapter#getCurrentCoordinateSystem() */ @Override public ICoordinateSystem getCurrentCoordinateSystem() throws GkException { return grblState.getCurrentContext().getCoordinateSystem(); } /** (inheritDoc) * @see org.goko.core.controller.ICoordinateSystemAdapter#setCurrentCoordinateSystem(org.goko.core.gcode.element.ICoordinateSystem) */ @Override public void setCurrentCoordinateSystem(ICoordinateSystem cs) throws GkException { communicator.send( GkUtils.toBytesList( cs.getCode()) ); communicator.send( GkUtils.toBytesList( "$G" ) ); } /** (inheritDoc) * @see org.goko.core.controller.ICoordinateSystemAdapter#resetCurrentCoordinateSystem() */ @Override public void resetCurrentCoordinateSystem() throws GkException { ICoordinateSystem cs = getGrblState().getCurrentContext().getCoordinateSystem(); String cmd = "G10"; switch (cs.getCode()) { case "G54": cmd +="P1"; break; case "G55": cmd +="P2"; break; case "G56": cmd +="P3"; break; case "G57": cmd +="P4"; break; case "G58": cmd +="P5"; break; case "G59": cmd +="P6"; break; default: throw new GkFunctionalException("GRBL-002", cs.getCode()); } Tuple6b offsets = getCoordinateSystemOffset(getCurrentCoordinateSystem()); Tuple6b mPos = new Tuple6b(getPosition()); mPos = mPos.add(offsets); cmd += "L2"; cmd += "X"+getPositionAsString(mPos.getX()); cmd += "Y"+getPositionAsString(mPos.getY()); cmd += "Z"+getPositionAsString(mPos.getZ()); communicator.send( GkUtils.toBytesList( cmd ) ); communicator.send( GkUtils.toBytesList( Grbl.VIEW_PARAMETERS ) ); } /** * Returns the given Length quantity as a String, formatted using the goko preferences for decimal numbers * @param q the quantity to format * @return a String * @throws GkException GkException */ protected String getPositionAsString(Length q) throws GkException{ return GokoPreference.getInstance().format( q.to(getCurrentUnit()), true, false); } /** * Returns the current unit in the Grbl Conteext. It can be different from the unit in the goko preferences * @return Unit */ private Unit<Length> getCurrentUnit() { return grblState.getContextUnit().getUnit(); } /** (inheritDoc) * @see org.goko.controller.grbl.v08.IGrblControllerService#setActivePollingEnabled(boolean) */ @Override public void setActivePollingEnabled(boolean enabled) throws GkException { grblState.setActivePolling(enabled); } /** (inheritDoc) * @see org.goko.controller.grbl.v08.IGrblControllerService#isActivePollingEnabled() */ @Override public boolean isActivePollingEnabled() throws GkException { return grblState.isActivePolling(); } /** (inheritDoc) * @see org.goko.controller.grbl.v08.IGrblControllerService#setCheckModeEnabled(boolean) */ @Override public void setCheckModeEnabled(boolean enabled) throws GkException { if((enabled && ObjectUtils.equals(GrblMachineState.READY, getState())) || // Check mode is disabled and we want to enable it (!enabled && ObjectUtils.equals(GrblMachineState.CHECK, getState())) ){ // Check mode is enabled and we want to disable it communicator.send(GkUtils.toBytesList(Grbl.CHECK_MODE)); }else{ throw new GkFunctionalException("GRBL-001", String.valueOf(enabled), getState().getLabel()); } } /** * @return the eventAdmin */ public EventAdmin getEventAdmin() { return eventAdmin; } /** * @param eventAdmin the eventAdmin to set */ public void setEventAdmin(EventAdmin eventAdmin) { this.eventAdmin = eventAdmin; } /** * @return the monitorService */ public IExecutionService<ExecutionTokenState, ExecutionToken<ExecutionTokenState>> getMonitorService() { return executionService; } /** * @param monitorService the monitorService to set * @throws GkException GkException */ public void setMonitorService(IExecutionService<ExecutionTokenState, ExecutionToken<ExecutionTokenState>> monitorService) throws GkException { this.executionService = monitorService; this.grblExecutor = new GrblExecutor(this, gcodeService); this.executionService.setExecutor(grblExecutor);//new GrblDebugExecutor(gcodeService)); } /** (inheritDoc) * @see org.goko.core.controller.IControllerConfigurationFileExporter#getFileExtension() */ @Override public String getFileExtension() { return "grbl.cfg"; } /** (inheritDoc) * @see org.goko.core.controller.IControllerConfigurationFileExporter#canExport() */ @Override public boolean canExport() throws GkException { return GrblMachineState.READY.equals(getState()); } /** (inheritDoc) * @see org.goko.core.controller.IControllerConfigurationFileExporter#exportTo(java.io.OutputStream) */ @Override public void exportTo(OutputStream stream) throws GkException { GrblConfiguration config = getConfiguration(); StringBuffer buffer = new StringBuffer(); for (GrblSetting<?> setting : config.getLstGrblSetting()) { buffer.append(setting.getIdentifier()+"="+setting.getValueAsString()); buffer.append(System.lineSeparator()); } try { stream.write(buffer.toString().getBytes()); } catch (IOException e) { throw new GkTechnicalException(e); } } /** (inheritDoc) * @see org.goko.core.controller.IControllerConfigurationFileImporter#canImport() */ @Override public boolean canImport() throws GkException { return GrblMachineState.READY.equals(getState()); } /** (inheritDoc) * @see org.goko.core.controller.IControllerConfigurationFileImporter#importFrom(java.io.InputStream) */ @Override public void importFrom(InputStream inputStream) throws GkException { GrblConfiguration cfg = getConfiguration(); Scanner scanner = new Scanner(inputStream); while(scanner.hasNextLine()){ String line = scanner.nextLine(); String[] tokens = line.split("="); if(tokens != null && tokens.length == 2){ cfg.setValue(tokens[0], tokens[1]); }else{ LOG.warn("Ignoring configuration line ["+line+"] because it's malformatted."); } } scanner.close(); setConfiguration(cfg); } /** (inheritDoc) * @see org.goko.core.controller.IJogService#jog(org.goko.core.controller.bean.EnumControllerAxis, org.goko.core.common.measure.quantity.Length, org.goko.core.common.measure.quantity.Speed) */ @Override public void jog(EnumControllerAxis axis, Length step, Speed feedrate) throws GkException { grblJogging.jog(axis, step, feedrate); } private void initPersistedValues() throws GkException{ String feedStr = preferenceStore.getString(PERSISTED_FEED); if(StringUtils.isBlank(feedStr)){ feedStr = GokoPreference.getInstance().format(Speed.valueOf(600, SpeedUnit.MILLIMETRE_PER_MINUTE), true, true); } this.feed = Speed.parse(feedStr); String stepStr = preferenceStore.getString(PERSISTED_STEP); if(StringUtils.isBlank(stepStr)){ stepStr = GokoPreference.getInstance().format(Length.valueOf(1, LengthUnit.MILLIMETRE), true, true); } this.step = Length.parse(stepStr); } private void persistValues() throws GkException{ if(feed != null){ preferenceStore.putValue(PERSISTED_FEED, GokoPreference.getInstance().format(feed, true, true)); } if(step != null){ preferenceStore.putValue(PERSISTED_STEP, step.value(Units.MILLIMETRE).toPlainString()); } } /** (inheritDoc) * @see org.goko.core.controller.IControllerService#verifyReadyForExecution() */ @Override public void verifyReadyForExecution() throws GkException { if(!isReadyForFileStreaming()){ throw new GkFunctionalException("Grbl is not ready for GCode execution."); } } /** (inheritDoc) * @see org.goko.controller.grbl.v08.IGrblControllerService#resetGrbl() */ @Override public void resetGrbl() throws GkException { List<Byte> resetCommand = new ArrayList<Byte>(); resetCommand.add(Grbl.RESET_COMMAND); communicator.sendImmediately(resetCommand); } }