/* * Copyright (C) 2013-2014 Synthetos LLC. All Rights reserved. * http://www.synthetos.com */ package tgfx; import org.json.*; import java.util.Iterator; import java.util.Observable; import javafx.application.Platform; import jfxtras.labs.dialogs.MonologFX; import jfxtras.labs.dialogs.MonologFXBuilder; import jfxtras.labs.dialogs.MonologFXButton; import static jfxtras.labs.dialogs.MonologFXButton.Type.YES; import jfxtras.labs.dialogs.MonologFXButtonBuilder; import org.apache.log4j.Level; import org.apache.log4j.Logger; import static tgfx.tinyg.MnemonicManager.MNEMONIC_GROUP_AXIS_A; import static tgfx.tinyg.MnemonicManager.MNEMONIC_GROUP_AXIS_B; import static tgfx.tinyg.MnemonicManager.MNEMONIC_GROUP_AXIS_C; import static tgfx.tinyg.MnemonicManager.MNEMONIC_GROUP_AXIS_X; import static tgfx.tinyg.MnemonicManager.MNEMONIC_GROUP_AXIS_Y; import static tgfx.tinyg.MnemonicManager.MNEMONIC_GROUP_AXIS_Z; import static tgfx.tinyg.MnemonicManager.MNEMONIC_GROUP_MOTOR_1; import static tgfx.tinyg.MnemonicManager.MNEMONIC_GROUP_MOTOR_2; import static tgfx.tinyg.MnemonicManager.MNEMONIC_GROUP_MOTOR_3; import static tgfx.tinyg.MnemonicManager.MNEMONIC_GROUP_MOTOR_4; import static tgfx.tinyg.MnemonicManager.MNEMONIC_GROUP_SYSTEM; import static tgfx.tinyg.MnemonicManager.MNEMONIC_GROUP_STATUS_REPORT; import static tgfx.tinyg.MnemonicManager.MNEMONIC_GROUP_EMERGENCY_SHUTDOWN; import tgfx.tinyg.TinygDriver; import tgfx.tinyg.responseCommand; /** * * @author ril3y */ public class ResponseParser extends Observable implements Runnable { /** * logger instance */ private static final Logger logger = Logger.getLogger(ResponseParser.class); private boolean TEXT_MODE = false; private String[] message = new String[2]; boolean RUN = true; String buf = ""; public ResponseFooter responseFooter = new ResponseFooter(); //our holder for ResponseFooter Data //These values are for mapping what n'Th element inthe json footer array maps to which values. private static final int FOOTER_ELEMENT_PROTOCOL_VERSION = 0; private static final int FOOTER_ELEMENT_STATUS_CODE = 1; private static final int FOOTER_ELEMENT_RX_RECVD = 2; private static final int FOOTER_ELEMENT_CHECKSUM = 3; private JSONArray footerValues; private String line; public ResponseParser() { //Setup Logging for ResponseParser if (Main.LOGLEVEL.equals("INFO")) { logger.setLevel(Level.INFO); } else if (Main.LOGLEVEL.equals("ERROR")) { logger.setLevel(Level.ERROR); } else { logger.setLevel(Level.OFF); } } public boolean isTEXT_MODE() { return TEXT_MODE; } public void setTEXT_MODE(boolean TEXT_MODE) { this.TEXT_MODE = TEXT_MODE; } @Override public void run() { logger.info("Response Parser Running"); if (!Main.LOGLEVEL.equals("OFF")) { Main.print("[+]Response Parser Thread Running..."); } while (RUN) { try { line = TinygDriver.jsonQueue.take(); if (line.equals("")) { continue; } if (line.startsWith("{")) { if (isTEXT_MODE()) { setTEXT_MODE(false); //This checks to see if we WERE in textmode. If we were we notify the user that we are not longer and update the system state. setChanged(); message[0] = "TEXTMODE_REPORT"; message[1] = "[+]JSON Response Detected... Leaving Text mode.. Querying System State....\n"; notifyObservers(message); try { TinygDriver.getInstance().cmdManager.queryAllMachineSettings(); TinygDriver.getInstance().cmdManager.queryAllHardwareAxisSettings(); TinygDriver.getInstance().cmdManager.queryAllMotorSettings(); } catch (Exception ex) { logger.error("Error leaving Text mode and querying Motor, Machine and Axis Settings."); } } parseJSON(line); //Take a line from the response queue when its ready and parse it. } else { //Text Mode Response if (!isTEXT_MODE()) { //We are just entering text mode and need to alert the user. //This will fire the every time user is entering text mode. setTEXT_MODE(true); setChanged(); message[0] = "TEXTMODE_REPORT"; message[1] = "[+]User has entered text mode. To exit type \"{\" and hit enter.\n"; notifyObservers(message); } setChanged(); message[0] = "TEXTMODE_REPORT"; message[1] = line + "\n"; notifyObservers(message); } } catch (InterruptedException | JSONException ex) { logger.error("[!]Error in responseParser run(): " + ex.getMessage()); } } } private boolean isJsonObject(JSONObject js, String strVal) throws Exception { if (js.get(strVal).getClass().toString().contains("JSONObject")) { return true; } else { return false; } } public void applySettingMasterGroup(JSONObject js, String pg) throws Exception { if (pg.equals(MNEMONIC_GROUP_STATUS_REPORT)) { //This is a status report master object that came in through a response object. //meaning that the response was asked for like this {"sr":""} and returned like this. //{"r":{"sr":{"line":0,"posx":0.000,"posy":0.000,"posz":0.000,"posa":0.000,"vel":0.000,"unit":1,"momo":0,"stat":3},"f":[1,0,10,885]}} //Right now its parsed down to JUST the json object for the SR like so. //{"sr":{"line":0,"posx":0.000,"posy":0.000,"posz":0.000,"posa":0.000,"vel":0.000,"unit":1,"momo":0,"stat":3},"f":[1,0,10,885]} //so we can now just pass it to the applySettingStatusReport method. applySettingStatusReport(js); } else { if (js.keySet().size() > 1) { Iterator ii = js.keySet().iterator(); //This is a special multi single value response object while (ii.hasNext()) { String key = ii.next().toString(); if (key.equals("f")) { parseFooter(js.getJSONArray("f")); //This is very important. We break out our response footer.. error codes.. bytes availble in hardware buffer etc. } else { responseCommand rc = TinygDriver.getInstance().mneManager.lookupSingleGroupMaster(key, pg); if (rc == null) { //This happens when a new mnemonic has been added to the tinyG firmware but not added to tgFX's MnemonicManger //This is the error case logger.error("Mnemonic Lookup Failed in applySettingsMasterGroup. \n\tMake sure there are not new elements added to TinyG and not to the MnemonicManager Class.\n\tMNEMONIC FAILED: " + key); } else { //This is the normal case rc.setSettingValue(js.get(key).toString()); String parentGroup = rc.getSettingParent(); _applySettings(rc.buildJsonObject(), rc.getSettingParent()); //we will supply the parent object name for each key pai } } } } } } public void applySettingStatusReport(JSONObject js) { /** * This breaks the mold a bit.. but its more efficient. This gets called * off the top of ParseJson if it has an "SR" in it. Sr's are called so * often that instead of walking the normal parsing tree.. this skips to * the end */ try { Iterator ii = js.keySet().iterator(); //This is a special multi single value response object while (ii.hasNext()) { String key = ii.next().toString(); responseCommand rc = new responseCommand(MNEMONIC_GROUP_SYSTEM, key.toString(), js.get(key).toString()); TinygDriver.getInstance().machine.applyJsonStatusReport(rc); // _applySettings(rc.buildJsonObject(), rc.getSettingParent()); //we will supply the parent object name for each key pair } setChanged(); message[0] = "STATUS_REPORT"; message[1] = null; notifyObservers(message); } catch (Exception ex) { logger.error("[!] Error in applySettingStatusReport(JsonOBject js) : " + ex.getMessage()); logger.error("[!]js.tostring " + js.toString()); setChanged(); message[0] = "STATUS_REPORT"; message[1] = null; notifyObservers(message); } } public void set_Changed() { this.setChanged(); } public void applySetting(JSONObject js) { try { if (js.length() == 0) { //This is a blank object we just return and move on } else if (js.keySet().size() > 1) { //If there are more than one object in the json response Iterator ii = js.keySet().iterator(); //This is a special multi single value response object while (ii.hasNext()) { String key = ii.next().toString(); switch (key) { case "f": parseFooter(js.getJSONArray("f")); //This is very important. //We break out our response footer.. error codes.. bytes availble in hardware buffer etc. break; case "msg": message[0] = "TINYG_USER_MESSAGE"; message[1] = (String) js.get(key) + "\n"; logger.info("[+]TinyG Message Sent: " + js.get(key) + "\n"); setChanged(); notifyObservers(message); break; case "rx": TinygDriver.getInstance().serialWriter.setBuffer(js.getInt(key)); break; default: if (TinygDriver.getInstance().mneManager.isMasterGroupObject(key)) { // logger.info("Group Status Report Detected: " + key); applySettingMasterGroup(js.getJSONObject(key), key); continue; } responseCommand rc = TinygDriver.getInstance().mneManager.lookupSingleGroup(key); rc.setSettingValue(js.get(key).toString()); _applySettings(rc.buildJsonObject(), rc.getSettingParent()); //we will supply the parent object name for each key pair break; } } } else { /* This else follows: * Contains a single object in the JSON response */ if (js.keySet().contains("f")) { /** * This is a single response footer object: Like So: * {"f":[1,0,5,3330]} */ parseFooter(js.getJSONArray("f")); } else { /** * Contains a single object in the json response I am not * sure this else is needed any longer. */ _applySettings(js, js.keys().next().toString()); } } } catch (JSONException ex) { logger.error("[!] Error in applySetting(JsonOBject js) : " + ex.getMessage()); logger.error("[!]JSON String Was: " + js.toString()); logger.error("Error in Line: " + js); } catch (Exception ex) { logger.error("[!] Error in applySetting(JsonOBject js) : " + ex.getMessage()); logger.error(ex.getClass().toString()); } } private void _applySettings(JSONObject js, String pg) throws Exception { switch (pg) { case (MNEMONIC_GROUP_MOTOR_1): TinygDriver.getInstance().machine.getMotorByNumber(MNEMONIC_GROUP_MOTOR_1) .applyJsonSystemSetting(js.getJSONObject(MNEMONIC_GROUP_MOTOR_1), MNEMONIC_GROUP_MOTOR_1); setChanged(); message[0] = "CMD_GET_MOTOR_SETTINGS"; message[1] = MNEMONIC_GROUP_MOTOR_1; notifyObservers(message); break; case (MNEMONIC_GROUP_MOTOR_2): TinygDriver.getInstance().machine.getMotorByNumber(MNEMONIC_GROUP_MOTOR_2) .applyJsonSystemSetting(js.getJSONObject(MNEMONIC_GROUP_MOTOR_2), MNEMONIC_GROUP_MOTOR_2); setChanged(); message[0] = "CMD_GET_MOTOR_SETTINGS"; message[1] = MNEMONIC_GROUP_MOTOR_2; notifyObservers(message); break; case (MNEMONIC_GROUP_MOTOR_3): TinygDriver.getInstance().machine.getMotorByNumber(MNEMONIC_GROUP_MOTOR_3) .applyJsonSystemSetting(js.getJSONObject(MNEMONIC_GROUP_MOTOR_3), MNEMONIC_GROUP_MOTOR_3); setChanged(); message[0] = "CMD_GET_MOTOR_SETTINGS"; message[1] = MNEMONIC_GROUP_MOTOR_3; notifyObservers(message); break; case (MNEMONIC_GROUP_MOTOR_4): TinygDriver.getInstance().machine.getMotorByNumber(MNEMONIC_GROUP_MOTOR_4) .applyJsonSystemSetting(js.getJSONObject(MNEMONIC_GROUP_MOTOR_4), MNEMONIC_GROUP_MOTOR_4); setChanged(); message[0] = "CMD_GET_MOTOR_SETTINGS"; message[1] = MNEMONIC_GROUP_MOTOR_4; notifyObservers(message); break; case (MNEMONIC_GROUP_AXIS_X): TinygDriver.getInstance().machine.getAxisByName(MNEMONIC_GROUP_AXIS_X).applyJsonSystemSetting(js.getJSONObject(MNEMONIC_GROUP_AXIS_X), MNEMONIC_GROUP_AXIS_X); setChanged(); message[0] = "CMD_GET_AXIS_SETTINGS"; message[1] = MNEMONIC_GROUP_AXIS_X; notifyObservers(message); break; case (MNEMONIC_GROUP_AXIS_Y): TinygDriver.getInstance().machine.getAxisByName(MNEMONIC_GROUP_AXIS_Y) .applyJsonSystemSetting(js.getJSONObject(MNEMONIC_GROUP_AXIS_Y), MNEMONIC_GROUP_AXIS_Y); setChanged(); message[0] = "CMD_GET_AXIS_SETTINGS"; message[1] = MNEMONIC_GROUP_AXIS_Y; notifyObservers(message); break; case (MNEMONIC_GROUP_AXIS_Z): TinygDriver.getInstance().machine.getAxisByName(MNEMONIC_GROUP_AXIS_Z) .applyJsonSystemSetting(js.getJSONObject(MNEMONIC_GROUP_AXIS_Z), MNEMONIC_GROUP_AXIS_Z); setChanged(); message[0] = "CMD_GET_AXIS_SETTINGS"; message[1] = MNEMONIC_GROUP_AXIS_Z; notifyObservers(message); break; case (MNEMONIC_GROUP_AXIS_A): TinygDriver.getInstance().machine.getAxisByName(MNEMONIC_GROUP_AXIS_A) .applyJsonSystemSetting(js.getJSONObject(MNEMONIC_GROUP_AXIS_A), MNEMONIC_GROUP_AXIS_A); setChanged(); message[0] = "CMD_GET_AXIS_SETTINGS"; message[1] = MNEMONIC_GROUP_AXIS_A; notifyObservers(message); break; case (MNEMONIC_GROUP_AXIS_B): TinygDriver.getInstance().machine.getAxisByName(MNEMONIC_GROUP_AXIS_B) .applyJsonSystemSetting(js.getJSONObject(MNEMONIC_GROUP_AXIS_B), MNEMONIC_GROUP_AXIS_B); setChanged(); message[0] = "CMD_GET_AXIS_SETTINGS"; message[1] = MNEMONIC_GROUP_AXIS_B; notifyObservers(message); break; case (MNEMONIC_GROUP_AXIS_C): TinygDriver.getInstance().machine.getAxisByName(MNEMONIC_GROUP_AXIS_C) .applyJsonSystemSetting(js.getJSONObject(MNEMONIC_GROUP_AXIS_C), MNEMONIC_GROUP_AXIS_C); setChanged(); message[0] = "CMD_GET_AXIS_SETTINGS"; message[1] = MNEMONIC_GROUP_AXIS_C; notifyObservers(message); break; case ("hom"): logger.info("HOME"); break; case ("msg"): //NOP break; case (MNEMONIC_GROUP_SYSTEM): TinygDriver.getInstance().machine.applyJsonSystemSetting(js.getJSONObject(MNEMONIC_GROUP_SYSTEM), MNEMONIC_GROUP_SYSTEM); /** * UNCOMMENT THIS BELOW WHEN WE HAVE MACHINE SETTINGS THAT NEED * TO UPDATE THE GU */ message[0] = "MACHINE_UPDATE"; message[1] = null; setChanged(); notifyObservers(message); break; case (MNEMONIC_GROUP_STATUS_REPORT): logger.info("Status Report"); applySettingMasterGroup(js, MNEMONIC_GROUP_STATUS_REPORT); setChanged(); message[0] = "STATUS_REPORT"; message[1] = null; notifyObservers(message); break; case (MNEMONIC_GROUP_EMERGENCY_SHUTDOWN): Platform.runLater(new Runnable() { @Override public void run() { Main.postConsoleMessage("TinyG Alarm " + line); MonologFXButton btnYes = MonologFXButtonBuilder.create() .defaultButton(true) .icon("/testmonologfx/dialog_apply.png") .type(MonologFXButton.Type.YES) .build(); MonologFXButton btnNo = MonologFXButtonBuilder.create() .cancelButton(true) .icon("/testmonologfx/dialog_cancel.png") .type(MonologFXButton.Type.CANCEL) .build(); MonologFX mono = MonologFXBuilder.create() .titleText("Error Occured") .message("You have triggered a limit switch. TinyG is now in DISABLED mode. \n" + "Manually back your machine off of its limit switches.\n Once done, if you would like to re-enable TinyG click yes.") .button(btnYes) .button(btnNo) .type(MonologFX.Type.ERROR) .build(); MonologFXButton.Type retval = mono.showDialog(); switch (retval) { case YES: logger.info("Clicked Yes"); try { TinygDriver.getInstance().priorityWrite((byte) 0x18); } catch (Exception ex) { logger.error(ex); } break; case CANCEL: logger.info("Clicked No"); Main.postConsoleMessage("TinyG will remain in diabled mode until you power cycle or click the reset button."); break; } } }); default: //This is for single settings xfr, 1tr etc... //This is pretty ugly but it gets the key and the value. For single values. responseCommand rc = TinygDriver.getInstance().mneManager.lookupSingleGroup(pg); // String _parent = String.valueOf(parentGroup.charAt(0)); String newJs; // String _key = parentGroup; //I changed this to deal with the fb mnemonic.. not sure if this works all over. rc.setSettingValue(String.valueOf(js.get(js.keys().next().toString()))); logger.info("Single Key Value: Group:" + rc.getSettingParent() + " key:" + rc.getSettingKey() + " value:" + rc.getSettingValue()); if (rc.getSettingValue().equals((""))) { logger.info(rc.getSettingKey() + " value was null"); } else { this.applySetting(rc.buildJsonObject()); //We pass the new json object we created from the string above } } } public void applySettings(String newJsObjString) { //When a single key value pair is sent without the group object //We use this method to create a new json object try { JSONObject newJs = new JSONObject(newJsObjString); applySetting(newJs); } catch (Exception ex) { logger.error("Invalid Attempt to create newJs object"); } } private void parseFooter(JSONArray footerValues) { try { //Checking to see if we have a footer response //Status reports will not have a footer so this is for everything else responseFooter.setProtocolVersion(footerValues.getInt(FOOTER_ELEMENT_PROTOCOL_VERSION)); responseFooter.setStatusCode(footerValues.getInt(FOOTER_ELEMENT_STATUS_CODE)); responseFooter.setRxRecvd(footerValues.getInt(FOOTER_ELEMENT_RX_RECVD)); responseFooter.setCheckSum(footerValues.getInt(FOOTER_ELEMENT_STATUS_CODE)); //Out footer object is not populated int beforeBytesReturned = TinygDriver.getInstance().serialWriter.getBufferValue(); //Make sure we do not add bytes to a already full buffer if (beforeBytesReturned != TinygDriver.MAX_BUFFER) { TinygDriver.getInstance().serialWriter.addBytesReturnedToBuffer(responseFooter.getRxRecvd()); int afterBytesReturned = TinygDriver.getInstance().serialWriter.getBufferValue(); logger.debug("Returned " + responseFooter.getRxRecvd() + " to buffer... Buffer was " + beforeBytesReturned + " is now " + afterBytesReturned); TinygDriver.getInstance().serialWriter.notifyAck(); //We let our serialWriter thread know we have added some space to the buffer. //Lets tell the UI the new size of the buffer message[0] = "BUFFER_UPDATE"; message[1] = String.valueOf(afterBytesReturned); setChanged(); notifyObservers(message); } } catch (Exception ex) { logger.error("Error parsing json footer"); } } public synchronized void parseJSON(String line) throws JSONException { //logger.info("Got Line: " + line + " from TinyG."); if (!Main.LOGLEVEL.equals("OFF")) { Main.print("-" + line); } final JSONObject js = new JSONObject(line); if (js.has("r") || (js.has("sr")) || (js.has("tgfx"))) { //tgfx is for messages like timeout connections Platform.runLater(new Runnable() { @Override public void run() { try { if (js.has("tgfx")) { //This is for when tgfx times out when trying to connect to TinyG. //tgFX puts a message in the response parser queue to be parsed here. setChanged(); message[0] = "TINYG_CONNECTION_TIMEOUT"; message[1] = (String) js.get("tgfx") + "\n"; notifyObservers(message); } else if (js.has("f")) { //The new version of TinyG's footer has a footer element in each response. //We parse it here parseFooter(js.getJSONArray("f")); if (js.has("r")) { applySetting(js.getJSONObject("r")); } else if (js.has("sr")) { applySettingStatusReport(js.getJSONObject("sr")); } } else { //This is where the old footer style is dealt with //These are the 2 types of responses we will get back. switch (js.keys().next().toString()) { case ("r"): applySetting(js.getJSONObject("r")); break; case ("sr"): applySettingStatusReport(js.getJSONObject("sr")); break; } } } catch (JSONException ex) { logger.error(ex); } } }); } else if (js.has("qr")) { TinygDriver.getInstance().qr.parse(js); } else if (js.has("er")) { applySetting(js); } } }