/** * */ package org.goko.controller.tinyg.commons; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.collections.CollectionUtils; import org.goko.core.common.exception.GkException; import org.goko.core.execution.monitor.executor.AbstractStreamingExecutor; import org.goko.core.gcode.element.GCodeLine; import org.goko.core.gcode.element.IGCodeProvider; import org.goko.core.gcode.execution.ExecutionToken; import org.goko.core.gcode.execution.ExecutionTokenState; import org.goko.core.gcode.execution.IExecutionToken; import org.goko.core.gcode.execution.IExecutor; import org.goko.core.gcode.service.IGCodeExecutionListener; import org.goko.core.log.GkLog; /** * TinyG executor implementation * * @author PsyKo * @date 20 nov. 2015 */ public class AbstractTinyGExecutor<T extends ITinyGControllerService<?>> extends AbstractStreamingExecutor<ExecutionTokenState, IExecutionToken<ExecutionTokenState>> implements IExecutor<ExecutionTokenState, IExecutionToken<ExecutionTokenState>>, IGCodeExecutionListener<ExecutionTokenState, IExecutionToken<ExecutionTokenState>>{ /** LOG */ private static final GkLog LOG = GkLog.getLogger(AbstractTinyGExecutor.class); /** The number of command sent but not confirmed */ private AtomicInteger pendingCommandCount; ConcurrentLinkedQueue<GCodeLine> queue; /** The underlying service */ private T tinygService; /** Required space in TinyG planner buffer to send a new command */ private int requiredBufferSpace = 5; /** * Constructor * @param tinygService the underlying TinyG service */ public AbstractTinyGExecutor(T tinygService) { super(); this.tinygService = tinygService; this.pendingCommandCount = new AtomicInteger(0); queue = new ConcurrentLinkedQueue<>(); } /** (inheritDoc) * @see org.goko.core.gcode.execution.IExecutor#createToken(org.goko.core.gcode.element.IGCodeProvider) */ @Override public IExecutionToken<ExecutionTokenState> createToken(IGCodeProvider provider) throws GkException { return new ExecutionToken<ExecutionTokenState>(provider, ExecutionTokenState.NONE); } /** (inheritDoc) * @see org.goko.core.execution.monitor.executor.AbstractStreamingExecutor#send(org.goko.core.gcode.element.GCodeLine) */ @Override protected void send(GCodeLine line) throws GkException { pendingCommandCount.incrementAndGet(); queue.add(line); getToken().setLineState(line.getId(), ExecutionTokenState.SENT); tinygService.send(line); } /** (inheritDoc) * @see org.goko.core.execution.monitor.executor.AbstractStreamingExecutor#isReadyForNextLine() */ @Override protected boolean isReadyForNextLine() throws GkException { int actuallyAvailableBuffer = tinygService.getAvailablePlannerBuffer() - pendingCommandCount.intValue(); return ( !tinygService.isPlannerBufferCheck() || actuallyAvailableBuffer >= requiredBufferSpace); } /** * Notification method when the available buffer space changed * @param availableBufferSpace the available space buffer * @throws GkException GkException */ public void onBufferSpaceAvailableChange(int availableBufferSpace) throws GkException{ if(availableBufferSpace >= requiredBufferSpace && isReadyForNextLine()){ notifyReadyForNextLineIfRequired(); } } /** * Notification method called when a line is confirmed by TinyG * @throws GkException */ public void confirmNextLineExecution() throws GkException{ List<GCodeLine> lstLines = getToken().getLineByState(ExecutionTokenState.SENT); if(CollectionUtils.isNotEmpty(lstLines)){ GCodeLine line = lstLines.get(0); System.out.println("Confirming line "+line.getId()); confirmLineExecution(line); }else{ System.out.println("No sent"); } } protected void confirmLineExecution(GCodeLine line) throws GkException{ pendingCommandCount.decrementAndGet(); queue.poll(); decrementRemainingCommandCount(); getToken().setLineState(line.getId(), ExecutionTokenState.EXECUTED); getExecutionService().notifyCommandStateChanged(getToken(), line.getId()); notifyReadyForNextLineIfRequired(); notifyTokenCompleteIfRequired(); } /** * Notification method called when a line is throwing error by TinyG * @throws GkException */ protected void markNextLineAsError() throws GkException{ pendingCommandCount.decrementAndGet(); List<GCodeLine> lstLines = getToken().getLineByState(ExecutionTokenState.SENT); GCodeLine line = lstLines.get(0); getToken().setLineState(line.getId(), ExecutionTokenState.ERROR); getExecutionService().notifyCommandStateChanged(getToken(), line.getId()); } /** * Notify the parent executor if the conditions are met * @throws GkException GkException */ private void notifyTokenCompleteIfRequired() throws GkException { if(getToken().getLineCountByState(ExecutionTokenState.SENT) == 0 && pendingCommandCount.get() <= 0 && getRemainingCommands() <= 0){ notifyTokenComplete(); } } /** * Handles any TinyG Status that is not TG_OK * @param status the received status or <code>null</code> if unknown * @throws GkException GkException */ public void handleNonOkStatus(ITinyGStatus status) throws GkException { if(status == null || status.isError()){ LOG.warn("Pausing execution queue from TinyG Executor due to received status ["+status.getValue()+"]"); getExecutionService().pauseQueueExecution(); markNextLineAsError(); }else{ confirmNextLineExecution(); } } /** * Method used to confirm all command being marked as error. * This is the result of the user continuing the execution after an error was reported. * If the user continues, it meas the error is ignored. * @throws GkException GkException */ protected void confirmErrorCommands() throws GkException{ List<GCodeLine> lstErrorToken = getToken().getLineByState(ExecutionTokenState.ERROR); if(CollectionUtils.isNotEmpty(lstErrorToken)){ for (GCodeLine line : lstErrorToken) { pendingCommandCount.decrementAndGet(); getToken().setLineState(line.getId(), ExecutionTokenState.EXECUTED); getExecutionService().notifyCommandStateChanged(getToken(), line.getId()); notifyReadyForNextLineIfRequired(); notifyTokenCompleteIfRequired(); } } } /** * Notify the parent executor if the conditions are met * @throws GkException GkException */ protected void notifyReadyForNextLineIfRequired() throws GkException{ if(isReadyForNextLine()){ notifyReadyForNextLine(); } } /** (inheritDoc) * @see org.goko.core.gcode.execution.IExecutor#isReadyForQueueExecution() */ @Override public boolean isReadyForQueueExecution() throws GkException { tinygService.verifyReadyForExecution(); return tinygService.isReadyForFileStreaming(); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onQueueExecutionStart() */ @Override public void onQueueExecutionStart() throws GkException { this.pendingCommandCount.set(0); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onExecutionStart(org.goko.core.gcode.execution.IExecutionToken) */ @Override public void onExecutionStart(IExecutionToken<ExecutionTokenState> token) throws GkException {} /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onExecutionCanceled(org.goko.core.gcode.execution.IExecutionToken) */ @Override public void onExecutionCanceled(IExecutionToken<ExecutionTokenState> token) throws GkException {} /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onExecutionPause(org.goko.core.gcode.execution.IExecutionToken) */ @Override public void onExecutionPause(IExecutionToken<ExecutionTokenState> token) throws GkException {} /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onExecutionResume(org.goko.core.gcode.execution.IExecutionToken) */ @Override public void onExecutionResume(IExecutionToken<ExecutionTokenState> token) throws GkException { // We confirm commands in error state: // - if it's a non blocking warning, it's fine // - if it's an error and the user continue the execution, it assumes the error can be ignored // - if it's an error and the user stops the execution, this confirm never happens confirmErrorCommands(); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onExecutionComplete(org.goko.core.gcode.execution.IExecutionToken) */ @Override public void onExecutionComplete(IExecutionToken<ExecutionTokenState> token) throws GkException { } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onQueueExecutionComplete() */ @Override public void onQueueExecutionComplete() throws GkException {} /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onQueueExecutionCanceled() */ @Override public void onQueueExecutionCanceled() throws GkException { this.pendingCommandCount.set(0); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeLineExecutionListener#onLineStateChanged(org.goko.core.gcode.execution.IExecutionToken, java.lang.Integer) */ @Override public void onLineStateChanged(IExecutionToken<ExecutionTokenState> token, Integer idLine) throws GkException { } /** (inheritDoc) * @see org.goko.core.execution.monitor.executor.AbstractStreamingExecutor#stop() */ @Override public void stop() throws GkException { super.stop(); tinygService.stopMotion(); } /** (inheritDoc) * @see org.goko.core.execution.monitor.executor.AbstractStreamingExecutor#pause() */ @Override public void pause() throws GkException { super.pause(); tinygService.pauseMotion(); } /** (inheritDoc) * @see org.goko.core.execution.monitor.executor.AbstractStreamingExecutor#start() */ @Override public void start() throws GkException { super.start(); tinygService.start(); } /** (inheritDoc) * @see org.goko.core.execution.monitor.executor.AbstractStreamingExecutor#resume() */ @Override public void resume() throws GkException { super.resume(); tinygService.resumeMotion(); } /** * @return the requiredBufferSpace */ public int getRequiredBufferSpace() { return requiredBufferSpace; } /** * @param requiredBufferSpace the requiredBufferSpace to set */ public void setRequiredBufferSpace(int requiredBufferSpace) { this.requiredBufferSpace = requiredBufferSpace; } }