Backend architecture
Similar to the front-end there are more layers on the backend to help with supporting differences between different gcode controllers and the different ways to communicate with these controllers. Because UGS depends on serial events from CNC devices, the communication between layers is also event driven. This is implemented using a series of Listener classes which pass messages from the lower levels to the upper levels whenever data is detected on the serial port (USB).
Controller
A controller is primarily responsible for implementing controller-specific
features. Different features can be things like what happens when a
Perform Homing
command is requested, or how to issue status requests and
parse their results. GRBL
and TinyG
are both supported, they share a
lot of code with the AbstractController.java abstract class.
Internally the AbstractController class implements several important
things. It manages the stream lifecycle, keeping track of which commands have
been sent, which have been completed and in some cases which are queued for
sending. The controller also figures out when the stream has finished. Finally
the AbstractController implements the SerialCommunicatorListener
, which
how its able to detect all of this state information (and allows commands to
be sent to the CNC controller).
The controller provides a ControllerListener
interface which is used to
provide real time status.
Finally, the AbstractController defines a number of abstract methods which can be used by device specific controllers as needed to hook into important lifecycle events:
abstract protected void closeCommBeforeEvent();
abstract protected void closeCommAfterEvent();
protected void openCommAfterEvent() throws Exception {}
abstract protected void cancelSendBeforeEvent();
abstract protected void cancelSendAfterEvent();
abstract protected void pauseStreamingEvent() throws Exception;
abstract protected void resumeStreamingEvent() throws Exception;
abstract protected void isReadyToSendCommandsEvent() throws Exception;
abstract protected void statusUpdatesEnabledValueChanged(boolean enabled);
abstract protected void statusUpdatesRateValueChanged(int rate);
// This one is special, because it is responsible for parsing device
// responses, such as a command complete, status string, or parsing a
// status event. In the case of a command complete, it must call
// `commandComplete` to push the stream lifecycle along.
abstract protected void rawResponseHandler(String response);
Here is the public interface which controlles conform to:
public interface IController {
/*
Observable
*/
public void addListener(ControllerListener cl);
/*
Actions
*/
public void performHomingCycle() throws Exception;
public void returnToHome() throws Exception;
public void resetCoordinatesToZero() throws Exception;
public void resetCoordinateToZero(final char coord) throws Exception;
public void killAlarmLock() throws Exception;
public void toggleCheckMode() throws Exception;
public void viewParserState() throws Exception;
public void issueSoftReset() throws Exception;
/*
Behavior
*/
public void setSingleStepMode(boolean enabled);
public boolean getSingleStepMode();
public void setStatusUpdatesEnabled(boolean enabled);
public boolean getStatusUpdatesEnabled();
public void setStatusUpdateRate(int rate);
public int getStatusUpdateRate();
public GcodeCommandCreator getCommandCreator();
public long getJobLengthEstimate(File gcodeFile);
/*
Serial
*/
public Boolean openCommPort(String port, int portRate) throws Exception;
public Boolean closeCommPort() throws Exception;
public Boolean isCommOpen();
/*
Stream information
*/
public Boolean isReadyToStreamFile() throws Exception;
public Boolean isStreamingFile();
public long getSendDuration();
public int rowsInSend();
public int rowsSent();
public int rowsRemaining();
/*
Stream control
*/
public void beginStreaming() throws Exception;
public void pauseStreaming() throws Exception;
public void resumeStreaming() throws Exception;
public void cancelSend();
/*
Stream content
*/
public GcodeCommand createCommand(String gcode) throws Exception;
public void sendCommandImmediately(GcodeCommand cmd) throws Exception;
public void queueCommand(GcodeCommand cmd) throws Exception;
public void queueStream(GcodeStreamReader r);
public void queueRawStream(Reader r);
}
Communicator
A communicator handles all levels of sending data to the device. Raw responses
are returned to any listeners via the SerialCommunicatorListener
.
The AbstractCommunicator
implements several listener utilities which are used
by implementing classes.
The BufferedCommunicator
abstract class handles the process of buffering
multiple commands at once in order to keep a constant stream of commands
available to the CNC device. It does this in the streamCommands method by
maintaining a list of active commands, and the current size of those commands. A
method named processedCommand
must be implemented in a subclass to determine
whether a raw response indicates a command has completed. This notifies the
BufferedCommunicator that it should attempt to send more commands.
GrblCommunicator
and TinyGCommunicator
are two concrete implementations of
the BufferedCommunicator.
Connection
This is a very thin layer which provides a way to write and receive data:
abstract public boolean openPort(String name, int baud) throws Exception;
abstract public void closePort() throws Exception;
abstract public boolean isOpen();
abstract public void sendByteImmediately(byte b) throws Exception;
abstract public void sendStringToComm(String command) throws Exception;
Streaming strategy
UGS attempts to use a fixed amount of memory when streaming a file. In this way
it can send gcode files of any size. Files are preprocessed at the BackendAPI
level using the GcodeStreamWriter
class. This will serialize all the required
metadata into a file. Later on that file can be opened with the
GcodeStreamReader
class, the Controller and Communicator classes use
this. Using the reader, the Communicator class can pull out commands one at
a time and send them to the Connection.