/*
*
* 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.math.BigDecimal;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.goko.controller.grbl.v08.bean.StatusReport;
import org.goko.core.common.GkUtils;
import org.goko.core.common.buffer.ByteCommandBuffer;
import org.goko.core.common.exception.GkException;
import org.goko.core.common.exception.GkFunctionalException;
import org.goko.core.common.measure.quantity.Length;
import org.goko.core.common.measure.units.Unit;
import org.goko.core.connection.DataPriority;
import org.goko.core.connection.EnumConnectionEvent;
import org.goko.core.connection.IConnectionDataListener;
import org.goko.core.connection.IConnectionListener;
import org.goko.core.connection.IConnectionService;
import org.goko.core.gcode.rs274ngcv3.context.CoordinateSystemFactory;
import org.goko.core.log.GkLog;
import org.goko.core.math.Tuple6b;
public class GrblCommunicator implements IConnectionDataListener, IConnectionListener {
/** LOG */
private static final GkLog LOG = GkLog.getLogger(GrblCommunicator.class);
/** The target Grbl service */
private GrblControllerService grbl;
/** Grbl end line delimiter */
private char endLineCharDelimiter;
/** Buffer for incoming data */
private ByteCommandBuffer incomingBuffer;
/** The connection service */
private IConnectionService connectionService;
/**
* Constructor
*/
protected GrblCommunicator(GrblControllerService grbl) {
this.grbl = grbl;
endLineCharDelimiter = '\n';
incomingBuffer = new ByteCommandBuffer((byte) endLineCharDelimiter);
}
/** (inheritDoc)
* @see org.goko.core.connection.IConnectionDataListener#onDataReceived(java.util.List)
*/
@Override
public void onDataReceived(List<Byte> data) throws GkException {
incomingBuffer.addAll(data);
while(incomingBuffer.hasNext()){
handleIncomingData(GkUtils.toString(incomingBuffer.unstackNextCommand()));
}
}
/** (inheritDoc)
* @see org.goko.core.connection.IConnectionDataListener#onDataSent(java.util.List)
*/
@Override
public void onDataSent(List<Byte> data) throws GkException {
// TODO Auto-generated method stub
}
@Override
public void onConnectionEvent(EnumConnectionEvent event) throws GkException {
if(event == EnumConnectionEvent.CONNECTED){
incomingBuffer.clear();
getConnectionService().addInputDataListener(this);
grbl.startStatusPolling();
}else if(event == EnumConnectionEvent.DISCONNECTED){
getConnectionService().removeInputDataListener(this);
grbl.stopStatusPolling();
grbl.setState(GrblMachineState.UNDEFINED);
incomingBuffer.clear();
}
}
/**
* Handling of incoming data
* @param data the received data
* @throws GkException GkException
*/
protected void handleIncomingData(String data) throws GkException{
String trimmedData = StringUtils.trim(data);
if(StringUtils.isNotEmpty(trimmedData)){
/* Received OK response */
if(StringUtils.equals(trimmedData, Grbl.OK_RESPONSE)){
grbl.handleOkResponse();
/* Received error */
}else if(StringUtils.startsWith(trimmedData, "error:")){
grbl.handleError(trimmedData);
/* Received status report */
}else if(StringUtils.startsWith(trimmedData, "<") && StringUtils.endsWith(trimmedData, ">")){
grbl.handleStatusReport(parseStatusReport(trimmedData));
/* Received Grbl header */
}else if(StringUtils.startsWith(trimmedData, "Grbl")){
handleHeader(trimmedData);
grbl.initialiseConnectedState();
// refreshStatus();
/* Received a configuration confirmation */
}else if(StringUtils.defaultString(trimmedData).matches("\\$[0-9]*=.*")){
grbl.handleConfigurationReading(trimmedData);
/* Received a work position report */
}else if(StringUtils.defaultString(trimmedData).matches("\\[G5.*\\]")){
Tuple6b targetPoint = new Tuple6b().setNull();
String offsetName = parseCoordinateSystem(trimmedData, targetPoint);
grbl.setCoordinateSystemOffset(new CoordinateSystemFactory().get(offsetName), targetPoint);
/* Received an offset position report */
}else if(StringUtils.defaultString(trimmedData).matches("\\[(G92|G28|G30).*\\]")){
// Tuple6b targetPoint = new Tuple6b().setNull();
// String coordinateSystemName = parseCoordinateSystem(trimmedData, targetPoint);
// grbl.setOffsetCoordinate(coordinateSystemName, targetPoint);
// TODO Handle G92
/* Parser state report */
}else if(StringUtils.defaultString(trimmedData).matches("\\[(G0|G1|G2|G3).*\\]")){
grbl.receiveParserState(StringUtils.substringBetween(trimmedData, "[","]"));
/* Unkown format received */
}else{
LOG.error("Ignoring received data "+ trimmedData);
grbl.getApplicativeLogService().warning("Ignoring received data "+ trimmedData, GrblControllerService.SERVICE_ID);
}
}
}
/**
* Parse Grbl header
* @param grblHeader the received header
*/
private void handleHeader(String grblHeader) {
String[] tokens = StringUtils.split(grblHeader, " ");
if(tokens != null && tokens.length >= 2){
LOG.info("Grbl version is "+tokens[1]);
}
}
/**
* Create a status report from the given string
* @param strStatusReport the String representing the status report
* @return {@link StatusReport}
* @throws GkException
*/
private StatusReport parseStatusReport(String strStatusReport) throws GkException{
StatusReport result = new StatusReport();
int comma = StringUtils.indexOf(strStatusReport, ",");
String state = StringUtils.substring(strStatusReport, 1, comma);
GrblMachineState grblState = grbl.getGrblStateFromString (state);
result.setState(grblState);
// Looking for MPosition
String mpos = StringUtils.substringBetween(strStatusReport, "MPos:", ",WPos");
String wpos = StringUtils.substringBetween(strStatusReport, "WPos:",">");
Tuple6b machinePosition = new Tuple6b().setNull();
Tuple6b workPosition = new Tuple6b().setNull();
String[] machineCoordinates = StringUtils.split(mpos,",");
parseTuple(machineCoordinates, machinePosition);
result.setMachinePosition(machinePosition);
String[] workCoordinates = StringUtils.split(wpos,",");
parseTuple(workCoordinates, workPosition);
result.setWorkPosition(workPosition);
return result;
}
private void parseTuple(String[] values, Tuple6b target) throws GkException{
if(values != null && values.length >= 3){
Unit<Length> unit = grbl.getConfiguration().getReportUnit();
if(NumberUtils.isNumber(values[0])){
target.setX(Length.valueOf(new BigDecimal(values[0]), unit));
}
if(NumberUtils.isNumber(values[1])){
target.setY(Length.valueOf(new BigDecimal(values[1]), unit));
}
if(NumberUtils.isNumber(values[2])){
target.setZ(Length.valueOf(new BigDecimal(values[2]), unit));
}
}
}
private String parseCoordinateSystem(String strOrigin, Tuple6b targetPoint) throws GkException{
String identifier = StringUtils.substringBetween(strOrigin, "[", ":");
String valuesGroup = StringUtils.substringBetween(strOrigin, ":", "]");
String[] values = StringUtils.split(valuesGroup, ",");
Unit<Length> unit = grbl.getConfiguration().getReportUnit();
if(values == null || values.length < 3){
throw new GkFunctionalException("Received incomplete offset report "+strOrigin+". Ignoring...");
}
BigDecimal x = new BigDecimal(values[0]);
BigDecimal y = new BigDecimal(values[1]);
BigDecimal z = new BigDecimal(values[2]);
targetPoint.setX(Length.valueOf(x, unit));
targetPoint.setY(Length.valueOf(y, unit));
targetPoint.setZ(Length.valueOf(z, unit));
return identifier;
}
/**
* Add the end line character at the end of the given list
* @param list the list
*/
private void addEndLineCharacter(List<Byte> list){
//command.add(new Byte((byte) endLineCharDelimiter));
list.add(new Byte((byte) endLineCharDelimiter));
}
/**
* @return the connectionService
*/
protected IConnectionService getConnectionService() {
return connectionService;
}
/**
* @param connectionService the connectionService to set
* @throws GkException GkException
*/
protected void setConnectionService(IConnectionService connectionService) throws GkException {
this.connectionService = connectionService;
this.connectionService.addConnectionListener(this);
}
protected void send(List<Byte> lstByte) throws GkException{
addEndLineCharacter(lstByte);
getConnectionService().send(lstByte);
}
protected void sendWithoutEndLineCharacter(List<Byte> lstByte) throws GkException{
getConnectionService().send(lstByte);
}
protected void sendImmediately(List<Byte> lstByte) throws GkException{
getConnectionService().send(lstByte, DataPriority.IMPORTANT);
}
}