/** * */ package org.goko.controller.g2core.controller; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletionService; import org.apache.commons.lang3.StringUtils; import org.goko.controller.g2core.configuration.G2CoreConfiguration; import org.goko.controller.g2core.controller.action.G2CoreActionFactory; import org.goko.controller.g2core.controller.preferences.G2CorePreferences; import org.goko.controller.g2core.controller.topic.G2CoreExecutionErrorTopic; import org.goko.controller.tinyg.commons.AbstractTinyGControllerService; import org.goko.controller.tinyg.commons.ITinyGStatus; import org.goko.controller.tinyg.commons.bean.EnumTinyGAxis; import org.goko.controller.tinyg.commons.bean.TinyGExecutionError; import org.goko.controller.tinyg.commons.probe.ProbeUtility; import org.goko.core.common.applicative.logging.ApplicativeLogEvent; import org.goko.core.common.event.EventBrokerUtils; import org.goko.core.common.exception.GkException; import org.goko.core.common.measure.Units; import org.goko.core.common.measure.quantity.AngleUnit; import org.goko.core.common.measure.quantity.Length; import org.goko.core.config.GokoPreference; import org.goko.core.controller.action.ControllerActionFactory; import org.goko.core.controller.bean.ProbeRequest; import org.goko.core.controller.bean.ProbeResult; 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.rs274ngcv3.context.CoordinateSystemFactory; import org.goko.core.log.GkLog; import org.goko.core.math.Tuple6b; import org.osgi.service.event.EventAdmin; import com.eclipsesource.json.JsonObject; /** * @author Psyko * @date 8 janv. 2017 */ public class G2CoreControllerService extends AbstractTinyGControllerService<G2CoreControllerService, G2CoreState, G2CoreConfiguration, G2CoreCommunicator, G2CoreJogger, G2CoreExecutor> implements IG2CoreControllerService{ /** Log */ private static final GkLog LOG = GkLog.getLogger(G2CoreControllerService.class); /** Service ID */ public static final String SERVICE_ID = "org.goko.controller.g2core.v099"; /** Event admin service */ private EventAdmin eventAdmin; /** The probe utility */ private ProbeUtility probeUtility; /** * @param communicator * @throws GkException GkException */ public G2CoreControllerService(G2CoreCommunicator communicator) throws GkException { super(new G2CoreState(), new G2CoreConfiguration(), communicator); communicator.setControllerService(this); } /** (inheritDoc) * @see org.goko.core.common.service.IGokoService#getServiceId() */ @Override public String getServiceId() throws GkException { return SERVICE_ID; } /** (inheritDoc) * @see org.goko.controller.tinyg.commons.AbstractTinyGControllerService#createActionFactory() */ @Override protected ControllerActionFactory createActionFactory() throws GkException { return new G2CoreActionFactory(this); } /** (inheritDoc) * @see org.goko.controller.tinyg.commons.AbstractTinyGControllerService#createJogger() */ @Override protected G2CoreJogger createJogger() { return new G2CoreJogger(this, getCommunicator()); } /** (inheritDoc) * @see org.goko.controller.tinyg.commons.AbstractTinyGControllerService#createExecutor() */ @Override protected G2CoreExecutor createExecutor() { return new G2CoreExecutor(this); } /** (inheritDoc) * @see org.goko.core.controller.ICoordinateSystemAdapter#setCurrentCoordinateSystem(org.goko.core.gcode.element.ICoordinateSystem) */ @Override public void setCurrentCoordinateSystem(ICoordinateSystem cs) throws GkException { getCommunicator().send( cs.getCode(), true ); } /** (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#resetCurrentCoordinateSystem() */ @Override public void resetCurrentCoordinateSystem() throws GkException { ICoordinateSystem current = getCurrentCoordinateSystem(); Tuple6b offsets = getCoordinateSystemOffset(current); Tuple6b mPos = new Tuple6b(getInternalState().getWorkPosition()); mPos = mPos.add(offsets); JsonObject xyzPosition = new JsonObject(); xyzPosition.add(EnumTinyGAxis.X_POSITIVE.getAxisCode(), getPositionAsDouble(mPos.getX())); xyzPosition.add(EnumTinyGAxis.Y_POSITIVE.getAxisCode(), getPositionAsDouble(mPos.getY())); xyzPosition.add(EnumTinyGAxis.Z_POSITIVE.getAxisCode(), getPositionAsDouble(mPos.getZ())); JsonObject csObject = new JsonObject(); csObject.add(current.getCode(), xyzPosition); getCommunicator().send( csObject , true ); getCommunicator().requestCoordinateSystemUpdate(current); } /** * Returns the given length as a string * @param q the length * @return Double * @throws GkException GkException */ protected double getPositionAsDouble(Length q) throws GkException{ return Double.valueOf(GokoPreference.getInstance().format(q.to(getCurrentUnit()), true, false)); } /** (inheritDoc) * @see org.goko.controller.g2core.controller.IG2CoreControllerService#killAlarm() */ @Override public void killAlarm() throws GkException { getCommunicator().killAlarm(); } /** (inheritDoc) * @see org.goko.controller.g2core.controller.IG2CoreControllerService#stopMotion() */ @Override public void stopMotion() throws GkException { if(probeUtility != null && probeUtility.isProbingInProgress()){ probeUtility.cancelActiveProbing(); } getCommunicator().stopMotion(); getExecutionService().stopQueueExecution(); } /** (inheritDoc) * @see org.goko.controller.g2core.controller.IG2CoreControllerService#pauseMotion() */ @Override public void pauseMotion() throws GkException { getCommunicator().pauseMotion(); getExecutionService().pauseQueueExecution(); } /** (inheritDoc) * @see org.goko.controller.g2core.controller.IG2CoreControllerService#resumeMotion() */ @Override public void resumeMotion() throws GkException { getCommunicator().resumeMotion(); getExecutionService().resumeQueueExecution(); } /** (inheritDoc) * @see org.goko.controller.g2core.controller.IG2CoreControllerService#startMotion() */ @Override public void startMotion() throws GkException { getCommunicator().startMotion(); getExecutionService().beginQueueExecution(ExecutionQueueType.DEFAULT); } /** (inheritDoc) * @see org.goko.controller.g2core.controller.IG2CoreControllerService#resetTinyG() */ @Override public void resetTinyG() throws GkException { getCommunicator().resetG2Core(); } /** (inheritDoc) * @see org.goko.controller.g2core.controller.IG2CoreControllerService#turnSpindleOn() */ @Override public void turnSpindleOn() throws GkException { getCommunicator().turnSpindleOn(); } /** (inheritDoc) * @see org.goko.controller.g2core.controller.IG2CoreControllerService#turnSpindleOff() */ @Override public void turnSpindleOff() throws GkException { getCommunicator().turnSpindleOff(); } /** (inheritDoc) * @see org.goko.controller.g2core.controller.IG2CoreControllerService#resetZero(java.util.List) */ @Override public void resetZero(List<String> axes) throws GkException { getCommunicator().resetZero(axes); } /** (inheritDoc) * @see org.goko.controller.g2core.controller.IG2CoreControllerService#startHomingSequence() */ @Override public void startHomingSequence() throws GkException { String homingCommand = "G28.2"; if(G2CorePreferences.getInstance().isHomingEnabledAxisX()){ homingCommand += " X0"; } if(G2CorePreferences.getInstance().isHomingEnabledAxisY()){ homingCommand += " Y0"; } if(G2CorePreferences.getInstance().isHomingEnabledAxisZ()){ homingCommand += " Z0"; } if(G2CorePreferences.getInstance().isHomingEnabledAxisA()){ homingCommand += " A0"; } getCommunicator().send(homingCommand, true); } /** (inheritDoc) * @see org.goko.core.controller.IWorkVolumeProvider#getWorkVolumeProviderName() */ @Override public String getWorkVolumeProviderName() { return "G2 Core"; } /** (inheritDoc) * @see org.goko.core.controller.IWorkVolumeProvider#findWorkVolumeMinimalPosition() */ @Override public Tuple6b findWorkVolumeMinimalPosition() throws GkException { G2CoreConfiguration cfg = getConfiguration(); Tuple6b min = null; if(cfg != null){ min = new Tuple6b(Units.MILLIMETRE, AngleUnit.DEGREE_ANGLE); min.setX( Length.valueOf( cfg.getSetting(G2Core.Configuration.Groups.X_AXIS, G2Core.Configuration.Axes.TRAVEL_MINIMUM, BigDecimal.class), Units.MILLIMETRE)); min.setY( Length.valueOf( cfg.getSetting(G2Core.Configuration.Groups.Y_AXIS, G2Core.Configuration.Axes.TRAVEL_MINIMUM, BigDecimal.class), Units.MILLIMETRE)); min.setZ( Length.valueOf( cfg.getSetting(G2Core.Configuration.Groups.Z_AXIS, G2Core.Configuration.Axes.TRAVEL_MINIMUM, BigDecimal.class), Units.MILLIMETRE)); } return min; } /** (inheritDoc) * @see org.goko.core.controller.IWorkVolumeProvider#findWorkVolumeMaximalPosition() */ @Override public Tuple6b findWorkVolumeMaximalPosition() throws GkException { G2CoreConfiguration cfg = getConfiguration(); Tuple6b min = null; if(cfg != null){ min = new Tuple6b(Units.MILLIMETRE, AngleUnit.DEGREE_ANGLE); min.setX( Length.valueOf( cfg.getSetting(G2Core.Configuration.Groups.X_AXIS, G2Core.Configuration.Axes.TRAVEL_MAXIMUM, BigDecimal.class), Units.MILLIMETRE)); min.setY( Length.valueOf( cfg.getSetting(G2Core.Configuration.Groups.Y_AXIS, G2Core.Configuration.Axes.TRAVEL_MAXIMUM, BigDecimal.class), Units.MILLIMETRE)); min.setZ( Length.valueOf( cfg.getSetting(G2Core.Configuration.Groups.Z_AXIS, G2Core.Configuration.Axes.TRAVEL_MAXIMUM, BigDecimal.class), Units.MILLIMETRE)); } return min; } /** (inheritDoc) * @see org.goko.controller.tinyg.commons.AbstractTinyGControllerService#detectWorkVolumeUpdate(org.goko.controller.tinyg.commons.configuration.AbstractTinyGConfiguration, org.goko.controller.tinyg.commons.configuration.AbstractTinyGConfiguration) */ @Override protected boolean detectWorkVolumeUpdate(G2CoreConfiguration currentConfiguration, G2CoreConfiguration newConfiguration) { return newConfiguration.isCompletelyLoaded() && (detectWorkVolumeUpdateOnAxis(G2Core.Configuration.Groups.X_AXIS, currentConfiguration, newConfiguration) || detectWorkVolumeUpdateOnAxis(G2Core.Configuration.Groups.Y_AXIS, currentConfiguration, newConfiguration) || detectWorkVolumeUpdateOnAxis(G2Core.Configuration.Groups.Z_AXIS, currentConfiguration, newConfiguration) || detectWorkVolumeUpdateOnAxis(G2Core.Configuration.Groups.A_AXIS, currentConfiguration, newConfiguration) || detectWorkVolumeUpdateOnAxis(G2Core.Configuration.Groups.B_AXIS, currentConfiguration, newConfiguration) || detectWorkVolumeUpdateOnAxis(G2Core.Configuration.Groups.C_AXIS, currentConfiguration, newConfiguration)); } /** * Detect any work volume update on the given axis * @param axis the axis * @param currentConfiguration the current configuration * @param newConfiguration the new configuration * @return <code>true</code> if an update was detected, <code>false</code> otherwise */ protected boolean detectWorkVolumeUpdateOnAxis(String axis, G2CoreConfiguration currentConfiguration, G2CoreConfiguration newConfiguration){ try { BigDecimal oldMinValue = currentConfiguration.getSetting(axis, G2Core.Configuration.Axes.TRAVEL_MINIMUM, BigDecimal.class); BigDecimal newMinValue = newConfiguration.getSetting(axis, G2Core.Configuration.Axes.TRAVEL_MINIMUM, BigDecimal.class); if(oldMinValue != null && newMinValue != null && oldMinValue.compareTo(newMinValue) != 0){ return true; } BigDecimal oldMaxValue = currentConfiguration.getSetting(axis, G2Core.Configuration.Axes.TRAVEL_MAXIMUM, BigDecimal.class); BigDecimal newMaxValue = newConfiguration.getSetting(axis, G2Core.Configuration.Axes.TRAVEL_MAXIMUM, BigDecimal.class); if(oldMaxValue != null && newMaxValue != null && oldMaxValue.compareTo(newMaxValue) != 0){ return true; } } catch (GkException e) { LOG.error(e); } return false; } /** (inheritDoc) * @see org.goko.core.controller.IControllerConfigurationFileExporter#getFileExtension() */ @Override public String getFileExtension() { return "g2v099.cfg"; } /** (inheritDoc) * @see org.goko.core.controller.IControllerConfigurationFileExporter#canExport() */ @Override public boolean canExport() throws GkException { return G2Core.State.READY.equals(getState()) || G2Core.State.PROGRAM_STOP.equals(getState()) || G2Core.State.PROGRAM_END.equals(getState()); } /** (inheritDoc) * @see org.goko.core.controller.IControllerConfigurationFileImporter#canImport() */ @Override public boolean canImport() throws GkException { return canExport(); } /** (inheritDoc) * @see org.goko.controller.g2core.controller.IG2CoreControllerService#getAvailablePlannerBuffer() */ @Override public int getAvailablePlannerBuffer() { try { return getInternalState().getAvailableBuffer(); } catch (GkException e) { LOG.error(e); return 0; } } /** (inheritDoc) * @see org.goko.controller.g2core.controller.IG2CoreControllerService#setAvailablePlannerBuffer(int) */ @Override public void setAvailablePlannerBuffer(int available) { try { getInternalState().setAvailableBuffer(available); // TODO : find a better way to deal with this getExecutor().onBufferSpaceAvailableChange(available); } catch (GkException e) { LOG.error(e); } } /** * Handle a GCode response * @param command the confirmed command. Can be <code>null</code> depending on board verbosity * @param status the returned status */ public void handleGCodeResponse(String command, ITinyGStatus status) throws GkException{ ExecutionState state = getExecutionService().getExecutionState(); if(state == ExecutionState.RUNNING || state == ExecutionState.PAUSED || state == ExecutionState.ERROR ){ if(status == G2CoreStatusCode.STAT_OK){ getExecutor().confirmNextLineExecution(); }else{ notifyNonOkStatus(status, StringUtils.EMPTY); getExecutor().handleNonOkStatus(status); } }else{ if(status != G2CoreStatusCode.STAT_OK){ notifyNonOkStatus(status, command); } } } /** (inheritDoc) * @see org.goko.controller.tinyg.commons.AbstractTinyGControllerService#onQueueExecutionComplete() */ @Override public void onQueueExecutionComplete() throws GkException { super.onQueueExecutionComplete(); if(probeUtility != null){ probeUtility.clearProbingGCode(); } } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onQueueExecutionCanceled() */ @Override public void onQueueExecutionCanceled() throws GkException { super.onQueueExecutionCanceled(); if(probeUtility != null){ probeUtility.clearProbingGCode(); } } /** (inheritDoc) * @see org.goko.core.controller.IProbingService#isReadyToProbe() */ @Override public boolean isReadyToProbe() { try { return getCommunicator().isConnected() && isReadyForFileStreaming(); } catch (GkException e) { LOG.error(e); return false; } } /** (inheritDoc) * @see org.goko.core.controller.IProbingService#probe(java.util.List) */ @Override public CompletionService<ProbeResult> probe(List<ProbeRequest> lstProbeRequest) throws GkException { probeUtility = new ProbeUtility(this); probeUtility.prepare(lstProbeRequest); IGCodeProvider probeGCodeProvider = probeUtility.getProbeGCodeProvider(); getExecutionService().clearExecutionQueue(ExecutionQueueType.SYSTEM); getExecutionService().addToExecutionQueue(ExecutionQueueType.SYSTEM, probeGCodeProvider); getExecutionService().beginQueueExecution(ExecutionQueueType.SYSTEM); return probeUtility.getExecutorCompletionService(); } /** (inheritDoc) * @see org.goko.controller.tinyg.commons.AbstractTinyGControllerService#handleProbeResult(boolean, org.goko.core.math.Tuple6b) */ @Override public void handleProbeResult(boolean probeSuccess, Tuple6b probePosition) throws GkException { probeUtility.handleProbeResult(probeSuccess, probePosition); } /** * Handles any TinyG Status that is not TG_OK * @param status the received status or <code>null</code> if unknown * @param receivedCommand the received command * @throws GkException GkException */ protected void notifyNonOkStatus(ITinyGStatus status, String receivedCommand) throws GkException { String message = StringUtils.EMPTY; if(status == null){ message = " Unknown error status"; }else{ if(status.isError()){ // Error report message = "Error status returned : "+status.getValue() +" - "+status.getLabel(); LOG.error(message); getApplicativeLogService().log(ApplicativeLogEvent.LOG_ERROR, message, "G2Core"); EventBrokerUtils.send(eventAdmin, new G2CoreExecutionErrorTopic(), new TinyGExecutionError("Error reported durring execution", "Execution was paused after TinyG reported an error. You can resume, or stop the execution at your own risk.", message)); }else if(status.isWarning()){ // Warning report message = "Warning status returned : "+status.getValue() +" - "+status.getLabel(); LOG.warn(message); getApplicativeLogService().log(ApplicativeLogEvent.LOG_WARNING, message, "G2Core"); } } } /** * @return the eventAdmin */ public EventAdmin getEventAdmin() { return eventAdmin; } /** * @param eventAdmin the eventAdmin to set */ public void setEventAdmin(EventAdmin eventAdmin) { this.eventAdmin = eventAdmin; } /** * Set the last received message * @param message the message * @throws GkException GkException */ public void setMessage(String message) throws GkException{ getInternalState().setMessage(message); } /** (inheritDoc) * @see org.goko.controller.tinyg.commons.AbstractTinyGControllerService#resetConfiguration() */ @Override public void resetConfiguration() { setConfiguration( new G2CoreConfiguration() ); } }