/**
* tgFX Driver Class
* Copyright (C) 2014 Synthetos LLC. All Rights reserved.
* http://www.synthetos.com
*
*/
package tgfx.tinyg;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Tab;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import jssc.SerialPortException;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.json.JSONException;
import tgfx.Main;
import tgfx.ResponseParser;
import tgfx.SerialDriver;
import tgfx.SerialWriter;
import tgfx.ui.gcode.GcodeLine;
import tgfx.system.Axis;
import tgfx.system.Machine;
import tgfx.system.Motor;
import tgfx.hardwarePlatforms.HardwarePlatformManager;
import tgfx.utility.AsyncTimer;
public class TinygDriver extends Observable {
static final Logger logger = Logger.getLogger(TinygDriver.class);
public Machine machine = Machine.getInstance();
public QueueReport qr = QueueReport.getInstance();
public MnemonicManager mneManager = new MnemonicManager();
public ResponseManager resManager = new ResponseManager();
public CommandManager cmdManager = new CommandManager();
private String[] message = new String[2];
public SimpleBooleanProperty connectionStatus = new SimpleBooleanProperty(false);
public HardwarePlatformManager hardwarePlatformManager = new HardwarePlatformManager();
/**
* Static commands for TinyG to get settings from the TinyG Driver Board
*/
public ArrayList<String> connections = new ArrayList<>();
private final SerialDriver ser = SerialDriver.getInstance();
public static ArrayBlockingQueue<String> jsonQueue = new ArrayBlockingQueue<>(10000);
public static ArrayBlockingQueue<byte[]> queue = new ArrayBlockingQueue<>(30);
public static ArrayBlockingQueue<GcodeLine[]> writerQueue = new ArrayBlockingQueue<>(50000);
public ResponseParser resParse = new ResponseParser();
public SerialWriter serialWriter = new SerialWriter(writerQueue);
private boolean PAUSED = false;
public final static int MAX_BUFFER = 1024;
private final AtomicBoolean connectionSemaphore = new AtomicBoolean(false);
private AsyncTimer connectionTimer;
private boolean timedout = false;
public void setAsyncTimer(AsyncTimer value){
connectionTimer = value;
}
public AsyncTimer getAsyncTimer(){
return connectionTimer;
}
/**
* gets the Connection semaphore
* @return the Connection semaphore
*/
public AtomicBoolean getConnectionSemaphore(){
return connectionSemaphore;
}
public boolean isTimedout() {
return timedout;
}
public void setTimedout(boolean timedout) {
this.timedout = timedout;
}
public void notifyBuildChanged() throws IOException, JSONException {
if(machine.hardwarePlatform.getMinimalBuildVersion() < this.machine.getFirmwareBuild()){
//This checks to see if the current build version on TinyG is greater than what tgFX's hardware profile needs.
}
if (machine.getFirmwareBuild() < TinygDriver.getInstance().machine.hardwarePlatform.getMinimalBuildVersion() &&
this.machine.getFirmwareBuild() != 0.0) {
//too old of a build we need to tell the GUI about this... This is where PUB/SUB will fix this
//bad way of alerting the gui about model changes.
message[0] = "BUILD_ERROR";
message[1] = Double.toString(TinygDriver.getInstance().machine.getFirmwareBuild());
setChanged();
notifyObservers(message);
logger.debug("Build Version: " + TinygDriver.getInstance().machine.getFirmwareBuild() + " is NOT OK");
} else if(machine.getFirmwareBuild() == 0.0){
}else {
logger.debug("Build Version: " + TinygDriver.getInstance().machine.getFirmwareBuild() + " is OK");
message[0] = "BUILD_OK";
message[1] = null;
setChanged();
notifyObservers(message);
}
}
public void sendReconnectRequest(){
Main.postConsoleMessage("Attempting to reconnecto to TinyG...");
logger.info("Reconnect Request Sent.");
message[0] = "RECONNECT";
message[1] = null;
setChanged();
notifyObservers(message);
}
public void sendDisconnectRequest(){
logger.info("Disconnect Request Sent.");
message[0] = "DISCONNECT";
message[1] = null;
setChanged();
notifyObservers(message);
}
public static TinygDriver getInstance() {
return TinygDriverHolder.INSTANCE;
}
public void queryHardwareSingleAxisSettings(char c) {
//Our queryHardwareSingleAxisSetting function for chars
queryHardwareSingleAxisSettings(String.valueOf(c));
}
public void queryHardwareSingleAxisSettings(String _axis) {
try {
switch (_axis.toLowerCase()) {
case "x":
serialWriter.write(CommandManager.CMD_QUERY_AXIS_X);
break;
case "y":
ser.write(CommandManager.CMD_QUERY_AXIS_Y);
break;
case "z":
ser.write(CommandManager.CMD_QUERY_AXIS_Z);
break;
case "a":
ser.write(CommandManager.CMD_QUERY_AXIS_A);
break;
case "b":
ser.write(CommandManager.CMD_QUERY_AXIS_B);
break;
case "c":
ser.write(CommandManager.CMD_QUERY_AXIS_C);
break;
}
} catch (Exception ex) {
Main.print("[!]Error in queryHardwareSingleMotorSettings() " + ex.getMessage());
}
}
public void applyHardwareAxisSettings(Tab _tab) throws Exception {
GridPane _gp = (GridPane) _tab.getContent();
int size = _gp.getChildren().size();
Axis _axis = this.machine.getAxisByName(String.valueOf(_gp.getId().charAt(0)));
int i;
for (i = 0; i < size; i++) {
if (_gp.getChildren().get(i).getClass().toString().contains("TextField")) {
//This ia a TextField... Lets get the value and apply it if it needs to be applied.
TextField tf = (TextField) _gp.getChildren().get(i);
applyHardwareAxisSettings(_axis, tf);
} else if (_gp.getChildren().get(i) instanceof ChoiceBox) {
//This ia a ChoiceBox... Lets get the value and apply it if it needs to be applied.
@SuppressWarnings("unchecked")
ChoiceBox<Object> cb = (ChoiceBox<Object>) _gp.getChildren().get(i);
if (cb.getId().contains("AxisMode")) {
int axisMode = cb.getSelectionModel().getSelectedIndex();
String configObj = String.format("{\"%s%s\":%s}\n", _axis.getAxis_name().toLowerCase(), MnemonicManager.MNEMONIC_AXIS_AXIS_MODE, axisMode);
this.write(configObj);
continue;
} else if (cb.getId().contains("switchModeMax")) {
int switchMode = cb.getSelectionModel().getSelectedIndex();
String configObj = String.format("{\"%s%s\":%s}\n", _axis.getAxis_name().toLowerCase(), MnemonicManager.MNEMONIC_AXIS_MAX_SWITCH_MODE, switchMode);
this.write(configObj);
} else if (cb.getId().contains("switchModeMin")) {
int switchMode = cb.getSelectionModel().getSelectedIndex();
String configObj = String.format("{\"%s%s\":%s}\n", _axis.getAxis_name().toLowerCase(), MnemonicManager.MNEMONIC_AXIS_MIN_SWITCH_MODE, switchMode);
this.write(configObj);
}
}
}
Main.print("[+]Applying Axis Settings...");
}
public void applyHardwareMotorSettings(Motor _motor, TextField tf) throws Exception {
if (tf.getId().contains("StepAngle")) {
if (_motor.getStep_angle() != Float.valueOf(tf.getText())) {
this.write("{\"" + _motor.getId_number() + MnemonicManager.MNEMONIC_MOTOR_STEP_ANGLE + "\":" + tf.getText() + "}\n");
}
} else if (tf.getId().contains("TravelPer")) {
if (_motor.getStep_angle() != Float.valueOf(tf.getText())) {
this.write("{\"" + _motor.getId_number() + MnemonicManager.MNEMONIC_MOTOR_TRAVEL_PER_REVOLUTION + "\":" + tf.getText() + "}\n");
}
}
}
public void applyHardwareAxisSettings(Axis _axis, TextField tf) throws Exception {
/**
* Apply Axis Settings to TinyG from GUI
*/
if (tf.getId().contains("maxVelocity")) {
if (_axis.getVelocityMaximum() != Double.valueOf(tf.getText())) {
//We check to see if the value passed was already set in TinyG
//To avoid un-needed EEPROM Writes.
this.write("{\"" + _axis.getAxis_name().toLowerCase() + MnemonicManager.MNEMONIC_AXIS_VELOCITY_MAXIMUM + "\":" + tf.getText() + "}\n");
}
} else if (tf.getId().contains("maxFeed")) {
if (_axis.getFeed_rate_maximum() != Double.valueOf(tf.getText())) {
//We check to see if the value passed was already set in TinyG
//To avoid un-needed EEPROM Writes.
this.write("{\"" + _axis.getAxis_name().toLowerCase() + MnemonicManager.MNEMONIC_AXIS_FEEDRATE_MAXIMUM + "\":" + tf.getText() + "}\n");
}
} else if (tf.getId().contains("maxTravel")) {
if (_axis.getTravel_maximum() != Double.valueOf(tf.getText())) {
//We check to see if the value passed was already set in TinyG
//To avoid un-needed EEPROM Writes.
this.write("{\"" + _axis.getAxis_name().toLowerCase() + MnemonicManager.MNEMONIC_AXIS_TRAVEL_MAXIMUM + "\":" + tf.getText() + "}\n");
}
} else if (tf.getId().contains("maxJerk")) {
if (_axis.getJerkMaximum() != Double.valueOf(tf.getText())) {
//We check to see if the value passed was already set in TinyG
//To avoid un-needed EEPROM Writes.
this.write("{\"" + _axis.getAxis_name().toLowerCase() + MnemonicManager.MNEMONIC_AXIS_JERK_MAXIMUM + "\":" + tf.getText() + "}\n");
}
} else if (tf.getId().contains("junctionDeviation")) {
if (Double.valueOf(_axis.getJunction_devation()).floatValue() != Double.valueOf(tf.getText())) {
//We check to see if the value passed was already set in TinyG
//To avoid un-needed EEPROM Writes.
this.write("{\"" + _axis.getAxis_name().toLowerCase() + MnemonicManager.MNEMONIC_AXIS_JUNCTION_DEVIATION + "\":" + tf.getText() + "}\n");
}
} else if (tf.getId().contains("radius")) {
if (_axis.getAxisType().equals(Axis.AXIS_TYPE.ROTATIONAL)) {
//Check to see if its a ROTATIONAL AXIS...
if (_axis.getRadius() != Double.valueOf(tf.getText())) {
//We check to see if the value passed was already set in TinyG
//To avoid un-needed EEPROM Writes.
this.write("{\"" + _axis.getAxis_name().toLowerCase() + MnemonicManager.MNEMONIC_AXIS_RADIUS + "\":" + tf.getText() + "}\n");
}
}
} else if (tf.getId().contains("searchVelocity")) {
if (_axis.getSearch_velocity() != Double.valueOf(tf.getText())) {
//We check to see if the value passed was already set in TinyG
//To avoid un-needed EEPROM Writes.
this.write("{\"" + _axis.getAxis_name().toLowerCase() + MnemonicManager.MNEMONIC_AXIS_SEARCH_VELOCITY + "\":" + tf.getText() + "}\n");
}
} else if (tf.getId().contains("latchVelocity")) {
if (_axis.getLatch_velocity() != Double.valueOf(tf.getText())) {
//We check to see if the value passed was already set in TinyG
//To avoid un-needed EEPROM Writes.
this.write("{\"" + _axis.getAxis_name().toLowerCase() + MnemonicManager.MNEMONIC_AXIS_LATCH_VELOCITY + "\":" + tf.getText() + "}\n");
}
} else if (tf.getId().contains("latchBackoff")) {
if (_axis.getLatch_backoff() != Double.valueOf(tf.getText())) {
//We check to see if the value passed was already set in TinyG
//To avoid un-needed EEPROM Writes.
this.write("{\"" + _axis.getAxis_name().toLowerCase() + MnemonicManager.MNEMONIC_AXIS_LATCH_BACKOFF + "\":" + tf.getText() + "}\n");
}
} else if (tf.getId().contains("zeroBackoff")) {
if (_axis.getZero_backoff() != Double.valueOf(tf.getText())) {
//We check to see if the value passed was already set in TinyG
//To avoid un-needed EEPROM Writes.
this.write("{\"" + _axis.getAxis_name().toLowerCase() + MnemonicManager.MNEMONIC_AXIS_ZERO_BACKOFF + "\":" + tf.getText() + "}\n");
}
}
Main.print("[+]Applying " + _axis.getAxis_name() + " settings");
}
public void getMotorSettings(int motorNumber) {
try {
if (motorNumber == 1) {
ser.write(CommandManager.CMD_QUERY_MOTOR_1_SETTINGS);
} else if (motorNumber == 2) {
ser.write(CommandManager.CMD_QUERY_MOTOR_2_SETTINGS);
} else if (motorNumber == 3) {
ser.write(CommandManager.CMD_QUERY_MOTOR_3_SETTINGS);
} else if (motorNumber == 4) {
ser.write(CommandManager.CMD_QUERY_MOTOR_4_SETTINGS);
} else {
TinygDriver.logger.error("Invalid Motor Number.. Please try again..");
}
} catch (Exception ex) {
TinygDriver.logger.error(ex.getMessage());
}
}
public void applyResponseCommand(responseCommand rc) {
char _ax;
switch (rc.getSettingKey()) {
case (MnemonicManager.MNEMONIC_STATUS_REPORT_LINE):
TinygDriver.getInstance().machine.setLineNumber(Integer.valueOf(rc.getSettingValue()));
TinygDriver.logger.debug("[APPLIED:" + rc.getSettingParent() + " " + rc.getSettingKey() + ":" + rc.getSettingValue());
break;
case (MnemonicManager.MNEMONIC_STATUS_REPORT_MOTION_MODE):
TinygDriver.logger.debug("[DID NOT APPLY NEED TO CODE THIS IN:" + rc.getSettingParent() + " " + rc.getSettingKey() + ":" + rc.getSettingValue());
// TinygDriver.getInstance().m.setMotionMode(Integer.valueOf(rc.getSettingValue()));
break;
case (MnemonicManager.MNEMONIC_STATUS_REPORT_POSA):
_ax = rc.getSettingKey().charAt(rc.getSettingKey().length() - 1);
TinygDriver.getInstance().machine.getAxisByName(String.valueOf(_ax)).setWorkPosition(Float.valueOf(rc.getSettingValue()));
TinygDriver.logger.debug("[APPLIED:" + rc.getSettingParent() + " " + rc.getSettingKey() + ":" + rc.getSettingValue());
break;
case (MnemonicManager.MNEMONIC_STATUS_REPORT_POSX):
_ax = rc.getSettingKey().charAt(rc.getSettingKey().length() - 1);
TinygDriver.getInstance().machine.getAxisByName(String.valueOf(_ax)).setWorkPosition(Float.valueOf(rc.getSettingValue()));
TinygDriver.logger.debug("[APPLIED:" + rc.getSettingParent() + " " + rc.getSettingKey() + ":" + rc.getSettingValue());
break;
case (MnemonicManager.MNEMONIC_STATUS_REPORT_POSY):
_ax = rc.getSettingKey().charAt(rc.getSettingKey().length() - 1);
TinygDriver.getInstance().machine.getAxisByName(String.valueOf(_ax)).setWorkPosition(Float.valueOf(rc.getSettingValue()));
TinygDriver.logger.debug("[APPLIED:" + rc.getSettingParent() + " " + rc.getSettingKey() + ":" + rc.getSettingValue());
break;
case (MnemonicManager.MNEMONIC_STATUS_REPORT_POSZ):
_ax = rc.getSettingKey().charAt(rc.getSettingKey().length() - 1);
TinygDriver.getInstance().machine.getAxisByName(String.valueOf(_ax)).setWorkPosition(Float.valueOf(rc.getSettingValue()));
TinygDriver.logger.debug("[APPLIED:" + rc.getSettingParent() + " " + rc.getSettingKey() + ":" + rc.getSettingValue());
break;
case (MnemonicManager.MNEMONIC_STATUS_REPORT_STAT):
//TinygDriver.getInstance()(Float.valueOf(rc.getSettingValue()));
TinygDriver.logger.debug("[APPLIED:" + rc.getSettingParent() + " " + rc.getSettingKey() + ":" + rc.getSettingValue());
break;
case (MnemonicManager.MNEMONIC_STATUS_REPORT_VELOCITY):
TinygDriver.getInstance().machine.setVelocity(Double.valueOf(rc.getSettingValue()));
TinygDriver.logger.debug("[APPLIED:" + rc.getSettingParent() + " " + rc.getSettingKey() + ":" + rc.getSettingValue());
break;
default:
logger.error("[ERROR] in ApplyResponseCommand: Command Was:" + rc.getSettingParent() + " " + rc.getSettingKey() + ":" + rc.getSettingValue());
break;
}
}
public void applyHardwareMotorSettings(Tab _tab) throws Exception {
/**
* Apply Motor Settings to TinyG from GUI
*/
Tab selectedTab = _tab.getTabPane().getSelectionModel().getSelectedItem();
int _motorNumber = Integer.valueOf(selectedTab.getText().split(" ")[1].toString());
Motor _motor = this.machine.getMotorByNumber(_motorNumber);
GridPane _gp = (GridPane) _tab.getContent();
int size = _gp.getChildren().size();
int i;
//Iterate though each gridpane child... Picking out text fields and choice boxes
for (i = 0; i < size; i++) {
if (_gp.getChildren().get(i).toString().contains("TextField")) {
TextField tf = (TextField) _gp.getChildren().get(i);
try {
applyHardwareMotorSettings(_motor, tf);
} catch (Exception _ex) {
logger.error("[!]Exception in applyHardwareMotorSettings(Tab _tab)");
}
} else if (_gp.getChildren().get(i) instanceof ChoiceBox) {
@SuppressWarnings("unchecked")
ChoiceBox<Object> _cb = (ChoiceBox<Object>) _gp.getChildren().get(i);
if (_cb.getId().contains("MapAxis")) {
int mapAxis;
switch (_cb.getSelectionModel().getSelectedItem().toString()) {
case "X":
mapAxis = 0;
break;
case "Y":
mapAxis = 1;
break;
case "Z":
mapAxis = 2;
break;
case "A":
mapAxis = 3;
break;
case "B":
mapAxis = 4;
break;
case "C":
mapAxis = 5;
break;
default:
mapAxis = 0; //Defaults to map to X
}
String configObj = String.format("{\"%s\":{\"%s\":%s}}\n", _motorNumber, MnemonicManager.MNEMONIC_MOTOR_MAP_AXIS, mapAxis);
this.write(configObj);
} else if (_cb.getId().contains("MicroStepping")) {
//This is the MapAxis Choice Box... Lets apply that
int microSteps;
switch (_cb.getSelectionModel().getSelectedIndex()) {
case 0:
microSteps = 1;
break;
case 1:
microSteps = 2;
break;
case 2:
microSteps = 4;
break;
case 3:
microSteps = 8;
break;
default:
microSteps = 1;
}
String configObj = String.format("{\"%s%s\":%s}\n", _motorNumber, MnemonicManager.MNEMONIC_MOTOR_MICROSTEPS, microSteps);
this.write(configObj);
} else if (_cb.getId().contains("Polarity")) {
String configObj = String.format("{\"%s%s\":%s}\n", _motorNumber, MnemonicManager.MNEMONIC_MOTOR_POLARITY, _cb.getSelectionModel().getSelectedIndex());
this.write(configObj);
} else if (_cb.getId().contains("PowerMode")) {
String configObj = String.format("{\"%s%s\":%s}\n", _motorNumber, MnemonicManager.MNEMONIC_MOTOR_POWER_MANAGEMENT, _cb.getSelectionModel().getSelectedIndex());
this.write(configObj);
}
}
}
}
public void queryHardwareSingleMotorSettings(int motorNumber) {
try {
if (motorNumber == 1) {
ser.write(CommandManager.CMD_QUERY_MOTOR_1_SETTINGS);
} else if (motorNumber == 2) {
ser.write(CommandManager.CMD_QUERY_MOTOR_2_SETTINGS);
} else if (motorNumber == 3) {
ser.write(CommandManager.CMD_QUERY_MOTOR_3_SETTINGS);
} else if (motorNumber == 4) {
ser.write(CommandManager.CMD_QUERY_MOTOR_4_SETTINGS);
} else {
Main.print("Invalid Motor Number.. Please try again..");
setChanged();
}
} catch (Exception ex) {
Main.print("[!]Error in queryHardwareSingleMotorSettings() " + ex.getMessage());
}
}
private TinygDriver() {
//Setup Logging for TinyG Driver
if (Main.LOGLEVEL.equals("INFO")) {
logger.setLevel(Level.INFO);
} else if (Main.LOGLEVEL.equals("ERROR")) {
logger.setLevel(Level.ERROR);
} else {
logger.setLevel(Level.OFF);
}
}
private static class TinygDriverHolder {
private static final TinygDriver INSTANCE = new TinygDriver();
}
@Override
public synchronized void addObserver(Observer obsrvr) {
super.addObserver(obsrvr);
}
public void appendJsonQueue(String line) {
// This adds full normalized json objects to our jsonQueue.
TinygDriver.jsonQueue.add(line);
}
public synchronized void appendResponseQueue(byte[] queue) {
// Add byte arrays to the buffer queue from tinyG's responses.
try {
TinygDriver.queue.put((byte[]) queue);
} catch (Exception e) {
Main.print("ERROR appending to the Response Queue");
}
}
public boolean isPAUSED() {
return PAUSED;
}
public void setPAUSED(boolean choice) throws Exception {
if (choice) { // if set to pause
ser.priorityWrite(CommandManager.CMD_APPLY_PAUSE);
PAUSED = choice;
} else { // set to resume
ser.priorityWrite(CommandManager.CMD_QUERY_OK_PROMPT);
ser.priorityWrite(CommandManager.CMD_APPLY_RESUME);
ser.priorityWrite(CommandManager.CMD_QUERY_OK_PROMPT);
PAUSED = false;
}
}
/**
* Connection Methods
* @param choice
*/
public void setConnected(boolean choice) {
this.ser.setConnected(choice);
}
public boolean initialize(String portName, int dataRate) throws SerialPortException {
return (this.ser.initialize(portName, dataRate));
}
public void disconnect() throws SerialPortException {
this.ser.disconnect();
}
public SimpleBooleanProperty isConnected() {
//Our binding to keep tabs in the us of if we are connected to TinyG or not.
//This is mostly used to disable the UI if we are not connected.
connectionStatus.set(this.ser.isConnected());
return (connectionStatus);
}
/**
* All Methods involving writing to TinyG.. This messages will call the
* SerialDriver write methods from here.
* @param msg
* @throws java.lang.Exception
*/
public synchronized void write(String msg) throws Exception {
TinygDriver.getInstance().serialWriter.addCommandToBuffer(msg);
if(!Main.LOGLEVEL.equals("OFF")){
Main.print("+" + msg);
}
}
public void priorityWrite(Byte b) throws Exception {
this.ser.priorityWrite(b);
if(!Main.LOGLEVEL.equals("OFF")){
Main.print("+" + String.valueOf(b));
}
}
public void priorityWrite(String msg) throws Exception {
if (!msg.contains("\n")) {
msg = msg + "\n";
}
ser.write(msg);
if(!Main.LOGLEVEL.equals("OFF")){
Main.print("+" + msg);
}
}
/**
*
*
*
*
* Utility Methods
*
* @return
*/
public String[] listSerialPorts() {
// Get a listing current system serial ports
String portArray[];
portArray = SerialDriver.listSerialPorts();
return portArray;
}
public String getPortName() {
// Return the serial port name that is connected.
return ser.serialPort.getPortName();
}
public List<Axis> getInternalAllAxis() {
return (Machine.getInstance().getAllAxis());
}
}