From 3613d475ba2c4ed16f1595302cec4201fc626190 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 21 Oct 2020 09:49:48 -0700 Subject: [PATCH 001/125] Add Start/Stop Mission Functionality -Added static state values to Serial.java -Added Start/Stop Mission Button to waypoint panel -Implemented start/stop serial communication. -Added stop movement message to clear waypoints command. -Enabled baud rate selector on map panel for easier APM communications setup and testing at varying baud rates. --- src/Dashboard.java | 3 +- src/map/WaypointPanel.java | 35 +++++++++++++++++++++-- src/map/command/WaypointCommandClear.java | 1 + src/serial/Messages/Message.java | 21 ++++++++++++++ src/serial/Messages/WordMessage.java | 14 ++++++--- src/serial/Serial.java | 25 ++++++++++++++++ src/serial/SerialSender.java | 18 ++++++++++++ 7 files changed, 110 insertions(+), 7 deletions(-) diff --git a/src/Dashboard.java b/src/Dashboard.java index f053025..dfd95a2 100644 --- a/src/Dashboard.java +++ b/src/Dashboard.java @@ -127,7 +127,8 @@ public void disconnectRequest() { } }; SerialConnectPanel serialPanel = new SerialConnectPanel(connectActions); - + serialPanel.showBaudSelector(true); + JPanel messageBox = createAlertBox(); MapPanel mapPanel = new MapPanel(context, diff --git a/src/map/WaypointPanel.java b/src/map/WaypointPanel.java index 9a31522..30bd55a 100644 --- a/src/map/WaypointPanel.java +++ b/src/map/WaypointPanel.java @@ -183,11 +183,14 @@ private void buildPanel() { JButton redoButton = theme.makeButton(redoCommandAction); JButton saveButton = theme.makeButton(saveWaypoints); JButton loadButton = theme.makeButton(loadWaypoints); + JButton missionButton = theme.makeButton(toggleMovement); JComponent[] format = new JComponent[] { tileButton, dataPanel, graphButton, - reTarget, looping, config, logPanelButton, clearWaypoints + reTarget, looping, config, logPanelButton, + clearWaypoints, missionButton }; + for(JComponent jc : format) { jc.setAlignmentX(Component.CENTER_ALIGNMENT); jc.setMaximumSize(buttonSize); @@ -248,7 +251,11 @@ public EditBoxSpec(JTextField ref, String label) { editorPanels.add(panel); } - JPanel waypointOptions = new JPanel(new GridLayout(3,2,5,5)); + int COLS = 3; + int ROWS = 2; + int PADDING = 5; + JPanel waypointOptions = new JPanel( + new GridLayout(COLS, ROWS, PADDING, PADDING)); waypointOptions.setOpaque(false); waypointOptions.add(newButton); waypointOptions.add(enterButton); @@ -282,6 +289,8 @@ public EditBoxSpec(JTextField ref, String label) { add(reTarget); add(Box.createRigidArea(space)); add(looping); + add(Box.createRigidArea(space)); + add(missionButton); } private double fixedToDouble(int i) { @@ -447,6 +456,28 @@ public void actionPerformed(ActionEvent e) { } }; + private boolean isUnitMoving = false; + private Action toggleMovement = new AbstractAction() { + { + String text = "Start Mission"; + putValue(Action.NAME, text); + putValue(Action.SHORT_DESCRIPTION, "Start/Stop Mission"); + } + + public void actionPerformed(ActionEvent e) { + if(isUnitMoving) { + context.sender.changeMovement(false); + putValue(Action.NAME, "Start Mission"); + isUnitMoving = false; + } + else { + context.sender.changeMovement(true); + putValue(Action.NAME, "Stop Mission"); + isUnitMoving = true; + } + } + }; + private Action previousWaypoint = new AbstractAction() { { String text = "<"; diff --git a/src/map/command/WaypointCommandClear.java b/src/map/command/WaypointCommandClear.java index 6d88ce6..3fd83a1 100644 --- a/src/map/command/WaypointCommandClear.java +++ b/src/map/command/WaypointCommandClear.java @@ -36,6 +36,7 @@ public boolean execute() { if(context.sender != null) { context.sender.sendWaypointList(); + context.sender.changeMovement(false); } return true; diff --git a/src/serial/Messages/Message.java b/src/serial/Messages/Message.java index cd7271a..9f2877e 100644 --- a/src/serial/Messages/Message.java +++ b/src/serial/Messages/Message.java @@ -73,43 +73,64 @@ public String toString() { public static Message setWaypoint(byte index, Dot dot) { return new WaypointMessage(Serial.ALTER_WAYPOINT, index, dot); } + public static Message addWaypoint(byte index, Dot dot) { return new WaypointMessage(Serial.ADD_WAYPOINT, index, dot); } + public static Message confirmSum(int sum) { return new WordMessage(Serial.CONFIRMATION, sum); } + public static Message syncMessage(byte resync) { return new WordMessage(Serial.SYNC_WORD, resync, (byte)0); } + public static Message telemetry(byte index, float data) { return new DataMessage(Serial.TELEMETRY_DATA, index, data); } + public static Message setSetting(byte index, float data) { return new DataMessage(Serial.SETTING_DATA, index, data); } + public static Message errorString(String err) { return new StringMessage(Serial.ERROR_STRING, err); } + public static Message stateString(String state) { return new StringMessage(Serial.STATE_STRING, state); } + public static Message command(byte cmd, byte spec) { return new WordMessage(Serial.COMMAND_WORD, cmd, spec); } + public static Message estop() { return new WordMessage(Serial.COMMAND_WORD, Serial.ESTOP_CMD, (byte)0); } + public static Message setTarget(byte index) { return new WordMessage(Serial.COMMAND_WORD, Serial.TARGET_CMD, index); } + public static Message setLooping(byte index) { return new WordMessage(Serial.COMMAND_WORD, Serial.LOOPING_CMD, index); } + public static Message clearWaypoints() { return new WordMessage(Serial.COMMAND_WORD, Serial.CLEAR_CMD, (byte)0); } + public static Message deleteWaypoint(byte index) { return new WordMessage(Serial.COMMAND_WORD, Serial.DELETE_CMD, index); } + + public static Message stopDriving() { + return new WordMessage(Serial.COMMAND_WORD, Serial.STOP_CMD, (byte)0); + } + + public static Message startDriving() { + return new WordMessage(Serial.COMMAND_WORD, Serial.START_CMD, (byte)0); + } } diff --git a/src/serial/Messages/WordMessage.java b/src/serial/Messages/WordMessage.java index 77b9294..3d75b6f 100644 --- a/src/serial/Messages/WordMessage.java +++ b/src/serial/Messages/WordMessage.java @@ -11,21 +11,22 @@ public WordMessage(int subtype, byte a, byte b) { msgType = subtype; content = new byte[3]; - content[0] = Serial.buildMessageLabel(Serial.WORD_TYPE, - subtype); + content[0] = Serial.buildMessageLabel(Serial.WORD_TYPE, subtype); content[1] = a; content[2] = b; buildChecksum(); - subType = a; + this.subType = a; } public WordMessage(int subtype, int ab) { this(subtype, (byte)((subtype>>8)&0xff), (byte)((subtype)&0xff) ); } + @Override public boolean needsConfirm() { return false; } + @Override public String toString() { switch(msgType) { @@ -34,10 +35,11 @@ public String toString() { case Serial.SYNC_WORD: return "Syncronization Message"; case Serial.COMMAND_WORD: - return nameCommand(subType); + return nameCommand(this.subType); } return "Word Message"; } + private String nameCommand(int command) { switch (command) { case Serial.ESTOP_CMD: @@ -50,6 +52,10 @@ private String nameCommand(int command) { return "Clear Waypoints Command"; case Serial.DELETE_CMD: return "Delete Waypoint Command"; + case Serial.STOP_CMD: + return "Stop Movement Command"; + case Serial.START_CMD: + return "Start Movement Command"; } return "Command Message"; } diff --git a/src/serial/Serial.java b/src/serial/Serial.java index fb20074..fc49c93 100644 --- a/src/serial/Serial.java +++ b/src/serial/Serial.java @@ -20,6 +20,7 @@ public class Serial { public static final int CONFIRMATION = 0x0; public static final int SYNC_WORD = 0x1; public static final int COMMAND_WORD = 0x2; + public static final int STATE_WORD = 0x3; //string type public static final int ERROR_STRING = 0x0; @@ -31,6 +32,30 @@ public class Serial { public static final byte LOOPING_CMD = 0x2; public static final byte CLEAR_CMD = 0x3; public static final byte DELETE_CMD = 0x4; + public static final byte STOP_CMD = 0x5; + public static final byte START_CMD = 0x6; + + //state types + public static final byte APM_STATE = 0x0; + public static final byte DRIVE_STATE = 0x1; + public static final byte AUTO_STATE = 0x2; + + //APM state sub values + public static final byte APM_STATE_INIT = 0x0; + public static final byte APM_STATE_SELF_TEST = 0x1; + public static final byte APM_STATE_DRIVE = 0x2; + + //drive state sub values + public static final byte DRIVE_STATE_STOP = 0x0; + public static final byte DRIVE_STATE_AUTO = 0x1; + public static final byte DRIVE_STATE_RADIO = 0x2; + + //auto state sub values + public static final byte AUTO_STATE_FULL = 0x0; + public static final byte AUTO_STATE_CAUTION = 0x1; + public static final byte AUTO_STATE_AVOID = 0x2; + public static final byte AUTO_STATE_APPROACH = 0x3; + public static final byte AUTO_STATE_STALLED = 0x4; //sync public static final byte SYNC_REQUEST = 0x00; diff --git a/src/serial/SerialSender.java b/src/serial/SerialSender.java index 24a3997..9e72553 100644 --- a/src/serial/SerialSender.java +++ b/src/serial/SerialSender.java @@ -153,6 +153,24 @@ private void advanceWaypointList(int confirm) { } } + public void changeMovement(boolean shouldMove) { + Message msg; + + if(context.connected) { + if(shouldMove) { + msg = Message.startDriving(); + System.err.println("Sending start driving message."); + } + else { + msg = Message.stopDriving(); + System.err.println("Sending stop driving message."); + } + + sendMessage(msg); + } + + } + public void sendSync() { Message msg = Message.syncMessage(Serial.SYNC_REQUEST); sendMessage(msg); From 008be698eeb8219856632776ce4c7b3da1e2ab22 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 28 Oct 2020 22:51:05 -0700 Subject: [PATCH 002/125] Add initial scaffolding for Dashboard StateWidget --- src/serial/SerialParser.java | 52 +++++++++++-- src/ui/StateWidget.java | 140 +++++++++++++++++++++++++++++++++++ src/ui/UIConfigPanel.java | 4 +- 3 files changed, 187 insertions(+), 9 deletions(-) create mode 100644 src/ui/StateWidget.java diff --git a/src/serial/SerialParser.java b/src/serial/SerialParser.java index 5f58578..ff1b465 100644 --- a/src/serial/SerialParser.java +++ b/src/serial/SerialParser.java @@ -118,43 +118,81 @@ public void handle(byte[] msg) { } } } + private class WordReader implements PacketReader { public int claim(byte data) { - if(Serial.getMsgType(data) == Serial.WORD_TYPE) return 255; - else return -1; + if(Serial.getMsgType(data) == Serial.WORD_TYPE) { + return 255; + } + else { + return -1; + } } + public void handle(byte[] msg) { int subtype = Serial.getSubtype(msg[0]); - byte a = (byte)(msg[1]&0xff); - byte b = (byte)(msg[2]&0xff); - int join = ( ((int)(a<<8)&0xFF00) | ((int)(b&0xFF)) ); + byte a = (byte)(msg[1] & 0xff); + byte b = (byte)(msg[2] & 0xff); + int join = ( ((int)(a<<8) & 0xFF00) | ((int)(b & 0xFF)) ); + switch(subtype) { case Serial.CONFIRMATION: context.sender.notifyOfConfirm(join); break; + case Serial.SYNC_WORD: { if(a == Serial.SYNC_REQUEST) { Message message = Message.syncMessage(Serial.SYNC_RESPOND); context.sender.sendMessage(message); context.onConnection(); - } else if (a == Serial.SYNC_RESPOND) { //resync seen + } + else if (a == Serial.SYNC_RESPOND) { //resync seen context.onConnection(); } } break; + case Serial.COMMAND_WORD: if(a == Serial.TARGET_CMD) { if(b < 0 || b >= waypoints.size()){ seriallog.severe("Rover transmitted inconsistent target; resyncing"); context.sender.sendWaypointList(); - } else { + } + else { waypoints.setTarget(b, WaypointListener.Source.REMOTE); } } break; + + //TODO - CP - Update the StateWidget through the Dashboard here (context->Dashboard->StateWidget). + case Serial.STATE_WORD: //Subtype + switch(a) { + case Serial.APM_STATE: + //Possible b States: + //-INIT + //-SELF_TEST + //-DRIVE - transitional? + break; + case Serial.DRIVE_STATE: + //Possible b states + //-STOP + //-AUTO - transitional? + //-RADIO + break; + case Serial.AUTO_STATE: + //Possible b states (how do we get back from here? on stop?) + //-FULL + //-CAUTION + //-AVOID + //-APPROACH + //-STALLED + break; + } + break; } } } + private class StringReader implements PacketReader { private StateMap sm; { diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java new file mode 100644 index 0000000..77efa6b --- /dev/null +++ b/src/ui/StateWidget.java @@ -0,0 +1,140 @@ +package com.ui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +import com.Context; +import com.serial.Serial; + +/** + * + * @author Chris Park @ Infinetix Corp. + * Date: 10-28-20 + * Description: Dashboard Widget used to display the current state of a connected unit as + * described over serial communication. + * + */ +public class StateWidget extends JPanel { + private Context context; + private byte currentState; + private byte currentSubState; + + private JFrame infoFrame; + private String stateInfoStr; + + public StateWidget(Context ctx) { + context = ctx; + //Default state and substate to something inactive here? + } + + + public void update(byte state, byte substate) { + currentState = state; + currentSubState = substate; + updateStateInfo(); + } + + private MouseAdapter stateDetailsMouseAdapter = new MouseAdapter() { + @Override + public void mousePressed(MouseEvent me) { + + if(infoFrame != null && infoFrame.isVisible()) { + infoFrame.toFront(); + return; + } + else { + infoFrame = new JFrame("state info"); + JOptionPane.showMessageDialog(infoFrame, stateInfoStr); + } + } + }; + + private void updateStateInfo() { + switch(currentState) { + case Serial.APM_STATE: + setAPMStateInfo(); + //Update Color A + break; + case Serial.DRIVE_STATE: + setDriveStateInfo(); + //Update Color B + break; + case Serial.AUTO_STATE: + setAutoStateInfo(); + //Update Color C + break; + default: + stateInfoStr = "No details or unrecognized state."; + //Update Color Default for primary and secondary + } + } + + private void setAPMStateInfo() { + switch(currentSubState) { + case Serial.APM_STATE_INIT: + stateInfoStr = ""; + //Update Secondary Color + break; + case Serial.APM_STATE_SELF_TEST: + stateInfoStr = ""; + //Update Secondary Color + break; + case Serial.APM_STATE_DRIVE: + //Update Secondary Color + stateInfoStr = ""; + break; + default: + stateInfoStr = "No details or unrecognized state."; + //Update Secondary Color + } + } + + private void setDriveStateInfo() { + switch(currentSubState) { + case Serial.DRIVE_STATE_STOP: + stateInfoStr = ""; + //Update Secondary Color + break; + case Serial.DRIVE_STATE_AUTO: + stateInfoStr = ""; + //Update Secondary Color + break; + case Serial.DRIVE_STATE_RADIO: + stateInfoStr = ""; + //Update Secondary Color + break; + default: + stateInfoStr = "No details or unrecognized state."; + //Update Secondary Color + } + } + + private void setAutoStateInfo() { + switch(currentSubState) { + case Serial.AUTO_STATE_FULL: + stateInfoStr = ""; + //Update Secondary Color + break; + case Serial.AUTO_STATE_CAUTION: + stateInfoStr = ""; + //Update Secondary Color + break; + case Serial.AUTO_STATE_AVOID: + stateInfoStr = ""; + //Update Secondary Color + break; + case Serial.AUTO_STATE_APPROACH: + stateInfoStr = ""; + //Update Secondary Color + break; + case Serial.AUTO_STATE_STALLED: + stateInfoStr = ""; + //Update Secondary Color + break; + default: + stateInfoStr = "No details or unrecognized state."; + //Update Secondary Color + } + } +} diff --git a/src/ui/UIConfigPanel.java b/src/ui/UIConfigPanel.java index 64fbb25..485cac8 100644 --- a/src/ui/UIConfigPanel.java +++ b/src/ui/UIConfigPanel.java @@ -48,7 +48,7 @@ public UIConfigPanel(Context cxt, boolean isWindows) { toggleButton = new JButton(toggleLocaleAction); toggleButton.setPreferredSize( - new Dimension(DEF_BUTTON_WIDTH,DEF_BUTTON_HEIGHT)); + new Dimension(DEF_BUTTON_WIDTH, DEF_BUTTON_HEIGHT)); constraints.gridx = 0; constraints.gridy = 0; this.add(toggleButton, constraints); @@ -56,7 +56,7 @@ public UIConfigPanel(Context cxt, boolean isWindows) { if(isWindows) { driverButton = new JButton(driverExecAction); driverButton.setPreferredSize( - new Dimension(DEF_BUTTON_WIDTH,DEF_BUTTON_HEIGHT)); + new Dimension(DEF_BUTTON_WIDTH, DEF_BUTTON_HEIGHT)); constraints.gridx = 0; constraints.gridy = 1; this.add(driverButton, constraints); From dd237b5846dde15387d7ae41a584df014814061e Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Fri, 30 Oct 2020 09:12:35 -0700 Subject: [PATCH 003/125] Cont. StateWidget development Focus on representing data as strings first, to include visual design for dashboard UI after all data states are properly represented. --- src/Dashboard.java | 6 ++ src/serial/Serial.java | 12 ++- src/serial/SerialParser.java | 26 +------ src/ui/StateWidget.java | 147 +++++++++++++++++------------------ 4 files changed, 90 insertions(+), 101 deletions(-) diff --git a/src/Dashboard.java b/src/Dashboard.java index dfd95a2..1dbcbe6 100644 --- a/src/Dashboard.java +++ b/src/Dashboard.java @@ -229,6 +229,12 @@ public void mouseClicked(MouseEvent e){ context, Serial.ROLL, context.theme.roverFront)); } + //TODO - CP - Create the StateWidget and add it to the dash panel here? + /* + * StateWidget stateWidget = new StateWidget(context); + * dashPanel.add(stateWidget); + */ + return dashPanel; } diff --git a/src/serial/Serial.java b/src/serial/Serial.java index fc49c93..9e5d5dc 100644 --- a/src/serial/Serial.java +++ b/src/serial/Serial.java @@ -39,6 +39,7 @@ public class Serial { public static final byte APM_STATE = 0x0; public static final byte DRIVE_STATE = 0x1; public static final byte AUTO_STATE = 0x2; + public static final byte AUTO_FLAGS = 0x3; //APM state sub values public static final byte APM_STATE_INIT = 0x0; @@ -52,10 +53,13 @@ public class Serial { //auto state sub values public static final byte AUTO_STATE_FULL = 0x0; - public static final byte AUTO_STATE_CAUTION = 0x1; - public static final byte AUTO_STATE_AVOID = 0x2; - public static final byte AUTO_STATE_APPROACH = 0x3; - public static final byte AUTO_STATE_STALLED = 0x4; + public static final byte AUTO_STATE_CAUTION = 0x1; + public static final byte AUTO_STATE_STALLED = 0x2; + + //auto flags sub values + public static final byte AUTO_STATE_FLAGS_NONE = 0x00000000; + public static final byte AUTO_STATE_FLAGS_AVOID = 0x00000001; + public static final byte AUTO_STATE_FLAGS_APPROACH = 0x00000010; //sync public static final byte SYNC_REQUEST = 0x00; diff --git a/src/serial/SerialParser.java b/src/serial/SerialParser.java index ff1b465..2f28528 100644 --- a/src/serial/SerialParser.java +++ b/src/serial/SerialParser.java @@ -166,28 +166,10 @@ else if (a == Serial.SYNC_RESPOND) { //resync seen //TODO - CP - Update the StateWidget through the Dashboard here (context->Dashboard->StateWidget). case Serial.STATE_WORD: //Subtype - switch(a) { - case Serial.APM_STATE: - //Possible b States: - //-INIT - //-SELF_TEST - //-DRIVE - transitional? - break; - case Serial.DRIVE_STATE: - //Possible b states - //-STOP - //-AUTO - transitional? - //-RADIO - break; - case Serial.AUTO_STATE: - //Possible b states (how do we get back from here? on stop?) - //-FULL - //-CAUTION - //-AVOID - //-APPROACH - //-STALLED - break; - } + //Send A and B to the state widget to update the visible readout. + + //Should look something like this... + //context.dash.satewidget.update(a, b); break; } } diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java index 77efa6b..0307733 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/StateWidget.java @@ -17,124 +17,121 @@ */ public class StateWidget extends JPanel { private Context context; - private byte currentState; - private byte currentSubState; - private JFrame infoFrame; - private String stateInfoStr; + + private String apmStateStr; + private String driveStateStr; + private String autoStateStr; + private String flagStateStr; public StateWidget(Context ctx) { context = ctx; - //Default state and substate to something inactive here? + + apmStateStr = "Uninitialized"; + driveStateStr = "Uninitialized"; + autoStateStr = "Uninitialized"; + flagStateStr = "Uninitialized"; } - public void update(byte state, byte substate) { - currentState = state; - currentSubState = substate; - updateStateInfo(); - } - - private MouseAdapter stateDetailsMouseAdapter = new MouseAdapter() { - @Override - public void mousePressed(MouseEvent me) { - - if(infoFrame != null && infoFrame.isVisible()) { - infoFrame.toFront(); - return; - } - else { - infoFrame = new JFrame("state info"); - JOptionPane.showMessageDialog(infoFrame, stateInfoStr); - } - } - }; - - private void updateStateInfo() { - switch(currentState) { + switch(state) { case Serial.APM_STATE: - setAPMStateInfo(); - //Update Color A + setAPMState(substate); break; case Serial.DRIVE_STATE: - setDriveStateInfo(); - //Update Color B + setDriveState(substate); break; case Serial.AUTO_STATE: - setAutoStateInfo(); - //Update Color C + setAutoState(substate); break; - default: - stateInfoStr = "No details or unrecognized state."; - //Update Color Default for primary and secondary + case Serial.AUTO_FLAGS: + setFlagState(substate); } } - private void setAPMStateInfo() { - switch(currentSubState) { + private void setAPMState(byte substate) { + switch(substate) { case Serial.APM_STATE_INIT: - stateInfoStr = ""; - //Update Secondary Color + apmStateStr = "APM - Initializing"; break; case Serial.APM_STATE_SELF_TEST: - stateInfoStr = ""; - //Update Secondary Color + apmStateStr= "APM - Performing Self Test"; break; case Serial.APM_STATE_DRIVE: - //Update Secondary Color - stateInfoStr = ""; + apmStateStr = "APM - Driving"; break; default: - stateInfoStr = "No details or unrecognized state."; - //Update Secondary Color + apmStateStr = "APM - No details or unrecognized state"; } } - private void setDriveStateInfo() { - switch(currentSubState) { + private void setDriveState(byte substate) { + switch(substate) { case Serial.DRIVE_STATE_STOP: - stateInfoStr = ""; - //Update Secondary Color + driveStateStr = "Drive - Stopped"; break; case Serial.DRIVE_STATE_AUTO: - stateInfoStr = ""; - //Update Secondary Color + driveStateStr = "Drive - Auto Mode"; break; case Serial.DRIVE_STATE_RADIO: - stateInfoStr = ""; - //Update Secondary Color + driveStateStr = "Drive - Radio Manual Mode"; break; default: - stateInfoStr = "No details or unrecognized state."; - //Update Secondary Color + driveStateStr = "Drive - No details or unrecognized state"; } } - private void setAutoStateInfo() { - switch(currentSubState) { + private void setAutoState(byte substate) { + switch(substate) { case Serial.AUTO_STATE_FULL: - stateInfoStr = ""; - //Update Secondary Color + autoStateStr = "AUTO - Full"; break; case Serial.AUTO_STATE_CAUTION: - stateInfoStr = ""; - //Update Secondary Color - break; - case Serial.AUTO_STATE_AVOID: - stateInfoStr = ""; - //Update Secondary Color - break; - case Serial.AUTO_STATE_APPROACH: - stateInfoStr = ""; - //Update Secondary Color + autoStateStr = "AUTO - Caution"; break; case Serial.AUTO_STATE_STALLED: - stateInfoStr = ""; - //Update Secondary Color + autoStateStr = "AUTO - Stalled"; break; default: - stateInfoStr = "No details or unrecognized state."; - //Update Secondary Color + autoStateStr = "AUTO - No details or unrecognized state."; } } + + private void setFlagState(byte substate) { + boolean avoid = ((substate & 0x00000001) > 0 ) ? true : false; + boolean approach = ((substate & 0x00000010) > 0 ) ? true : false; + + if(avoid && approach) { + flagStateStr = "FLAG - Approach & Avoid"; + //Severity High. Approaching a clear obstable? + } + else if(approach) { + flagStateStr = "FLAG - Approach"; + //Severity Standard. Approaching an obstacle, slowing down? + } + else if(avoid) { + flagStateStr = "FLAG - Avoid"; + //Severity Standard. Avoiding an obstacle? + } + else { + flagStateStr = "FLAG - None"; + //Severity Low. No hazards detected? + } + } + + private MouseAdapter stateDetailsMouseAdapter = new MouseAdapter() { + @Override + public void mousePressed(MouseEvent me) { + + if(infoFrame != null && infoFrame.isVisible()) { + infoFrame.toFront(); + return; + } + else { + infoFrame = new JFrame("state info"); + JOptionPane.showMessageDialog(infoFrame, "Click Info String Here"); + } + } + }; + } From dfdf4c963f7a641782cf45ed5d7646217718ed06 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Fri, 30 Oct 2020 09:24:00 -0700 Subject: [PATCH 004/125] Update auto state binary flag representations --- src/serial/Serial.java | 6 +++--- src/ui/StateWidget.java | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/serial/Serial.java b/src/serial/Serial.java index 9e5d5dc..7ea7cc3 100644 --- a/src/serial/Serial.java +++ b/src/serial/Serial.java @@ -57,9 +57,9 @@ public class Serial { public static final byte AUTO_STATE_STALLED = 0x2; //auto flags sub values - public static final byte AUTO_STATE_FLAGS_NONE = 0x00000000; - public static final byte AUTO_STATE_FLAGS_AVOID = 0x00000001; - public static final byte AUTO_STATE_FLAGS_APPROACH = 0x00000010; + public static final byte AUTO_STATE_FLAGS_NONE = 0B00; + public static final byte AUTO_STATE_FLAGS_AVOID = 0B01; + public static final byte AUTO_STATE_FLAGS_APPROACH = 0B10; //sync public static final byte SYNC_REQUEST = 0x00; diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java index 0307733..2c55e78 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/StateWidget.java @@ -98,8 +98,8 @@ private void setAutoState(byte substate) { } private void setFlagState(byte substate) { - boolean avoid = ((substate & 0x00000001) > 0 ) ? true : false; - boolean approach = ((substate & 0x00000010) > 0 ) ? true : false; + boolean avoid = ((substate & Serial.AUTO_STATE_FLAGS_AVOID) > 0 ) ? true : false; + boolean approach = ((substate & Serial.AUTO_STATE_FLAGS_APPROACH) > 0 ) ? true : false; if(avoid && approach) { flagStateStr = "FLAG - Approach & Avoid"; @@ -107,11 +107,11 @@ private void setFlagState(byte substate) { } else if(approach) { flagStateStr = "FLAG - Approach"; - //Severity Standard. Approaching an obstacle, slowing down? + //Severity Medium. Approaching an obstacle, slowing down? } else if(avoid) { flagStateStr = "FLAG - Avoid"; - //Severity Standard. Avoiding an obstacle? + //Severity Medium. Avoiding an obstacle? } else { flagStateStr = "FLAG - None"; From a6ee012ad29c2ac79b4b16cbed7d7ed319cf5051 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Fri, 30 Oct 2020 10:40:58 -0700 Subject: [PATCH 005/125] Basic StateWidget update impl States now update from SerialParser and are represented/updated via text labels within the StateWidget --- src/Dashboard.java | 10 ++--- src/serial/SerialParser.java | 6 +-- src/ui/StateWidget.java | 71 +++++++++++++++++++++++++----------- 3 files changed, 56 insertions(+), 31 deletions(-) diff --git a/src/Dashboard.java b/src/Dashboard.java index 1dbcbe6..10aaa04 100644 --- a/src/Dashboard.java +++ b/src/Dashboard.java @@ -42,6 +42,9 @@ public class Dashboard implements Runnable { private TelemetryWidget dataWidget; + //TODO - CP - Change this to private and make a getter that can be called + public StateWidget stateWidget; + private final Logger seriallog = Logger.getLogger("d.serial"); private final Logger iolog = Logger.getLogger("d.io"); private final Logger rootlog = Logger.getLogger("d"); @@ -229,11 +232,8 @@ public void mouseClicked(MouseEvent e){ context, Serial.ROLL, context.theme.roverFront)); } - //TODO - CP - Create the StateWidget and add it to the dash panel here? - /* - * StateWidget stateWidget = new StateWidget(context); - * dashPanel.add(stateWidget); - */ + stateWidget = new StateWidget(context); + dashPanel.add(stateWidget); return dashPanel; } diff --git a/src/serial/SerialParser.java b/src/serial/SerialParser.java index 2f28528..dacb987 100644 --- a/src/serial/SerialParser.java +++ b/src/serial/SerialParser.java @@ -164,12 +164,8 @@ else if (a == Serial.SYNC_RESPOND) { //resync seen } break; - //TODO - CP - Update the StateWidget through the Dashboard here (context->Dashboard->StateWidget). case Serial.STATE_WORD: //Subtype - //Send A and B to the state widget to update the visible readout. - - //Should look something like this... - //context.dash.satewidget.update(a, b); + context.dash.stateWidget.update(a,b); break; } } diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java index 2c55e78..edf2956 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/StateWidget.java @@ -19,6 +19,12 @@ public class StateWidget extends JPanel { private Context context; private JFrame infoFrame; + + private JLabel apmLabel; + private JLabel driveLabel; + private JLabel autoLabel; + private JLabel flagLabel; + private String apmStateStr; private String driveStateStr; private String autoStateStr; @@ -27,10 +33,22 @@ public class StateWidget extends JPanel { public StateWidget(Context ctx) { context = ctx; - apmStateStr = "Uninitialized"; - driveStateStr = "Uninitialized"; - autoStateStr = "Uninitialized"; - flagStateStr = "Uninitialized"; + apmStateStr = "APM - Uninit"; + driveStateStr = "DRIVE - Uninit"; + autoStateStr = "AUTO - Uninit"; + flagStateStr = "FLAGS - None"; + + apmLabel = new JLabel(apmStateStr); + driveLabel = new JLabel(driveStateStr); + autoLabel = new JLabel(autoStateStr); + flagLabel = new JLabel(flagStateStr); + + + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + this.add(apmLabel); + this.add(driveLabel); + this.add(autoLabel); + this.add(flagLabel); } public void update(byte state, byte substate) { @@ -46,75 +64,86 @@ public void update(byte state, byte substate) { break; case Serial.AUTO_FLAGS: setFlagState(substate); + break; + default: + System.err.println("Error - Unrecognized State"); } } private void setAPMState(byte substate) { + System.err.println("StateWidget - Updating APM State"); + switch(substate) { case Serial.APM_STATE_INIT: - apmStateStr = "APM - Initializing"; + apmLabel.setText("APM - Initializing"); break; case Serial.APM_STATE_SELF_TEST: - apmStateStr= "APM - Performing Self Test"; + apmLabel.setText("APM - Performing Self Test"); break; case Serial.APM_STATE_DRIVE: - apmStateStr = "APM - Driving"; + apmLabel.setText("APM - Driving"); break; default: - apmStateStr = "APM - No details or unrecognized state"; + apmLabel.setText("APM - Unknown State"); } } private void setDriveState(byte substate) { + System.err.println("StateWidget - Updating Drive State"); + switch(substate) { case Serial.DRIVE_STATE_STOP: - driveStateStr = "Drive - Stopped"; + driveLabel.setText("DRIVE - Stopped"); break; case Serial.DRIVE_STATE_AUTO: - driveStateStr = "Drive - Auto Mode"; + driveLabel.setText("DRIVE - Auto Mode"); break; case Serial.DRIVE_STATE_RADIO: - driveStateStr = "Drive - Radio Manual Mode"; + driveLabel.setText("DRIVE - Radio Manual Mode"); break; default: - driveStateStr = "Drive - No details or unrecognized state"; + driveLabel.setText("DRIVE - Unknown State"); } } private void setAutoState(byte substate) { + System.err.println("StateWidget - Updating Auto State"); + switch(substate) { case Serial.AUTO_STATE_FULL: - autoStateStr = "AUTO - Full"; + autoLabel.setText("AUTO - Full"); break; case Serial.AUTO_STATE_CAUTION: - autoStateStr = "AUTO - Caution"; + autoLabel.setText("AUTO - Caution"); break; case Serial.AUTO_STATE_STALLED: - autoStateStr = "AUTO - Stalled"; + autoLabel.setText("AUTO - Stalled"); break; default: - autoStateStr = "AUTO - No details or unrecognized state."; + autoLabel.setText("AUTO - Unknown State"); } } private void setFlagState(byte substate) { - boolean avoid = ((substate & Serial.AUTO_STATE_FLAGS_AVOID) > 0 ) ? true : false; + boolean avoid = ((substate & Serial.AUTO_STATE_FLAGS_AVOID) > 0 ) ? true : false; boolean approach = ((substate & Serial.AUTO_STATE_FLAGS_APPROACH) > 0 ) ? true : false; + System.out.println("StateWidget - Updating Flag State"); + if(avoid && approach) { - flagStateStr = "FLAG - Approach & Avoid"; + flagLabel.setText("FLAG - Approach & Avoid"); //Severity High. Approaching a clear obstable? } else if(approach) { - flagStateStr = "FLAG - Approach"; + flagLabel.setText("FLAG - Approach"); //Severity Medium. Approaching an obstacle, slowing down? } else if(avoid) { - flagStateStr = "FLAG - Avoid"; + flagLabel.setText("FLAG - Avoid"); //Severity Medium. Avoiding an obstacle? } else { - flagStateStr = "FLAG - None"; + flagLabel.setText("FLAG - None"); //Severity Low. No hazards detected? } } From 5841da0e212aa833497600f14746a33125514422 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Tue, 3 Nov 2020 09:54:45 -0800 Subject: [PATCH 006/125] Updated state enums/flags per Bens new specifications --- src/serial/Serial.java | 4 ++-- src/ui/StateWidget.java | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/serial/Serial.java b/src/serial/Serial.java index 7ea7cc3..a4e621d 100644 --- a/src/serial/Serial.java +++ b/src/serial/Serial.java @@ -53,12 +53,12 @@ public class Serial { //auto state sub values public static final byte AUTO_STATE_FULL = 0x0; - public static final byte AUTO_STATE_CAUTION = 0x1; + public static final byte AUTO_STATE_AVOID = 0x1; public static final byte AUTO_STATE_STALLED = 0x2; //auto flags sub values public static final byte AUTO_STATE_FLAGS_NONE = 0B00; - public static final byte AUTO_STATE_FLAGS_AVOID = 0B01; + public static final byte AUTO_STATE_FLAGS_CAUTION = 0B01; public static final byte AUTO_STATE_FLAGS_APPROACH = 0B10; //sync diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java index edf2956..f855028 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/StateWidget.java @@ -113,8 +113,8 @@ private void setAutoState(byte substate) { case Serial.AUTO_STATE_FULL: autoLabel.setText("AUTO - Full"); break; - case Serial.AUTO_STATE_CAUTION: - autoLabel.setText("AUTO - Caution"); + case Serial.AUTO_STATE_AVOID: + autoLabel.setText("AUTO - Avoid"); break; case Serial.AUTO_STATE_STALLED: autoLabel.setText("AUTO - Stalled"); @@ -124,22 +124,23 @@ private void setAutoState(byte substate) { } } + //TODO - CP - Update flag string AND set an icon indicating severity level private void setFlagState(byte substate) { - boolean avoid = ((substate & Serial.AUTO_STATE_FLAGS_AVOID) > 0 ) ? true : false; + boolean caution = ((substate & Serial.AUTO_STATE_FLAGS_CAUTION) > 0 ) ? true : false; boolean approach = ((substate & Serial.AUTO_STATE_FLAGS_APPROACH) > 0 ) ? true : false; System.out.println("StateWidget - Updating Flag State"); - if(avoid && approach) { - flagLabel.setText("FLAG - Approach & Avoid"); + if(caution && approach) { + flagLabel.setText("FLAG - Approach & Caution"); //Severity High. Approaching a clear obstable? } else if(approach) { flagLabel.setText("FLAG - Approach"); //Severity Medium. Approaching an obstacle, slowing down? } - else if(avoid) { - flagLabel.setText("FLAG - Avoid"); + else if(caution) { + flagLabel.setText("FLAG - Caution"); //Severity Medium. Avoiding an obstacle? } else { From ade65d30cc73dbf33b3d4d1896a5ad0f3a669848 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Tue, 3 Nov 2020 16:39:35 -0800 Subject: [PATCH 007/125] Added in method header comments Covers all individual classes and their functions created under Infinetix up to this point. --- src/map/command/CommandManager.java | 6 +++ src/map/command/WaypointCommand.java | 17 ++++++- src/map/command/WaypointCommandAdd.java | 18 ++++++++ src/map/command/WaypointCommandClear.java | 17 +++++++ src/map/command/WaypointCommandEdit.java | 17 +++++++ src/map/command/WaypointCommandMove.java | 16 ++++++- src/map/command/WaypointCommandRemove.java | 18 ++++++++ src/map/command/WaypointCommandTarget.java | 17 ++++++- src/ui/StateWidget.java | 53 ++++++++++++++-------- src/ui/UIConfigPanel.java | 27 ++++++++++- 10 files changed, 183 insertions(+), 23 deletions(-) diff --git a/src/map/command/CommandManager.java b/src/map/command/CommandManager.java index 72540fe..f0f5433 100644 --- a/src/map/command/CommandManager.java +++ b/src/map/command/CommandManager.java @@ -15,6 +15,9 @@ public class CommandManager { private LinkedList processedCommands; private LinkedList revertedCommands; + /** + * Constructor (Private, accessed by getInstance) + */ private CommandManager() { processedCommands = new LinkedList(); revertedCommands = new LinkedList(); @@ -106,6 +109,9 @@ public boolean redo() { return result; } + /** + * Clears all tracked command lists. + */ public void clearTrackedCommands() { processedCommands.clear(); revertedCommands.clear(); diff --git a/src/map/command/WaypointCommand.java b/src/map/command/WaypointCommand.java index b985211..8797f27 100644 --- a/src/map/command/WaypointCommand.java +++ b/src/map/command/WaypointCommand.java @@ -29,19 +29,34 @@ public enum CommandType {ADD, REMOVE, MOVE, EDIT, CLEAR, TARGET}; protected Dot startPoint; protected Dot endPoint; + /** + * Constructor + * @param waypoints - The current list of navigational waypoints + * @param type - The Type of command being created + */ public WaypointCommand(WaypointList waypoints, CommandType type) { this.waypoints = waypoints; this.type = type; } - + + //Abstract functions to be overriden in concrete classes. public abstract boolean execute(); public abstract boolean undo(); public abstract boolean redo(); + /** + * + * @return CommandType - The command type of this command instance. + */ public CommandType getType() { return type; } + /** + * Sets the final point for a movement command, making it ready to be executed. + * If this point is not set, then the corresponding move command will not execute. + * @param ep - The ending point to be moved to. + */ public void finalize(Dot ep) { this.endPoint = ep; } diff --git a/src/map/command/WaypointCommandAdd.java b/src/map/command/WaypointCommandAdd.java index 76cdfe9..6b3168d 100644 --- a/src/map/command/WaypointCommandAdd.java +++ b/src/map/command/WaypointCommandAdd.java @@ -12,6 +12,12 @@ */ public class WaypointCommandAdd extends WaypointCommand { + /** + * Constructor + * @param waypoints - List of current navigational waypoints. + * @param point - Waypoint to be manually edited by this command + * @param index - index in the waypoint list of the waypoint being added. + */ public WaypointCommandAdd(WaypointList waypoints, Dot point, int index) { super(waypoints, CommandType.ADD); @@ -19,6 +25,10 @@ public WaypointCommandAdd(WaypointList waypoints, Dot point, int index) { this.index = index; } + /** + * Adds a new waypoint at the specified location. + * @return Boolean - Whether or not the command was executed successfully. + */ @Override public boolean execute() { waypoints.add(point, index); @@ -33,6 +43,10 @@ public boolean execute() { return true; } + /** + * Removes the added waypoint described by this command. + * @return Boolean - Whether or not the operation was successful. + */ @Override public boolean undo() { waypoints.remove(index); @@ -40,6 +54,10 @@ public boolean undo() { return true; } + /** + * Re-adds the detailed waypoint to the list. + * @return Boolean - Whether or not the operation was successful. + */ @Override public boolean redo() { return execute(); diff --git a/src/map/command/WaypointCommandClear.java b/src/map/command/WaypointCommandClear.java index 3fd83a1..66e3021 100644 --- a/src/map/command/WaypointCommandClear.java +++ b/src/map/command/WaypointCommandClear.java @@ -16,6 +16,11 @@ public class WaypointCommandClear extends WaypointCommand { protected WaypointList waypointsBackup; protected Context context; + /** + * Constructor + * @param waypoints - List of current navigational waypoints. + * @param context - Application context + */ public WaypointCommandClear(WaypointList waypoints, Context context) { super(waypoints, CommandType.CLEAR); this.context = context; @@ -27,6 +32,10 @@ public WaypointCommandClear(WaypointList waypoints, Context context) { } } + /** + * Clears the waypoint list. + * @return Boolean - Whether or not the command was successful. + */ @Override public boolean execute() { //Event is labeled as coming from the rover to avoid sending @@ -42,6 +51,10 @@ public boolean execute() { return true; } + /** + * Restores the waypoint list to its previous state before being cleared. + * @return Boolean - Whether or not the operation was successful. + */ @Override public boolean undo() { @@ -53,6 +66,10 @@ public boolean undo() { return true; } + /** + * Re-clears the waypoint list. + * @return Boolean - Whether or not the operation was successful. + */ @Override public boolean redo() { return execute(); diff --git a/src/map/command/WaypointCommandEdit.java b/src/map/command/WaypointCommandEdit.java index 4bd2c65..e887306 100644 --- a/src/map/command/WaypointCommandEdit.java +++ b/src/map/command/WaypointCommandEdit.java @@ -16,6 +16,11 @@ */ public class WaypointCommandEdit extends WaypointCommand { + /** + * Constructor + * @param waypoints - List of current navigational waypoints. + * @param index - index in the waypoint list of the waypoint being modified. + */ public WaypointCommandEdit(WaypointList waypoints, int index) { super(waypoints, CommandType.EDIT); @@ -28,6 +33,10 @@ public WaypointCommandEdit(WaypointList waypoints, int index) { this.index = index; } + /** + * Edits the manual details of the selected point. + * @return Boolean - Whether or not the operation was successful. + */ @Override public boolean execute() { if(endPoint == null) { @@ -40,12 +49,20 @@ public boolean execute() { return true; } + /** + * Reverts the manual changes made to a waypoint with this command. + * @return Boolean - Whether or not the operation was successful. + */ @Override public boolean undo() { waypoints.set(startPoint, index); return true; } + /** + * Re executes the manual changes to the waypoint specified by this command. + * @return Boolean - Whether or not the operation was successful. + */ @Override public boolean redo() { return execute(); diff --git a/src/map/command/WaypointCommandMove.java b/src/map/command/WaypointCommandMove.java index 38c40a8..712209b 100644 --- a/src/map/command/WaypointCommandMove.java +++ b/src/map/command/WaypointCommandMove.java @@ -8,10 +8,14 @@ * @author Chris Park @ Infinetix Corp. * Date: 9-14-2020 * Description: Command responsible for moving a waypoints location. - * */ public class WaypointCommandMove extends WaypointCommand { + /** + * Constructor + * @param waypoints - list of current navigation waypoints + * @param index -index in the waypoint list of the waypoint being moved. + */ public WaypointCommandMove(WaypointList waypoints, int index) { super(waypoints, CommandType.MOVE); @@ -21,7 +25,7 @@ public WaypointCommandMove(WaypointList waypoints, int index) { } /** - * Moves the point to the + * Moves the point to the endpoint detailed by the finalize method * @return Whether or not the operation was successful. In the * case of a failure, ensure that the end point was set before * attempting execution. @@ -38,12 +42,20 @@ public boolean execute() { return true; } + /** + * Returns the waypoint outlined by this command to its original position + * @return Boolean - Whether or not the operation was successful. + */ @Override public boolean undo() { waypoints.set(startPoint, index); return true; } + /** + * Moves the waypoint back to its modified position again. + * @return Boolean - Whether or not the operation was successful. + */ @Override public boolean redo() { return execute(); diff --git a/src/map/command/WaypointCommandRemove.java b/src/map/command/WaypointCommandRemove.java index cc1f14d..7b01546 100644 --- a/src/map/command/WaypointCommandRemove.java +++ b/src/map/command/WaypointCommandRemove.java @@ -12,6 +12,11 @@ */ public class WaypointCommandRemove extends WaypointCommand { + /** + * Constructor + * @param waypoints - List of current navigational waypoints. + * @param index - Index of the waypoint effected by this command. + */ public WaypointCommandRemove(WaypointList waypoints, int index) { super(waypoints, CommandType.REMOVE); @@ -19,6 +24,10 @@ public WaypointCommandRemove(WaypointList waypoints, int index) { this.point = waypoints.get(index).dot(); } + /** + * Removes the waypoint at the selected index from the waypoint list. + * @return Boolean - Whether or not the operation was successful. + */ @Override public boolean execute() { waypoints.remove(index); @@ -26,6 +35,11 @@ public boolean execute() { return true; } + /** + * Adds the waypoint selected by this removal back into the waypoint list at its original + * location. + * @return Boolean - Whether or not the operation was successful. + */ @Override public boolean undo() { waypoints.add(point, index); @@ -40,6 +54,10 @@ public boolean undo() { return true; } + /** + * Re-executes the previous removal operation after an undo. + * @return Boolean - Whether or not the operation was successful. + */ @Override public boolean redo() { return execute(); diff --git a/src/map/command/WaypointCommandTarget.java b/src/map/command/WaypointCommandTarget.java index d133fb5..51fc545 100644 --- a/src/map/command/WaypointCommandTarget.java +++ b/src/map/command/WaypointCommandTarget.java @@ -14,6 +14,10 @@ public class WaypointCommandTarget extends WaypointCommand { protected int newTarget; protected int prevTarget; + /** + * Constructor + * @param waypoints - The list of current navigational waypoints. + */ public WaypointCommandTarget(WaypointList waypoints) { super(waypoints, CommandType.TARGET); @@ -21,19 +25,30 @@ public WaypointCommandTarget(WaypointList waypoints) { this.newTarget = waypoints.getSelected(); } - + /** + * Sets the waypoint target to the currently selected one. + * @return Boolean - Whether or not the operation was successful. + */ @Override public boolean execute() { waypoints.setTarget(newTarget); return true; } + /** + * Sets the waypoint target to where it was previous to this command. + * @return Boolean - Whether or not the operation was successful. + */ @Override public boolean undo() { waypoints.setTarget(prevTarget); return true; } + /** + * See execute. + * @return - Boolean - Whether or not the operation was successful. + */ @Override public boolean redo() { return execute(); diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java index f855028..d1346e2 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/StateWidget.java @@ -8,41 +8,31 @@ import com.serial.Serial; /** - * * @author Chris Park @ Infinetix Corp. * Date: 10-28-20 * Description: Dashboard Widget used to display the current state of a connected unit as * described over serial communication. - * */ public class StateWidget extends JPanel { private Context context; private JFrame infoFrame; - private JLabel apmLabel; private JLabel driveLabel; private JLabel autoLabel; private JLabel flagLabel; - private String apmStateStr; - private String driveStateStr; - private String autoStateStr; - private String flagStateStr; - + /** + * Class constructor + * @param ctx - The application context + */ public StateWidget(Context ctx) { context = ctx; - apmStateStr = "APM - Uninit"; - driveStateStr = "DRIVE - Uninit"; - autoStateStr = "AUTO - Uninit"; - flagStateStr = "FLAGS - None"; - - apmLabel = new JLabel(apmStateStr); - driveLabel = new JLabel(driveStateStr); - autoLabel = new JLabel(autoStateStr); - flagLabel = new JLabel(flagStateStr); - + apmLabel = new JLabel("APM - Uninit"); + driveLabel = new JLabel("DRIVE - Uninit"); + autoLabel = new JLabel("AUTO - Uninit"); + flagLabel = new JLabel("FLAGS - None"); this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); this.add(apmLabel); @@ -51,6 +41,12 @@ public StateWidget(Context ctx) { this.add(flagLabel); } + /** + * Updates the internal state of the widget using byte data received from + * serial communications with a device. + * @param state - The main state type being updated + * @param substate - The main state variation to be updated to + */ public void update(byte state, byte substate) { switch(state) { case Serial.APM_STATE: @@ -70,6 +66,10 @@ public void update(byte state, byte substate) { } } + /** + * Sets the current APM state. + * @param substate - The state variation to be set + */ private void setAPMState(byte substate) { System.err.println("StateWidget - Updating APM State"); @@ -88,6 +88,10 @@ private void setAPMState(byte substate) { } } + /** + * Sets the current Drive state. + * @param substate - The state variation to be set + */ private void setDriveState(byte substate) { System.err.println("StateWidget - Updating Drive State"); @@ -106,6 +110,10 @@ private void setDriveState(byte substate) { } } + /** + * Sets the current Auto state. + * @param substate - The state variation to be set + */ private void setAutoState(byte substate) { System.err.println("StateWidget - Updating Auto State"); @@ -125,6 +133,11 @@ private void setAutoState(byte substate) { } //TODO - CP - Update flag string AND set an icon indicating severity level + + /** + * Sets the current state flag if any. + * @param substate - The flag type to be set + */ private void setFlagState(byte substate) { boolean caution = ((substate & Serial.AUTO_STATE_FLAGS_CAUTION) > 0 ) ? true : false; boolean approach = ((substate & Serial.AUTO_STATE_FLAGS_APPROACH) > 0 ) ? true : false; @@ -149,6 +162,10 @@ else if(caution) { } } + /** + * Generates an information panel on click describing the warnings, errors, + * and details of teh current state. + */ private MouseAdapter stateDetailsMouseAdapter = new MouseAdapter() { @Override public void mousePressed(MouseEvent me) { diff --git a/src/ui/UIConfigPanel.java b/src/ui/UIConfigPanel.java index 485cac8..f1ebc98 100644 --- a/src/ui/UIConfigPanel.java +++ b/src/ui/UIConfigPanel.java @@ -1,6 +1,5 @@ package com.ui; - import com.Context; import com.map.*; @@ -14,6 +13,15 @@ import java.text.Format; import javax.swing.*; + +/** + * + * @author Chris Park @ Infinetix Corp. + * Date: 09-2020 + * Description: UI Panel responsible for providing UI focused options to the user. + * Allows for toggling air/ground mode, and saving the current Home location to persistent + * settings. + */ public class UIConfigPanel extends JPanel { private static final int DEF_TEXT_FIELD_WIDTH = 6; @@ -35,6 +43,11 @@ public class UIConfigPanel extends JPanel { private JTextField latField; private JButton setHomeButton; + /** + * Class constructor + * @param cxt - the application context + * @param isWindows - boolean check to see if the application is running in windows + */ public UIConfigPanel(Context cxt, boolean isWindows) { this.context = cxt; @@ -94,6 +107,10 @@ public UIConfigPanel(Context cxt, boolean isWindows) { this.add(setHomeButton, constraints); } + /** + * Action used to toggle the user interface between Air and Ground mode. + * Requires a program restart for the setting to take effect. + */ private Action toggleLocaleAction = new AbstractAction() { { String text = "Toggle ground/air mode"; @@ -107,6 +124,10 @@ public void actionPerformed(ActionEvent e) { } }; + /** + * Action used to install the Required Radio Telemtry drivers. + * (Deprecated) - This is now handled by the program installer. + */ private Action driverExecAction = new AbstractAction() { { String text = "Launch driver installer"; @@ -122,6 +143,10 @@ public void actionPerformed(ActionEvent e) { } }; + /** + * Action used to set the home longitude and latitude and save it to persistent + * settings. + */ private Action setHomeAction = new AbstractAction() { { String text = "Set Home"; From 5a376591e38d48706590a6e94532bcbaf3ff75f5 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 4 Nov 2020 15:34:05 -0800 Subject: [PATCH 008/125] Add bordering to current state labels --- src/map/WaypointPanel.java | 28 ++++++++++++++++++++++++++++ src/ui/StateWidget.java | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/src/map/WaypointPanel.java b/src/map/WaypointPanel.java index 30bd55a..bbf63f2 100644 --- a/src/map/WaypointPanel.java +++ b/src/map/WaypointPanel.java @@ -364,6 +364,10 @@ public void actionPerformed(ActionEvent e) { } }; + /** + * Starts a periodic timer while the mouse button is held which + * triggers repeated zoom in actions. The timer is stopped on release. + */ private MouseAdapter zoomInMouseAdapter = new MouseAdapter() { @Override public void mousePressed(MouseEvent me) { @@ -384,6 +388,9 @@ public void mouseReleased(MouseEvent me) { } }; + /** + * The zoom action performed when a zoomIn Timer is triggered + */ private Action zoomInTimerAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { map.zoomIn(new Point(map.getWidth() / 2, map.getHeight() / 2)); @@ -402,6 +409,10 @@ public void actionPerformed(ActionEvent e) { } }; + /** + * Starts a periodic timer while the mouse button is held which + * triggers repeated zoom out actions. The timer is stopped on release. + */ private MouseAdapter zoomOutMouseAdapter = new MouseAdapter() { @Override public void mousePressed(MouseEvent me) { @@ -422,12 +433,18 @@ public void mouseReleased(MouseEvent me) { } }; + /** + * The zoom action performed when a zoomOut Timer is triggered + */ private Action zoomOutTimerAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { map.zoomOut(new Point(map.getWidth() / 2, map.getHeight() / 2)); } }; + /** + * Zooms the map to the closest level. + */ private Action zoomFullAction = new AbstractAction() { { String text = "Full"; @@ -456,6 +473,9 @@ public void actionPerformed(ActionEvent e) { } }; + /** + * Action that starts or stops a unit for the currently active mission. + */ private boolean isUnitMoving = false; private Action toggleMovement = new AbstractAction() { { @@ -556,6 +576,10 @@ public void actionPerformed(ActionEvent e) { } }; + /** + * Action responsible for triggering an undo operation on the + * last command performed. + */ private Action undoCommandAction = new AbstractAction() { { String text = "Undo"; @@ -567,6 +591,10 @@ public void actionPerformed(ActionEvent e) { } }; + /** + * Action responsible for triggering a redo action on the + * last command undone. + */ private Action redoCommandAction = new AbstractAction() { { String text = "Redo"; diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java index d1346e2..89e1b2c 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/StateWidget.java @@ -6,6 +6,7 @@ import com.Context; import com.serial.Serial; +import com.ui.ninePatch.NinePatchPanel; /** * @author Chris Park @ Infinetix Corp. @@ -13,7 +14,7 @@ * Description: Dashboard Widget used to display the current state of a connected unit as * described over serial communication. */ -public class StateWidget extends JPanel { +public class StateWidget extends NinePatchPanel { private Context context; private JFrame infoFrame; @@ -22,25 +23,53 @@ public class StateWidget extends JPanel { private JLabel autoLabel; private JLabel flagLabel; + protected static final int BORDER_SIZE = 25; + /** * Class constructor * @param ctx - The application context */ public StateWidget(Context ctx) { + super(ctx.theme.panelPatch); context = ctx; + initPanel(); + } + + /** + * Construct and place the visual elements of the widget + */ + private void initPanel() { +// Theme theme = context.theme; + Dimension spacer = new Dimension(0, 5); + Dimension labelSize = new Dimension(80, 25); apmLabel = new JLabel("APM - Uninit"); driveLabel = new JLabel("DRIVE - Uninit"); autoLabel = new JLabel("AUTO - Uninit"); flagLabel = new JLabel("FLAGS - None"); - this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + JComponent[] formatList = new JComponent[] { + apmLabel, driveLabel, autoLabel, flagLabel + }; + + for(JComponent jc : formatList) { + jc.setAlignmentX(Component.CENTER_ALIGNMENT); + jc.setMaximumSize(labelSize); + } + + this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); + this.setBorder(BorderFactory.createEmptyBorder( + BORDER_SIZE, BORDER_SIZE,BORDER_SIZE, BORDER_SIZE)); + this.add(apmLabel); + this.add(Box.createRigidArea(spacer)); this.add(driveLabel); + this.add(Box.createRigidArea(spacer)); this.add(autoLabel); + this.add(Box.createRigidArea(spacer)); this.add(flagLabel); } - + /** * Updates the internal state of the widget using byte data received from * serial communications with a device. @@ -164,7 +193,7 @@ else if(caution) { /** * Generates an information panel on click describing the warnings, errors, - * and details of teh current state. + * and details of the current state. */ private MouseAdapter stateDetailsMouseAdapter = new MouseAdapter() { @Override From 67b525034c66fcf2d0d4d9e7e2a27b3540696e81 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 5 Nov 2020 15:47:11 -0800 Subject: [PATCH 009/125] Update state values 0 is now used as an unknown state so all states are pushed forward one. --- src/serial/Serial.java | 18 +++++++++--------- src/ui/StateWidget.java | 11 ++++++----- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/serial/Serial.java b/src/serial/Serial.java index a4e621d..5d4309b 100644 --- a/src/serial/Serial.java +++ b/src/serial/Serial.java @@ -42,19 +42,19 @@ public class Serial { public static final byte AUTO_FLAGS = 0x3; //APM state sub values - public static final byte APM_STATE_INIT = 0x0; - public static final byte APM_STATE_SELF_TEST = 0x1; - public static final byte APM_STATE_DRIVE = 0x2; + public static final byte APM_STATE_INIT = 0x1; + public static final byte APM_STATE_SELF_TEST = 0x2; + public static final byte APM_STATE_DRIVE = 0x3; //drive state sub values - public static final byte DRIVE_STATE_STOP = 0x0; - public static final byte DRIVE_STATE_AUTO = 0x1; - public static final byte DRIVE_STATE_RADIO = 0x2; + public static final byte DRIVE_STATE_STOP = 0x1; + public static final byte DRIVE_STATE_AUTO = 0x2; + public static final byte DRIVE_STATE_RADIO = 0x3; //auto state sub values - public static final byte AUTO_STATE_FULL = 0x0; - public static final byte AUTO_STATE_AVOID = 0x1; - public static final byte AUTO_STATE_STALLED = 0x2; + public static final byte AUTO_STATE_FULL = 0x1; + public static final byte AUTO_STATE_AVOID = 0x2; + public static final byte AUTO_STATE_STALLED = 0x3; //auto flags sub values public static final byte AUTO_STATE_FLAGS_NONE = 0B00; diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java index 89e1b2c..ca881cd 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/StateWidget.java @@ -41,7 +41,7 @@ public StateWidget(Context ctx) { private void initPanel() { // Theme theme = context.theme; Dimension spacer = new Dimension(0, 5); - Dimension labelSize = new Dimension(80, 25); + Dimension labelSize = new Dimension(140, 25); apmLabel = new JLabel("APM - Uninit"); driveLabel = new JLabel("DRIVE - Uninit"); @@ -100,7 +100,7 @@ public void update(byte state, byte substate) { * @param substate - The state variation to be set */ private void setAPMState(byte substate) { - System.err.println("StateWidget - Updating APM State"); +// System.err.println("StateWidget - Updating APM State"); switch(substate) { case Serial.APM_STATE_INIT: @@ -114,6 +114,7 @@ private void setAPMState(byte substate) { break; default: apmLabel.setText("APM - Unknown State"); + System.err.println("State value: " + substate); } } @@ -122,7 +123,7 @@ private void setAPMState(byte substate) { * @param substate - The state variation to be set */ private void setDriveState(byte substate) { - System.err.println("StateWidget - Updating Drive State"); +// System.err.println("StateWidget - Updating Drive State"); switch(substate) { case Serial.DRIVE_STATE_STOP: @@ -144,7 +145,7 @@ private void setDriveState(byte substate) { * @param substate - The state variation to be set */ private void setAutoState(byte substate) { - System.err.println("StateWidget - Updating Auto State"); +// System.err.println("StateWidget - Updating Auto State"); switch(substate) { case Serial.AUTO_STATE_FULL: @@ -171,7 +172,7 @@ private void setFlagState(byte substate) { boolean caution = ((substate & Serial.AUTO_STATE_FLAGS_CAUTION) > 0 ) ? true : false; boolean approach = ((substate & Serial.AUTO_STATE_FLAGS_APPROACH) > 0 ) ? true : false; - System.out.println("StateWidget - Updating Flag State"); +// System.out.println("StateWidget - Updating Flag State"); if(caution && approach) { flagLabel.setText("FLAG - Approach & Caution"); From 9a23a2d5378b5c37b948a745e1db02f7b5095bf2 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 5 Nov 2020 17:45:02 -0800 Subject: [PATCH 010/125] Simplify state labels and add to jpanels --- src/Dashboard.java | 5 +++- src/ui/StateWidget.java | 65 +++++++++++++++++++++++------------------ 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/Dashboard.java b/src/Dashboard.java index 10aaa04..a53b9fb 100644 --- a/src/Dashboard.java +++ b/src/Dashboard.java @@ -205,6 +205,7 @@ private JPanel createRightPanel() { dashPanel.add( AngleWidget.createDial( context, Serial.HEADING, context.theme.roverTop)); + if(context.getResource("widget_type", "Angles").equals("Horizon")){ // Initialize the horizon widget JPanel horizon = @@ -220,10 +221,12 @@ public void mouseClicked(MouseEvent e){ }).setVisible(true); } }); + // Add to the panel dashPanel.add(horizon); dashPanel.add(RadioWidget.create(context, WIDGET_SIZE)); - } else { + } + else { dashPanel.add( AngleWidget.createDial( context, Serial.PITCH, context.theme.roverSide)); diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java index ca881cd..19eb186 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/StateWidget.java @@ -1,5 +1,6 @@ package com.ui; +import java.util.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; @@ -41,13 +42,15 @@ public StateWidget(Context ctx) { private void initPanel() { // Theme theme = context.theme; Dimension spacer = new Dimension(0, 5); - Dimension labelSize = new Dimension(140, 25); + Dimension labelSize = new Dimension(90, 25); - apmLabel = new JLabel("APM - Uninit"); - driveLabel = new JLabel("DRIVE - Uninit"); - autoLabel = new JLabel("AUTO - Uninit"); - flagLabel = new JLabel("FLAGS - None"); + apmLabel = new JLabel("APM - ??"); + driveLabel = new JLabel("DRV - ??"); + autoLabel = new JLabel("AUT - ??"); + flagLabel = new JLabel("FLG - None"); + + ArrayList statePanels = new ArrayList(); JComponent[] formatList = new JComponent[] { apmLabel, driveLabel, autoLabel, flagLabel }; @@ -55,19 +58,25 @@ private void initPanel() { for(JComponent jc : formatList) { jc.setAlignmentX(Component.CENTER_ALIGNMENT); jc.setMaximumSize(labelSize); + + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS)); + panel.setPreferredSize(labelSize); + panel.setOpaque(false); + panel.add(jc); + + statePanels.add(panel); + } this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); this.setBorder(BorderFactory.createEmptyBorder( BORDER_SIZE, BORDER_SIZE,BORDER_SIZE, BORDER_SIZE)); - - this.add(apmLabel); - this.add(Box.createRigidArea(spacer)); - this.add(driveLabel); - this.add(Box.createRigidArea(spacer)); - this.add(autoLabel); - this.add(Box.createRigidArea(spacer)); - this.add(flagLabel); + + for(JPanel panel : statePanels) { + this.add(panel); + this.add(Box.createRigidArea(spacer)); + } } /** @@ -104,16 +113,16 @@ private void setAPMState(byte substate) { switch(substate) { case Serial.APM_STATE_INIT: - apmLabel.setText("APM - Initializing"); + apmLabel.setText("APM - Init"); break; case Serial.APM_STATE_SELF_TEST: - apmLabel.setText("APM - Performing Self Test"); + apmLabel.setText("APM - Self Test"); break; case Serial.APM_STATE_DRIVE: apmLabel.setText("APM - Driving"); break; default: - apmLabel.setText("APM - Unknown State"); + apmLabel.setText("APM - Unknown"); System.err.println("State value: " + substate); } } @@ -127,16 +136,16 @@ private void setDriveState(byte substate) { switch(substate) { case Serial.DRIVE_STATE_STOP: - driveLabel.setText("DRIVE - Stopped"); + driveLabel.setText("DRV - Stopped"); break; case Serial.DRIVE_STATE_AUTO: - driveLabel.setText("DRIVE - Auto Mode"); + driveLabel.setText("DRV - Auto"); break; case Serial.DRIVE_STATE_RADIO: - driveLabel.setText("DRIVE - Radio Manual Mode"); + driveLabel.setText("DRV - Manual"); break; default: - driveLabel.setText("DRIVE - Unknown State"); + driveLabel.setText("DRV - Unknown"); } } @@ -149,16 +158,16 @@ private void setAutoState(byte substate) { switch(substate) { case Serial.AUTO_STATE_FULL: - autoLabel.setText("AUTO - Full"); + autoLabel.setText("AUT - Full"); break; case Serial.AUTO_STATE_AVOID: - autoLabel.setText("AUTO - Avoid"); + autoLabel.setText("AUT - Avoid"); break; case Serial.AUTO_STATE_STALLED: - autoLabel.setText("AUTO - Stalled"); + autoLabel.setText("AUT - Stalled"); break; default: - autoLabel.setText("AUTO - Unknown State"); + autoLabel.setText("AUT - Unknown State"); } } @@ -175,19 +184,19 @@ private void setFlagState(byte substate) { // System.out.println("StateWidget - Updating Flag State"); if(caution && approach) { - flagLabel.setText("FLAG - Approach & Caution"); + flagLabel.setText("FLG - App. & Caut."); //Severity High. Approaching a clear obstable? } else if(approach) { - flagLabel.setText("FLAG - Approach"); + flagLabel.setText("FLG - Approach"); //Severity Medium. Approaching an obstacle, slowing down? } else if(caution) { - flagLabel.setText("FLAG - Caution"); + flagLabel.setText("FLG - Caution"); //Severity Medium. Avoiding an obstacle? } else { - flagLabel.setText("FLAG - None"); + flagLabel.setText("FLG - None"); //Severity Low. No hazards detected? } } From e0a4ff3b7d178820285590b6bc07054234810b47 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Fri, 6 Nov 2020 10:38:18 -0800 Subject: [PATCH 011/125] Added coloring to state displays Current colors cycle between Red/Yellow/Green based on severity of the state --- resources/resources_en.properties | 2 +- src/ui/StateWidget.java | 86 +++++++++++++++++++++++-------- 2 files changed, 65 insertions(+), 23 deletions(-) diff --git a/resources/resources_en.properties b/resources/resources_en.properties index f5ec4c5..e45f6d3 100644 --- a/resources/resources_en.properties +++ b/resources/resources_en.properties @@ -1,5 +1,5 @@ version_id =1.0.2 -release_date =2020-10-20 +release_date =2020-11-06 image_folder =resources/images/ patch_folder =resources/images/nP/ font_folder =resources/fonts/ diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java index 19eb186..ff49444 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/StateWidget.java @@ -16,16 +16,22 @@ * described over serial communication. */ public class StateWidget extends NinePatchPanel { + //Constants + protected static final int BORDER_SIZE = 25; + private Context context; private JFrame infoFrame; - private JLabel apmLabel; + //State readouts + private JPanel apmPanel; + private JLabel apmLabel; + private JPanel drivePanel; private JLabel driveLabel; + private JPanel autoPanel; private JLabel autoLabel; + private JPanel flagPanel; private JLabel flagLabel; - protected static final int BORDER_SIZE = 25; - /** * Class constructor * @param ctx - The application context @@ -40,39 +46,60 @@ public StateWidget(Context ctx) { * Construct and place the visual elements of the widget */ private void initPanel() { -// Theme theme = context.theme; Dimension spacer = new Dimension(0, 5); Dimension labelSize = new Dimension(90, 25); + Dimension panelSize = new Dimension(90, 25); + this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); + this.setBorder(BorderFactory.createEmptyBorder( + BORDER_SIZE, BORDER_SIZE,BORDER_SIZE, BORDER_SIZE)); + + //Configure state labels apmLabel = new JLabel("APM - ??"); driveLabel = new JLabel("DRV - ??"); autoLabel = new JLabel("AUT - ??"); flagLabel = new JLabel("FLG - None"); - - ArrayList statePanels = new ArrayList(); - JComponent[] formatList = new JComponent[] { + JComponent[] labelList = new JComponent[] { apmLabel, driveLabel, autoLabel, flagLabel }; - for(JComponent jc : formatList) { + for(JComponent jc : labelList) { jc.setAlignmentX(Component.CENTER_ALIGNMENT); jc.setMaximumSize(labelSize); - - JPanel panel = new JPanel(); - panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS)); - panel.setPreferredSize(labelSize); - panel.setOpaque(false); - panel.add(jc); - - statePanels.add(panel); - } - this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); - this.setBorder(BorderFactory.createEmptyBorder( - BORDER_SIZE, BORDER_SIZE,BORDER_SIZE, BORDER_SIZE)); - + //Configure state panels + ArrayList statePanels = new ArrayList(); + apmPanel = new JPanel(); + apmPanel.setLayout(new BoxLayout(apmPanel, BoxLayout.LINE_AXIS)); + apmPanel.setPreferredSize(panelSize); + apmPanel.setOpaque(true); + apmPanel.add(apmLabel); + statePanels.add(apmPanel); + + drivePanel = new JPanel(); + drivePanel.setLayout(new BoxLayout(drivePanel, BoxLayout.LINE_AXIS)); + drivePanel.setPreferredSize(panelSize); + drivePanel.setOpaque(true); + drivePanel.add(driveLabel); + statePanels.add(drivePanel); + + autoPanel = new JPanel(); + autoPanel.setLayout(new BoxLayout(autoPanel, BoxLayout.LINE_AXIS)); + autoPanel.setPreferredSize(panelSize); + autoPanel.setOpaque(true); + autoPanel.add(autoLabel); + statePanels.add(autoPanel); + + flagPanel = new JPanel(); + flagPanel.setLayout(new BoxLayout(flagPanel, BoxLayout.LINE_AXIS)); + flagPanel.setPreferredSize(panelSize); + flagPanel.setOpaque(true); + flagPanel.add(flagLabel); + statePanels.add(flagPanel); + + //Add panels to widget for(JPanel panel : statePanels) { this.add(panel); this.add(Box.createRigidArea(spacer)); @@ -114,16 +141,19 @@ private void setAPMState(byte substate) { switch(substate) { case Serial.APM_STATE_INIT: apmLabel.setText("APM - Init"); + apmPanel.setBackground(Color.white); break; case Serial.APM_STATE_SELF_TEST: apmLabel.setText("APM - Self Test"); + apmPanel.setBackground(Color.white); break; case Serial.APM_STATE_DRIVE: apmLabel.setText("APM - Driving"); + apmPanel.setBackground(Color.green); break; default: apmLabel.setText("APM - Unknown"); - System.err.println("State value: " + substate); + apmPanel.setBackground(Color.red); } } @@ -137,15 +167,19 @@ private void setDriveState(byte substate) { switch(substate) { case Serial.DRIVE_STATE_STOP: driveLabel.setText("DRV - Stopped"); + drivePanel.setBackground(Color.white); break; case Serial.DRIVE_STATE_AUTO: driveLabel.setText("DRV - Auto"); + drivePanel.setBackground(Color.green); break; case Serial.DRIVE_STATE_RADIO: driveLabel.setText("DRV - Manual"); + drivePanel.setBackground(Color.green); break; default: driveLabel.setText("DRV - Unknown"); + drivePanel.setBackground(Color.red); } } @@ -159,15 +193,19 @@ private void setAutoState(byte substate) { switch(substate) { case Serial.AUTO_STATE_FULL: autoLabel.setText("AUT - Full"); + autoPanel.setBackground(Color.green); break; case Serial.AUTO_STATE_AVOID: autoLabel.setText("AUT - Avoid"); + autoPanel.setBackground(Color.yellow); break; case Serial.AUTO_STATE_STALLED: autoLabel.setText("AUT - Stalled"); + autoPanel.setBackground(Color.red); break; default: autoLabel.setText("AUT - Unknown State"); + autoPanel.setBackground(Color.red); } } @@ -185,18 +223,22 @@ private void setFlagState(byte substate) { if(caution && approach) { flagLabel.setText("FLG - App. & Caut."); + flagPanel.setBackground(Color.yellow); //Severity High. Approaching a clear obstable? } else if(approach) { flagLabel.setText("FLG - Approach"); + flagPanel.setBackground(Color.yellow); //Severity Medium. Approaching an obstacle, slowing down? } else if(caution) { flagLabel.setText("FLG - Caution"); + flagPanel.setBackground(Color.yellow); //Severity Medium. Avoiding an obstacle? } else { flagLabel.setText("FLG - None"); + flagPanel.setBackground(Color.green); //Severity Low. No hazards detected? } } From b52207b23b49b56c491cb099e957177ad1cf7d4d Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 19 Nov 2020 16:57:24 -0800 Subject: [PATCH 012/125] Data/Telemetry widget updates -Added a ground mode Telemetry widget which excludes sea level and altitude. -Modified the State widget to be more like that TelemtryWidget. -Began designing StateWidget format to match the same XML parsing behavior as the TelemtryDataWidget. --- resources/resources_en.properties | 3 +- resources/telemetryWidget.xml | 2 +- resources/telemetryWidgetGround.xml | 4 + src/Context.java | 4 + src/Dashboard.java | 31 +++--- src/ui/SampleSource.java | 4 + src/ui/StateWidget.java | 147 ++++++++++++++++++---------- src/ui/TelemetryWidget.java | 85 +++++++++------- 8 files changed, 174 insertions(+), 106 deletions(-) create mode 100644 resources/telemetryWidgetGround.xml diff --git a/resources/resources_en.properties b/resources/resources_en.properties index e45f6d3..76dd7f2 100644 --- a/resources/resources_en.properties +++ b/resources/resources_en.properties @@ -6,7 +6,8 @@ font_folder =resources/fonts/ console_log_level =FINE file_log_level =FINE stateDescriptions =resources/stateMessageDb.xml -telemetryWidetSpec =resources/telemetryWidget.xml +telemetryWidgetAir =resources/telemetryWidget.xml +telemetryWidgetGnd =resources/telemetryWidgetGround.xml tile_server_list =tile_servers default_tile_server =satellite text_font =DroidSansMono.ttf diff --git a/resources/telemetryWidget.xml b/resources/telemetryWidget.xml index e2591ce..a7fb956 100644 --- a/resources/telemetryWidget.xml +++ b/resources/telemetryWidget.xml @@ -1,4 +1,4 @@ - + diff --git a/resources/telemetryWidgetGround.xml b/resources/telemetryWidgetGround.xml new file mode 100644 index 0000000..03d0c2d --- /dev/null +++ b/resources/telemetryWidgetGround.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/Context.java b/src/Context.java index f37399b..4038762 100644 --- a/src/Context.java +++ b/src/Context.java @@ -159,6 +159,10 @@ public void toggleLocale() { saveProps(); } + public String getCurrentLocale() { + return (String) persist.get("subject"); + } + public void setHomeProp(String lat, String lng) { persist.setProperty("homeLat", lat); persist.setProperty("homeLng", lng); diff --git a/src/Dashboard.java b/src/Dashboard.java index a53b9fb..0909c8b 100644 --- a/src/Dashboard.java +++ b/src/Dashboard.java @@ -190,20 +190,30 @@ public void update(double alt) { } private JPanel createRightPanel() { + JPanel dashPanel = new JPanel(); + dashPanel.setOpaque(false); + dashPanel.setLayout(new BoxLayout(dashPanel, BoxLayout.PAGE_AXIS)); + + //Add Telemetry Data Widget (Ground or Air) try { - dataWidget = TelemetryWidget.fromXML(context, "telemetryWidetSpec"); - } catch(Exception e) { + if(context.getCurrentLocale() == "ground") { + dataWidget = TelemetryWidget.fromXML(context, "telemetryWidgetGnd"); + } + else { + dataWidget = TelemetryWidget.fromXML(context, "telemetryWidgetAir"); + } + } + catch(Exception e) { iolog.severe("Failed to load telemetry widget line spec "+e); e.printStackTrace(); } - - JPanel dashPanel = new JPanel(); - dashPanel.setOpaque(false); - dashPanel.setLayout(new BoxLayout(dashPanel, BoxLayout.PAGE_AXIS)); - dashPanel.add(dataWidget); - dashPanel.add( - AngleWidget.createDial( + + //TODO - CP - Add State widget here + stateWidget = new StateWidget(context); + dashPanel.add(stateWidget); + + dashPanel.add(AngleWidget.createDial( context, Serial.HEADING, context.theme.roverTop)); if(context.getResource("widget_type", "Angles").equals("Horizon")){ @@ -235,9 +245,6 @@ public void mouseClicked(MouseEvent e){ context, Serial.ROLL, context.theme.roverFront)); } - stateWidget = new StateWidget(context); - dashPanel.add(stateWidget); - return dashPanel; } diff --git a/src/ui/SampleSource.java b/src/ui/SampleSource.java index 6cc179f..0844201 100644 --- a/src/ui/SampleSource.java +++ b/src/ui/SampleSource.java @@ -14,15 +14,18 @@ public class SampleSource implements DataSource, TelemetryListener { private List data; private int oldestPosition; private final String name; + public SampleSource(String name) { this.name = name; data = new ArrayList(SAMPLES); for(int i=0; i 1.0 || x < 0.0) return 0.0; double xPoint = (x*((double)SAMPLES-1)); @@ -33,6 +36,7 @@ public double get(double x) { +data.get(dataPrv) * (1.0d-ratio); return rtn; } + public String getName() { return name; } diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java index ff49444..d10877d 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/StateWidget.java @@ -15,9 +15,12 @@ * Description: Dashboard Widget used to display the current state of a connected unit as * described over serial communication. */ -public class StateWidget extends NinePatchPanel { +public class StateWidget extends JPanel { //Constants - protected static final int BORDER_SIZE = 25; + protected static final int BORDER_SIZE = 0; + //TODO - CP - Replace this with a settable line width for future xml integration + protected static final int LINE_WIDTH = 8; + private Context context; private JFrame infoFrame; @@ -37,15 +40,21 @@ public class StateWidget extends NinePatchPanel { * @param ctx - The application context */ public StateWidget(Context ctx) { - super(ctx.theme.panelPatch); context = ctx; initPanel(); } /** - * Construct and place the visual elements of the widget + * Construct and place the visual layout elements of the widget. Sets + * properties of the elements such as size and format restrictions, + * dimensional spacing, and border insets. */ private void initPanel() { + float fontSize = 16.0f; + Font font = context.theme.text.deriveFont(fontSize); + + + Dimension spacer = new Dimension(0, 5); Dimension labelSize = new Dimension(90, 25); Dimension panelSize = new Dimension(90, 25); @@ -55,10 +64,29 @@ private void initPanel() { BORDER_SIZE, BORDER_SIZE,BORDER_SIZE, BORDER_SIZE)); //Configure state labels - apmLabel = new JLabel("APM - ??"); - driveLabel = new JLabel("DRV - ??"); - autoLabel = new JLabel("AUT - ??"); - flagLabel = new JLabel("FLG - None"); + apmLabel = new JLabel("Apm:--"); + apmLabel.setFont(font); + apmLabel.setForeground(Color.decode("0xEA8300")); + apmLabel.setBackground(Color.decode("0xDFDFDF")); + apmLabel.setOpaque(true); + + driveLabel = new JLabel("Drv:--"); + driveLabel.setFont(font); + driveLabel.setForeground(Color.decode("0xEA8300")); + driveLabel.setBackground(Color.decode("0xEEEEEE")); + driveLabel.setOpaque(true); + + autoLabel = new JLabel("Aut:--"); + autoLabel.setFont(font); + autoLabel.setForeground(Color.decode("0xEA8300")); + autoLabel.setBackground(Color.decode("0xDFDFDF")); + autoLabel.setOpaque(true); + + flagLabel = new JLabel("Flg:None"); + flagLabel.setFont(font); + flagLabel.setForeground(Color.decode("0xEA8300")); + flagLabel.setBackground(Color.decode("0xEEEEEE")); + flagLabel.setOpaque(true); JComponent[] labelList = new JComponent[] { apmLabel, driveLabel, autoLabel, flagLabel @@ -110,7 +138,7 @@ private void initPanel() { * Updates the internal state of the widget using byte data received from * serial communications with a device. * @param state - The main state type being updated - * @param substate - The main state variation to be updated to + * @param substate - The sub state variation to be updated to */ public void update(byte state, byte substate) { switch(state) { @@ -133,114 +161,125 @@ public void update(byte state, byte substate) { /** * Sets the current APM state. - * @param substate - The state variation to be set + * @param substate - The sub state variation to be set */ private void setAPMState(byte substate) { -// System.err.println("StateWidget - Updating APM State"); + int finalWidth; + String fmt; + String fmtStr = "Apm:%s"; + System.err.println("StateWidget - Updating APM State"); switch(substate) { case Serial.APM_STATE_INIT: - apmLabel.setText("APM - Init"); - apmPanel.setBackground(Color.white); + fmt = String.format(fmtStr, "Init"); + finalWidth = Math.min(fmt.length(), LINE_WIDTH); break; case Serial.APM_STATE_SELF_TEST: - apmLabel.setText("APM - Self Test"); - apmPanel.setBackground(Color.white); + + fmt = String.format(fmtStr, "Self Test"); + finalWidth = Math.min(fmt.length(), LINE_WIDTH); break; case Serial.APM_STATE_DRIVE: - apmLabel.setText("APM - Driving"); - apmPanel.setBackground(Color.green); + fmt = String.format(fmtStr, "Driving"); + finalWidth = Math.min(fmt.length(), LINE_WIDTH); break; default: - apmLabel.setText("APM - Unknown"); - apmPanel.setBackground(Color.red); + fmt = String.format(fmtStr, "Unknown"); + finalWidth = Math.min(fmt.length(), LINE_WIDTH); } + apmLabel.setText(fmt.substring(0, finalWidth)); } /** * Sets the current Drive state. - * @param substate - The state variation to be set + * @param substate - The sub state variation to be set */ private void setDriveState(byte substate) { -// System.err.println("StateWidget - Updating Drive State"); + int finalWidth; + String fmt; + String fmtStr = "Drv:%s"; +// System.err.println("StateWidget - Updating Drive State"); switch(substate) { case Serial.DRIVE_STATE_STOP: - driveLabel.setText("DRV - Stopped"); - drivePanel.setBackground(Color.white); + fmt = String.format(fmtStr, "Stopped"); + finalWidth = Math.min(fmt.length(), LINE_WIDTH); break; case Serial.DRIVE_STATE_AUTO: - driveLabel.setText("DRV - Auto"); - drivePanel.setBackground(Color.green); + fmt = String.format(fmtStr, "Auto"); + finalWidth = Math.min(fmt.length(), LINE_WIDTH); break; case Serial.DRIVE_STATE_RADIO: - driveLabel.setText("DRV - Manual"); - drivePanel.setBackground(Color.green); + fmt = String.format(fmtStr, "Manual"); + finalWidth = Math.min(fmt.length(), LINE_WIDTH); break; default: - driveLabel.setText("DRV - Unknown"); - drivePanel.setBackground(Color.red); + fmt = String.format(fmtStr, "Unknown"); + finalWidth = Math.min(fmt.length(), LINE_WIDTH); } + driveLabel.setText(fmt.substring(0, finalWidth)); } /** * Sets the current Auto state. - * @param substate - The state variation to be set + * @param substate - The sub state variation to be set */ private void setAutoState(byte substate) { -// System.err.println("StateWidget - Updating Auto State"); + int finalWidth; + String fmt; + String fmtStr = "Aut:%s"; +// System.err.println("StateWidget - Updating Auto State"); switch(substate) { case Serial.AUTO_STATE_FULL: - autoLabel.setText("AUT - Full"); - autoPanel.setBackground(Color.green); + fmt = String.format(fmtStr, "Full"); + finalWidth = Math.min(fmt.length(), LINE_WIDTH); break; case Serial.AUTO_STATE_AVOID: - autoLabel.setText("AUT - Avoid"); - autoPanel.setBackground(Color.yellow); + fmt = String.format(fmtStr, "Avoid"); + finalWidth = Math.min(fmt.length(), LINE_WIDTH); break; case Serial.AUTO_STATE_STALLED: - autoLabel.setText("AUT - Stalled"); - autoPanel.setBackground(Color.red); + fmt = String.format(fmtStr, "Stalled"); + finalWidth = Math.min(fmt.length(), LINE_WIDTH); break; default: - autoLabel.setText("AUT - Unknown State"); - autoPanel.setBackground(Color.red); + fmt = String.format(fmtStr, "Unknown"); + finalWidth = Math.min(fmt.length(), LINE_WIDTH); } + autoLabel.setText(fmt.substring(0, finalWidth)); } - //TODO - CP - Update flag string AND set an icon indicating severity level - /** * Sets the current state flag if any. * @param substate - The flag type to be set */ private void setFlagState(byte substate) { + int finalWidth; + String fmt; + String fmtStr = "Flg:%s"; + boolean caution = ((substate & Serial.AUTO_STATE_FLAGS_CAUTION) > 0 ) ? true : false; boolean approach = ((substate & Serial.AUTO_STATE_FLAGS_APPROACH) > 0 ) ? true : false; // System.out.println("StateWidget - Updating Flag State"); - if(caution && approach) { - flagLabel.setText("FLG - App. & Caut."); - flagPanel.setBackground(Color.yellow); - //Severity High. Approaching a clear obstable? + fmt = String.format(fmtStr, "App. & Caut."); + finalWidth = Math.min(fmt.length(), LINE_WIDTH); } else if(approach) { - flagLabel.setText("FLG - Approach"); - flagPanel.setBackground(Color.yellow); - //Severity Medium. Approaching an obstacle, slowing down? + fmt = String.format(fmtStr, "Approach"); + finalWidth = Math.min(fmt.length(), LINE_WIDTH); } else if(caution) { - flagLabel.setText("FLG - Caution"); - flagPanel.setBackground(Color.yellow); - //Severity Medium. Avoiding an obstacle? + fmt = String.format(fmtStr, "Caution"); + finalWidth = Math.min(fmt.length(), LINE_WIDTH); } else { - flagLabel.setText("FLG - None"); - flagPanel.setBackground(Color.green); - //Severity Low. No hazards detected? + fmt = String.format(fmtStr, "None"); + finalWidth = Math.min(fmt.length(), LINE_WIDTH); } + flagLabel.setText(fmt.substring(0, finalWidth)); } /** diff --git a/src/ui/TelemetryWidget.java b/src/ui/TelemetryWidget.java index d852ff3..2b40809 100644 --- a/src/ui/TelemetryWidget.java +++ b/src/ui/TelemetryWidget.java @@ -22,12 +22,14 @@ public class TelemetryWidget extends JPanel{ // around the internal text private final static Border insets = new EmptyBorder(9,10,40,10); private final static Border padding = new EmptyBorder(0,4,0,4); + // Maximum number of chars wide to make the lines private final int lineWidth; + // Collection of line instances being rendered in the telemetry widget private final Collection lines = new ArrayList(); - private final NinePatch np; +// private final NinePatch np; public static class LineItem { private String fmt; @@ -43,6 +45,32 @@ public LineItem(String fmt, int telemetryID, Color bgcolor){ public Color getBgColor(){ return bgcolor; } } + private class Line extends JLabel implements TelemetryListener { + private String fmtStr; + + public Line(Context ctx, String fmtStr) { + this.fmtStr = fmtStr; + update(0.0); + } + + public void update(double data) { + String fmt = String.format(fmtStr, data); + int finalWidth = Math.min(fmt.length(), lineWidth); + setText(fmt.substring(0,finalWidth)); + } + + @Override + public void repaint() { + super.repaint(); + // Since the parent component (lower in the component tree) could + // render a transparent border on top of it's Lines + // (higher in the view window), it must be repainted after + // this component or the layers will change order depending on + // who gets repainted + TelemetryWidget.this.repaint(getX(), getY(), getWidth(), getHeight()); + } + } + /** * Create a TelemetryWidget. * Each line will be `lineWidth` characters @@ -64,11 +92,15 @@ public static TelemetryWidget fromXML(Context ctx, String resourceKey) String defaultFormat = "% f"; Color textColor = ctx.theme.textColor; Collection items = new LinkedList(); - try (Reader source = new FileReader(ctx.getResource(resourceKey))){ - XMLStreamReader r = XMLInputFactory.newInstance(). - createXMLStreamReader(source); + + try (Reader source = new FileReader(ctx.getResource(resourceKey))) { + XMLStreamReader r = + XMLInputFactory.newInstance().createXMLStreamReader(source); + while(r.hasNext()) { + switch(r.next()) { + case XMLStreamConstants.START_ELEMENT: if(r.getLocalName().equals("telemetryWidget")) { width = Integer.valueOf(r.getAttributeValue(null, "width")); @@ -79,7 +111,8 @@ public static TelemetryWidget fromXML(Context ctx, String resourceKey) if(color != null){ textColor = Color.decode(color); } - } else if(r.getLocalName().equals("line")) { + } + else if(r.getLocalName().equals("line")) { String fmt = r.getAttributeValue(null,"fmt"); String idx = r.getAttributeValue(null,"telem"); @@ -89,11 +122,12 @@ public static TelemetryWidget fromXML(Context ctx, String resourceKey) : Color.WHITE; String format = (fmt == null)? defaultFormat : fmt; - int index = (idx == null)? -1 : Integer.valueOf(idx); + int index = (idx == null) ? -1 : Integer.valueOf(idx); items.add(new LineItem(format,index, bg)); } break; + default: break; } @@ -114,15 +148,13 @@ public void reset(){ } } - private TelemetryWidget( - Context ctx, - int lineWidth, - float fontSize, - Color textColor, - Collection items){ + //Private Constructor + private TelemetryWidget(Context ctx, int lineWidth, float fontSize, + Color textColor, Collection items) { + this.lineWidth = lineWidth; - np = ctx.theme.screenPatch; +// np = ctx.theme.screenPatch; JPanel dataPanel = new JPanel(); dataPanel.setBorder(insets); dataPanel.setLayout(new BoxLayout(dataPanel, BoxLayout.PAGE_AXIS)); @@ -137,7 +169,7 @@ private TelemetryWidget( l.setBackground(i.getBgColor()); l.setOpaque(true); l.setBorder(padding); - if(i.getTelemetryId() != -1){ + if(i.getTelemetryId() != -1) { ctx.telemetry.registerListener(i.getTelemetryId(), l); } @@ -152,29 +184,6 @@ private TelemetryWidget( @Override public void paint(Graphics g) { super.paint(g); - np.paintIn(g, getWidth(), getHeight()); +// np.paintIn(g, getWidth(), getHeight()); } - - private class Line extends JLabel implements TelemetryListener { - private String fmtStr; - public Line(Context ctx, String fmtStr) { - this.fmtStr = fmtStr; - update(0.0); - } - public void update(double data) { - String fmt = String.format(fmtStr, data); - int finalWidth = Math.min(fmt.length(), lineWidth); - setText(fmt.substring(0,finalWidth)); - } - @Override public void repaint(){ - super.repaint(); - // Since the parent component (lower in the component tree) could - // render a transparent border on top of it's Lines - // (higher in the view window), it must be repainted after - // this component or the layers will change order depending on - // who gets repainted - TelemetryWidget.this.repaint(getX(), getY(), getWidth(), getHeight()); - } - } - } From 17ccad8e7882f2fc7e19a8b05d97500c37195926 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Tue, 1 Dec 2020 17:58:44 -0800 Subject: [PATCH 013/125] Added UIWidget base class and data widget child Created UIWidget base class and ported TelemetryWidget over as a child. This will be the new Heirarchy framework for the new Dashboard right side data panel. --- src/serial/SerialSender.java | 2 - src/ui/StateWidget.java | 2 - src/ui/TelemetryDataWidget.java | 220 ++++++++++++++++++++++++++++++++ src/ui/UIWidget.java | 47 +++++++ 4 files changed, 267 insertions(+), 4 deletions(-) create mode 100644 src/ui/TelemetryDataWidget.java create mode 100644 src/ui/UIWidget.java diff --git a/src/serial/SerialSender.java b/src/serial/SerialSender.java index 9e72553..a6fff09 100644 --- a/src/serial/SerialSender.java +++ b/src/serial/SerialSender.java @@ -165,10 +165,8 @@ public void changeMovement(boolean shouldMove) { msg = Message.stopDriving(); System.err.println("Sending stop driving message."); } - sendMessage(msg); } - } public void sendSync() { diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java index d10877d..179cc5e 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/StateWidget.java @@ -53,8 +53,6 @@ private void initPanel() { float fontSize = 16.0f; Font font = context.theme.text.deriveFont(fontSize); - - Dimension spacer = new Dimension(0, 5); Dimension labelSize = new Dimension(90, 25); Dimension panelSize = new Dimension(90, 25); diff --git a/src/ui/TelemetryDataWidget.java b/src/ui/TelemetryDataWidget.java new file mode 100644 index 0000000..61862e0 --- /dev/null +++ b/src/ui/TelemetryDataWidget.java @@ -0,0 +1,220 @@ +package com.ui; + +import com.Context; +import com.ui.UIWidget; + +import com.telemetry.TelemetryListener; + +import java.io.Reader; +import java.io.FileReader; +import java.util.*; + +import javax.xml.stream.*; +import java.text.ParseException; + +import javax.swing.*; +import java.awt.*; + +/** + * + * @author Chris Park @ Infinetix Corp. + * Date: 11-25-2020 + * Description: Widget Child class use for displaying Telemetry data. + */ +public class TelemetryDataWidget extends UIWidget { + private int lineWidth; + private Collection lines = new ArrayList(); + + /** + * @author Chris Park @ Infinetix Corp. + * Date: 11-25-2020 + * Description: Nested static internal class used to display telemetry data. + */ + public static class LineItem { + private String formatStr; + private int telemetryID; + private Color bgColor; + + /** + * Class Constructor + * @param format - the format string for this LineItem + * @param id - The Telemetry ID for this LineItem + * @param bgc - The Background color for this LineItem + */ + public LineItem(String format, int id, Color bgc) { + formatStr = format; + telemetryID = id; + bgColor = bgc; + } + + /** + * Returns the format string + * @return - The current format string; + */ + public String getFormatString() { + return formatStr; + } + + /** + * Returns the telemtry ID + * @return - the current telemetry ID + */ + public int getTelemetryID() { + return telemetryID; + } + + /** + * Returns the background color + * @return - The current background color + */ + public Color getBackgroundColor() { + return bgColor; + } + } + + /** + * @author Chris Park @ Infinetix Corp. + * Date: 11-25-2020 + * Description: Nested internal class used to display telemetry data. + */ + private class Line extends JLabel implements TelemetryListener { + private String formatStr; + + /** + * Class Constructor + * @param ctx - The application Context + * @param format - format String for the line text. + */ + public Line(Context ctx, String format) { + formatStr = format; + update(0.0); + } + + /** + * Updates the text and formatting help by this line + */ + public void update(double data) { + String format = String.format(formatStr, data); + int finalWidth = Math.min(format.length(), lineWidth); + setText(format.substring(0, finalWidth)); + } + + /** + * Repaints the parent widget to avoid a change in order layering + */ + @Override + public void repaint() { + super.repaint(); + //Repaint the parent to ensure no change in layer order + TelemetryDataWidget.this.repaint(getX(), getY(), + getWidth(), getHeight()); + } + } + + /** + * Class Constructor + * @param ctx - The application context + * @param lineWidth - Maximum Line width for a line of text + * @param fontSize - Font size of a line of + * @param textColor - Color of the displayed text + * @param items - Collection of LineItems to be displayed + */ + public TelemetryDataWidget(Context ctx, int lineWidth, float fontSize, + Color textColor, Collection items) { + super(ctx); + + this.lineWidth = lineWidth; + + JPanel panel = new JPanel(); + panel.setBorder(insets); + panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); + panel.setOpaque(false); + + Font font = ctx.theme.text.deriveFont(fontSize); + + for(LineItem i : items){ + Line l = new Line(ctx, i.getFormatString()); + l.setFont(font); + l.setForeground(textColor); + l.setBackground(i.getBackgroundColor()); + l.setOpaque(true); + if(i.getTelemetryID() != -1) { + ctx.telemetry.registerListener(i.getTelemetryID(), l); + } + + lines.add(l); + panel.add(l); + } + + this.setOpaque(false); + this.add(panel); + } + + /** + * Resets all data lines to 0.0 + */ + public void reset() { + for(Line l : lines) { + l.update(0.0); + } + } + + /** + * Generates a data widget from an xml resource file. + * @param ctx - The application context + * @param resourceKey - The XML Resource key + * @return - Telemetry Data Widget + * @throws ParseException + */ + public static TelemetryDataWidget fromXML(Context ctx, String resourceKey) + throws ParseException { + int width = 0; + float fontSize = 0f; + String defaultFormat = "% f"; + Color textColor = ctx.theme.textColor; + Collection items = new LinkedList(); + + try (Reader source = new FileReader(ctx.getResource(resourceKey))) { + XMLStreamReader r = + XMLInputFactory.newInstance().createXMLStreamReader(source); + + while(r.hasNext()) { + switch(r.next()) { + case XMLStreamConstants.START_ELEMENT: + if(r.getLocalName().equals("telemetryWidget")) { + width = Integer.valueOf(r.getAttributeValue(null, "width")); + fontSize = Float.valueOf(r.getAttributeValue(null, "fontsize")); + defaultFormat = String.format(" %% %df", (width - 1)); + + String color = r.getAttributeValue(null, "color"); + if(color != null) { + textColor = Color.decode(color); + } + } + else if(r.getLocalName().equals("line")) { + String fmt = r.getAttributeValue(null, "fmt"); + String idx = r.getAttributeValue(null, "telem"); + + String bgString = r.getAttributeValue(null,"bg"); + Color bg = (bgString != null) + ? Color.decode(bgString) + : Color.WHITE; + + String format = (fmt == null) ? defaultFormat : fmt; + int index = (idx == null) ? -1 : Integer.valueOf(idx); + + items.add(new LineItem(format,index, bg)); + } + break; + } + } + } + catch (Exception e) { + throw new ParseException("Failed to parse XML from the stream"+ e, 0); + } + + return new TelemetryDataWidget(ctx, width, fontSize, textColor, items); + } + + +} diff --git a/src/ui/UIWidget.java b/src/ui/UIWidget.java new file mode 100644 index 0000000..2e6736a --- /dev/null +++ b/src/ui/UIWidget.java @@ -0,0 +1,47 @@ +package com.ui; + +import com.Context; + +import java.io.Reader; +import java.io.FileReader; + +import javax.swing.*; +import javax.swing.border.*; + +import javax.xml.stream.*; +import java.text.ParseException; + +/** + * + * @author Chris Park @ Infinetix Corp. + * Date: 11-20-2020 + * Descriptions: Base class from which a UI widget derives its + * general functionality. + */ +public class UIWidget extends JPanel { + protected Context context; + protected Border insets; + + /** + * Standard class Constructor + */ + public UIWidget(Context ctx) { + context = ctx; + insets = new EmptyBorder(0, 0, 0, 0); + } + + /** + * Class constructor that parses widget configuration + * via XML + * @param ctx + * @param resourceKey + */ + public UIWidget(Context ctx, String resourceKey) { + //TODO - CP - Flesh out XML routines to call from here. + } + + public void setInsets (int top, int left, int bottom, int right) { + insets = new EmptyBorder(top, left, bottom, right); + this.setBorder(insets); + } +} From d47a0af19bfebcbfc196527aae087597b90e4ee9 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 2 Dec 2020 09:59:05 -0800 Subject: [PATCH 014/125] StateWidget now extends UIWidget Base UIWidget child references in the dashboard have been updated and now pull from the new heirarchy --- src/Dashboard.java | 8 ++++---- src/ui/StateWidget.java | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Dashboard.java b/src/Dashboard.java index 0909c8b..04b3eab 100644 --- a/src/Dashboard.java +++ b/src/Dashboard.java @@ -40,7 +40,8 @@ public class Dashboard implements Runnable { private static final int WIDGET_SIZE = 140; private Context context; - private TelemetryWidget dataWidget; +// private TelemetryWidget dataWidget; + private TelemetryDataWidget dataWidget; //TODO - CP - Change this to private and make a getter that can be called public StateWidget stateWidget; @@ -197,10 +198,10 @@ private JPanel createRightPanel() { //Add Telemetry Data Widget (Ground or Air) try { if(context.getCurrentLocale() == "ground") { - dataWidget = TelemetryWidget.fromXML(context, "telemetryWidgetGnd"); + dataWidget = TelemetryDataWidget.fromXML(context, "telemetryWidgetGnd"); } else { - dataWidget = TelemetryWidget.fromXML(context, "telemetryWidgetAir"); + dataWidget = TelemetryDataWidget.fromXML(context, "telemetryWidgetAir"); } } catch(Exception e) { @@ -209,7 +210,6 @@ private JPanel createRightPanel() { } dashPanel.add(dataWidget); - //TODO - CP - Add State widget here stateWidget = new StateWidget(context); dashPanel.add(stateWidget); diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java index 179cc5e..94175c7 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/StateWidget.java @@ -15,14 +15,12 @@ * Description: Dashboard Widget used to display the current state of a connected unit as * described over serial communication. */ -public class StateWidget extends JPanel { +public class StateWidget extends UIWidget { //Constants protected static final int BORDER_SIZE = 0; //TODO - CP - Replace this with a settable line width for future xml integration protected static final int LINE_WIDTH = 8; - - private Context context; private JFrame infoFrame; //State readouts @@ -40,7 +38,8 @@ public class StateWidget extends JPanel { * @param ctx - The application context */ public StateWidget(Context ctx) { - context = ctx; +// context = ctx; + super(ctx); initPanel(); } From 4ea042b133be71a694f0527f69b986b4147d994d Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 2 Dec 2020 11:09:24 -0800 Subject: [PATCH 015/125] Widget size/color adjustments. General cleanup --- resources/telemetryWidgetGround.xml | 2 +- src/Dashboard.java | 10 +++--- src/ui/StateWidget.java | 53 +++++++++++++++-------------- src/ui/TelemetryDataWidget.java | 3 +- src/ui/TelemetryWidget.java | 3 ++ src/ui/UIWidget.java | 10 ------ 6 files changed, 40 insertions(+), 41 deletions(-) diff --git a/resources/telemetryWidgetGround.xml b/resources/telemetryWidgetGround.xml index 03d0c2d..ce075e5 100644 --- a/resources/telemetryWidgetGround.xml +++ b/resources/telemetryWidgetGround.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/src/Dashboard.java b/src/Dashboard.java index 04b3eab..febfe14 100644 --- a/src/Dashboard.java +++ b/src/Dashboard.java @@ -35,17 +35,19 @@ import jssc.SerialPortList; public class Dashboard implements Runnable { + //Standard References + private Context context; + + //Static Values private static final int START_WIDTH = 1200; //default window width private static final int START_HEIGHT = 900; //default window height private static final int WIDGET_SIZE = 140; - private Context context; -// private TelemetryWidget dataWidget; + //UI Widgets private TelemetryDataWidget dataWidget; - - //TODO - CP - Change this to private and make a getter that can be called public StateWidget stateWidget; + //Logging private final Logger seriallog = Logger.getLogger("d.serial"); private final Logger iolog = Logger.getLogger("d.io"); private final Logger rootlog = Logger.getLogger("d"); diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java index 94175c7..5ed8f39 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/StateWidget.java @@ -17,9 +17,14 @@ */ public class StateWidget extends UIWidget { //Constants - protected static final int BORDER_SIZE = 0; - //TODO - CP - Replace this with a settable line width for future xml integration - protected static final int LINE_WIDTH = 8; + protected static final int BORDER_SIZE = 0; + protected static final int LINE_WIDTH = 14; + protected static final float FONT_SIZE = 14.0f; + + //Color Defaults + protected static final Color DEF_FONT_COLOR = Color.decode("0xEA8300"); + protected static final Color DEF_BACK_COLOR_A = Color.decode("0xDFDFDF"); + protected static final Color DEF_BACK_COLOR_B = Color.decode("0xEEEEEE"); private JFrame infoFrame; @@ -38,7 +43,6 @@ public class StateWidget extends UIWidget { * @param ctx - The application context */ public StateWidget(Context ctx) { -// context = ctx; super(ctx); initPanel(); } @@ -49,12 +53,8 @@ public StateWidget(Context ctx) { * dimensional spacing, and border insets. */ private void initPanel() { - float fontSize = 16.0f; - Font font = context.theme.text.deriveFont(fontSize); - - Dimension spacer = new Dimension(0, 5); - Dimension labelSize = new Dimension(90, 25); - Dimension panelSize = new Dimension(90, 25); + Font font = context.theme.text.deriveFont(FONT_SIZE); + Dimension labelSize = new Dimension(100, 20); this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); this.setBorder(BorderFactory.createEmptyBorder( @@ -63,26 +63,26 @@ private void initPanel() { //Configure state labels apmLabel = new JLabel("Apm:--"); apmLabel.setFont(font); - apmLabel.setForeground(Color.decode("0xEA8300")); - apmLabel.setBackground(Color.decode("0xDFDFDF")); + apmLabel.setForeground(DEF_FONT_COLOR); + apmLabel.setBackground(DEF_BACK_COLOR_A); apmLabel.setOpaque(true); driveLabel = new JLabel("Drv:--"); driveLabel.setFont(font); - driveLabel.setForeground(Color.decode("0xEA8300")); - driveLabel.setBackground(Color.decode("0xEEEEEE")); + driveLabel.setForeground(DEF_FONT_COLOR); + driveLabel.setBackground(DEF_BACK_COLOR_B); driveLabel.setOpaque(true); autoLabel = new JLabel("Aut:--"); autoLabel.setFont(font); - autoLabel.setForeground(Color.decode("0xEA8300")); - autoLabel.setBackground(Color.decode("0xDFDFDF")); + autoLabel.setForeground(DEF_FONT_COLOR); + autoLabel.setBackground(DEF_BACK_COLOR_A); autoLabel.setOpaque(true); flagLabel = new JLabel("Flg:None"); flagLabel.setFont(font); - flagLabel.setForeground(Color.decode("0xEA8300")); - flagLabel.setBackground(Color.decode("0xEEEEEE")); + flagLabel.setForeground(DEF_FONT_COLOR); + flagLabel.setBackground(DEF_BACK_COLOR_B); flagLabel.setOpaque(true); JComponent[] labelList = new JComponent[] { @@ -90,36 +90,39 @@ private void initPanel() { }; for(JComponent jc : labelList) { - jc.setAlignmentX(Component.CENTER_ALIGNMENT); jc.setMaximumSize(labelSize); } //Configure state panels ArrayList statePanels = new ArrayList(); apmPanel = new JPanel(); + apmPanel.setBorder(insets); apmPanel.setLayout(new BoxLayout(apmPanel, BoxLayout.LINE_AXIS)); - apmPanel.setPreferredSize(panelSize); + apmPanel.setPreferredSize(labelSize); apmPanel.setOpaque(true); apmPanel.add(apmLabel); statePanels.add(apmPanel); drivePanel = new JPanel(); + drivePanel.setBorder(insets); drivePanel.setLayout(new BoxLayout(drivePanel, BoxLayout.LINE_AXIS)); - drivePanel.setPreferredSize(panelSize); + drivePanel.setPreferredSize(labelSize); drivePanel.setOpaque(true); drivePanel.add(driveLabel); statePanels.add(drivePanel); autoPanel = new JPanel(); + autoPanel.setBorder(insets); autoPanel.setLayout(new BoxLayout(autoPanel, BoxLayout.LINE_AXIS)); - autoPanel.setPreferredSize(panelSize); + autoPanel.setPreferredSize(labelSize); autoPanel.setOpaque(true); autoPanel.add(autoLabel); statePanels.add(autoPanel); flagPanel = new JPanel(); + flagPanel.setBorder(insets); flagPanel.setLayout(new BoxLayout(flagPanel, BoxLayout.LINE_AXIS)); - flagPanel.setPreferredSize(panelSize); + flagPanel.setPreferredSize(labelSize); flagPanel.setOpaque(true); flagPanel.add(flagLabel); statePanels.add(flagPanel); @@ -127,7 +130,7 @@ private void initPanel() { //Add panels to widget for(JPanel panel : statePanels) { this.add(panel); - this.add(Box.createRigidArea(spacer)); +// this.add(Box.createRigidArea(spacer)); } } @@ -280,7 +283,7 @@ else if(caution) { } /** - * Generates an information panel on click describing the warnings, errors, + * Generates an information panel on click describing any warnings, errors, * and details of the current state. */ private MouseAdapter stateDetailsMouseAdapter = new MouseAdapter() { diff --git a/src/ui/TelemetryDataWidget.java b/src/ui/TelemetryDataWidget.java index 61862e0..994655c 100644 --- a/src/ui/TelemetryDataWidget.java +++ b/src/ui/TelemetryDataWidget.java @@ -127,12 +127,13 @@ public TelemetryDataWidget(Context ctx, int lineWidth, float fontSize, JPanel panel = new JPanel(); panel.setBorder(insets); + panel.setPreferredSize(new Dimension(100, (20 * items.size()))); panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); panel.setOpaque(false); Font font = ctx.theme.text.deriveFont(fontSize); - for(LineItem i : items){ + for(LineItem i : items) { Line l = new Line(ctx, i.getFormatString()); l.setFont(font); l.setForeground(textColor); diff --git a/src/ui/TelemetryWidget.java b/src/ui/TelemetryWidget.java index 2b40809..d874e74 100644 --- a/src/ui/TelemetryWidget.java +++ b/src/ui/TelemetryWidget.java @@ -16,6 +16,9 @@ import javax.xml.stream.*; import java.text.ParseException; +//TODO - CP - DEPRECATED - This class has been ported to the UIWidget Heirarchy, and is no longer +//used. Once any necessary information that remains has been gleaned out it should +//be removed public class TelemetryWidget extends JPanel{ // The top,left,bottom,right border widths of the nine patch image rendered diff --git a/src/ui/UIWidget.java b/src/ui/UIWidget.java index 2e6736a..3d804c6 100644 --- a/src/ui/UIWidget.java +++ b/src/ui/UIWidget.java @@ -29,16 +29,6 @@ public UIWidget(Context ctx) { context = ctx; insets = new EmptyBorder(0, 0, 0, 0); } - - /** - * Class constructor that parses widget configuration - * via XML - * @param ctx - * @param resourceKey - */ - public UIWidget(Context ctx, String resourceKey) { - //TODO - CP - Flesh out XML routines to call from here. - } public void setInsets (int top, int left, int bottom, int right) { insets = new EmptyBorder(top, left, bottom, right); From d1a1d7b5bcd5e5f769a3d533733bb48ec45a28d2 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 2 Dec 2020 17:52:39 -0800 Subject: [PATCH 016/125] Watermark and ping sensor widget scaffolding added ... --- PingWidget.java | 33 ++++++++++++++++++++++++++++++ resources/images/watermark.png | Bin 0 -> 7526 bytes resources/resources_en.properties | 1 + src/Dashboard.java | 11 ++++++++++ src/ui/StateWidget.java | 5 ++--- src/ui/TelemetryDataWidget.java | 2 +- src/ui/Theme.java | 2 ++ 7 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 PingWidget.java create mode 100644 resources/images/watermark.png diff --git a/PingWidget.java b/PingWidget.java new file mode 100644 index 0000000..d5890af --- /dev/null +++ b/PingWidget.java @@ -0,0 +1,33 @@ +package com.ui; + +import java.util.*; +import java.awt.*; +import java.swing.*; + +import com.Context; + +/** + * + * @author Chris Park @ Infinetix Corp. + * Date: 12-2-20 + * Description: Dashboard Widget child class used to display a units + * ping sensor data. + * + */ +public class PingWidget extends UIWidget { + protected Collection sensorA; + protected Collection sensorB; + protected Collection sensorC; + protected Collection sensorD; + + /** + * Class Constructor + * @param ctx - The application context + */ + public PingWidget(Context ctx) { + super(ctx); + + } + + +} diff --git a/resources/images/watermark.png b/resources/images/watermark.png new file mode 100644 index 0000000000000000000000000000000000000000..0d7418dbd1347acba2787789042e23328e1f1f7f GIT binary patch literal 7526 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 literal 0 HcmV?d00001 diff --git a/resources/resources_en.properties b/resources/resources_en.properties index 76dd7f2..4cc7a78 100644 --- a/resources/resources_en.properties +++ b/resources/resources_en.properties @@ -30,3 +30,4 @@ display_panel =display info_screen =screen telemetryLabels =telemetryLabels home_icon =Home-Icon.png +watermark =watermark.png diff --git a/src/Dashboard.java b/src/Dashboard.java index febfe14..a9d1eba 100644 --- a/src/Dashboard.java +++ b/src/Dashboard.java @@ -132,6 +132,7 @@ public void disconnectRequest() { resetData(); } }; + SerialConnectPanel serialPanel = new SerialConnectPanel(connectActions); serialPanel.showBaudSelector(true); @@ -247,6 +248,16 @@ public void mouseClicked(MouseEvent e){ context, Serial.ROLL, context.theme.roverFront)); } + //TODO - CP - Relocate this watermark to a corner position + //Watermark Image + BufferedImage watermark = context.theme.logoWatermark; + JLabel watermarkLabel = new JLabel(new ImageIcon(watermark)); + watermarkLabel.setOpaque(false); + JPanel logo = new JPanel(); + logo.setOpaque(false); + logo.add(watermarkLabel); + dashPanel.add(logo); + return dashPanel; } diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java index 5ed8f39..331c4da 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/StateWidget.java @@ -7,13 +7,12 @@ import com.Context; import com.serial.Serial; -import com.ui.ninePatch.NinePatchPanel; /** * @author Chris Park @ Infinetix Corp. * Date: 10-28-20 - * Description: Dashboard Widget used to display the current state of a connected unit as - * described over serial communication. + * Description: Dashboard Widget child class used to display the + * current state of a connected unit as described over serial communication. */ public class StateWidget extends UIWidget { //Constants diff --git a/src/ui/TelemetryDataWidget.java b/src/ui/TelemetryDataWidget.java index 994655c..180abbd 100644 --- a/src/ui/TelemetryDataWidget.java +++ b/src/ui/TelemetryDataWidget.java @@ -19,7 +19,7 @@ * * @author Chris Park @ Infinetix Corp. * Date: 11-25-2020 - * Description: Widget Child class use for displaying Telemetry data. + * Description: Widget child class use for displaying Telemetry data. */ public class TelemetryDataWidget extends UIWidget { private int lineWidth; diff --git a/src/ui/Theme.java b/src/ui/Theme.java index 1651a88..bbe3b54 100644 --- a/src/ui/Theme.java +++ b/src/ui/Theme.java @@ -29,6 +29,7 @@ public class Theme { public BufferedImage roverImage; public BufferedImage appIcon; public BufferedImage homeIcon; + public BufferedImage logoWatermark; public Font number; public Font text; public Font alertFont; @@ -58,6 +59,7 @@ public Theme(Context ctx) { gaugeGlare = ImageIO.read(new File(img+ctx.getResource("gauge_glare"))); appIcon = ImageIO.read(new File(img+ctx.getResource("app_icon"))); homeIcon = ImageIO.read(new File(img+ctx.getResource("home_icon"))); + logoWatermark = ImageIO.read(new File(img+ctx.getResource("watermark"))); buttonPatch = NinePatch.loadFrom(Paths.get(np+ctx.getResource("button"))); buttonHover = NinePatch.loadFrom(Paths.get(np+ctx.getResource("button_hovered"))); buttonPress = NinePatch.loadFrom(Paths.get(np+ctx.getResource("button_pressed"))); From 90119dee4370c1219ad33afa6e141eac77ee4d35 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 3 Dec 2020 11:32:25 -0800 Subject: [PATCH 017/125] More scaffolding for ping sensor widget, disabled HW flow control Disabled hardware flow control to address a serial communications send bug. --- PingWidget.java | 34 +++++++++++++++++++++++++++++- resources/resources_en.properties | 2 +- src/serial/SerialConnectPanel.java | 9 +++++--- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/PingWidget.java b/PingWidget.java index d5890af..153d1ca 100644 --- a/PingWidget.java +++ b/PingWidget.java @@ -12,9 +12,15 @@ * Date: 12-2-20 * Description: Dashboard Widget child class used to display a units * ping sensor data. - * */ public class PingWidget extends UIWidget { + + //Constants + protected static final int NUM_SENSORS = 3; + protected static final int MIN_SENSOR_LEVEL = 0; + protected static final int MAX_SENSOR_LEVEL = 3; + + //Ping Sensor Indicators protected Collection sensorA; protected Collection sensorB; protected Collection sensorC; @@ -27,7 +33,33 @@ public class PingWidget extends UIWidget { public PingWidget(Context ctx) { super(ctx); + } + public void update(int sensorIndex, int level) { + + //If not a valid sensor index, return + if(sensorIndex > (NUM_SENSORS - 1) || sensorIndex < 0) { + return; + } + + //If the level provided exceeds the allowed range then do nothing. + if(level > MAX_SENSOR_LEVEL || level < MIN_SENSOR_LEVEL) { + return; + } + + + + //cases on level + // case 0 + //show image at index 0, hide index 1, 2 + // case 1 + //Show image at index 0, 1, hide 2 + // case 2 + //show all + + + + } } diff --git a/resources/resources_en.properties b/resources/resources_en.properties index 4cc7a78..546bec5 100644 --- a/resources/resources_en.properties +++ b/resources/resources_en.properties @@ -1,5 +1,5 @@ version_id =1.0.2 -release_date =2020-11-06 +release_date =2020-12-03 image_folder =resources/images/ patch_folder =resources/images/nP/ font_folder =resources/fonts/ diff --git a/src/serial/SerialConnectPanel.java b/src/serial/SerialConnectPanel.java index 4ef5558..2dbce8b 100644 --- a/src/serial/SerialConnectPanel.java +++ b/src/serial/SerialConnectPanel.java @@ -218,10 +218,13 @@ public void run() { SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); + +// serialPort.setFlowControlMode( +// SerialPort.FLOWCONTROL_RTSCTS_IN | +// SerialPort.FLOWCONTROL_RTSCTS_OUT); - serialPort.setFlowControlMode( - SerialPort.FLOWCONTROL_RTSCTS_IN | - SerialPort.FLOWCONTROL_RTSCTS_OUT); + + serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE); } catch(SerialPortException ex) { System.err.println(ex.getMessage()); From 579c531e7de604404eb04474bfe6132f1fb1b2ba Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Mon, 7 Dec 2020 10:28:12 -0800 Subject: [PATCH 018/125] Added initial art assets for PingWidget --- PingWidget.java | 18 ++++++++++++++++-- resources/images/pingGreen.png | Bin 0 -> 120 bytes resources/images/pingRed.png | Bin 0 -> 120 bytes resources/images/pingYellow.png | Bin 0 -> 121 bytes resources/resources_en.properties | 3 +++ src/ui/Theme.java | 6 ++++++ 6 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 resources/images/pingGreen.png create mode 100644 resources/images/pingRed.png create mode 100644 resources/images/pingYellow.png diff --git a/PingWidget.java b/PingWidget.java index 153d1ca..dadc950 100644 --- a/PingWidget.java +++ b/PingWidget.java @@ -33,6 +33,22 @@ public class PingWidget extends UIWidget { public PingWidget(Context ctx) { super(ctx); + sensorA.add(new BufferedImage(ctx.theme.pingGreen)); + sensorA.add(new BufferedImage(ctx.theme.pingYellow)); + sensorA.add(new BufferedImage(ctx.theme.pingRed)); + + sensorB.add(new BufferedImage(ctx.theme.pingGreen)); + sensorB.add(new BufferedImage(ctx.theme.pingYellow)); + sensorB.add(new BufferedImage(ctx.theme.pingRed)); + + sensorC.add(new BufferedImage(ctx.theme.pingGreen)); + sensorC.add(new BufferedImage(ctx.theme.pingYellow)); + sensorC.add(new BufferedImage(ctx.theme.pingRed)); + + sensorD.add(new BufferedImage(ctx.theme.pingGreen)); + sensorD.add(new BufferedImage(ctx.theme.pingYellow)); + sensorD.add(new BufferedImage(ctx.theme.pingRed)); + } @@ -57,8 +73,6 @@ public void update(int sensorIndex, int level) { //Show image at index 0, 1, hide 2 // case 2 //show all - - } diff --git a/resources/images/pingGreen.png b/resources/images/pingGreen.png new file mode 100644 index 0000000000000000000000000000000000000000..6167149a117c6c15d7d3ad38bef91f3ce99162d2 GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^B0$W=!2~3&&0lT=Qk(@Ik;M!Q+`=Ht$S`Y;1W=H@ z#M9T6{Rt1VumQI1_s9lhChkwcJ4qW N44$rjF6*2UngD-797g~E literal 0 HcmV?d00001 diff --git a/resources/images/pingRed.png b/resources/images/pingRed.png new file mode 100644 index 0000000000000000000000000000000000000000..3a20ab8f2a5ec1e37a6653544ba3dfbd31daac2a GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^B0$W=!2~3&&0lT=Qk(@Ik;M!Q+`=Ht$S`Y;1W=H@ z#M9T6{Rt1Vu!hEiGpiN>g~UBw978nDC+|6@b;v<4A!z|aPnMkIEXGiAMwa(^mktA! OFnGH9xvX!uMq3e3NW#;_F+}5h@}K_)=QhS1IO4!4an;7igqb0_lF5tVQ`2;y O8U{~SKbLh*2~7Zw2OQ)8 literal 0 HcmV?d00001 diff --git a/resources/resources_en.properties b/resources/resources_en.properties index 546bec5..56663b2 100644 --- a/resources/resources_en.properties +++ b/resources/resources_en.properties @@ -31,3 +31,6 @@ info_screen =screen telemetryLabels =telemetryLabels home_icon =Home-Icon.png watermark =watermark.png +ping_red =pingRed.png +ping_yellow =pingYellow.png +ping_green =pingGreen.png \ No newline at end of file diff --git a/src/ui/Theme.java b/src/ui/Theme.java index bbe3b54..490d5c0 100644 --- a/src/ui/Theme.java +++ b/src/ui/Theme.java @@ -30,6 +30,9 @@ public class Theme { public BufferedImage appIcon; public BufferedImage homeIcon; public BufferedImage logoWatermark; + public BufferedImage pingRed; + public BufferedImage pingYellow; + public BufferedImage pingGreen; public Font number; public Font text; public Font alertFont; @@ -60,6 +63,9 @@ public Theme(Context ctx) { appIcon = ImageIO.read(new File(img+ctx.getResource("app_icon"))); homeIcon = ImageIO.read(new File(img+ctx.getResource("home_icon"))); logoWatermark = ImageIO.read(new File(img+ctx.getResource("watermark"))); + pingRed = ImageIO.read(new File(img+ctx.getResource("ping_red"))); + pingYellow = ImageIO.read(new File(img+ctx.getResource("ping_yellow"))); + pingGreen = ImageIO.read(new File(img+ctx.getResource("ping_green"))); buttonPatch = NinePatch.loadFrom(Paths.get(np+ctx.getResource("button"))); buttonHover = NinePatch.loadFrom(Paths.get(np+ctx.getResource("button_hovered"))); buttonPress = NinePatch.loadFrom(Paths.get(np+ctx.getResource("button_pressed"))); From 4239c2a399963a6a9ad82f3a15a90e5e03a7235e Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Mon, 7 Dec 2020 14:06:47 -0800 Subject: [PATCH 019/125] Update NinePatch Images Reduced the overall size and thickness of border images --- PingWidget.java | 2 +- resources/images/nP/display/BottomBorder.png | Bin 167 -> 158 bytes resources/images/nP/display/BottomLeft.png | Bin 887 -> 296 bytes resources/images/nP/display/BottomRight.png | Bin 879 -> 294 bytes resources/images/nP/display/LeftBorder.png | Bin 141 -> 160 bytes resources/images/nP/display/RightBorder.png | Bin 148 -> 163 bytes resources/images/nP/display/TopBorder.png | Bin 169 -> 159 bytes resources/images/nP/display/TopLeft.png | Bin 766 -> 275 bytes resources/images/nP/display/TopRight.png | Bin 848 -> 306 bytes 9 files changed, 1 insertion(+), 1 deletion(-) diff --git a/PingWidget.java b/PingWidget.java index dadc950..3e40c5e 100644 --- a/PingWidget.java +++ b/PingWidget.java @@ -66,7 +66,7 @@ public void update(int sensorIndex, int level) { - //cases on level + //cases (level) // case 0 //show image at index 0, hide index 1, 2 // case 1 diff --git a/resources/images/nP/display/BottomBorder.png b/resources/images/nP/display/BottomBorder.png index a9e53ae35ce6bed5991fc074ac672928a97777e8..7162c0ba9a285c5f788af94253b64569aa98c2a3 100755 GIT binary patch delta 96 zcmZ3^IFE6HBnKM<1H-KFEqf*^8u&ipVHY*i*q>B411PBN>Eal|aeeABLtX|29)=BR zX*1Xl9B8VyE@)t^5-khSWxRO2ra5C~`G&t1a`RXud=}i@4b;Zq>FVdQ&MBb@0Mv&d AvH$=8 delta 105 zcmV-v0G9ur0jB|w83+OZ000#=BkPeNFjn9R5f?5Z_$wZi0000qNkllFzskrs_>PAi`M}fAB^+%odSY2V`yDf4s zVjbhwzRi;jjRSdHp7`Ap*UR}ZBXZl*owwxsJSW|@u;W`ly?9cJgx|_lK5Je7sR$4W zBJir4@Lkt_=as10_DkNEhh+;1N9J#nvp%ssEczopr048@wy8r+H delta 819 zcmV-31I+xW0`~@xQGehG5f?ZaRk%WD00097Nkl6N3xA*`FUfn?MVoYH(wWkQ;(bwnk)* zy6e*YiZ6)D+IZpiE4=%i-+h&?rKXMu10r{r(ppE<*ss!FOU;(4%AH2xn-8QfwSr)5&1eZM1(xgmyWSbEg-%oqEosE#QLhG*VmI1 zwqhR;!GAXiy#D^r?;!xfv*)|KfLLoOgt&0w^RqMDDd{9H3i$E3a?)@xz;H0&1!OWY z-dY+Fp$lLG0A|x^rT7&^0q;FHhuhwMbRMJmYps0>;64C^5RhfrQdOVLW_-ndbadp2 z>}Tg} zmx#K!e9Sb2a8We{{CSv}+w0z5e=E!47b5J=Sy_C^TJ~VFtA=oT8iM=i96ChU0nn)o zSjMmhW6A}u*XwO`I(K)RGw%RADTCo26(t`ZACJ91`xXKn0{9NViv%JWjA`m>gTJh*iz!no@AdjnppUDa%aPz57}>{kNBia((?TO@wTexr5kL=K5?DeuW1qm PbU%ZqtDnm{r-UW|guF?A delta 811 zcmV+`1JwMc0`CTpQGehG5f?i!JSU0B0008~NklTzPqs$vLR zvzM>hH@JU`L<9h^5V(vn9URA>b%H>a)v(DXBYkXHZBM?h%zuI@tq3HF{XY5tV0tuT zwEzf;ks(k~Bhnbs0i@Xkjftftmz~CSWaL5TkrUEyIIb0AeALLpPKc2aX&O-%<4w=E zv9zSjBXy<4^czd-F-MeELkyjfs1XU>JTfAo4epZ@8Ijrat)|WXODJ*Fm1e(zybKK| zYlU(^*X{C=5r28POO@D%TuW&~?0X#=?f3tQJsv*DVuab~b*(lKj-#%yQCD?DL3pm! zWrQg95m^(Wve4Lhg^ zQMLUBD)vBKOEi#E{!l3zNRlLXw_?$Z?10qF47!$N`+w~+qE_tb2|z?glq5~VU}UJY zRAsFp`_-k9$52{(Ktzi@82b&Sw7O!id&QyDD7v$E8EI~wLKS<>Rw5OMwH9fbVw2~1 z^7I*q2t)*CZhwftL~={Z7z6J;3gVKAzrh_$syngdm?40hd9sEughs2)ex$Mvg07X&Y^5-SC+ik0lG=Vhliv=>B zPVxEEqIGXP9`~{%7RdW|S-YcmnqgH{?N@SVXU`c5)_5{$U2hut004WJ=}Q8LWBasS z`lPMNWU^Ns=7QR%KZ>+kfZUdvk6_qmfuP4pmDV1RTIiA}myB zr2jO?^-?ica^ diff --git a/resources/images/nP/display/LeftBorder.png b/resources/images/nP/display/LeftBorder.png index e38b72f534420b0914abc0933d749759d411fc7b..df8d218029f72cebd6f05c4135bef5c3724c9301 100755 GIT binary patch delta 98 zcmeBWT);R%l7o$bfr0VP7psYi2L4ZY*hS5?Jj(UH0tIzFT^vI=uBV>Z$jcxgz;Iym ze;K8~ZdL>K*?asRpYpDo=*^n`etI_Fg=%5J2TXFWdH!W;U7rTj$l&Sf=d#Wzp$P!> CJ|Qar delta 79 zcmZ3$*vmLUl7oqXfuV0-a_&S$1Dgk&g5r8gKeXkh00pHyT^vI=t|#YY&t;ucLK6V2Ss7FS diff --git a/resources/images/nP/display/RightBorder.png b/resources/images/nP/display/RightBorder.png index c18357eb495cc09b8a6a3ffdd9e23975ec021377..3ca022023ec20c0610cdcd4971f7a54b2c884921 100755 GIT binary patch delta 101 zcmbQjxR`N*BnKM<0|VooFIE#34T7HVu#1|D3&$F`3+DngQu&X%Q~lo FCIEllBG&)_ delta 86 zcmZ3?IE8V7BnJ}%14G}w?{y8d(4U diff --git a/resources/images/nP/display/TopBorder.png b/resources/images/nP/display/TopBorder.png index 617a7f6804ce1f4830979e68241bc6de171a5ba5..b49d28386b2914bca4b893ff84bd11a01f484cea 100755 GIT binary patch delta 97 zcmZ3!#^;1E`O|)78&qol`;+0F)IR$p8QV delta 107 zcmbQwxRP;#BnJ}%1B0lk^6QC;1`!W91;zE0erU^00ScOVx;Tb#Tn{~Qkdr}y=jes| zI?3Xp1$w1ibC-TqtKhI%bjxbezOLWJX+nZ?_TG;!R^Viq*?5!nMkFu4P<{Azpa~3~ Lu6{1-oD!Md>K*ut8y85}Sb4q9e0JGvaZvX%Q delta 697 zcmV;q0!ICl0{#V%QGehG5&!@pVfkDc0007uNklk7{%X=k;a8pAq5(l z6wiVBpwk?S+6` ziQpW74~I`{F9h%bH8{EyBHo-qqc#u)MYa%jb0t>J^Dq0pkHK&V-}l`G$wBNW;dvf{ z(a1@F0!SRk0a9Fw6qRrtVha+6p-EGW0eP3NPH#_7VT{4Pe&Ym49LIqbxd0Gk9}b5@ zbUGbaYwHpA&3{H4rP4s;S&jZ+fOZZdiXu!O9-IX6dcB@WlH@&rj{v&A9=z%IAtKvP z6h-(tow}ow*%h$KmHbMpn!8g z)*%Q%{(k~QHw6T=7C{I?5P}edAa3u7$7(H55rX{bWR8X$RxMU7D#EJ8szqxNt3*r6L6RM`5QT z1UbGwQoMFilS~|>h|~!re_hXI`*7A``6URaUou*YAOtymms`^jg&83Tr{6<2 f#7y|>w43|@u^bbPr+9)900000NkvXXu0mjfr35y$ diff --git a/resources/images/nP/display/TopRight.png b/resources/images/nP/display/TopRight.png index ffbc257c5287be81ed7ab2822ac50905532220cc..cdd0816b3680a86465dfb64015b255cfa2f3c7aa 100755 GIT binary patch delta 233 zcmcb>wuxy%VEq#wc2QF%|1VE^85kJ$d%8G=RNQ)dbt5N}p-96+@tux)EU&QgWjoIC z=UeksEa16?dFR|-jT@>v-|J6)R;!{YI7vk_$mK~_dj0je$GuCNE`R*6aa-2YtkUz9 z>(XwN-mkx#{mkXo&Zz$1YhN&$-@bduFJ;y`S3WbI-~SaXr+=4ES~g|c?;DN|)Bttg ZnZ7d#T)8DbT?gnF22WQ%mvv4FO#pJBUGM+^ delta 780 zcmV+n1M~c{0?-DKQGehG5f?cm6w5z10008rNklS8B8Xkk z&;(Edd&QOh2lZdbLRao&qlpXMpub4{3A(T?8(0)<<)a-l#m-zecGBsW2`S;d=G^2Z zo%bf~J9Eyt_r5eEZ{EIBm>E?8UUoWK6>-9a1^;Mwceg$2W`8jlNLvJl0NScJazs7= z(LpYkI~)ecfw|$SVqF#603Iv!Q>@O%^Yh^0{{Fsa6OaK=03K!B%j) zbteNI0OQ`<+%$5>VHo1LRznzuL%}%ES3wXw7m=!}suTPc@~-T0kC0r`ABHJ3|Ydm>m5rX$t zc;x5F2~;&1b8~Y&rdLlD>+aVtpN-pietsTXTieKHvwz84tJO*YSX^2{qh2?=vbUN| zeEd`fH0TA`|)XNqtW@eh0z8To449ot+(ezblTpHYt~XU}NKMa(#As zikX?2;Xs@TKgmhPuSNsdNYAMbKR(HyPRF=|)zc@>lF#=a?4;IiEiNXXFE20AYBtR^ zyW=>?IDe>C1{mp%-mK=D3MBEx*;#6BR7+(f5RCEdD32J=NPom}Y!8reHOVd@W7ez! zGEQ1bY25^nF^oiUoB-|3VAe6BaSWXXX^jf&%x)EssbSHm*be*p}G|LCT(eFgvk002ov KP6b4+LSTZE!ff3D From 1d6c9ef3ec52278ac5a91d41edcdf9cb0abdb037 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Mon, 7 Dec 2020 15:18:20 -0800 Subject: [PATCH 020/125] Rescaled Gauge assets to 75% original size ... --- resources/images/6x6-Front.png | Bin 5685 -> 4767 bytes resources/images/6x6-Side.png | Bin 6184 -> 5368 bytes resources/images/6x6-Top.png | Bin 9148 -> 7705 bytes resources/images/Gauge.png | Bin 12196 -> 10808 bytes resources/images/UAV-Front.png | Bin 2379 -> 1918 bytes resources/images/UAV-Side.png | Bin 2305 -> 1827 bytes resources/images/UAV-Top.png | Bin 15707 -> 11593 bytes resources/images/gaugeGlare.png | Bin 1285 -> 1355 bytes src/map/WaypointPanel.java | 6 ++++-- src/ui/ninePatch/NinePatch.java | 3 +++ 10 files changed, 7 insertions(+), 2 deletions(-) diff --git a/resources/images/6x6-Front.png b/resources/images/6x6-Front.png index 3d557400837d76ff6b7e6629f91b690bac7dfe40..8c65689073432b982eb6fed5749b01fec9f2f43d 100644 GIT binary patch literal 4767 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 GiTXb|qwwbd diff --git a/resources/images/6x6-Side.png b/resources/images/6x6-Side.png index 323b90a5db8642324db08e4979b495ed862073a7..02e00119e31c2e6f2ffacb4c0ed4fd0176602706 100644 GIT binary patch literal 5368 zcmb7IWmFX2(_T6y7No;Px@%$SmUW3GrJDtjS_A>5mhR4_Ls~inB&AaX6(l4?x4Hvm9J^Pk`Va`PFmMk1f5IvPY<cgbp8){c$fxQm zM*d4jORtfZngv9so|q5Y)79wIC(*CW+PRj`$`{Y6m#d z>w#y&jLYWk_80mvHw&vLIKtVJzg~7){ts%wM*4gyxJM8t1wg1ga_32^Y=;^bv1ECg z`>!eZF?a$e+G;(Y7DcU$yPlu?m;@!>4-9#wVLo3K(j}|Rya0YZluC^!U4OnouNLKN zI~MqyA938H=jAJiSrtdW`f+tUtrF}wq+D*JI%^`vi=!oZ)0EXB@$?|Bt+2=x(FjlP0mG%O~m{SA8JQf zUGd~QFonqZ$cmahQMa4`vJ*qVOJ}@)I51d_gSx;Ky?+t+#h}`)D=?O)3WiV0#qd;| zGoHmnXuj#+yX&Y)y1B@sKl|I1HmoC70cpCfZ68%-BdK;gPu9GWohOsp8%EzO8(9aA zN|6%*>TOt8eBVsmZ_P11uw&P-8V-bkL5$K7Tl?9XQ|@YBpU6;WH5EOVPDMLsW!LaS z^MhpWcou5*$X~?jj$!U~*4MrC=h@Y4=P?M|{f2UAp7lbF5;(t6tww$QEAfaSxkmb6 z1GRb&XYu6X3fQITL(%vvjlXl%rg0G~=Etu4b>y(#vuQdFU z@WEp~LNqWxujRCUTIH?QiWvHl10OfjV{!C^to>8%rb_Xt_WbL#4585P-Qn9Y36072 z9D}pwb);6@_myzEDwFbA$FN>TvPoAk`6sighLn;q{GNHbdtcWWdTnb#b5(6;);WXn z*<%u#KX#0Uj8UW_8UwV?6UMMn`x4E?&`t$bi7iT8t|A+sgU(%6165|$846oKhGA97 zn?oIulpd5DlOj%D7Mg&fwW6S|fUs4<#<}4@p5fk*QeHo}oZ4iKeuz`9i@?6*mng@; z-?nVa+51a!T3vzE{=>rcKBp>lRt@Pkw9ZPV zpgXh0;fh>LdPK=S#aEri%R_qS=&fEO?$-PKEAB%uh$OrP^^5po$jp~>_*0-QT>J|| z){?nH+=>DslaOX+9M)$fn$xemqH2*C*F8`&fsd0|>ODl2nE7(|k|Jc(yN9c*(V*>i z)7SYsupIp7{8yK1F-*CADwP+qxMs4cAmMiPi@NOf^CY1#j)eiIaC+}lbv4tG-$710 zoifxep0lmmvD`WvHny^*`?!f+#%!ANqd26-;ntxKV&TxRbvi_{X@!boja6F9#Q7My4S==JG zY9kMd-HcaoIgANW`LR(d`|7J|$~BqXXmvgh7fdK1P#nST=J_s0!KXtV0J1t%h)tRj z8#9uWj6evzHGEF0(!5?gC$iy;M`&JZ;LTydUtdi*y-iL*m4V3A7M+&_6i;Y5Az(v0 zg<1}Ss+5kfxMs~8Hk2A*_h5NzzoP2SkGgM*U@WR6;z1sXb_JvOuj_{koifb%@#cD0 zwShyTZYsTAZ+$qN*V(?R>6%6v69N|d3Q$&s|4x+(pRRVgH2FDb>b;`0K4 zCfY!TYKRe>tTL(VpaidX!KpujoF_b!Z+@8=+#o`_-|DIbgvTu}5H&iWF_}uDP7Dc* zQJF%JZ)5pFQ9!ABi;d3fndyN%9q!_Z{`$7y4ENB2+MwN{ri|M)%QNJ_I>yMKFN`fW zo%fC4py0pQPTI=3OBGeru$}yAr`6|%94Yz)yrK?JLWvo3L*G!mVLEs9MOxhsFze)8 zqXs`OJ-!nPe(y=FSSaM3P{Ht{T`Wex{FSy7gNNxaHH)@5|CxalS`6YSyTjQEsW3uJvMe;563%fY!B?ob1P;IumJXLh zN{j4WBm{XI1>lyT;80vbPMV)wUZ-cpd%mi4e|{GDLH5z=c?qlCTP+HyHh)ozb?6p@ z5gG-kqJUM^rkvf60!^RQZeF~NJmw2HP0YRlYPn_*ocKdRrQ2^8>yCOyigkZw!3aL| zD*n`VHanh|HdzYz5T7$8nk(>oOu;i|HU=JcMBHjz3jy1yW|FW7D-)||wh$EsaYq`Z zIvDrFiS*>;{a7gtxxS~D7(aOSCc90#O04e*^(AaK*`kVMd@N)8)^Jf^!!oVuhs#0T zLc>K`-nT2SH*-VZq*$FRg-(CdoYsG^SDqbDhF=Yg+wWE49Y(2+2X2(| z&+QX_`mzXL*8t)c;i%$5p$Ge z2eEVT^rP;A>50myvXOuaJ~mKyyI29ZY5Z$yWAMVc4v&OE``L}&ZIsK)KGc7-sz@dv zl9nTzIdSO2yTbR1g6TvdscMnBV(EUT?TIQwceF|n;~}FckjeJ*1pXAhQ1wuYr643k zuNlAhG>|ad`yFQe(Fb6olW;D_YP%v8kFp!)1|aNuwmw=JkA)5*?`S)W`!GmCN=oG1 z7VeBQohklIc$SI`3X!sZWae`^hmtWgERnxe4e4xsXYR5#`_8xfRd^8_JNPWMQo{K+ zp_1<^%!V)X{hD60|KZI;Yiw^G-Qy!uBVmT;^KPd4jhtRJ;qD7KvdB0WiuE0 zO%gf&RQyq3<01p0=-YasP-Mau^CmWhHj6cws;xLqEU@X3x|F)l%hhjOY|VAX z(VFEDsTcMf(~SWI>F2fl5#qN)Tn|JH-~aSeGV;3cU>BBd#Mq7CXq6QlGDa* zX-^{^d?m-6e>9ArL{u5TSb!3+8rBpX}JRg}vhFK7Bs^Al(EX$aEQ()py?{r|(! z!-8Gew2~pWt;svl4>RP`v{`BzX7jni3MRS+&Hh@`;pfQ&D&WvZJ~9kzo<2J*k@x8N z5GsTaqKe0}at?N8b#dyU(zv=0QCt69Qt6adTKdPiq`k|O zL4eBA8SNh-2YHn(^Zyl2WG1ONBI$ERqK=U4rO$R#m-XPcXRM=7bjZ2fTAv9(nBO18{sEcghtnND>%gQ;2&!IT8d4dH2;^4Gu558mZl z)#Jo{89e7VLjuiMFJ!amG7^4wk0l#~VR_h*yR2iZtwn7T)}KCWMvAcPV&_tH(FLu( zsLY6=6a&%B^mqUHB><3s2-B02eADn|xqgE60}(+9ZUj_NMnU!(?Z>A04|&waXK>%2 z?ox&si%Hjt1Qcsa){g69z720Bu}=12>qrx1YF(>FydKm*w$b?mi1mHWJ^IjHwwrXo zF>2$Si|u0C^lA*VgM3{}(V4*n98XoKsmTNZtnq!0sfclKIQU<`(Qr^V$Dl0)dSbir zMhMykFHwLF2M0g^oL-+f&C7PI-~9lu>Rijex-X)wy6 z~{L0GyijO&QSE{SMo}uII^NzM zh3L>;(dQs?``CVFLI7EAW~J=yIWa5kiYP2CbnyrY*6E*K@3dUaCXwD@LRn;U6{<+} z@D*q!WQgc!{)0g=ank|L?d6!zvmkqMr?>QpFu4Oh~95h&#@_qyt*z2W9PRTyu z;F1`pNwISVm*Yobk_}9OhK=u@5zs3`Dd`*d{Koi%zkSO14dbP zuM6V#`vV`J07|lmkdjQ^=pt#wTs<+%9{?17D@6;6cfgR(gBCuhKOBTA} z*f^A|O+YH^TUDl^S^NF6$d1=&1%MaSC?lF>Z~CmPxa@m)s96-2Pq}Fpn-}BHXa|q~ z;IOcYiRJnx4gdC!u?V@+e?MMrPE%}hB4?=jMbqnuROC&iyH8>s@h%YS7n1F`o4lMO z0O|0Jq>NM7qfZmdL_jk&nnOmenKGZ!`{l2FkSv5QOXSf9{0O$4JmI{kGh3Ser$S1=N!&6 z?DFkT>A!z2)?IsjP=0UgZS-WJBN98TqzEE+`BiqRVo^`1X8ELqFKoTE2b7dDhdvQb z<_HnXFL)}sga_UxTag3iEG+(jd3fGEEb5hsYq%4P$MgtCXvhBdLG@cw9oQoF=M=0l zXbh^Rr|iyA8K7NUwi+rF{wc}(It5_0;x(k9&j~0WsY*i&K;$q5!X-W9;jxl~`)?l9 z(pBv~zwB?&$0q;T)E>}!U&P_BDy!0=aFc}BeO2)V2zSbBC;9mH>N%5$#EfT9Q{SPP zVFQONaYPTT4mE+0u{hnZ(U<5Q=X1&sk=PG@PrQ7FK4RcmAs&#eV4V@Kk^8HDRyL_Z z*EoLHD4I(k;CbD%R6)pMv#F+_@@IqUVnx9+kl;_7Lfy?I9nj&4fSu$o&WhigeJn{l zIWE2X(INB~ouie$Lc6`ro^u|=^8&2GvtSQ<3v5hf$*?~^GUrL?Lr5Ug@Xt}w9H`u| z+T)qWbUc_zTD;4mB|U_DoJ{xoIZfYubaT&6niT(EAOlgMj3F)vUrFA$|Hg@uf|A+) z)RrRp$zeDmI+(iOJIl^I#2!{*Vp3R1CGZeNhtajb##!=-qr7h`inJjjSs`@@+aIyE zyHBa|U6rQo&RI(KGFKH2X!@<7XfpG+JaR38U-v7D*!LzCZAA0IK+wkbu*Zp9?!Yrx z3RhTAr4}?B^85AMaUNYXC22|nO&HtlC$lpow>Z}~=?vR|{l_rF-l;sv$h3N|bi!Ap z#)O4Od_Vh(H~fuqT9+46YQf(pPT zFQ(UPPpel1Y!h{2>DdgqneOenNG>`TabW3Ox|CFXkQPsnc%e9@``Sca>V!sCDkBF0 z7Q0Wvv5iRRe8lJV_2{#yhrfSOSF>Alg6PeKhX&!hTlC)$RP^+d@-uZdasnKzIFluQu(2b$ja5!@g>thd| zb%Qm<=-=dRVrrg~=-M5D1BHt9rqCQ@;9KY!+Ky4ChV-r;K-cE!EluTKu2halilZ*vQ2Y|0Z6`$GX9ns6XeC%FN#zn={Ch+AFMg zWg|y=TRb)cc+YO~d)(nGv?3fyXib#mpY4N8v4?3poz*vnx-P!56u_;a)wt*xTx;kf zn74e|6v~E z_ylOyMcea8R$nAxX-;L8Wkvp`dsF?Y?YmoeRxklxCBbvT(1H2qisD(Y9Z%D$;GNU^ zrqG7?&f#tAH}%x6Y)XHRHo_n~Lp(dhOhw!2!K+bnN>dzZ+M3hLXTgJjmpJaQ9b42@Q>PkC4lJ z%aX?iMLN?-zl$|Eb7a0PdRwgAgdnz%9T0#igpf{B)$Hx%asoyY>PE5FR?SXzZD{Or8MLC~;&Vc(cx@s|Efsb?RqX(UE7m@*fX zZT9|(lAxw^bhKBt5{EggNS$K6R;B8gW{D`DTw?Xz zYY=op%F_lYxb+3M8A8s#A6u#@OflBM78^Jb@9hF|Z$QNcm81JH1eTaZkPMY3tinu9=u#G<$FLQtcja0;(>osM+xO zxpuwku}3%Am88$>K5;YOW@O)o&qAbYHfQ>m#|viH>4vLKb6>l;eiXghURK~o%(u?{ zc-1CCy}PQaYkx6BTe{*=aR1lMzC_IVk-+fFwEQp82E=Qk1g&OmITIiY-$mHN5iiBu9&^QRH-jX&`$ganufEc)9m*ZEzF26XWQy`E9rW@cQzz zW^^@j-xf832_PS8bLTRjhwG94SDlH!|4^L8@}ATu{IPxZPgb?$$5*3otxrZA`yR~M zgYb)QjYu;`z;AT>4iu9%oO`<7=3m7P$MQcDt0S_Cmh>l!aw2-`o%|l5_g|#(B zf6#p#_W)M5h+-4MC|HCgP}5H^X4t6z^$7}m*krmx!8GJ$4i|}*qkEVwqGFI=@>`mW z$R~q?Co{f2MIP3uK4qQCP~|-g5=y9X?%Q{N61a=&Uhdiqd(^ZKVGEhXG^Hz8(Cmiu z$bu8jE9uAuZI#LA?DXL*h#rJ1;?8M;ksE%dV~#47+scT-yR(D28Zi+hH}X=mr;%=p zH*Dj5(jY#4rx7CUnb5%qusducB%J;__YB`&$V*|FGQBTJw)-bi)7y6XJz|ykeezF7 zvZRrlx6dSs6*n{qCc_Pmdn<-E)syd=idp)_!+5$@E9@9eu$)m?eNQvZW_;;B&8HM5 zf|eA;9~dDZT9fG?CZqx!L$gHNj|^#*SLUP%TG|C6ZvY)Fn^W__1#>^kA%82aZe%vg zv3h8+D^-ov!b__(EDRfD-ZdirUHEfKAr}3F5Iigii70sen$swq5tRQ#p9)Y~MCGt= zzm@_hkX`lIRSYrRtbMw6|0PBQX-rFwK0!W96?kX)bbiv-8k0q)g@~nq;SUmecbKPM zv*7GWLvlo&fQD#sOTtcR*A74+cs+NbXj_orVOGW_Yc5iTE;1fRE}qh1n;G&uAR`Wk z0zgkd57pD(^nzfzCB=&hop`YbGuxIX&|sWJ1ixwwf;|TZn|qDgOe&rD*0_#!`-Qtd z)L|Kt|F}?zT3d*4j`hl{HL+$sI#1iG5nh$GWUWv(?x~xa!%47 zn`IamFWDO$Ho?s|>I@*?c>t2Rc&9#OZFkRH;gZ~3BQq{ACAo|!E8LpBoQAVJOqplF zQ`r1qly%R%=gS)t%k^4;phvM7WhhcC#na%i#Nl@|eESW}X?6mcEZn!v;w+$@RmJ|B zL)(&jOiEQFGTc76Y_!}FkEc+zT)DcZl{2(p$=9vxPrnjyI==fH@7N2GyEx<3l}Z0o z|AQR4^v@z=$({jbsO9mbGwz^7%$1spc65!v<9CT*(VFg^Fv+DAKd@>>@I^J9prGhm z0Gnr=5tjVtpVELX45PFlMiqSlawP36Kp8Y5q)jgTl`uvntu}*#LI5&EP9P!s?{Ltcb zU))&->AByWsZK(;)j()9s{Lyy-T0m{e(;a%Xd5EgqcYaQaO~MiC$#`!PqLW|GTBZ} zID~;K2TW34)Vdgyq~vl#i*CVn5zj;Cy22zkq=s6{4?w6XiBamCuyo5oF;Xe+XJKsH zoGyH*fh*P%NHv8Xo35mqC5&1(^xH7h|9r6l+CRnd9jDDkf%4WN^O6L0Bj;9J7(S_n zP7`NxkWf-*c2RDbY0BP%TuiN5QJ2Tut+AZNpJ_qcI>fa$mQ9(dcfkvw1ky}Mz-5Hp zJ|lfwDs2+j|CMr|9>)cl{RJD^@SiF>BqY%v%dfT`pJk?~%0AksMVN(GjY1OvxHf#3 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 Z=Vay=>7Rv7|3&{8I+_L=a5cyH{{d7ewg3PC diff --git a/resources/images/6x6-Top.png b/resources/images/6x6-Top.png index 4c8a63cecdcbed190d74737d552e0b203600253a..8f9b84297d6770fc42c4937ad9161dcc8fe0ed59 100644 GIT binary patch literal 7705 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 literal 9148 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 a*N>3M%cgbA-a>Gn4WJ~aE?XmG8u~whOj(Zr diff --git a/resources/images/Gauge.png b/resources/images/Gauge.png index e471c5e5f10d0a79f567f3f5b9dfedc07c5bb2f6..03a367bea25bedee654a5e97a13d58500f20a9f4 100644 GIT binary patch literal 10808 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 literal 12196 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 mO@G(GhPUBuc%!%fFTeo#f(GG%zcrNr0000 zZWwpih#$ZkHkRf9-mfWaD$U{(G5i(RdjNpA)GrADp5@8%p=hv`orUNuSX2hA;|)#S z;uCQz^Gi-)V{4<>JI9CR7~gn?x-i%~k8xb?PFxQa2tIwr{Pe~0vP<#s>MvwFmbw-d zqi-KIO|{QZrb{NX;Mvv?zk-1){?aJF7fw)LX;9HC2dUd}fUb*yj2*|wR*#MQT2;FD zkcy2!@DYJ5VZUb1;A)uWLf%FKoIgT7%X)8y<8fWGN)ZBC%f2PhkfKzM2Pi&!Aw*gJ75oOu6Fi+5}~qy zUTfe!fGuwE9R66{55t#j%6;N(kCDf=gSM})MSAaJaVrO9tt0X?#jL|m$6Zn9vR4=fqj;7^$&#M7MncW=IxeLC$Kld%Q$ zNDoFWBeK{#OQaB5{42kTjWZ~_hBCpLp+}>*@!SM6_X4>>8qb#&F!FN`Cm$M}%l<~r z-2FyweO>UIcv{r5{XhdJ&j#zo9q}8T$oxYdQ`C?47zqj8Y`Q6m%*9x)Znc+<+lzO7 z6b>MN^qH!2-*ZS4M3=KV-+<7`fgi%uRAzja;i}=4h+ej}(dhYOl|`*Kk$NbRY%FZD zYUC$mm;L}v(92&-MUbA=@`k=^)H{v$qW|^gMZdo1-;dGdskA$la-!Ed+Hyrf1cl<| z5|f+ngH%p7GEzuYLi;Fg9|hY|H{s+Gt$<2zc_e7?TQ9IkAAh5-gI}*8{&cUk->$CQc%62R?Mfu zQ|&4Q&Sr{@T6OSwh)b?rW*>-yYTSP<=CMDZlhR~jW~Tctw60uShyoWr?TC{axjKn_a}F_l#_1Y8~ld#ASCBk zLEc~8Exe~^Njd;A>Y-F)K;xn86Rtnn9z!eF=?#x%uF)%c?@wM|vK~Zj1Sz3Q0^WYg zaW1IUJbuP9M-_8$!$dnE=7O`K$i7rvkeR6d*Tos?zf+evOU>{T9_x2EcVRl~Ralr~ z=Ts61_x6;Qf`WX2t&t*}KEaW|vd_6{9)(o_BV!h zrYg=Ub1TXh{0!&C?M|gOe<)K)8%E^qa+#FrCyb}OTFFQUgPPN9!g+lOxv*O(1%z=R z32l#jLxFTUNd(a)Eg3L|JpF0JcMUj9N2-~FKuu~(J^S=}J6M0q&Y2roRjM?KAn|YV zgh-^%{#CaWjuzKu}3v_DN>BSv=kgo8Pq&QR+H5`)zam z$beXC0X}Ry=v;W9`t5n8w>|9HCnb5-?SlgN{jPkL^nr|s)Mnk9qH_*`)n$T~ z((n)ja3#9QRL&rWlcNk{JqGFRc;eq+Tn=KfFC3O&srHs^tZxIaUh)DF8s6o<>%uhyMZBC0o zj4)D&l(6LM=*P`61@+n2>~XeFO8YVmJy#}A=(}$vaX;VRULvOK+5S~Z(A{(IZ0Q0< z9ZkHC$@F;urK{p;shV+`o@EAJ5$ohlw_s*NJv+wsEQlcw8iJAtgo2mwbRndm)b>{s zeE-HV`!v+$SgXR}&o!4c3PndeY_zr^B0_0`m?>fiMRifh#si2q$L6RJF+ X{Qa7ku?ckwzbOG$7D#iNsb}1O$Rw7_ literal 2379 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 PJH*`B(x~?DC(-`}LKk_L diff --git a/resources/images/UAV-Top.png b/resources/images/UAV-Top.png index 35e2658192117a4422d0dddda8b921b22bd91388..03c59d69faa82628fc51a27658bd9aadcf1ae18a 100644 GIT binary patch literal 11593 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 literal 15707 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≥Ps6MPwEA1v!SbFn9NvTXPq zkAvV>m!y7(Q{QCDY7NJ5@&4aP4g#pzvsI=a>yUFh3e7D!Zq#9fl$%3Kk>rT1P=}p8K9v^^{@jQ50bz> zcTm;)!k{L;;9?0?Wc*2GaUQp|=rd3Z`vJZt&fFR&IG_ai$XBJ=Gdg&P8O>t+(8x`G zm6UprL8m~|a4<-)F}J4WL+-7nlj{@Y2ZfUWw}&>prS9#s>vDHr@U1s*6X9~zk?Ez? z$oIJSFA(GHal>-yv`V()bFeNcq!XDO5LrC5`MY}P@PnhNPbHwc4^MzE@S?R?D&8Qk z+KR2u+6y`m+~xVka&f+0G%Puug4TTI9kY0?umxa;I|L3gki9GJK6niv(e9n^sQ#n8 z`~FhhVs2S@mu^$z*Ry^87N+HPw4HHDG^O>su}Rf5Y{7ayh8jQ&EFb`~rOQRxuHTYe zw@8l2kb!}2%4{29GJn-8$vYz18CoD_bkojv$w3BR*qx@r`p@h>Cq)__XK~W2tU_UF zX{;JlGgAU4#P#bw4k^qUVPpdzds6VRYxpWR;cZUgTJiRzQy%KZq9jaVA#h_{fsqhs zkOCnRpXP0~ZBXT839N=kr!&5WMypbb$Qk3*k!oj^3zI>SH{ry2^NaI6K~uH~tdWCt zZc`bQ;`b|^JhF+Ml)CZt6yU@_F%l7>OteSmbvr|FC@A~y7L!jZCB-JP(BOtXzT3#b zLR(-mE(*Is&x_CRb+yWtT#Cc`T4*xN62wqVwQhE4qVyHBLhe}0jhs43`gj_a9+rZ3 zE&jP(D;GNTY?|3>zG|_i)@8#7Ah|$=@P9mGxsvq}3Z~PdXk}XmZSEsJt6M;bH|P&V zHYmaw14{pDpk3Hk-aycxaJ<1YlD=h@Te`~ad)7^IBehDc7Y?WME zGVCn}U*O9{F;MU~#A9IobWNrf-Sgf#d>F1s4=pfz^r)*M9gpuFHcea2`3XafL|LIx z|H1naflfveA_`ge5cPM}$h!~5LO$*G^=Y6wM)250S~Ty!elecXU;qbR^xyoDA-g{W zJ;*@tpCAAl_cxiYq~DipWW}XtX*W$oU zG(^2oYT;+YDnMv6pm6SO=}04+FnP7`#G_-r3k7_IBWdpG7N{cHMea7GLI<*mm? zLISRh<3&{~JiP!Kxb!B&btjKriLRj`K`j#zK}?(~8C}}wOTZbIpCRJ$R3$R7pn}e- z*#PHgCL2Lm*#i+ZawPngc+#e2+*QMpR6~3TQ^!ZZYd4vn#177mwddi6NBClZ!tN`M z4A?PiHRsS(5{|?#XJM4LeaxGxY{|1=<%BS_3k#Vil57MruRs}Talw8&!A0yB5`vzP z%hck%B3}stgNQyymC90II7}2KTPDY0r$VUoUHl|rJ#Ee64;#ipz^V*B z=|t^c>Cr=Z0cnXUC0l&r+M8=v`Eh~CLY;R7zVBdw*uiS}SFCu#z*xZq=~1}<)t!MI zl=2-HQ{wpqEuV)%_efPo*G|icx21f}-{0NWR8i5Y^iII@9SLPNoJ*BP5^a)~^UoVf zrePPcXi)`=6;i-v&sl(Z<%NVGl^}n?tH7Oa5InbG5%9b;6&Zu&&}u(w?8VT4-6*1$ zv~J|qh6BYJY^CWra642bao(obStH(ed#>*T@Uk~Ty1C0ld7~GetbaX$YlyzXXiXQ; zS4n(agNX|Iy3}m{gptJ|poJT{T_4@WVM-`sL;)cp4qD(n6U3I7mz3lyO=LH61rITz#?Hs?c>=X^(*6XIh4RZ6VOyPO>I# z{CC@S-CW%s4&PvWavG;ZqyHMlTGk1bY%#L&<3hPd0o$tUcAhP_Sz~)#f@QHw<6GzO zmZQ2mW`+QTV=VFUh!<-1rfL76oxtYQB1oWl7`U7?bvCY8$D%1FwbqURb%yXkSS^x7 z6?RA7U=|e!DM5!;H>{t85!iT?R0}cF=`t7()DR`@_?cR@q9L#RVZVg&+T7JIWS`-8 z5t`-wsnLNK{HL2_#nqrpFbF~TbRbxJF8EIa&~vUgWH$q!z9hJu1W$p~D6coL5&^4; z4hMmY?op#`^4w_%IjP~m6r7v&Mc_~dSP^DfHMbg@-S`hX@_dY9SqZZPw)x86ws)3_ zpZl)22gIo~s?+A#=Gi#3mn1H=h3Zou2n*=QYl>x!<~LZmxWcmoOQn~O(`$T6k%ww- zKtEYa4Q!8dZHEJW_cDLG0DEU+<(ErI0_ic4zjDHg#fu3P1_wfy{ATL2@pNWlFB|q6$!jUDFn^7+C*)~mzuMF zF-1m93q!>83+5FFB2P_u)8zfNx!49KOezzvkC;=aKdL`R>=$M1Dtdt$J24be`Vi<# z&&+PBvT4~RtE*v$U4zgIT|N%?Xj9&X-D4kRaP17H0+tA~83xHa{jcdR^AO&ATSVkwV}5 zxqUZ+ddB0i$r`4@?dh%Q6C0e$rap&F(5)UMK54!!j=tF@M1;AC=mYJEb-34nlVy>7 zFgS9;u`Bf+c^g)H{?g`-dwf2d#;&C=ES5XFf&RAxkQ90c^pdTw$O)FV>&z)>?BAJN z)d}t~5?Q#BeVweV)|rSj{107adSNuDo49<|0e_)hjA$pZD<%A@DK@ilVH7(;REGC= zeFEq=C-DxQ)H`LYn5EwF#QfOFy$+y;ntziB`CP>2>F|iuT-o>x#n5-<2EabIm|&zJ z>UMA&a4ac}rNO0?`SnGH-|4qc$5Fey+9kcl#iJ6af_rL@XvPP6aIapM+4} zW?}%#eJ<=rrR?X`*b+ntb+?izD~o*W*_Yw(F$^ELn51;e(UtrP))@gk z6N8|24~vTj_akA z--PogE4}+GwGSeTDu%}8O^Fb=F%}26-*Dg(WUjd_$L<{ALLgH`n2sRtlNBQ(Hm&M6 z*)$`~82$N1>NPUScK-(54PzyTo$t+>dG@BxzJknceqcZ3@bsCqFqX~1!6zBBkwM}U6Ht2nV2Z#8 zw@!&j3Yf)ig zLxF)};eH()Z*W!jGXnKA+DhLN*YhqOb7&zO%U+!jdi|1NiRU@mRu+ldiu^bbX>YaA zQ2o13`;N!X1rLhEVR$!EjqouYw7QOed^OK%A)(MJs?%c^xp!WtfxVGRwdM;IB)M_U z{c(@8vV2Yu-pvRatj=K|Mkrue%Vqm`sgC&Z3+9XPY9v-3?xYZsT+9j{3fw6cDT1l_ zFFj}U+`vy0wc5sqUyx2J^I%~El=NkL$h-V^O`5u{o*kmRdb#`bGF~z84Ssql~?|9P^QQALer5Gj8AFZ5K~it znEg5Bq-rbN^Z?sXKZkrt!8kc z&8k?=l?IhmztyU-s6r#@v+C)&g?>o2TP}uw+5}8=&WLnAAt~0;jen!2s|6}WluQ_? z0!6`~HFEC&GI!0zq(6z0)i=<8=*FSf{$o=X+}h@r>!K1dh$~%;!r*C+>3$6E3kmZP=H(0W}CtiL2{ zVg@!#Mz9u~@B{ zbB21eg@lk$CcecU=4$_8rQarLrL(#z05udM-~WK3n7P>&_zIQJ+N>dn1FE2vQl?V zmZu@lC$}u=b*BIa){H6Ue5<89yf{glmEpbra`Lt3@@TQWwH-nX3f~{B;x{tyyLl zX|~CWMcZl1pU3BR*kQ>>jvoY&l7-uucP5nx2W+6YCw{u-5=zz|!g}*6yVLWl$Wf8A z_YCQTL(>zxxl9lo-Aj2HBtNRs#y`%y4u~25fP16?m+6_%UP(w1HK&qc^a~aSqCvwj z5hSRf#Z$tfn6?LcO?`Jci$T)Gv^zKmX{8S55s;YSvg}QXZ$J&sg9%bob+N(3T zAL7sUO;9N+9Cpe?r5$svLe*r^#xvVBy5PCSCD1j0dLn!7PC-u^;hrc)3U_)bQcKN| zzh}v?kesfrS6(DGE?S{xgw4#3z1#`wt=d>9S#%xIoJ%lnix&^tHsUep?qu!>qI$x1 zWAUn8LqT3fNw{Ew(uEm0@CZ&_n@c(^+wK4d!c_+{5`MMY9 zZb(2W_T}ieT~{d*vO``g?`vK15p8jBo8%2KX`Uce=t%e> zTfGq=5P&urUmp1$&xQv_A!vG0RxtGYgH96JO9F7}N2=I~O1zkX&iX?M+Bmt#1f`~@ z`p;RvdCqBoxfKrpqw*L53bw=ta2vSMYYS7Qmr6z)5%~}8Lv#RZIFfCFe$oKD6$zuM z#W)p1L^l1OGn@B~raYALkryFAD4vmG=-uO0UO?AgoCzC_#-@c(blWlSF55Zd6|aho z%q;??E4`wYf}$R4cYsK!9R&3x#nh5Z`vnOsAS4CnliX#-%+W_}owv zrx1Gu3I!JT@(}c@W+0fYyDQg$MZ+Y`Kc(R?6Vh@CUtyKQL1Gs9?@5ka6a^2e;W0FD zPn*I%1Q6%VF%0XKW-1sC8QDVY!)wgzjx1gCvb^}}|4QTC`9pFmK$M;MgE6TG&Py5T zsgCwOg%K(4XJ}ZjMo?ErtYJRD!ac=_ z$Sy!NvQ)NWJx!88Gd7om@ZV=2mqG`BSEP{*=0ep^Y^sdK*b)hg_jdOUO(ctc$fdTCw6RB;Iom3=U| za#ztJ^E*(P3?`>(J*m zzg)`i7fEr>uXP(OoXw>F6fZa}gn-QrxmR@~lw{5&^`6Aq^(7l;5n2tJp*uRMoVkqA z>4^OaWgipWjYcqP5CTQY)7d1yK(>8_&-PJAFnw zXoSRXUFUx=PmIAh>6q`jed9rLU&J6m9@cJhl|QRKX^p_**l|jYwu4 zA61lwQvoMjiJcPG$_I`zc#5$j3oqyX4EY(juo?%==A4rK-VNKwT+_R53VeVSpWg3A z;USq0I~*-Mb*;Bc$R->^XpO%EbC{2zxDhTlaIOXdM9$fCFd4f87?mc}U$IFVx`^6R z)l_t*TfgMCif|1V9cisvGV?M|nIMsSgl`g{Y@$X?{>gyWE8Zfw}|C0y?5mY_|005qa>Kogh5nSBE=wu z2|x&S^iJa{4LIP|-V_Z@FlaYKOa5<@Yr$+Ta|c1uuQ~&Sa;J?c3~ZLvSM2Dabyw}) zfyW@`Qw9Z+DvbO>e0@o57c5$;AzNyM{oGuPzHF!)`|0K4fZX{!3Jqo6^ERekx6(gb zXO#uTuh+4~QMS+PP`X`2$PY%<8DQCxLe;V+AjQlrtF!52nCbXwYaz_dDs~?K4`27) z+4t~h(L%h%Gs>l;EClCok(5g+|D0eaL zs}=w8Nj&hPm1{<9C;&Xi0u8s&zZ@pb;2}LIGcW9unHVJcLLByOKLYo8jW~O})cE<_ z+7fub50DwP&G^q@l*9F7U49xD>4POHnK~D;mg-*%|Iz z3#|6Mp!8c9Hb17Z#=JYUAB;13wM1Y4;AXHsm9Ka8d8wKx{d^ zl#ICHnJ@9^%usdo!;(!Ss6Qk*ygyjb8LxcD41p;@XouEE#@6RgXMTHz(%nOIC-U@& zJeoh4%0&xqSN{VE5$b;NVf5#*2+87F0of8iBdNU{e(a38a&plRO#2s^kO5=dUFKq! z811TS1*&8Il=LFD){h^zcM$_&UvcWFbXo!lI(q-LvRQobd0w^XT#d@nHI!Dz=}O#R zyxc8uZ0;OF%91Kg51;S;F!I3wKS9whWB^Pjq|n1WMcB?M2~&tvN>lj*9O8<-{iF9r zsd)G|F@=RXICP}SfBJWh!#`qxu+M4atao+X_2!oniuo6KdY{=!M9>30n7^gMbX>Zm z#rQg%;3OWEQ^8kf-}16Z7hVL-WlBrkCXB-bk-gIE{G3Mlh?;sHY@}d+1Per ziWl8tvB)RmLj8-@gswa({)jj}#eM#0 zbYfEo*rADimy08NSIt}}XYt+|7hgRUQOboT%g%>PLB~rElku2T2q}%^LS0w1oZw12 z2_Em1gwWGq7oyMcSbezH|K&T&Fb)ybw zMEB(sZAKfa66{KA-JDAq^836W!SRi0N9Zc+Y0(o_kTckUk~K^?t0_^ASU%o<KFdx1oxp^Hzfm@*1xrR)4iDMpCto`}I~%l;Hkz@!GR zVsVxGrF!eriu&C57&zW$Sz=|N8>xlB&Y&+78FFpHj9C70q-$>Ld@r`nEmZ%GVm}Dl;-*GP zT2Sd~X5eLOxVyVKg4TuKe8>QY%(&}Y*~5r*oL2J%_+z)F=SRgPbcj@ut8xYSylbw# z>DPZXk!e$P`js02oh2a+gs(jeSS98ac(uSiKkIqJ5iI;2rP2pM*XcU~Xc-^J!icOo zx$8+-Ke`OjoSF=n!RA*oOhGiUxKKGIluqa4kdDSTUZ(v1mflomq}|gSFg8N%Ml+so|30JrDSfZ!nnLu}1@`NF6)ME^ z^+|*e-*)nJ45H$6;zvk<#y7Bw#5(6DcRr`r<`}W^SC0=67HP`(7qrtnX9<*OI}!RHO|i*!0^(M*R7#p)R*-{aT)xuu0rV>B?bkI_sF&wFKYUVbzeyH8uuo>Uf+F3Xf+gJXyd|gWn?UD7DMT=s@7{= zHrYG?60acX#M7{_8h)%xE;8Rkhtl`{vFJnKW`k}rHt>VU}e3Gr? zh6!Sabr{Y$c2aI)^fd!{oUY-)dtbQ^8OeDPpEN2t2`4kA-=^2r;)?Tg8g#aZnD4x_ zZXCk?=kGS+RP9bJ2D7*#8brJaYB|j3 zRkP&3JSBa-G~P8{Hd}Ix)N%4J(Ubk|a#2CgT(FW?K-l~JpIaMMDGpe4ZvgO`1Vk~F*_U=K@Cg9VU5!Wgk+YdGnTJ|bvWJEeGTuN(q zOCowCLMe3|Njru^yLs6#fR^*h-#M7#q6tBtr=wEqp%i=IkbJu;RxxV7EQW9}my0g~ zUC^Th#V_~hxSqo%+UYzg`9 zX^i+T5$FZQ8ai-ZYtt8^nzWv8*9T**hO?DtsO4`^l6a1Z_TlOHuY)bfn3GU|EMkj} zKLqfE2!Q|+f=xJN7Aj&dY_u!Kn_jJFK9qjdYs%ZQZTu1;A8W{EB~Q72?mGH9l>L+u zFn;{-mZU+hp2(IDI3nv9^P&1o!?&BmiVX%>%2W%UT(8ab&w+C$(>-9EFum?=6Rj5C zvIau&b|rCN;r&B;tR*w8m_MZ>~zVSk*%>eq;cqQ>0Nl z_x=MId2+o4*TjK)+AhD~RtA$`dckd=b(E89%q43JL(6ky2Saq_7)aUPh-oh`&&moz z(#PRaTZ+SE5CbhSUYLKqx`-4V5(rL;mng$=E;Sv|;RlX7bx#)!Q8|;Nj{E~Rq4Yxh z!UUd&8J*`P_%7yA>8Wj+z>h%l;a6{mD@Gnd!tzW8v4!7jGqL_`vZ;=k68VMcqqeJ6 zNl0S8v}{etaoA2bIv3o@9Q|zwjnm;$Q$nt0_7vDd2FM=$dI4CIQ#yEH*)TW2B^56n z%%aRxU_K^yg^l`Uz&PPW(t%y?1vK`9C#lCV($ID^Vsxdp{o|l8sST5(K|hCnr+O^g zlH+@pE*hZHF7Mjsisr`CFYkJ3Ak=Sf(CFDT9RUQsfvN2(BhW0Z@z23cbi~jGj u*XAGAm>sggp>6l^{}SI5SKp9000oMRez~W_s{gK~0n*|MVznYh!T$$T#yo=n diff --git a/resources/images/gaugeGlare.png b/resources/images/gaugeGlare.png index 93fc5a1c441e3ffdeefe34c3deb18686f9204cf4..11eb9e2aa218bf170b48ddfc61be4d14c0b8d67e 100644 GIT binary patch literal 1355 zcmb7^i#O8?0LOpx*ygdkm-pHTSN#y664__k7Ry`#I(FfgPcl>?VFhHud9@(k=TYuUPG08UlijQ4m&z>-^-<5hI+l8tW}9UNqp} zYkX!;ft$QCS=KW(WpQ5H3yM6ijVel=%VW#UglM6kB%&5!2$(DJ=I+@Foh?9_kXGf8 zk+tg>1KS%~hAJ}14xs2E4#Y%a;>;Hx<=;!M9vF|NgE!Q!f-_ZlBk`V`7nX~jbOaxg zZklePGMU9>hjo078_#b#CnePJ1X}|KC$aRQkp}GY)`A<%6nqGs!wFyHtZp!{(Ug#L z2|Jnq%k1}~@f1z~E?0O$Vn){ty}g>W5~Xi{D>$tc{%wy|hTfe>#+zi$j5sSKs|?*a zx`4Xi;ycvpn^h@{m6Vi=*Nn)r2Z(*@Ga4nq7pwfPP@Chrv)UP7KJ$3`H`gO1&%;4{ zPKh6vI!Zfdv=21%vPJ(?rPUIUgn75OwA5i)iTr97o)suG!Z&yJz8|W@EJ2fY@2Vzo7%>rU?c8P(1Z#~zdir6HE3KQ=!`tgpFw$wvZbl(*X|tQJ*-)uoIE0G5QHWxnnWrK zVJg>tl#9)*t%F;NK=MybwHcH)k=WZ6$9aRMwXuITDax$A2MbEcw$W ziilN(R;Zjn(VXGkcwntyaQ=%=!C~3aT7Qq=1rNISe)RHbdidOvirB=y@{Tem9g-u! zb>9yswxB{(22Bv?9X7lWncnWP|jpD$FPqjawXuUCaN7ozo zU+;9|?8>Iuz+LQ^-oDtLGfI6o36#JR@!8ta;!noA>#dls6H^XhLT{xOubts!b98w&d3fthO zC~ul38C)$41||I78FKJ4D1p%)H*?Zj=E%hxjz*fYCJ;~zZbL4b^)05^Sxy_LzdZak zO+DWe zzf6vWMUd;3$Hd(r{|<}Pu#~?uTEfFs8nO_h@wWpZ2KeECC=Xo*`nPtERgH*g^qKZH2V^w*>yT^}20zwlu&sS^+G zvu|*QkTJ#^O8fMd>vjDXc$VhEL2U+W%tYFED4kKAbI&at`R&_yw@?i)XjSiH5mrl` zifu_#hhuVu1c_bJ>ya_CFSmUbTyWv()r`@*I)rFRj3ki$aOB-&e)PSaPjPZ>H`3lf}Qr_#aa+k9e)RdysG}Fnn8k0GZ@RZ1tk& F`~!G|SQ!8S literal 1285 zcmeAS@N?(olHy`uVBq!ia0vp^Js`}%1|-)VaI^zbk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+SeARbIEGZ*dNaq{UpP>tA^M5n$_L7tIW>aQMTCSGuPZ&j z@AtdH_xHB`4q=YYaFwGM_za%;M*1|2u-_O;=7ZtH< zT)6l9>mB3rXJ*rxfv$jp2A+xMZ*kwddslYyGL67X`l{!=cy9M^wH3CxvPf-X-tkk` zoxZ>Cx!KLH?=^TiX>m%BkB3iN%8k{zHhJGlEy{f6eHYR?&pt7OH}TIz7uKCAfjs|L zoGjell4)~|XN%THrzBsNUrh$@Jc~jfEpl1dt5V2u`9{{ao%$PfKK*f-k)jf4xX$B@ zM~~^mL#l~oP9Gy4b#{DaOMJB2`;^&JktmKws|*}#rc`J=vT>equd8Tlr-U7s@R9V+ zYNd0$xgNW=Wa@k0+~jq-eQC@L1v}3>f`w)R%PIp`M=lk4`cP6(?SvV> z%dF6rm8Xj$XWdlW%5~_<b$$eUNq>451pSWA}Ub(L!OI9w6lbHO}tvfgkXt~b?y`y_C zTz|e$C*XP0mzOU?xSDlrAMg3{c4Y`Bkd+?wtHQ=ta#<6Q8MH+5W1K{#;8Z^Uxry~W zH@;kXzt-T>lh7%W$BfKEre*54Zqz;}dd#4#&*T35&t4MRMPHqg8b5lsEb2K}IN!); zrjmK#xh|9DbKJ`QMP0j&a`Ji3DT|Vr-IXS(ovv|W#+(An!!6%A8m+(j%r8FFvaPWp z`h|pe>=}dW64L3CZ^Ibxh$gPNplhh-SA0k^aYomk1dn?+oIAOl%RY7~>!nC+>bB=O zC0(OnnE0kj@a9o{KJ}T25f{H2pZ;=GuV?M6qWRs+FO~f2X5V>JEqBwNEn()JdwFZ) z`^}~0cFUFS->iFw9fXyL}~pY~TB5 z`M1W7vvwjMeWfp!l$M=1mt)d*yP`Dts+oTM{|PqR-$t}-Jk7A~-o5}{&y9=LmiV0A zUZQ@!acCR!VF-(Zm^)I%Buh)J(IRT%01OBZF49Pty4r zv7kyPK<7}2_Tei&lUP+gdTio*cJL?jKmVyN_9rT4zWd()ulCfX%reeRCv94@+ge^e z2#Io?DeNis;#CfpaH;y^uGGn@yDV-W+A@2R$>KYU>>MSQ$=&!Lc}XtaqgZ4!hp?-} z+TgrRZA{fC7yT}IuKMgva}z@pm$7Q$;diZ!usly4x}a%46A#0s^V^=UN}VSNGSk!5 K&t;ucLK6TTI4~Ok diff --git a/src/map/WaypointPanel.java b/src/map/WaypointPanel.java index bbf63f2..3cc56e3 100644 --- a/src/map/WaypointPanel.java +++ b/src/map/WaypointPanel.java @@ -31,7 +31,8 @@ class WaypointPanel extends NinePatchPanel { protected static final int MOVE_STEP = 32; - protected static final int BDR_SIZE = 25; + protected static final int BDR_SIZE_TB = 20; + protected static final int BDR_SIZE_LR = 15; protected static final String NO_WAYPOINT_MSG = "N / A"; private Context context; private MapPanel map; @@ -71,7 +72,8 @@ public WaypointPanel(Context cxt, MapPanel mapPanel) { setOpaque(false); LayoutManager layout = new BoxLayout(this, BoxLayout.PAGE_AXIS); setLayout(layout); - setBorder(BorderFactory.createEmptyBorder(BDR_SIZE,BDR_SIZE,BDR_SIZE,BDR_SIZE)); + setBorder(BorderFactory.createEmptyBorder(BDR_SIZE_TB, BDR_SIZE_LR, + BDR_SIZE_TB, BDR_SIZE_LR)); buildPanel(); addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent me) { diff --git a/src/ui/ninePatch/NinePatch.java b/src/ui/ninePatch/NinePatch.java index e490101..d709150 100644 --- a/src/ui/ninePatch/NinePatch.java +++ b/src/ui/ninePatch/NinePatch.java @@ -220,7 +220,9 @@ private void paintTexture(Graphics2D g, BufferedImage bi, g2d.dispose(); } + /* public static void main(String[] args) { + JFrame f = new JFrame("9-Patch Test"); NinePatch test3 = new NinePatch(); @@ -290,4 +292,5 @@ public static void main(String[] args) { } } + */ } From 8c47a201ee8e2e694aaac3f50634e5ed67df0208 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 9 Dec 2020 00:43:58 -0800 Subject: [PATCH 021/125] Updates to Dashboard UIWidget construction Refactored the createRightPanel function, moving widget creation to their own functions. Added in the new WidgetPanel outer container. --- PingWidget.java | 3 +- src/Dashboard.java | 119 ++++++++++++++++++++------------ src/ui/StateWidget.java | 6 +- src/ui/TelemetryDataWidget.java | 4 +- src/ui/TelemetryWidget.java | 2 +- src/ui/WidgetPanel.java | 66 ++++++++++++++++++ 6 files changed, 150 insertions(+), 50 deletions(-) create mode 100644 src/ui/WidgetPanel.java diff --git a/PingWidget.java b/PingWidget.java index 3e40c5e..aba9c9e 100644 --- a/PingWidget.java +++ b/PingWidget.java @@ -6,8 +6,7 @@ import com.Context; -/** - * +/** * @author Chris Park @ Infinetix Corp. * Date: 12-2-20 * Description: Dashboard Widget child class used to display a units diff --git a/src/Dashboard.java b/src/Dashboard.java index a9d1eba..87f056e 100644 --- a/src/Dashboard.java +++ b/src/Dashboard.java @@ -34,6 +34,8 @@ import jssc.SerialPortException; import jssc.SerialPortList; +//import com.ui.WidgetPanel; + public class Dashboard implements Runnable { //Standard References private Context context; @@ -41,8 +43,11 @@ public class Dashboard implements Runnable { //Static Values private static final int START_WIDTH = 1200; //default window width private static final int START_HEIGHT = 900; //default window height - private static final int WIDGET_SIZE = 140; + private static final int ROUND_WIDGET_SIZE = 105; + //UI Widget Frame + WidgetPanel widgetPanel; + //UI Widgets private TelemetryDataWidget dataWidget; public StateWidget stateWidget; @@ -193,57 +198,43 @@ public void update(double alt) { } } + /** + * Creates the right panel display for the dashboard. This display is populated + * with with preconfigured UIWidgets that plug in to a larger WidgetPanel. + * @return - The right panel + */ private JPanel createRightPanel() { - JPanel dashPanel = new JPanel(); - dashPanel.setOpaque(false); - dashPanel.setLayout(new BoxLayout(dashPanel, BoxLayout.PAGE_AXIS)); + JPanel outerPanel = new JPanel(); + outerPanel.setOpaque(false); + outerPanel.setLayout(new BoxLayout(outerPanel, BoxLayout.PAGE_AXIS)); + widgetPanel = new WidgetPanel(context); - //Add Telemetry Data Widget (Ground or Air) - try { - if(context.getCurrentLocale() == "ground") { - dataWidget = TelemetryDataWidget.fromXML(context, "telemetryWidgetGnd"); - } - else { - dataWidget = TelemetryDataWidget.fromXML(context, "telemetryWidgetAir"); - } - } - catch(Exception e) { - iolog.severe("Failed to load telemetry widget line spec "+e); + //Telemetry Data Widget + try { + widgetPanel.addWidget(createTelemetryWidget()); + } + catch(Exception e) { + iolog.severe("Failed to load telemetry widget line spec " + e); e.printStackTrace(); - } - dashPanel.add(dataWidget); - - stateWidget = new StateWidget(context); - dashPanel.add(stateWidget); + } + + //State Widget + widgetPanel.addWidget(new StateWidget(context)); + outerPanel.add(widgetPanel); - dashPanel.add(AngleWidget.createDial( + //Round Widgets + outerPanel.add(AngleWidget.createDial( context, Serial.HEADING, context.theme.roverTop)); if(context.getResource("widget_type", "Angles").equals("Horizon")){ - // Initialize the horizon widget - JPanel horizon = - HorizonWidgets.makeHorizonWidget(context, WIDGET_SIZE, (ArtificialHorizon ah)->{ - registerHorizonListeners(ah, false); - }); - // Add call back to pop out a new horizon window when clicked - horizon.addMouseListener(new MouseAdapter(){ - @Override - public void mouseClicked(MouseEvent e){ - HorizonWidgets.makeHorizonWindow(context, (ArtificialHorizon ah)->{ - registerHorizonListeners(ah, true); - }).setVisible(true); - } - }); - - // Add to the panel - dashPanel.add(horizon); - dashPanel.add(RadioWidget.create(context, WIDGET_SIZE)); + outerPanel.add(createHorizonWidget()); + outerPanel.add(RadioWidget.create(context, ROUND_WIDGET_SIZE)); } else { - dashPanel.add( + outerPanel.add( AngleWidget.createDial( context, Serial.PITCH, context.theme.roverSide)); - dashPanel.add( + outerPanel.add( AngleWidget.createDial( context, Serial.ROLL, context.theme.roverFront)); } @@ -256,11 +247,53 @@ public void mouseClicked(MouseEvent e){ JPanel logo = new JPanel(); logo.setOpaque(false); logo.add(watermarkLabel); - dashPanel.add(logo); + outerPanel.add(logo); - return dashPanel; + return outerPanel; } + + /** + * Creates and returns a TelementryDataWidget from xml. The type of + * telemetry in the widget is selected using the mode configuration + * found in the users persistent settings. + * @return - The telemetry data widget. + */ + private TelemetryDataWidget createTelemetryWidget() throws Exception { + if(context.getCurrentLocale() == "ground") { + return TelemetryDataWidget.fromXML(context, "telemetryWidgetGnd"); + } + else { + return TelemetryDataWidget.fromXML(context, "telemetryWidgetAir"); + } + } + + /** + * Creates a horizon widget with mouse event listener to pop out + * a new horizon window when clicked. + * @return - The horizon widget. + */ + private JPanel createHorizonWidget() { + // Initialize the horizon widget + JPanel horizon = + HorizonWidgets.makeHorizonWidget( + context, + ROUND_WIDGET_SIZE, + (ArtificialHorizon ah)->{registerHorizonListeners(ah, false); + }); + + horizon.addMouseListener(new MouseAdapter(){ + @Override + public void mouseClicked(MouseEvent e){ + HorizonWidgets.makeHorizonWindow(context, (ArtificialHorizon ah)->{ + registerHorizonListeners(ah, true); + }).setVisible(true); + } + }); + + return horizon; + } + private void resetData() { dataWidget.reset(); } diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java index 331c4da..f962bfb 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/StateWidget.java @@ -56,9 +56,11 @@ private void initPanel() { Dimension labelSize = new Dimension(100, 20); this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); - this.setBorder(BorderFactory.createEmptyBorder( - BORDER_SIZE, BORDER_SIZE,BORDER_SIZE, BORDER_SIZE)); +// this.setBorder(BorderFactory.createEmptyBorder( +// BORDER_SIZE, BORDER_SIZE,BORDER_SIZE, BORDER_SIZE)); +// this.setBorder(BorderFactory.createLineBorder(Color.black)); + //Configure state labels apmLabel = new JLabel("Apm:--"); apmLabel.setFont(font); diff --git a/src/ui/TelemetryDataWidget.java b/src/ui/TelemetryDataWidget.java index 180abbd..7bb18bf 100644 --- a/src/ui/TelemetryDataWidget.java +++ b/src/ui/TelemetryDataWidget.java @@ -16,10 +16,10 @@ import java.awt.*; /** - * + * Adapted from source originally written by Brett Menzies * @author Chris Park @ Infinetix Corp. * Date: 11-25-2020 - * Description: Widget child class use for displaying Telemetry data. + * Description: Widget child class used for displaying Telemetry data. */ public class TelemetryDataWidget extends UIWidget { private int lineWidth; diff --git a/src/ui/TelemetryWidget.java b/src/ui/TelemetryWidget.java index d874e74..17b6ef7 100644 --- a/src/ui/TelemetryWidget.java +++ b/src/ui/TelemetryWidget.java @@ -18,7 +18,7 @@ //TODO - CP - DEPRECATED - This class has been ported to the UIWidget Heirarchy, and is no longer //used. Once any necessary information that remains has been gleaned out it should -//be removed +//be removed. See the TelemetryDataWidget class instead. public class TelemetryWidget extends JPanel{ // The top,left,bottom,right border widths of the nine patch image rendered diff --git a/src/ui/WidgetPanel.java b/src/ui/WidgetPanel.java new file mode 100644 index 0000000..343bc8a --- /dev/null +++ b/src/ui/WidgetPanel.java @@ -0,0 +1,66 @@ +package com.ui; + + +import java.util.*; + +import com.Context; +import com.ui.ninePatch.NinePatchPanel; + +import java.awt.*; +import javax.swing.*; + +/** + * @author Chris Park @ Infinetix Corp. + * Date: 12-8-20 + * Description: Main panel container for the right side of the dashboard. + * Holds a collection of UIWidgets. + * + * Note: 12-8-20 - Future implementation improvements will aim + * to provide additional functionality for the manipulation of contained widgets. + */ +public class WidgetPanel extends NinePatchPanel { + //Constants + protected static final int BDR_SIZE_TB = 20; + protected static final int BDR_SIZE_LR = 10; + + protected Context context; + protected LinkedList widgets; + + /** + * Class Constructor + * @param ctx - The application context + */ + public WidgetPanel(Context ctx) { + super(ctx.theme.panelPatch); + context = ctx; + widgets = new LinkedList(); + + setOpaque(false); + LayoutManager layout = new BoxLayout(this, BoxLayout.PAGE_AXIS); + setLayout(layout); + setBorder(BorderFactory.createEmptyBorder(BDR_SIZE_TB, BDR_SIZE_LR, + BDR_SIZE_TB, BDR_SIZE_LR)); + } + + /** + * Adds a widget to the WidgetPanel and its tracked widgets list. + * @param widget - The UIWidget to be added to this panel container. + * @return - Whether or not the operation was successful. + */ + public boolean addWidget(UIWidget widget) { + add(widget); + return widgets.add(widget); + } + + /** + * Removes a widget from the WidgetPanel and its tracked widgets list. + * @param widget - The widget to be removed. + * @return - Whether or not the operation was successful. + */ + public boolean removeWidget(UIWidget widget) { + remove(widget); + widgets.remove(widget); + return true; + } + +} From e3a234341d956d96c01a15a93e348a0bdc3700e5 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 9 Dec 2020 14:35:47 -0800 Subject: [PATCH 022/125] Add widget title to UIWidget Classes First pass implementation. --- src/ui/StateWidget.java | 9 +------- src/ui/TelemetryDataWidget.java | 9 ++++---- src/ui/UIWidget.java | 37 +++++++++++++++++++++++++++++++-- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java index f962bfb..66aa15c 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/StateWidget.java @@ -16,9 +16,7 @@ */ public class StateWidget extends UIWidget { //Constants - protected static final int BORDER_SIZE = 0; protected static final int LINE_WIDTH = 14; - protected static final float FONT_SIZE = 14.0f; //Color Defaults protected static final Color DEF_FONT_COLOR = Color.decode("0xEA8300"); @@ -42,7 +40,7 @@ public class StateWidget extends UIWidget { * @param ctx - The application context */ public StateWidget(Context ctx) { - super(ctx); + super(ctx, "States"); initPanel(); } @@ -55,10 +53,6 @@ private void initPanel() { Font font = context.theme.text.deriveFont(FONT_SIZE); Dimension labelSize = new Dimension(100, 20); - this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); -// this.setBorder(BorderFactory.createEmptyBorder( -// BORDER_SIZE, BORDER_SIZE,BORDER_SIZE, BORDER_SIZE)); - // this.setBorder(BorderFactory.createLineBorder(Color.black)); //Configure state labels @@ -131,7 +125,6 @@ private void initPanel() { //Add panels to widget for(JPanel panel : statePanels) { this.add(panel); -// this.add(Box.createRigidArea(spacer)); } } diff --git a/src/ui/TelemetryDataWidget.java b/src/ui/TelemetryDataWidget.java index 7bb18bf..f5fd153 100644 --- a/src/ui/TelemetryDataWidget.java +++ b/src/ui/TelemetryDataWidget.java @@ -22,6 +22,8 @@ * Description: Widget child class used for displaying Telemetry data. */ public class TelemetryDataWidget extends UIWidget { + protected JPanel panel; + private int lineWidth; private Collection lines = new ArrayList(); @@ -121,13 +123,12 @@ public void repaint() { */ public TelemetryDataWidget(Context ctx, int lineWidth, float fontSize, Color textColor, Collection items) { - super(ctx); - + super(ctx, "Telemetry"); this.lineWidth = lineWidth; - JPanel panel = new JPanel(); + panel = new JPanel(); panel.setBorder(insets); - panel.setPreferredSize(new Dimension(100, (20 * items.size()))); + panel.setPreferredSize(new Dimension(80, (20 * items.size()))); panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); panel.setOpaque(false); diff --git a/src/ui/UIWidget.java b/src/ui/UIWidget.java index 3d804c6..0a58714 100644 --- a/src/ui/UIWidget.java +++ b/src/ui/UIWidget.java @@ -11,6 +11,8 @@ import javax.xml.stream.*; import java.text.ParseException; +import java.awt.*; + /** * * @author Chris Park @ Infinetix Corp. @@ -19,19 +21,50 @@ * general functionality. */ public class UIWidget extends JPanel { + //Constants + protected static final float FONT_SIZE = 14.0f; + protected Context context; protected Border insets; + protected JPanel titlePanel; + protected JLabel titleLabel; + + /** - * Standard class Constructor + * Class constructor + * @param ctx - The application context + * @param title - the title string for the widget */ - public UIWidget(Context ctx) { + public UIWidget(Context ctx, String title) { context = ctx; insets = new EmptyBorder(0, 0, 0, 0); + this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); + this.setOpaque(false); + + Font font = context.theme.text.deriveFont(FONT_SIZE); + Dimension labelSize= new Dimension(100, 25); + + titlePanel = new JPanel(); + titlePanel.setOpaque(true); + titlePanel.setPreferredSize(labelSize); + titlePanel.setBorder(BorderFactory.createLineBorder(Color.black)); + titlePanel.setBackground(Color.decode("0xFAAC2D")); + + titleLabel = new JLabel(title); + titleLabel.setOpaque(false); + titleLabel.setFont(font); + + titlePanel.add(titleLabel); + add(titlePanel); } public void setInsets (int top, int left, int bottom, int right) { insets = new EmptyBorder(top, left, bottom, right); this.setBorder(insets); } + + public void updateTitle(String title) { + titleLabel.setText(title); + } } From ff3a39d57e1f22d3ef0e6a2674480d13f2efaab0 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 10 Dec 2020 12:06:14 -0800 Subject: [PATCH 023/125] Update default widget bordering Tweaked some default border values. Added some documentation to UIWidget default override functions. --- src/ui/TelemetryDataWidget.java | 5 +++-- src/ui/UIWidget.java | 30 +++++++++++++++++++++--------- src/ui/WidgetPanel.java | 11 +++++++---- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/ui/TelemetryDataWidget.java b/src/ui/TelemetryDataWidget.java index f5fd153..3d3c559 100644 --- a/src/ui/TelemetryDataWidget.java +++ b/src/ui/TelemetryDataWidget.java @@ -128,8 +128,8 @@ public TelemetryDataWidget(Context ctx, int lineWidth, float fontSize, panel = new JPanel(); panel.setBorder(insets); - panel.setPreferredSize(new Dimension(80, (20 * items.size()))); - panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); + panel.setPreferredSize(new Dimension(115, (25 * items.size()))); +// panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); panel.setOpaque(false); Font font = ctx.theme.text.deriveFont(fontSize); @@ -149,6 +149,7 @@ public TelemetryDataWidget(Context ctx, int lineWidth, float fontSize, } this.setOpaque(false); +// this.setBackground(Color.black); this.add(panel); } diff --git a/src/ui/UIWidget.java b/src/ui/UIWidget.java index 0a58714..dcd7305 100644 --- a/src/ui/UIWidget.java +++ b/src/ui/UIWidget.java @@ -2,15 +2,9 @@ import com.Context; -import java.io.Reader; -import java.io.FileReader; - import javax.swing.*; import javax.swing.border.*; -import javax.xml.stream.*; -import java.text.ParseException; - import java.awt.*; /** @@ -22,11 +16,17 @@ */ public class UIWidget extends JPanel { //Constants - protected static final float FONT_SIZE = 14.0f; + protected static final float FONT_SIZE = 14.0f; + protected static final int BORDER_TOP = 0; + protected static final int BORDER_BOT = 0; + protected static final int BORDER_LFT = 0; + protected static final int BORDER_RHT = 0; + //Standard Variables protected Context context; protected Border insets; + //Title Panel and Label protected JPanel titlePanel; protected JLabel titleLabel; @@ -38,12 +38,13 @@ public class UIWidget extends JPanel { */ public UIWidget(Context ctx, String title) { context = ctx; - insets = new EmptyBorder(0, 0, 0, 0); + //Param order: Top, Left, Bottom, Right + insets = new EmptyBorder(BORDER_TOP, BORDER_LFT, BORDER_BOT, BORDER_RHT); this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); this.setOpaque(false); Font font = context.theme.text.deriveFont(FONT_SIZE); - Dimension labelSize= new Dimension(100, 25); + Dimension labelSize= new Dimension(115, 25); titlePanel = new JPanel(); titlePanel.setOpaque(true); @@ -59,11 +60,22 @@ public UIWidget(Context ctx, String title) { add(titlePanel); } + /** + * Updates the padding insets for the widget, overriding the default. + * @param top + * @param left + * @param bottom + * @param right + */ public void setInsets (int top, int left, int bottom, int right) { insets = new EmptyBorder(top, left, bottom, right); this.setBorder(insets); } + /** + * Updates the widget title, overriding the default. + * @param title - The string to update the title to. + */ public void updateTitle(String title) { titleLabel.setText(title); } diff --git a/src/ui/WidgetPanel.java b/src/ui/WidgetPanel.java index 343bc8a..2594346 100644 --- a/src/ui/WidgetPanel.java +++ b/src/ui/WidgetPanel.java @@ -20,8 +20,11 @@ */ public class WidgetPanel extends NinePatchPanel { //Constants - protected static final int BDR_SIZE_TB = 20; - protected static final int BDR_SIZE_LR = 10; + protected static final int BORDER_TOP = 18; + protected static final int BORDER_BOT = 18; + protected static final int BORDER_LFT = 12; + protected static final int BORDER_RHT = 12; + protected Context context; protected LinkedList widgets; @@ -38,8 +41,8 @@ public WidgetPanel(Context ctx) { setOpaque(false); LayoutManager layout = new BoxLayout(this, BoxLayout.PAGE_AXIS); setLayout(layout); - setBorder(BorderFactory.createEmptyBorder(BDR_SIZE_TB, BDR_SIZE_LR, - BDR_SIZE_TB, BDR_SIZE_LR)); + setBorder(BorderFactory.createEmptyBorder(BORDER_TOP, BORDER_LFT, + BORDER_BOT, BORDER_RHT)); } /** From 6193e28a2d29f868dbfb22aea5f5af449acf982c Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Mon, 4 Jan 2021 15:34:12 -0800 Subject: [PATCH 024/125] Widget updates (Ping, State) Add ping serial message Updated Ping and State widgets and their reference implementations in the dashboard. Added scaffolding for ping data in SerialParser. --- PingWidget.java | 78 ------------------------ resources/telemetryWidget.xml | 2 +- src/Context.java | 1 + src/Dashboard.java | 8 ++- src/serial/Messages/DataMessage.java | 4 ++ src/serial/Messages/Message.java | 2 + src/serial/Serial.java | 4 ++ src/serial/SerialParser.java | 33 ++++++++-- src/ui/PingWidget.java | 91 ++++++++++++++++++++++++++++ src/ui/StateWidget.java | 2 - src/ui/TelemetryDataWidget.java | 2 - src/ui/WidgetPanel.java | 2 - 12 files changed, 136 insertions(+), 93 deletions(-) delete mode 100644 PingWidget.java create mode 100644 src/ui/PingWidget.java diff --git a/PingWidget.java b/PingWidget.java deleted file mode 100644 index aba9c9e..0000000 --- a/PingWidget.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.ui; - -import java.util.*; -import java.awt.*; -import java.swing.*; - -import com.Context; - -/** - * @author Chris Park @ Infinetix Corp. - * Date: 12-2-20 - * Description: Dashboard Widget child class used to display a units - * ping sensor data. - */ -public class PingWidget extends UIWidget { - - //Constants - protected static final int NUM_SENSORS = 3; - protected static final int MIN_SENSOR_LEVEL = 0; - protected static final int MAX_SENSOR_LEVEL = 3; - - //Ping Sensor Indicators - protected Collection sensorA; - protected Collection sensorB; - protected Collection sensorC; - protected Collection sensorD; - - /** - * Class Constructor - * @param ctx - The application context - */ - public PingWidget(Context ctx) { - super(ctx); - - sensorA.add(new BufferedImage(ctx.theme.pingGreen)); - sensorA.add(new BufferedImage(ctx.theme.pingYellow)); - sensorA.add(new BufferedImage(ctx.theme.pingRed)); - - sensorB.add(new BufferedImage(ctx.theme.pingGreen)); - sensorB.add(new BufferedImage(ctx.theme.pingYellow)); - sensorB.add(new BufferedImage(ctx.theme.pingRed)); - - sensorC.add(new BufferedImage(ctx.theme.pingGreen)); - sensorC.add(new BufferedImage(ctx.theme.pingYellow)); - sensorC.add(new BufferedImage(ctx.theme.pingRed)); - - sensorD.add(new BufferedImage(ctx.theme.pingGreen)); - sensorD.add(new BufferedImage(ctx.theme.pingYellow)); - sensorD.add(new BufferedImage(ctx.theme.pingRed)); - - - } - - public void update(int sensorIndex, int level) { - - //If not a valid sensor index, return - if(sensorIndex > (NUM_SENSORS - 1) || sensorIndex < 0) { - return; - } - - //If the level provided exceeds the allowed range then do nothing. - if(level > MAX_SENSOR_LEVEL || level < MIN_SENSOR_LEVEL) { - return; - } - - - - //cases (level) - // case 0 - //show image at index 0, hide index 1, 2 - // case 1 - //Show image at index 0, 1, hide 2 - // case 2 - //show all - - } - -} diff --git a/resources/telemetryWidget.xml b/resources/telemetryWidget.xml index a7fb956..2ea6943 100644 --- a/resources/telemetryWidget.xml +++ b/resources/telemetryWidget.xml @@ -1,4 +1,4 @@ - + diff --git a/src/Context.java b/src/Context.java index 4038762..fbe9a0a 100644 --- a/src/Context.java +++ b/src/Context.java @@ -254,6 +254,7 @@ public void setSetting(int index, float value) { public void setSettingQuiet(int index, float value) { settingList.updateSettingVal(index, value); } + public void setTelemetry(int id, float value) { telemetry.updateTelemetry(id, (double)value); } diff --git a/src/Dashboard.java b/src/Dashboard.java index 87f056e..a45b840 100644 --- a/src/Dashboard.java +++ b/src/Dashboard.java @@ -12,6 +12,7 @@ import com.ui.*; import com.ui.ArtificialHorizon.DataAxis; import com.ui.ninePatch.*; +import com.ui.PingWidget; import java.awt.*; import java.awt.Dimension; import java.awt.event.*; @@ -51,6 +52,7 @@ public class Dashboard implements Runnable { //UI Widgets private TelemetryDataWidget dataWidget; public StateWidget stateWidget; + public PingWidget pingWidget; //Logging private final Logger seriallog = Logger.getLogger("d.serial"); @@ -211,7 +213,8 @@ private JPanel createRightPanel() { //Telemetry Data Widget try { - widgetPanel.addWidget(createTelemetryWidget()); + dataWidget = createTelemetryWidget(); + widgetPanel.addWidget(dataWidget); } catch(Exception e) { iolog.severe("Failed to load telemetry widget line spec " + e); @@ -219,7 +222,8 @@ private JPanel createRightPanel() { } //State Widget - widgetPanel.addWidget(new StateWidget(context)); + stateWidget = new StateWidget(context); + widgetPanel.addWidget(stateWidget); outerPanel.add(widgetPanel); //Round Widgets diff --git a/src/serial/Messages/DataMessage.java b/src/serial/Messages/DataMessage.java index db0c34f..1d6f162 100644 --- a/src/serial/Messages/DataMessage.java +++ b/src/serial/Messages/DataMessage.java @@ -21,10 +21,12 @@ public DataMessage(int type, byte index, float data) { buildChecksum(); } + @Override public boolean needsConfirm() { return (msgType == Serial.SETTING_DATA); } + @Override public String toString() { switch(msgType) { @@ -32,6 +34,8 @@ public String toString() { return "Telemetry Message"; case Serial.SETTING_DATA: return "Settings Change"; + case Serial.SENSOR_DATA: + return "Sensor Message"; } return "Data Message"; } diff --git a/src/serial/Messages/Message.java b/src/serial/Messages/Message.java index 9f2877e..4d4b021 100644 --- a/src/serial/Messages/Message.java +++ b/src/serial/Messages/Message.java @@ -94,6 +94,8 @@ public static Message setSetting(byte index, float data) { return new DataMessage(Serial.SETTING_DATA, index, data); } + //TODO - CP - Return new DataMessage here for Sensor Data + public static Message errorString(String err) { return new StringMessage(Serial.ERROR_STRING, err); } diff --git a/src/serial/Serial.java b/src/serial/Serial.java index 5d4309b..a034dd8 100644 --- a/src/serial/Serial.java +++ b/src/serial/Serial.java @@ -15,6 +15,10 @@ public class Serial { //data type public static final int TELEMETRY_DATA = 0x0; public static final int SETTING_DATA = 0x1; + public static final int SENSOR_DATA = 0x2; + + //Sensor Types + public static final int OBJDETECT_SONIC= 0x0; //word type public static final int CONFIRMATION = 0x0; diff --git a/src/serial/SerialParser.java b/src/serial/SerialParser.java index dacb987..a2475f1 100644 --- a/src/serial/SerialParser.java +++ b/src/serial/SerialParser.java @@ -101,20 +101,41 @@ public int claim(byte data) { else return -1; } public void handle(byte[] msg) { - int subtype = Serial.getSubtype(msg[0]); + int subtype = Serial.getSubtype(msg[0]); int index = msg[1]; - int tmpdata = ( ((msg[2]&0xff)<<24)| - ((msg[3]&0xff)<<16)| - ((msg[4]&0xff)<< 8)| - ((msg[5]&0xff) ) ); - float data = Float.intBitsToFloat(tmpdata); + + int tempdata; + float data; + switch(subtype) { case Serial.TELEMETRY_DATA: + tempdata = ( ((msg[2]&0xff)<<24)| + ((msg[3]&0xff)<<16)| + ((msg[4]&0xff)<< 8)| + ((msg[5]&0xff)) ); + data = Float.intBitsToFloat(tempdata); context.setTelemetry(index, data); break; case Serial.SETTING_DATA: + tempdata = ( ((msg[2]&0xff)<<24)| + ((msg[3]&0xff)<<16)| + ((msg[4]&0xff)<< 8)| + ((msg[5]&0xff)) ); + data = Float.intBitsToFloat(tempdata); context.setSettingQuiet(index, data); break; + case Serial.SENSOR_DATA: + byte[] sensorData = new byte[2]; + int sensorVal = 0; + + //[0]MSB [1]LSB + sensorData[0] = (byte) (msg[2] & 0xff); + sensorData[1] = (byte) (msg[3] & 0xff); + for( byte val : sensorData) { + sensorVal = (sensorVal << 8) | val; + } + context.dash.pingWidget.update(index, sensorVal); + break; } } } diff --git a/src/ui/PingWidget.java b/src/ui/PingWidget.java new file mode 100644 index 0000000..42bb5d5 --- /dev/null +++ b/src/ui/PingWidget.java @@ -0,0 +1,91 @@ +package com.ui; + +import java.util.*; +//import java.util.Hashtable; +import java.awt.*; +import java.awt.image.BufferedImage; +import javax.swing.*; + +import com.Context; + +/** + * @author Chris Park @ Infinetix Corp. + * Date: 12-2-20 + * Description: Dashboard Widget child class used to display a units + * ping sensor data. + */ +public class PingWidget extends UIWidget { + + //Constants + protected static final int NUM_SENSORS = 5; + protected static final int[] WARN_LEVELS = {1000, 1600, 3000, 1600, 1000}; + protected static final int[] BLOCK_LEVELS = {1500, 2400, 4500, 2400, 1500}; + + //Ping Sensor Indicators + protected Hashtable> sensors; + + //Sensor Values + protected int[] curSensorVals; + + /** + * Class Constructor + * @param ctx - The application context + */ + public PingWidget(Context ctx) { + super(ctx, "U-Sound Ping"); + + ArrayList temp; + + sensors = new Hashtable>(); + curSensorVals = new int[5]; + + for(int i = 0; i < NUM_SENSORS; i++) { + temp = new ArrayList(); + temp.add(ctx.theme.pingRed); + temp.add(ctx.theme.pingYellow); + temp.add(ctx.theme.pingGreen); + sensors.put(i, temp); + } + } + + /** + * Updates the tracked value and visual display for the indexed sensor. + * @param index - The sensor number + * @param data - The current value from this sensor. + */ + public void update(int index, int data) { + + //Return on out of bounds + if(index > (NUM_SENSORS - 1) || index < 0) { + return; + } + + curSensorVals[index] = data; + updateSensorBar(index); + } + + /** + * Updates the visual display for a sensor based on the current + * value. + * @param index - The index of the sensor + */ + protected void updateSensorBar(int index) { + + // + + } + + /** + * Gets the current value for a sensor. + * @param index - The index of the sensor + * @return - the current value stored for that sensor + */ + public int getSensorValue(int index) { + return curSensorVals[index]; + } + + public int getNumSensors() { + return NUM_SENSORS; + } + +} diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java index 66aa15c..2de7b51 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/StateWidget.java @@ -53,8 +53,6 @@ private void initPanel() { Font font = context.theme.text.deriveFont(FONT_SIZE); Dimension labelSize = new Dimension(100, 20); -// this.setBorder(BorderFactory.createLineBorder(Color.black)); - //Configure state labels apmLabel = new JLabel("Apm:--"); apmLabel.setFont(font); diff --git a/src/ui/TelemetryDataWidget.java b/src/ui/TelemetryDataWidget.java index 3d3c559..5dfee7d 100644 --- a/src/ui/TelemetryDataWidget.java +++ b/src/ui/TelemetryDataWidget.java @@ -129,7 +129,6 @@ public TelemetryDataWidget(Context ctx, int lineWidth, float fontSize, panel = new JPanel(); panel.setBorder(insets); panel.setPreferredSize(new Dimension(115, (25 * items.size()))); -// panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); panel.setOpaque(false); Font font = ctx.theme.text.deriveFont(fontSize); @@ -149,7 +148,6 @@ public TelemetryDataWidget(Context ctx, int lineWidth, float fontSize, } this.setOpaque(false); -// this.setBackground(Color.black); this.add(panel); } diff --git a/src/ui/WidgetPanel.java b/src/ui/WidgetPanel.java index 2594346..4fec8c3 100644 --- a/src/ui/WidgetPanel.java +++ b/src/ui/WidgetPanel.java @@ -1,6 +1,5 @@ package com.ui; - import java.util.*; import com.Context; @@ -25,7 +24,6 @@ public class WidgetPanel extends NinePatchPanel { protected static final int BORDER_LFT = 12; protected static final int BORDER_RHT = 12; - protected Context context; protected LinkedList widgets; From fb5a3367b6cbd68128439f61ceb604c180e40dff Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Mon, 4 Jan 2021 16:51:25 -0800 Subject: [PATCH 025/125] Add ping widget meters Rudimentary meter graphics are now in place and constrained to a GridBagLayout --- src/Dashboard.java | 6 +++ src/ui/PingWidget.java | 84 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/Dashboard.java b/src/Dashboard.java index a45b840..e043365 100644 --- a/src/Dashboard.java +++ b/src/Dashboard.java @@ -224,6 +224,12 @@ private JPanel createRightPanel() { //State Widget stateWidget = new StateWidget(context); widgetPanel.addWidget(stateWidget); + + + //Ping Widget + pingWidget = new PingWidget(context); + widgetPanel.add(pingWidget); + outerPanel.add(widgetPanel); //Round Widgets diff --git a/src/ui/PingWidget.java b/src/ui/PingWidget.java index 42bb5d5..2f96768 100644 --- a/src/ui/PingWidget.java +++ b/src/ui/PingWidget.java @@ -27,6 +27,14 @@ public class PingWidget extends UIWidget { //Sensor Values protected int[] curSensorVals; + //Sensor Image Panel + protected JPanel sensorOuterPanel; + protected JPanel sensorPanelA; + protected JPanel sensorPanelB; + protected JPanel sensorPanelC; + protected JPanel sensorPanelD; + protected JPanel sensorPanelE; + /** * Class Constructor * @param ctx - The application context @@ -46,6 +54,60 @@ public PingWidget(Context ctx) { temp.add(ctx.theme.pingGreen); sensors.put(i, temp); } + + sensorOuterPanel = new JPanel(); + sensorOuterPanel.setLayout(new GridBagLayout()); + + GridBagConstraints constraints = new GridBagConstraints(); + constraints.insets = new Insets(2, 2, 2, 2); + + sensorPanelA = new JPanel(); + sensorPanelA.setLayout(new BoxLayout(sensorPanelA, BoxLayout.Y_AXIS)); + sensorPanelA.add(new JLabel(new ImageIcon(sensors.get(0).get(0)))); + sensorPanelA.add(new JLabel(new ImageIcon(sensors.get(0).get(1)))); + sensorPanelA.add(new JLabel(new ImageIcon(sensors.get(0).get(2)))); + constraints.gridx = 0; + constraints.gridy = 0; + sensorOuterPanel.add(sensorPanelA, constraints); + + sensorPanelB = new JPanel(); + sensorPanelB.setLayout(new BoxLayout(sensorPanelB, BoxLayout.Y_AXIS)); + sensorPanelB.add(new JLabel(new ImageIcon(sensors.get(1).get(0)))); + sensorPanelB.add(new JLabel(new ImageIcon(sensors.get(1).get(1)))); + sensorPanelB.add(new JLabel(new ImageIcon(sensors.get(1).get(2)))); + constraints.gridx = 1; + constraints.gridy = 0; + sensorOuterPanel.add(sensorPanelB, constraints); + + sensorPanelC = new JPanel(); + sensorPanelC.setLayout(new BoxLayout(sensorPanelC, BoxLayout.Y_AXIS)); + sensorPanelC.add(new JLabel(new ImageIcon(sensors.get(2).get(0)))); + sensorPanelC.add(new JLabel(new ImageIcon(sensors.get(2).get(1)))); + sensorPanelC.add(new JLabel(new ImageIcon(sensors.get(2).get(2)))); + constraints.gridx = 2; + constraints.gridy = 0; + sensorOuterPanel.add(sensorPanelC, constraints); + + sensorPanelD = new JPanel(); + sensorPanelD.setLayout(new BoxLayout(sensorPanelD, BoxLayout.Y_AXIS)); + sensorPanelD.add(new JLabel(new ImageIcon(sensors.get(3).get(0)))); + sensorPanelD.add(new JLabel(new ImageIcon(sensors.get(3).get(1)))); + sensorPanelD.add(new JLabel(new ImageIcon(sensors.get(3).get(2)))); + constraints.gridx = 3; + constraints.gridy = 0; + sensorOuterPanel.add(sensorPanelD, constraints); + + sensorPanelE = new JPanel(); + sensorPanelE.setLayout(new BoxLayout(sensorPanelE, BoxLayout.Y_AXIS)); + sensorPanelE.add(new JLabel(new ImageIcon(sensors.get(4).get(0)))); + sensorPanelE.add(new JLabel(new ImageIcon(sensors.get(4).get(1)))); + sensorPanelE.add(new JLabel(new ImageIcon(sensors.get(4).get(2)))); + constraints.gridx = 4; + constraints.gridy = 0; + sensorOuterPanel.add(sensorPanelE, constraints); + + + this.add(sensorOuterPanel); } /** @@ -71,7 +133,27 @@ public void update(int index, int data) { */ protected void updateSensorBar(int index) { - // + //method A + //hide all + //if < warn /2 and greater than 0 + //show green + + //if >= warn level + //show yellow + + //if >= block level + //show red + + + //method B + //if less than warn level / 2 hide green + //else show green + + //if >= warn level show yellow + //else hide yellow + + //if >= block, show red + //else hide red } From 3508ba1772daf28d987c5bd122859f7397e792ea Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Tue, 5 Jan 2021 10:16:50 -0800 Subject: [PATCH 026/125] Update Ping Sensor widget meter graphics --- resources/images/pingGreen.png | Bin 120 -> 230 bytes resources/images/pingRed.png | Bin 120 -> 259 bytes resources/images/pingYellow.png | Bin 121 -> 268 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/images/pingGreen.png b/resources/images/pingGreen.png index 6167149a117c6c15d7d3ad38bef91f3ce99162d2..75b555d784838d1ecd74c24c6fd98c5145735874 100644 GIT binary patch delta 186 zcmb<;#yCMzij9GRVM}P;Ga$v5CYGe8D3oWGWGJ|M`Ua%vrLqHMygXeTLp07Or#LkH=b6IjW^wSq zfe-h;D5fr(JFQ*f)CnLjzSK;adACw3-;dHLVO6n+O;=;`X`vd$@?2>{HfLLC4A delta 101 zcmaFHSTR8|o{52h!P@-gMj*vm;1OBOz`!jG!i)^F=12eq*-JcqUD==TFbivNu35V4 z4^T+l)5S4F<9za!n};3D5|R=muG*}Uv}a&&Y+(44sBY&DRKnot>gTe~DWM4f8QvSK diff --git a/resources/images/pingRed.png b/resources/images/pingRed.png index 3a20ab8f2a5ec1e37a6653544ba3dfbd31daac2a..45565176eb35e5d5b3a75ddc2e35ad28f155bf0a 100644 GIT binary patch delta 215 zcmb7Fi*AsXl3URo&F;K0Lhko)t; zjwub5?XC$QL>yX08kYA;cvUpE&vXxToDjpf+AoH;p=PR=Lb!0lm!#t+?l!w>SFclJ zx?;N3_{$`x1%1qS_g(x@XfNR((_1rj*D^B}-S3^NJ8NveG51`OefUmi@nVo`JYD@< J);T3K0RT5aPB;Jn delta 101 zcmZo>s+b@d&&0sMU~T?#Baq@O@Q5sCVBi)8VMc~ob0mO*>?NMQuIx{En1wYo9-LXV z2q+}(>EaloaXxv^IjutudI?Dj7<#hgBxf;(iZimj&%1OOsD#1O)z4*}Q$iB}^Q{}r diff --git a/resources/images/pingYellow.png b/resources/images/pingYellow.png index 7025fc1b5c661eb1ac0a1e0392ab3ec36c299984..2dd58823d6028c0e590f85f79b964d9734d45ef8 100644 GIT binary patch delta 224 zcmb>IVVWQ*#m2zEuqCwa8IWR2@^*It(m?Rjd3XIpMY;N?jI08>R#lI0+5rV+GeaUu zobz*YQ}ar|s+@~b6H8K46v{J8G8EiBeFIYTQrUqr1)eUBAsXkOUQ*;c6d>Yq(RxWH z)2i7DjjRdT@rTlOiJ3VEMc-yEk@gQwV7t0()_>XXUaPDpJPcQQmno!kEVyZ~e!UQX zESt5n4bz@8w%cF-EMHrjr=gzprSkn|zKR$8j+V@&i>L3@Z+h3yneMT*|1ayzv&zYH SzdthqIm^@4&t;ucLK6VkCQ#%6 delta 102 zcmeBSs+=I1z{J47U~T?#Baq@O@Q5sCVBi)8VMc~ob0mO*>?NMQuIx{En1wafGc($X zfI Date: Thu, 7 Jan 2021 08:51:20 -0800 Subject: [PATCH 027/125] PingWidget Sensor Meter Update Continued work on visual representation of PingWidget meters. --- src/ui/PingWidget.java | 94 +++++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 34 deletions(-) diff --git a/src/ui/PingWidget.java b/src/ui/PingWidget.java index 2f96768..b24bda6 100644 --- a/src/ui/PingWidget.java +++ b/src/ui/PingWidget.java @@ -1,7 +1,6 @@ package com.ui; import java.util.*; -//import java.util.Hashtable; import java.awt.*; import java.awt.image.BufferedImage; import javax.swing.*; @@ -22,7 +21,8 @@ public class PingWidget extends UIWidget { protected static final int[] BLOCK_LEVELS = {1500, 2400, 4500, 2400, 1500}; //Ping Sensor Indicators - protected Hashtable> sensors; + protected Hashtable> sensors; + //Sensor Values protected int[] curSensorVals; @@ -37,21 +37,24 @@ public class PingWidget extends UIWidget { /** * Class Constructor + * Generates the initial layout for the widget, loading graphics and placing + * them within a defined JPanel layout * @param ctx - The application context */ public PingWidget(Context ctx) { super(ctx, "U-Sound Ping"); - ArrayList temp; + ArrayList temp; + Component widthSpacer = Box.createHorizontalStrut(20); - sensors = new Hashtable>(); + sensors = new Hashtable>(); curSensorVals = new int[5]; for(int i = 0; i < NUM_SENSORS; i++) { - temp = new ArrayList(); - temp.add(ctx.theme.pingRed); - temp.add(ctx.theme.pingYellow); - temp.add(ctx.theme.pingGreen); + temp = new ArrayList(); + temp.add(new JLabel(new ImageIcon(ctx.theme.pingRed))); + temp.add(new JLabel(new ImageIcon(ctx.theme.pingYellow))); + temp.add(new JLabel(new ImageIcon(ctx.theme.pingGreen))); sensors.put(i, temp); } @@ -59,55 +62,65 @@ public PingWidget(Context ctx) { sensorOuterPanel.setLayout(new GridBagLayout()); GridBagConstraints constraints = new GridBagConstraints(); - constraints.insets = new Insets(2, 2, 2, 2); + constraints.insets = new Insets(2, 0, 2, 0); + constraints.anchor = GridBagConstraints.CENTER; + sensorPanelA = new JPanel(); - sensorPanelA.setLayout(new BoxLayout(sensorPanelA, BoxLayout.Y_AXIS)); - sensorPanelA.add(new JLabel(new ImageIcon(sensors.get(0).get(0)))); - sensorPanelA.add(new JLabel(new ImageIcon(sensors.get(0).get(1)))); - sensorPanelA.add(new JLabel(new ImageIcon(sensors.get(0).get(2)))); + sensorPanelA.setLayout(new BoxLayout(sensorPanelA, BoxLayout.Y_AXIS)); + sensorPanelA.add(widthSpacer); + sensorPanelA.add(sensors.get(0).get(0)); + sensorPanelA.add(sensors.get(0).get(1)); + sensorPanelA.add(sensors.get(0).get(2)); constraints.gridx = 0; constraints.gridy = 0; sensorOuterPanel.add(sensorPanelA, constraints); - + sensorPanelB = new JPanel(); - sensorPanelB.setLayout(new BoxLayout(sensorPanelB, BoxLayout.Y_AXIS)); - sensorPanelB.add(new JLabel(new ImageIcon(sensors.get(1).get(0)))); - sensorPanelB.add(new JLabel(new ImageIcon(sensors.get(1).get(1)))); - sensorPanelB.add(new JLabel(new ImageIcon(sensors.get(1).get(2)))); + sensorPanelB.setLayout(new BoxLayout(sensorPanelB, BoxLayout.Y_AXIS)); + sensorPanelB.add(widthSpacer); + sensorPanelB.add(sensors.get(1).get(0)); + sensorPanelB.add(sensors.get(1).get(1)); + sensorPanelB.add(sensors.get(1).get(2)); constraints.gridx = 1; constraints.gridy = 0; sensorOuterPanel.add(sensorPanelB, constraints); sensorPanelC = new JPanel(); - sensorPanelC.setLayout(new BoxLayout(sensorPanelC, BoxLayout.Y_AXIS)); - sensorPanelC.add(new JLabel(new ImageIcon(sensors.get(2).get(0)))); - sensorPanelC.add(new JLabel(new ImageIcon(sensors.get(2).get(1)))); - sensorPanelC.add(new JLabel(new ImageIcon(sensors.get(2).get(2)))); + sensorPanelC.setLayout(new BoxLayout(sensorPanelC, BoxLayout.Y_AXIS)); + sensorPanelC.add(widthSpacer); + sensorPanelC.add(sensors.get(2).get(0)); + sensorPanelC.add(sensors.get(2).get(1)); + sensorPanelC.add(sensors.get(2).get(2)); constraints.gridx = 2; constraints.gridy = 0; sensorOuterPanel.add(sensorPanelC, constraints); - + sensorPanelD = new JPanel(); - sensorPanelD.setLayout(new BoxLayout(sensorPanelD, BoxLayout.Y_AXIS)); - sensorPanelD.add(new JLabel(new ImageIcon(sensors.get(3).get(0)))); - sensorPanelD.add(new JLabel(new ImageIcon(sensors.get(3).get(1)))); - sensorPanelD.add(new JLabel(new ImageIcon(sensors.get(3).get(2)))); + sensorPanelD.setLayout(new BoxLayout(sensorPanelD, BoxLayout.Y_AXIS)); + sensorPanelD.add(widthSpacer); + sensorPanelD.add(sensors.get(3).get(0)); + sensorPanelD.add(sensors.get(3).get(1)); + sensorPanelD.add(sensors.get(3).get(2)); constraints.gridx = 3; constraints.gridy = 0; sensorOuterPanel.add(sensorPanelD, constraints); - + sensorPanelE = new JPanel(); - sensorPanelE.setLayout(new BoxLayout(sensorPanelE, BoxLayout.Y_AXIS)); - sensorPanelE.add(new JLabel(new ImageIcon(sensors.get(4).get(0)))); - sensorPanelE.add(new JLabel(new ImageIcon(sensors.get(4).get(1)))); - sensorPanelE.add(new JLabel(new ImageIcon(sensors.get(4).get(2)))); + sensorPanelE.setLayout(new BoxLayout(sensorPanelE, BoxLayout.Y_AXIS)); + sensorPanelE.add(widthSpacer); + sensorPanelE.add(sensors.get(4).get(0)); + sensorPanelE.add(sensors.get(4).get(1)); + sensorPanelE.add(sensors.get(4).get(2)); constraints.gridx = 4; constraints.gridy = 0; sensorOuterPanel.add(sensorPanelE, constraints); - this.add(sensorOuterPanel); + + + //Test show/hide +// updateSensorBar(3); } /** @@ -132,6 +145,16 @@ public void update(int index, int data) { * @param index - The index of the sensor */ protected void updateSensorBar(int index) { + + if(index >= NUM_SENSORS) { + System.err.println("Error - Invalid PingSensor range access attempt."); + return; + } + + ArrayList temp = sensors.get(index); + for (JLabel meter : temp) { + meter.setVisible(false); + } //method A //hide all @@ -166,8 +189,11 @@ public int getSensorValue(int index) { return curSensorVals[index]; } + /** + * Gets the number of sensors traked by this widget. + * @return - the number of sensors + */ public int getNumSensors() { return NUM_SENSORS; } - } From 70e958a5d6a556905f9517a194d54e44ada929ef Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Mon, 11 Jan 2021 15:01:21 -0800 Subject: [PATCH 028/125] Connected Ping Meters to sensor data Ping sensors now update based on the relationship between incoming ultrasound data and pre-calibrated threshold values. --- resources/images/pingSpacer.png | Bin 0 -> 172 bytes resources/resources_en.properties | 3 +- src/serial/Messages/Message.java | 4 +- src/serial/SerialParser.java | 27 +++- src/ui/PingWidget.java | 258 +++++++++++++++++------------- src/ui/StateWidget.java | 2 +- src/ui/Theme.java | 2 + 7 files changed, 177 insertions(+), 119 deletions(-) create mode 100644 resources/images/pingSpacer.png diff --git a/resources/images/pingSpacer.png b/resources/images/pingSpacer.png new file mode 100644 index 0000000000000000000000000000000000000000..d25baed7b3e042a12abaad5e07bedcd34465fe7b GIT binary patch literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^B0$W=!3HF^gw{O+Qfx`y?k)`fL2$v|<&%LToCO|{ z#S9GG!XV7ZFl&wkP>{XE)7O>#DI>R_JnuPUQx~9+Y-UJAiF1B#Zfaf$kjuc}T$Gwv zlA5AWo>`Ki;O^-gkfN8$4ip#gba4#PIG>y#!MZp>M1YNfS&M> sensors; + protected static final int[] WARN_LEVELS = {1500, 2400, 4500, 2400, 1500}; + protected static final int[] BLOCK_LEVELS = {1000, 1600, 3000, 1600, 1000}; + //Ping Sensor Meters + protected HashMap> sensorMeters; //Sensor Values protected int[] curSensorVals; //Sensor Image Panel protected JPanel sensorOuterPanel; - protected JPanel sensorPanelA; - protected JPanel sensorPanelB; - protected JPanel sensorPanelC; - protected JPanel sensorPanelD; - protected JPanel sensorPanelE; - + /** * Class Constructor * Generates the initial layout for the widget, loading graphics and placing @@ -44,83 +38,81 @@ public class PingWidget extends UIWidget { public PingWidget(Context ctx) { super(ctx, "U-Sound Ping"); - ArrayList temp; - Component widthSpacer = Box.createHorizontalStrut(20); - - sensors = new Hashtable>(); curSensorVals = new int[5]; - - for(int i = 0; i < NUM_SENSORS; i++) { - temp = new ArrayList(); - temp.add(new JLabel(new ImageIcon(ctx.theme.pingRed))); - temp.add(new JLabel(new ImageIcon(ctx.theme.pingYellow))); - temp.add(new JLabel(new ImageIcon(ctx.theme.pingGreen))); - sensors.put(i, temp); - } sensorOuterPanel = new JPanel(); + sensorOuterPanel.setMinimumSize(new Dimension(100, 60)); sensorOuterPanel.setLayout(new GridBagLayout()); GridBagConstraints constraints = new GridBagConstraints(); constraints.insets = new Insets(2, 0, 2, 0); constraints.anchor = GridBagConstraints.CENTER; - - - sensorPanelA = new JPanel(); - sensorPanelA.setLayout(new BoxLayout(sensorPanelA, BoxLayout.Y_AXIS)); - sensorPanelA.add(widthSpacer); - sensorPanelA.add(sensors.get(0).get(0)); - sensorPanelA.add(sensors.get(0).get(1)); - sensorPanelA.add(sensors.get(0).get(2)); + + sensorMeters = new HashMap>(); + for(int i = 0; i < NUM_SENSORS; i++) { + sensorMeters.put(i, buildMeterSet()); + } + + //Zero out meters on init constraints.gridx = 0; constraints.gridy = 0; - sensorOuterPanel.add(sensorPanelA, constraints); - - sensorPanelB = new JPanel(); - sensorPanelB.setLayout(new BoxLayout(sensorPanelB, BoxLayout.Y_AXIS)); - sensorPanelB.add(widthSpacer); - sensorPanelB.add(sensors.get(1).get(0)); - sensorPanelB.add(sensors.get(1).get(1)); - sensorPanelB.add(sensors.get(1).get(2)); + sensorOuterPanel.add(sensorMeters.get(0).get(0), constraints); constraints.gridx = 1; - constraints.gridy = 0; - sensorOuterPanel.add(sensorPanelB, constraints); - - sensorPanelC = new JPanel(); - sensorPanelC.setLayout(new BoxLayout(sensorPanelC, BoxLayout.Y_AXIS)); - sensorPanelC.add(widthSpacer); - sensorPanelC.add(sensors.get(2).get(0)); - sensorPanelC.add(sensors.get(2).get(1)); - sensorPanelC.add(sensors.get(2).get(2)); + sensorOuterPanel.add(sensorMeters.get(1).get(0), constraints); constraints.gridx = 2; - constraints.gridy = 0; - sensorOuterPanel.add(sensorPanelC, constraints); - - sensorPanelD = new JPanel(); - sensorPanelD.setLayout(new BoxLayout(sensorPanelD, BoxLayout.Y_AXIS)); - sensorPanelD.add(widthSpacer); - sensorPanelD.add(sensors.get(3).get(0)); - sensorPanelD.add(sensors.get(3).get(1)); - sensorPanelD.add(sensors.get(3).get(2)); + sensorOuterPanel.add(sensorMeters.get(2).get(0), constraints); constraints.gridx = 3; - constraints.gridy = 0; - sensorOuterPanel.add(sensorPanelD, constraints); - - sensorPanelE = new JPanel(); - sensorPanelE.setLayout(new BoxLayout(sensorPanelE, BoxLayout.Y_AXIS)); - sensorPanelE.add(widthSpacer); - sensorPanelE.add(sensors.get(4).get(0)); - sensorPanelE.add(sensors.get(4).get(1)); - sensorPanelE.add(sensors.get(4).get(2)); + sensorOuterPanel.add(sensorMeters.get(3).get(0), constraints); constraints.gridx = 4; - constraints.gridy = 0; - sensorOuterPanel.add(sensorPanelE, constraints); + sensorOuterPanel.add(sensorMeters.get(4).get(0), constraints); this.add(sensorOuterPanel); - - - //Test show/hide -// updateSensorBar(3); + } + + /** + * Constructs a complete set of meter graphics to be used for a + * ping sensor. + * @return - The JPanel meter set + */ + public ArrayList buildMeterSet() { + ArrayList meterSet; + JPanel panel; + + meterSet = new ArrayList(); + + //Level: None + panel = new JPanel(); + panel.add(new JLabel(new ImageIcon(context.theme.pingSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.pingSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.pingSpacer))); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + meterSet.add(panel); + + //Level: Low + panel = new JPanel(); + panel.add(new JLabel(new ImageIcon(context.theme.pingSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.pingSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.pingGreen))); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + meterSet.add(panel); + + //Level: Medium + panel = new JPanel(); + panel.add(new JLabel(new ImageIcon(context.theme.pingSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.pingYellow))); + panel.add(new JLabel(new ImageIcon(context.theme.pingGreen))); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + meterSet.add(panel); + + //Level: High + panel = new JPanel(); + panel.add(new JLabel(new ImageIcon(context.theme.pingRed))); + panel.add(new JLabel(new ImageIcon(context.theme.pingYellow))); + panel.add(new JLabel(new ImageIcon(context.theme.pingGreen))); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + meterSet.add(panel); + + return meterSet; } /** @@ -132,52 +124,102 @@ public void update(int index, int data) { //Return on out of bounds if(index > (NUM_SENSORS - 1) || index < 0) { + System.err.println("Sensor index out of bounds"); return; } +// System.err.println("Sensor Data - [Index: " + index + ", Data: " + data + "]"); + curSensorVals[index] = data; - updateSensorBar(index); + updateMeters(); } /** - * Updates the visual display for a sensor based on the current - * value. - * @param index - The index of the sensor + * Updates the visual display for all sensor meters based on the + * currently detected values as they compare against pre calibrated + * warning levels. + * @param index = The index of the sensor to be updated */ - protected void updateSensorBar(int index) { - - if(index >= NUM_SENSORS) { - System.err.println("Error - Invalid PingSensor range access attempt."); - return; - } - - ArrayList temp = sensors.get(index); - for (JLabel meter : temp) { - meter.setVisible(false); - } - - //method A - //hide all - //if < warn /2 and greater than 0 - //show green - - //if >= warn level - //show yellow - - //if >= block level - //show red - - - //method B - //if less than warn level / 2 hide green - //else show green - - //if >= warn level show yellow - //else hide yellow + protected void updateMeters() { + GridBagConstraints constraints = new GridBagConstraints(); - //if >= block, show red - //else hide red + sensorOuterPanel.removeAll(); + constraints.gridy = 0; + constraints.gridx = 0; + //Warning High + if(curSensorVals[0] <= 1500) { + sensorOuterPanel.add(sensorMeters.get(0).get(3), constraints); + } //Warning Medium + else if (curSensorVals[0] <= 3000) { + sensorOuterPanel.add(sensorMeters.get(0).get(2), constraints); + } //Warning Low + else if (curSensorVals[0] <= 4500) { + sensorOuterPanel.add(sensorMeters.get(0).get(1), constraints); + } //Warning None + else { + sensorOuterPanel.add(sensorMeters.get(0).get(0), constraints); + } + + constraints.gridx = 1; + //Warning High + if(curSensorVals[1] <= 2400) { + sensorOuterPanel.add(sensorMeters.get(1).get(3), constraints); + } //Warning Medium + else if (curSensorVals[1] <= 4800) { + sensorOuterPanel.add(sensorMeters.get(1).get(2), constraints); + } //Warning Low + else if (curSensorVals[1] <= 9600) { + sensorOuterPanel.add(sensorMeters.get(1).get(1), constraints); + } //Warning None + else { + sensorOuterPanel.add(sensorMeters.get(1).get(0), constraints); + } + + constraints.gridx = 2; + //Warning High + if(curSensorVals[2] <= 4500) { + sensorOuterPanel.add(sensorMeters.get(2).get(3), constraints); + } //Warning Medium + else if (curSensorVals[2] <= 9000) { + sensorOuterPanel.add(sensorMeters.get(2).get(2), constraints); + } //Warning Low + else if (curSensorVals[2] <= 18000) { + sensorOuterPanel.add(sensorMeters.get(2).get(1), constraints); + } //Warning None + else { + sensorOuterPanel.add(sensorMeters.get(2).get(0), constraints); + } + + constraints.gridx = 3; + //Warning High + if(curSensorVals[3] <= 2400) { + sensorOuterPanel.add(sensorMeters.get(3).get(3), constraints); + } //Warning Medium + else if (curSensorVals[3] <= 4800) { + sensorOuterPanel.add(sensorMeters.get(3).get(2), constraints); + } //Warning Low + else if (curSensorVals[3] <= 9600) { + sensorOuterPanel.add(sensorMeters.get(3).get(1), constraints); + } //Warning None + else { + sensorOuterPanel.add(sensorMeters.get(3).get(0), constraints); + } + + constraints.gridx = 4; + //Warning High + if(curSensorVals[4] <= 1500) { + sensorOuterPanel.add(sensorMeters.get(4).get(3), constraints); + } //Warning Medium + else if (curSensorVals[4] <= 3000) { + sensorOuterPanel.add(sensorMeters.get(4).get(2), constraints); + } //Warning Low + else if (curSensorVals[4] <= 4500) { + sensorOuterPanel.add(sensorMeters.get(4).get(1), constraints); + } //Warning None + else { + sensorOuterPanel.add(sensorMeters.get(4).get(0), constraints); + } } /** diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java index 2de7b51..fe29d99 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/StateWidget.java @@ -160,7 +160,7 @@ private void setAPMState(byte substate) { String fmt; String fmtStr = "Apm:%s"; - System.err.println("StateWidget - Updating APM State"); +// System.err.println("StateWidget - Updating APM State"); switch(substate) { case Serial.APM_STATE_INIT: fmt = String.format(fmtStr, "Init"); diff --git a/src/ui/Theme.java b/src/ui/Theme.java index 490d5c0..1c9cfd2 100644 --- a/src/ui/Theme.java +++ b/src/ui/Theme.java @@ -33,6 +33,7 @@ public class Theme { public BufferedImage pingRed; public BufferedImage pingYellow; public BufferedImage pingGreen; + public BufferedImage pingSpacer; public Font number; public Font text; public Font alertFont; @@ -66,6 +67,7 @@ public Theme(Context ctx) { pingRed = ImageIO.read(new File(img+ctx.getResource("ping_red"))); pingYellow = ImageIO.read(new File(img+ctx.getResource("ping_yellow"))); pingGreen = ImageIO.read(new File(img+ctx.getResource("ping_green"))); + pingSpacer = ImageIO.read(new File(img+ctx.getResource("ping_spacer"))); buttonPatch = NinePatch.loadFrom(Paths.get(np+ctx.getResource("button"))); buttonHover = NinePatch.loadFrom(Paths.get(np+ctx.getResource("button_hovered"))); buttonPress = NinePatch.loadFrom(Paths.get(np+ctx.getResource("button_pressed"))); From ea536503b682220354b8b086f248db537987252c Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 14 Jan 2021 09:29:50 -0800 Subject: [PATCH 029/125] PingWidget Doc/Comment updates Improve descriptions of functionality post modification and clean up comments --- src/ui/PingWidget.java | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/ui/PingWidget.java b/src/ui/PingWidget.java index b1bc80d..f56bcbb 100644 --- a/src/ui/PingWidget.java +++ b/src/ui/PingWidget.java @@ -32,7 +32,7 @@ public class PingWidget extends UIWidget { /** * Class Constructor * Generates the initial layout for the widget, loading graphics and placing - * them within a defined JPanel layout + * them within a defined GridBagLayout. * @param ctx - The application context */ public PingWidget(Context ctx) { @@ -72,7 +72,7 @@ public PingWidget(Context ctx) { /** * Constructs a complete set of meter graphics to be used for a * ping sensor. - * @return - The JPanel meter set + * @return - The JPanel containing the meter set */ public ArrayList buildMeterSet() { ArrayList meterSet; @@ -116,7 +116,8 @@ public ArrayList buildMeterSet() { } /** - * Updates the tracked value and visual display for the indexed sensor. + * Updates a tracked sensor value and calls for a new visual meter display for + * all sensors. * @param index - The sensor number * @param data - The current value from this sensor. */ @@ -135,8 +136,8 @@ public void update(int index, int data) { } /** - * Updates the visual display for all sensor meters based on the - * currently detected values as they compare against pre calibrated + * Updates the visual meter display for all sensor meters based on the + * currently detected values as they compare against pre-calibrated * warning levels. * @param index = The index of the sensor to be updated */ @@ -145,6 +146,7 @@ protected void updateMeters() { sensorOuterPanel.removeAll(); + //Meter One constraints.gridy = 0; constraints.gridx = 0; //Warning High @@ -161,6 +163,7 @@ else if (curSensorVals[0] <= 4500) { sensorOuterPanel.add(sensorMeters.get(0).get(0), constraints); } + //Meter Two constraints.gridx = 1; //Warning High if(curSensorVals[1] <= 2400) { @@ -176,6 +179,7 @@ else if (curSensorVals[1] <= 9600) { sensorOuterPanel.add(sensorMeters.get(1).get(0), constraints); } + //Meter Three constraints.gridx = 2; //Warning High if(curSensorVals[2] <= 4500) { @@ -191,6 +195,7 @@ else if (curSensorVals[2] <= 18000) { sensorOuterPanel.add(sensorMeters.get(2).get(0), constraints); } + //Meter Four constraints.gridx = 3; //Warning High if(curSensorVals[3] <= 2400) { @@ -206,6 +211,7 @@ else if (curSensorVals[3] <= 9600) { sensorOuterPanel.add(sensorMeters.get(3).get(0), constraints); } + //Meter Five constraints.gridx = 4; //Warning High if(curSensorVals[4] <= 1500) { @@ -223,7 +229,7 @@ else if (curSensorVals[4] <= 4500) { } /** - * Gets the current value for a sensor. + * Gets the current value for a sensor by index. * @param index - The index of the sensor * @return - the current value stored for that sensor */ @@ -232,7 +238,7 @@ public int getSensorValue(int index) { } /** - * Gets the number of sensors traked by this widget. + * Gets the number of sensors tracked by this widget. * @return - the number of sensors */ public int getNumSensors() { From 19d3c74661b95b02268061821d6a7829d5d40f5d Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Mon, 18 Jan 2021 13:29:14 -0800 Subject: [PATCH 030/125] Added timing delay to meter updates This update delay is configurable using the UPDATE_DELAY_MS variable. This addition smooths out draw flicker that would otherwise be present when updating the values faster than the GUI can draw them. --- src/ui/PingWidget.java | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/ui/PingWidget.java b/src/ui/PingWidget.java index f56bcbb..067baaa 100644 --- a/src/ui/PingWidget.java +++ b/src/ui/PingWidget.java @@ -2,6 +2,8 @@ import java.util.*; import java.awt.*; +import java.awt.event.ActionListener; +import java.awt.event.ActionEvent; import java.awt.image.BufferedImage; import javax.swing.*; @@ -16,9 +18,10 @@ public class PingWidget extends UIWidget { //Constants - protected static final int NUM_SENSORS = 5; - protected static final int[] WARN_LEVELS = {1500, 2400, 4500, 2400, 1500}; - protected static final int[] BLOCK_LEVELS = {1000, 1600, 3000, 1600, 1000}; + protected static final int NUM_SENSORS = 5; + protected static final int UPDATE_DELAY_MS = 500; + protected static final int[] WARN_LEVELS = {1500, 2400, 4500, 2400, 1500}; + protected static final int[] BLOCK_LEVELS = {1000, 1600, 3000, 1600, 1000}; //Ping Sensor Meters protected HashMap> sensorMeters; @@ -26,6 +29,9 @@ public class PingWidget extends UIWidget { //Sensor Values protected int[] curSensorVals; + //Meter Update Frequency Timer + protected javax.swing.Timer meterUpdateTimer; + //Sensor Image Panel protected JPanel sensorOuterPanel; @@ -67,6 +73,9 @@ public PingWidget(Context ctx) { sensorOuterPanel.add(sensorMeters.get(4).get(0), constraints); this.add(sensorOuterPanel); + + meterUpdateTimer = new javax.swing.Timer(UPDATE_DELAY_MS, meterUpdateAction); + meterUpdateTimer.start(); } /** @@ -132,9 +141,19 @@ public void update(int index, int data) { // System.err.println("Sensor Data - [Index: " + index + ", Data: " + data + "]"); curSensorVals[index] = data; - updateMeters(); } + /** + * Timer event responsible for controlling the rate at which the sensor + * meters update. see constant value UPDATE_DELAY_MS for the currently + * configured timer delay. + */ + ActionListener meterUpdateAction = new ActionListener() { + public void actionPerformed(ActionEvent event) { + updateMeters(); + } + }; + /** * Updates the visual meter display for all sensor meters based on the * currently detected values as they compare against pre-calibrated @@ -143,7 +162,7 @@ public void update(int index, int data) { */ protected void updateMeters() { GridBagConstraints constraints = new GridBagConstraints(); - + sensorOuterPanel.removeAll(); //Meter One From bf1b64435f0eb0eec3ac53bcf47d7eced01d1c97 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Mon, 18 Jan 2021 16:13:54 -0800 Subject: [PATCH 031/125] Fixed Dial Widget centering bug Widgets were expanding into open space left by previous container dimensions instead of honoring their preferred sizes. Adding a maximum size constraint fixed this issue. --- src/Dashboard.java | 1 + src/ui/AngleWidget.java | 3 +++ src/ui/TelemetryDataWidget.java | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Dashboard.java b/src/Dashboard.java index e043365..a50dfec 100644 --- a/src/Dashboard.java +++ b/src/Dashboard.java @@ -210,6 +210,7 @@ private JPanel createRightPanel() { outerPanel.setOpaque(false); outerPanel.setLayout(new BoxLayout(outerPanel, BoxLayout.PAGE_AXIS)); widgetPanel = new WidgetPanel(context); + widgetPanel.setAlignmentX(Component.CENTER_ALIGNMENT); //Telemetry Data Widget try { diff --git a/src/ui/AngleWidget.java b/src/ui/AngleWidget.java index 8d10bef..825818c 100644 --- a/src/ui/AngleWidget.java +++ b/src/ui/AngleWidget.java @@ -43,7 +43,10 @@ private AngleWidget( overlay = foregroundImage; this.setPreferredSize(new Dimension(background.getWidth(), background.getHeight()) ); + this.setMaximumSize(new Dimension(background.getWidth(), + background.getHeight())); setOpaque(false); + } @Override diff --git a/src/ui/TelemetryDataWidget.java b/src/ui/TelemetryDataWidget.java index 5dfee7d..5df2d12 100644 --- a/src/ui/TelemetryDataWidget.java +++ b/src/ui/TelemetryDataWidget.java @@ -16,7 +16,7 @@ import java.awt.*; /** - * Adapted from source originally written by Brett Menzies + * Adapted from source originally written by Brett Menzies (See deprecated TelemetryWidget class) * @author Chris Park @ Infinetix Corp. * Date: 11-25-2020 * Description: Widget child class used for displaying Telemetry data. From 33786d96b8829775a5335a38dcb8f3030ca77662 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Tue, 19 Jan 2021 15:57:57 -0800 Subject: [PATCH 032/125] Telemetry and Config UI Updates Telemtry widget line items now span the container width correctly. Locked configuration window so that it can't be resized. --- .gpx | 4 ++++ src/ui/PingWidget.java | 2 +- src/ui/StateWidget.java | 2 +- src/ui/SystemConfigWindow.java | 3 ++- src/ui/TelemetryDataWidget.java | 3 +-- src/ui/UIWidget.java | 14 ++++++++------ src/ui/WidgetPanel.java | 9 +++++---- 7 files changed, 22 insertions(+), 15 deletions(-) create mode 100644 .gpx diff --git a/.gpx b/.gpx new file mode 100644 index 0000000..b486df2 --- /dev/null +++ b/.gpx @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/ui/PingWidget.java b/src/ui/PingWidget.java index 067baaa..1ca1f16 100644 --- a/src/ui/PingWidget.java +++ b/src/ui/PingWidget.java @@ -42,7 +42,7 @@ public class PingWidget extends UIWidget { * @param ctx - The application context */ public PingWidget(Context ctx) { - super(ctx, "U-Sound Ping"); + super(ctx, "Utrasound"); curSensorVals = new int[5]; diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java index fe29d99..97ea133 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/StateWidget.java @@ -27,7 +27,7 @@ public class StateWidget extends UIWidget { //State readouts private JPanel apmPanel; - private JLabel apmLabel; + private JLabel apmLabel; private JPanel drivePanel; private JLabel driveLabel; private JPanel autoPanel; diff --git a/src/ui/SystemConfigWindow.java b/src/ui/SystemConfigWindow.java index b6f026a..c856f1a 100644 --- a/src/ui/SystemConfigWindow.java +++ b/src/ui/SystemConfigWindow.java @@ -48,7 +48,7 @@ public SystemConfigWindow(Context cxt) { versionPane.setAlignmentX(Component.CENTER_ALIGNMENT); container.add(versionPane); - // Add copy right notices + // Add copyright notices JTextPane copyRights = new JTextPane(); Font tmp = copyRights.getFont(); copyRights.setFont( tmp.deriveFont(9f) ); @@ -63,6 +63,7 @@ public SystemConfigWindow(Context cxt) { frame.add(container); frame.pack(); + frame.setResizable(false); frame.setVisible(true); } diff --git a/src/ui/TelemetryDataWidget.java b/src/ui/TelemetryDataWidget.java index 5df2d12..cf0639f 100644 --- a/src/ui/TelemetryDataWidget.java +++ b/src/ui/TelemetryDataWidget.java @@ -89,6 +89,7 @@ private class Line extends JLabel implements TelemetryListener { */ public Line(Context ctx, String format) { formatStr = format; + setPreferredSize(new Dimension(100, 20)); update(0.0); } @@ -107,7 +108,6 @@ public void update(double data) { @Override public void repaint() { super.repaint(); - //Repaint the parent to ensure no change in layer order TelemetryDataWidget.this.repaint(getX(), getY(), getWidth(), getHeight()); } @@ -213,7 +213,6 @@ else if(r.getLocalName().equals("line")) { catch (Exception e) { throw new ParseException("Failed to parse XML from the stream"+ e, 0); } - return new TelemetryDataWidget(ctx, width, fontSize, textColor, items); } diff --git a/src/ui/UIWidget.java b/src/ui/UIWidget.java index dcd7305..d450166 100644 --- a/src/ui/UIWidget.java +++ b/src/ui/UIWidget.java @@ -16,11 +16,13 @@ */ public class UIWidget extends JPanel { //Constants - protected static final float FONT_SIZE = 14.0f; - protected static final int BORDER_TOP = 0; - protected static final int BORDER_BOT = 0; - protected static final int BORDER_LFT = 0; - protected static final int BORDER_RHT = 0; + protected static final float FONT_SIZE = 14.0f; + protected static final int BORDER_TOP = 0; + protected static final int BORDER_BOT = 0; + protected static final int BORDER_LFT = 0; + protected static final int BORDER_RHT = 0; + protected static final int LABEL_HEIGHT = 25; + protected static final int LABEL_WIDTH = 115; //Standard Variables protected Context context; @@ -44,7 +46,7 @@ public UIWidget(Context ctx, String title) { this.setOpaque(false); Font font = context.theme.text.deriveFont(FONT_SIZE); - Dimension labelSize= new Dimension(115, 25); + Dimension labelSize= new Dimension(LABEL_WIDTH, LABEL_HEIGHT); titlePanel = new JPanel(); titlePanel.setOpaque(true); diff --git a/src/ui/WidgetPanel.java b/src/ui/WidgetPanel.java index 4fec8c3..8e5f82e 100644 --- a/src/ui/WidgetPanel.java +++ b/src/ui/WidgetPanel.java @@ -19,10 +19,11 @@ */ public class WidgetPanel extends NinePatchPanel { //Constants - protected static final int BORDER_TOP = 18; - protected static final int BORDER_BOT = 18; - protected static final int BORDER_LFT = 12; - protected static final int BORDER_RHT = 12; + protected static final int BORDER_TOP = 18; + protected static final int BORDER_BOT = 18; + protected static final int BORDER_LFT = 13; + protected static final int BORDER_RHT = 13; + protected static final Color BG_COLOR = Color.decode("0xEEEEEE"); protected Context context; protected LinkedList widgets; From c4e9b83690c58671c6fb40190d674432a1d92ce2 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 20 Jan 2021 09:33:20 -0800 Subject: [PATCH 033/125] Locke graph config window size ... --- src/graph/GraphConfigWindow.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/graph/GraphConfigWindow.java b/src/graph/GraphConfigWindow.java index a638f02..a615262 100644 --- a/src/graph/GraphConfigWindow.java +++ b/src/graph/GraphConfigWindow.java @@ -26,6 +26,7 @@ public GraphConfigWindow(Graph subject) { container.add(buildCloseupPanel()); frame.add(container); + frame.setResizable(false); frame.pack(); } From ea00250b8d0fc975930a2347918ec207ee3bc30a Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 27 Jan 2021 11:25:18 -0800 Subject: [PATCH 034/125] Create StatusBarWidget class Multipurpose status bar intended to be used to communicate vehicle state and sensor imformation as context requires. With adjustable text, background color, and size. --- src/ui/StatusBarWidget.java | 62 +++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/ui/StatusBarWidget.java diff --git a/src/ui/StatusBarWidget.java b/src/ui/StatusBarWidget.java new file mode 100644 index 0000000..70c8b7c --- /dev/null +++ b/src/ui/StatusBarWidget.java @@ -0,0 +1,62 @@ +package com.ui; + +import java.util.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +import com.Context; +//import com.serial.Serial; + +/** + * @author Chris Park @ Infinetix Corp. + * Date: 1-26-21 + * Description: Dashboard Widget child class used to display contextual vehicle + * information in an easy to interpret visual format. Example use cases would be + * displaying a units current decision state, or perhaps a visual warning level + * indication related to a vehicle sensor or other data processing part over + * serial communication. + */ +public class StatusBarWidget extends UIWidget { + + //Constants + protected static final int MIN_BAR_WIDTH = 100; + protected static final int BAR_HEIGHT = 25; + + protected JLabel statusLabel; + + //Class Constructor + public StatusBarWidget(Context ctx, String barType) { + super(ctx, barType + "StatusBar"); + + statusLabel.setText("Undefined"); + statusLabel.setBackground(Color.white); + + statusLabel.setMinimumSize(new Dimension(MIN_BAR_WIDTH, BAR_HEIGHT)); + statusLabel.setPreferredSize(new Dimension(MIN_BAR_WIDTH, BAR_HEIGHT)); + } + + /** + * Updates the status bar text display to the given string parameter. + * @param text - The text to update the label to. + */ + public void setLabel(String text) { + statusLabel.setText(text); + } + + /** + * Updates the status bar background color to the given Color parameter + * @param color - The color to set the background do + */ + public void setColor(Color color) { + statusLabel.setBackground(color); + } + + /** + * Updates the preferred size of the status bar + * @param size - the Dimensional size (Width, Height) to set the bar to. + */ + public void setSize(Dimension size) { + statusLabel.setPreferredSize(size); + } +} From 480e51b9ea0a5d82770438fb9276fab8ffae1eca Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 27 Jan 2021 11:31:02 -0800 Subject: [PATCH 035/125] Change to inherit from Jpanel instead of UIWidget UI Widgets have a title bar which this doesn't really need. Changing to inherit directly from JPanel class. --- src/ui/StatusBarWidget.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ui/StatusBarWidget.java b/src/ui/StatusBarWidget.java index 70c8b7c..29a5d40 100644 --- a/src/ui/StatusBarWidget.java +++ b/src/ui/StatusBarWidget.java @@ -17,18 +17,19 @@ * indication related to a vehicle sensor or other data processing part over * serial communication. */ -public class StatusBarWidget extends UIWidget { +public class StatusBarWidget extends JPanel { //Constants protected static final int MIN_BAR_WIDTH = 100; protected static final int BAR_HEIGHT = 25; + protected String barType; protected JLabel statusLabel; //Class Constructor - public StatusBarWidget(Context ctx, String barType) { - super(ctx, barType + "StatusBar"); + public StatusBarWidget(Context ctx, String type) { + barType = type; statusLabel.setText("Undefined"); statusLabel.setBackground(Color.white); From 277fbd35e897f18128bf3ac06322e4fd55fa6770 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 27 Jan 2021 15:10:07 -0800 Subject: [PATCH 036/125] Add StateType enums Added enum for better state tracking and bar value updates --- src/ui/StatusBarWidget.java | 48 ++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/ui/StatusBarWidget.java b/src/ui/StatusBarWidget.java index 29a5d40..878b184 100644 --- a/src/ui/StatusBarWidget.java +++ b/src/ui/StatusBarWidget.java @@ -23,13 +23,30 @@ public class StatusBarWidget extends JPanel { protected static final int MIN_BAR_WIDTH = 100; protected static final int BAR_HEIGHT = 25; - protected String barType; + //Bar Display Label protected JLabel statusLabel; + /** + * Pre-define status type enums used to more concisely track + * status bar parameters by type and allow for easier updating. + */ + public enum StatusType { + NORMAL ("Normal", Color.white), + PROCESSING ("Process", Color.blue), + CAUTION ("Caution", Color.yellow), + ERROR ("Error", Color.red); + + private final String text; + private final Color color; + + StatusType(String text, Color color) { + this.text = text; + this.color = color; + } + }; + //Class Constructor public StatusBarWidget(Context ctx, String type) { - - barType = type; statusLabel.setText("Undefined"); statusLabel.setBackground(Color.white); @@ -37,27 +54,8 @@ public StatusBarWidget(Context ctx, String type) { statusLabel.setPreferredSize(new Dimension(MIN_BAR_WIDTH, BAR_HEIGHT)); } - /** - * Updates the status bar text display to the given string parameter. - * @param text - The text to update the label to. - */ - public void setLabel(String text) { - statusLabel.setText(text); - } - - /** - * Updates the status bar background color to the given Color parameter - * @param color - The color to set the background do - */ - public void setColor(Color color) { - statusLabel.setBackground(color); - } - - /** - * Updates the preferred size of the status bar - * @param size - the Dimensional size (Width, Height) to set the bar to. - */ - public void setSize(Dimension size) { - statusLabel.setPreferredSize(size); + public void update(StatusType type) { + statusLabel.setText(type.text); + statusLabel.setBackground(type.color); } } From baa4448a3af8f0db1aaf05470b8f6ded7ae213a4 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 3 Feb 2021 08:44:06 -0800 Subject: [PATCH 037/125] Add StatusBar hooks to StateWidget The status bar will now reflect Rover operational states related to flag events and Auto type avoid states. --- src/ui/StateWidget.java | 25 ++++++++++++++-- src/ui/StatusBarWidget.java | 59 ++++++++++++++++++++++++++++--------- 2 files changed, 68 insertions(+), 16 deletions(-) diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java index 97ea133..c875e23 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/StateWidget.java @@ -35,6 +35,8 @@ public class StateWidget extends UIWidget { private JPanel flagPanel; private JLabel flagLabel; + private StatusBarWidget statusBar; + /** * Class constructor * @param ctx - The application context @@ -50,6 +52,10 @@ public StateWidget(Context ctx) { * dimensional spacing, and border insets. */ private void initPanel() { + + statusBar = new StatusBarWidget(context); + this.add(statusBar); + Font font = context.theme.text.deriveFont(FONT_SIZE); Dimension labelSize = new Dimension(100, 20); @@ -124,6 +130,7 @@ private void initPanel() { for(JPanel panel : statePanels) { this.add(panel); } + } /** @@ -167,7 +174,6 @@ private void setAPMState(byte substate) { finalWidth = Math.min(fmt.length(), LINE_WIDTH); break; case Serial.APM_STATE_SELF_TEST: - fmt = String.format(fmtStr, "Self Test"); finalWidth = Math.min(fmt.length(), LINE_WIDTH); break; @@ -230,6 +236,17 @@ private void setAutoState(byte substate) { case Serial.AUTO_STATE_AVOID: fmt = String.format(fmtStr, "Avoid"); finalWidth = Math.min(fmt.length(), LINE_WIDTH); + + //TODO - CP - Currently the auto state flips between full and... + //avoid very quickly when a obstacle is detected by USound sensors. + //Need to talk with Ben about the best approach to smooth this out + //so the bar updates meaningfully and gives some time to be read. + //As is now it flip flops rapidly. Not sure if I should do this on + //the UI layer or not but I suspect so. + if(statusBar.getState() != StatusBarWidget.StatusType.CAUTION) { + statusBar.update(StatusBarWidget.StatusType.CAUTION); + } + break; case Serial.AUTO_STATE_STALLED: fmt = String.format(fmtStr, "Stalled"); @@ -258,18 +275,22 @@ private void setFlagState(byte substate) { if(caution && approach) { fmt = String.format(fmtStr, "App. & Caut."); finalWidth = Math.min(fmt.length(), LINE_WIDTH); + statusBar.update(StatusBarWidget.StatusType.CAUTION); } else if(approach) { fmt = String.format(fmtStr, "Approach"); finalWidth = Math.min(fmt.length(), LINE_WIDTH); + statusBar.update(StatusBarWidget.StatusType.PROCESSING); } else if(caution) { fmt = String.format(fmtStr, "Caution"); - finalWidth = Math.min(fmt.length(), LINE_WIDTH); + finalWidth = Math.min(fmt.length(), LINE_WIDTH); + statusBar.update(StatusBarWidget.StatusType.CAUTION); } else { fmt = String.format(fmtStr, "None"); finalWidth = Math.min(fmt.length(), LINE_WIDTH); + statusBar.update(StatusBarWidget.StatusType.NORMAL); } flagLabel.setText(fmt.substring(0, finalWidth)); } diff --git a/src/ui/StatusBarWidget.java b/src/ui/StatusBarWidget.java index 878b184..b696c77 100644 --- a/src/ui/StatusBarWidget.java +++ b/src/ui/StatusBarWidget.java @@ -6,7 +6,6 @@ import javax.swing.*; import com.Context; -//import com.serial.Serial; /** * @author Chris Park @ Infinetix Corp. @@ -20,42 +19,74 @@ public class StatusBarWidget extends JPanel { //Constants - protected static final int MIN_BAR_WIDTH = 100; - protected static final int BAR_HEIGHT = 25; + protected static final float FONT_SIZE = 12.0f; + protected static final int MIN_BAR_WIDTH = 100; + protected static final int BAR_HEIGHT = 20; + + //Standard Variables + protected Context context; //Bar Display Label protected JLabel statusLabel; + protected StatusType currentType; + /** * Pre-define status type enums used to more concisely track * status bar parameters by type and allow for easier updating. */ public enum StatusType { - NORMAL ("Normal", Color.white), - PROCESSING ("Process", Color.blue), - CAUTION ("Caution", Color.yellow), - ERROR ("Error", Color.red); + NORMAL ("Normal", Color.black, Color.white), + PROCESSING ("Processing", Color.black, Color.blue), + CAUTION ("Caution", Color.black, Color.yellow), + ERROR ("Error", Color.black, Color.red); private final String text; - private final Color color; + private final Color fgColor; + private final Color bgColor; - StatusType(String text, Color color) { + StatusType(String text, Color fgColor, Color bgColor) { this.text = text; - this.color = color; + this.fgColor = fgColor; + this.bgColor = bgColor; } }; //Class Constructor - public StatusBarWidget(Context ctx, String type) { - statusLabel.setText("Undefined"); - statusLabel.setBackground(Color.white); + public StatusBarWidget(Context ctx) { + context = ctx; + //configure initial label dimensions, font, and alignment + statusLabel = new JLabel(); + statusLabel.setOpaque(true); + statusLabel.setFont(context.theme.text.deriveFont(FONT_SIZE)); statusLabel.setMinimumSize(new Dimension(MIN_BAR_WIDTH, BAR_HEIGHT)); statusLabel.setPreferredSize(new Dimension(MIN_BAR_WIDTH, BAR_HEIGHT)); + statusLabel.setHorizontalAlignment(SwingConstants.CENTER); + update(StatusType.NORMAL); + + this.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0)); + this.setBorder(BorderFactory.createLineBorder(Color.black)); + this.add(statusLabel); } + /** + * Updates the text, foreground, and background color of this StatusBarWidget + * using the parameters defined in the enum type. + * @param type - the enum type to pull data from. + */ public void update(StatusType type) { statusLabel.setText(type.text); - statusLabel.setBackground(type.color); + statusLabel.setForeground(type.fgColor); + statusLabel.setBackground(type.bgColor); + currentType = type; + } + + /** + * Returns the current Status displayed by the bar. + * @return - StatusType + */ + public StatusType getState() { + return currentType; } } From af421db890e2cb9dbe4ee2e0772c2df350d4d164 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 4 Feb 2021 14:56:50 -0800 Subject: [PATCH 038/125] Created Arduino CLI command management class This class is intended to perform Arduino CLI operations such as configuring a board and uploading a sketch on behalf of the dashboard without the need to do so externally. --- src/util/ACLIManager.java | 93 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 src/util/ACLIManager.java diff --git a/src/util/ACLIManager.java b/src/util/ACLIManager.java new file mode 100644 index 0000000..2b47c6a --- /dev/null +++ b/src/util/ACLIManager.java @@ -0,0 +1,93 @@ +package com.util; + +import java.util.*; + +/** + * @author Chris Park @ Infinetix Corp + * Date: 2-3-2021 + * Description: Singleton class used to configure the arduino-cli and upload + * APM sketches from the dashboard. + */ +public class ACLIManager { + private static ACLIManager aclimInstance = null; + + private static final String CMD_EXEC = "cmd /c"; + private static final String ACLI_EXEC = "arduino-cli"; + private static final String ACLI_PATH = ""; + + private ProcessBuilder processBuilder; + private List params; + + /** + * Description: Enum used to create, package, and contain all necessary information + * to run an Arduino CLI command through the ACLIManager (Arduino CLI Manager). + */ + public enum ACLICommand { + //Installs required core for APM + INSTALL_AVR_CORE (Arrays.asList("core", "install", "arduino:avr")), + //Generates a list of attached Arduino Boards + GENERATE_BOARD_LIST (Arrays.asList("board", "list")), + //Parses port for an APM board and generates a config + ATTACH_APM_BOARD (Arrays.asList("board", "attach", "serial://")), + //Compiles and uploads a sketch using generated config + UPLOAD_SKETCH (Arrays.asList("compile", "upload")); + + public final List params; + + ACLICommand(List params) { + this.params = params; + } + }; + + /** + * Constructor (Private, accessed by getInstance) + */ + private ACLIManager() { + + } + + /** + * Returns the singleton instance of this class to be used system wide. + * @return The Arduino CLI Command manager instance. + */ + public static ACLIManager getInstance() { + if(aclimInstance == null) { + aclimInstance = new ACLIManager(); + } + + return aclimInstance; + } + + /** + * Executes the given ACLICommand object. as a command line operation using + * the Java ProcessBuilder class. + * @param command + */ + public void execute(ACLICommand command) { + params = new ArrayList(); + params.addAll(0, Arrays.asList(CMD_EXEC, ACLI_EXEC, ACLI_PATH)); + params.addAll(command.params); + + processBuilder = new ProcessBuilder(); + processBuilder.command(params); + + switch(command) { + case INSTALL_AVR_CORE: + //No stored information required + break; + case GENERATE_BOARD_LIST: + //No stored information required + break; + case ATTACH_APM_BOARD: + //Checks for board list + //Parses port for board + //Generates Config + break; + case UPLOAD_SKETCH: + //Checks for config + //Compiles Sketch + //Uploads sketch + break; + } + } +} From 12433182340abc221a282ed56a1ffbc682e1091a Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Mon, 8 Feb 2021 12:07:40 -0800 Subject: [PATCH 039/125] Update ACLIManager.java Added regex pattern matching and parsing for device port. Used to generate a config file for sketch upload. --- src/util/ACLIManager.java | 81 ++++++++++++++++++++++++++++++++------- 1 file changed, 67 insertions(+), 14 deletions(-) diff --git a/src/util/ACLIManager.java b/src/util/ACLIManager.java index 2b47c6a..96b43fb 100644 --- a/src/util/ACLIManager.java +++ b/src/util/ACLIManager.java @@ -1,6 +1,9 @@ package com.util; import java.util.*; +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.io.*; /** * @author Chris Park @ Infinetix Corp @@ -11,10 +14,10 @@ public class ACLIManager { private static ACLIManager aclimInstance = null; - private static final String CMD_EXEC = "cmd /c"; - private static final String ACLI_EXEC = "arduino-cli"; - private static final String ACLI_PATH = ""; - + private static final String CMD_EXEC = "cmd /c"; + private static final String ACLI_EXEC = "arduino-cli"; + private static final String ACLI_PATH = ""; + private static final String BOARD_LIST_FILE = "boardlist.txt"; private ProcessBuilder processBuilder; private List params; @@ -26,11 +29,11 @@ public enum ACLICommand { //Installs required core for APM INSTALL_AVR_CORE (Arrays.asList("core", "install", "arduino:avr")), //Generates a list of attached Arduino Boards - GENERATE_BOARD_LIST (Arrays.asList("board", "list")), + GENERATE_BOARD_LIST (Arrays.asList("board", "list", ">", BOARD_LIST_FILE)), //Parses port for an APM board and generates a config - ATTACH_APM_BOARD (Arrays.asList("board", "attach", "serial://")), + ATTACH_APM_BOARD (Arrays.asList("board", "attach")), //Compiles and uploads a sketch using generated config - UPLOAD_SKETCH (Arrays.asList("compile", "upload")); + UPLOAD_SKETCH (Arrays.asList("compile", "--upload")); public final List params; @@ -64,30 +67,80 @@ public static ACLIManager getInstance() { * @param command */ public void execute(ACLICommand command) { + File boardList; + params = new ArrayList(); params.addAll(0, Arrays.asList(CMD_EXEC, ACLI_EXEC, ACLI_PATH)); params.addAll(command.params); - processBuilder = new ProcessBuilder(); - processBuilder.command(params); - switch(command) { case INSTALL_AVR_CORE: - //No stored information required + //Go straight to exectuion break; case GENERATE_BOARD_LIST: - //No stored information required + //Go straight to execution break; case ATTACH_APM_BOARD: //Checks for board list - //Parses port for board - //Generates Config + boardList = new File(BOARD_LIST_FILE); + if(!boardList.exists()) { + //TODO - CP - Report this error to the UI level (Message popup) + System.err.println( + "ACLI Manager file error: Board list not found."); + return; + } + + Pattern pattern = Pattern.compile( + "^COM\\d+\\s\\w{0,6}\\s\\w{0,4}\\s\\(?\\w{0,5}\\)?\\sArduino"); + Matcher matcher; + boolean matchFound = false; + + try { + FileReader fileReader = new FileReader(BOARD_LIST_FILE); + BufferedReader bufferedReader = new BufferedReader(fileReader); + String temp; + + while((temp = bufferedReader.readLine()) != null) { + matcher = pattern.matcher(temp); + + //If a match was found + matchFound = matcher.find(); + if(matchFound) { + String[] substrings = temp.split(" "); + params.add("serial://" + substrings[0]); + break; + } + } + bufferedReader.close(); + + //If no match was found + if(!matchFound) { + System.err.println( + "ACLI Manager file error: Failed to find port."); + return; + } + } + catch(Exception e) { + System.err.println("ACLI Manager file error: " + e); + } break; case UPLOAD_SKETCH: //Checks for config //Compiles Sketch //Uploads sketch break; + default: } + + processBuilder = new ProcessBuilder(); + processBuilder.command(params); + + try { + processBuilder.start(); + } + catch (Exception e) { + System.err.println("ACLI Manager exec error: " + e); + } + } } From 558e5b07b458ba3d65ff959ee371cdf72df6ac19 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Mon, 8 Feb 2021 15:24:21 -0800 Subject: [PATCH 040/125] Update ACLIManager.java Finish first pass of Arduino CLI manager sub process routines. --- src/util/ACLIManager.java | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/util/ACLIManager.java b/src/util/ACLIManager.java index 96b43fb..5f41747 100644 --- a/src/util/ACLIManager.java +++ b/src/util/ACLIManager.java @@ -14,10 +14,11 @@ public class ACLIManager { private static ACLIManager aclimInstance = null; - private static final String CMD_EXEC = "cmd /c"; - private static final String ACLI_EXEC = "arduino-cli"; - private static final String ACLI_PATH = ""; - private static final String BOARD_LIST_FILE = "boardlist.txt"; + private static final String CMD_EXEC = "cmd /c"; + private static final String ACLI_EXEC = "arduino-cli"; + private static final String ACLI_PATH = ""; + private static final String BOARD_LIST_FILE = "boardlist.txt"; + private static final String SKETCH_CONFIG_FILE = "sketch.json"; private ProcessBuilder processBuilder; private List params; @@ -67,7 +68,7 @@ public static ACLIManager getInstance() { * @param command */ public void execute(ACLICommand command) { - File boardList; + File file; params = new ArrayList(); params.addAll(0, Arrays.asList(CMD_EXEC, ACLI_EXEC, ACLI_PATH)); @@ -82,8 +83,8 @@ public void execute(ACLICommand command) { break; case ATTACH_APM_BOARD: //Checks for board list - boardList = new File(BOARD_LIST_FILE); - if(!boardList.exists()) { + file = new File(BOARD_LIST_FILE); + if(!file.exists()) { //TODO - CP - Report this error to the UI level (Message popup) System.err.println( "ACLI Manager file error: Board list not found."); @@ -126,8 +127,13 @@ public void execute(ACLICommand command) { break; case UPLOAD_SKETCH: //Checks for config - //Compiles Sketch - //Uploads sketch + file = new File(SKETCH_CONFIG_FILE); + if(!file.exists()) { + //TODO - CP - Report this error to the UI level (Message popup) + System.err.println( + "ACLI Manager file error: board config file not found."); + return; + } break; default: } From af228903d15b8c38d86b20d116f29b06e6157a05 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Tue, 9 Feb 2021 12:50:49 -0800 Subject: [PATCH 041/125] Update ACLIManager.java Abandoned board config file generation in favor of manual port and core type specifications for uploading a sketch. Also added an overload to the execute function that allows for the sketch path to be passed in. --- src/util/ACLIManager.java | 173 ++++++++++++++++++++++++++------------ 1 file changed, 119 insertions(+), 54 deletions(-) diff --git a/src/util/ACLIManager.java b/src/util/ACLIManager.java index 5f41747..506d621 100644 --- a/src/util/ACLIManager.java +++ b/src/util/ACLIManager.java @@ -21,6 +21,9 @@ public class ACLIManager { private static final String SKETCH_CONFIG_FILE = "sketch.json"; private ProcessBuilder processBuilder; private List params; + private String port; + private String core; + private String errorString; /** * Description: Enum used to create, package, and contain all necessary information @@ -31,10 +34,11 @@ public enum ACLICommand { INSTALL_AVR_CORE (Arrays.asList("core", "install", "arduino:avr")), //Generates a list of attached Arduino Boards GENERATE_BOARD_LIST (Arrays.asList("board", "list", ">", BOARD_LIST_FILE)), - //Parses port for an APM board and generates a config - ATTACH_APM_BOARD (Arrays.asList("board", "attach")), - //Compiles and uploads a sketch using generated config - UPLOAD_SKETCH (Arrays.asList("compile", "--upload")); + //Parses the port and core of the APM as it appears in the board list file + PARSE_BOARD_INFO (Arrays.asList(BOARD_LIST_FILE)), + + COMPILE_SKETCH (Arrays.asList("compile")), + UPLOAD_SKETCH (Arrays.asList("upload")); public final List params; @@ -47,7 +51,9 @@ public enum ACLICommand { * Constructor (Private, accessed by getInstance) */ private ACLIManager() { - + port = ""; + core = ""; + errorString = "No error"; } /** @@ -65,11 +71,9 @@ public static ACLIManager getInstance() { /** * Executes the given ACLICommand object. as a command line operation using * the Java ProcessBuilder class. - * @param command + * @param command - The action/command to execute */ public void execute(ACLICommand command) { - File file; - params = new ArrayList(); params.addAll(0, Arrays.asList(CMD_EXEC, ACLI_EXEC, ACLI_PATH)); params.addAll(command.params); @@ -81,59 +85,60 @@ public void execute(ACLICommand command) { case GENERATE_BOARD_LIST: //Go straight to execution break; - case ATTACH_APM_BOARD: - //Checks for board list - file = new File(BOARD_LIST_FILE); - if(!file.exists()) { - //TODO - CP - Report this error to the UI level (Message popup) - System.err.println( - "ACLI Manager file error: Board list not found."); - return; + case PARSE_BOARD_INFO: + if(parseInfo()) { + params.add(port); } - - Pattern pattern = Pattern.compile( - "^COM\\d+\\s\\w{0,6}\\s\\w{0,4}\\s\\(?\\w{0,5}\\)?\\sArduino"); - Matcher matcher; - boolean matchFound = false; - - try { - FileReader fileReader = new FileReader(BOARD_LIST_FILE); - BufferedReader bufferedReader = new BufferedReader(fileReader); - String temp; - - while((temp = bufferedReader.readLine()) != null) { - matcher = pattern.matcher(temp); - - //If a match was found - matchFound = matcher.find(); - if(matchFound) { - String[] substrings = temp.split(" "); - params.add("serial://" + substrings[0]); - break; - } - } - bufferedReader.close(); - - //If no match was found - if(!matchFound) { - System.err.println( - "ACLI Manager file error: Failed to find port."); - return; - } + else { + System.err.println("ACLI Manager board attach error: " + + "parsing operation unsuccessful"); + return; } - catch(Exception e) { - System.err.println("ACLI Manager file error: " + e); + break; + default: + } + + processBuilder = new ProcessBuilder(); + processBuilder.command(params); + + try { + processBuilder.start(); + } + catch (Exception e) { + System.err.println("ACLI Manager exec error: " + e); + } + + } + + /** + * Executes the given ACLICommand object. as a command line operation using + * the Java ProcessBuilder class. Overloaded from previous function to accept a + * sketch path. + * @param command - The action/command to execute + * @param sketchPath - The user selected path to the sketch being used. + */ + public void execute(ACLICommand command, String sketchPath) { + params = new ArrayList(); + params.addAll(0, Arrays.asList(CMD_EXEC, ACLI_EXEC, ACLI_PATH)); + params.addAll(command.params); + + switch(command) { + case COMPILE_SKETCH: + if(core.isEmpty() || port.isEmpty()) { + //TODO - CP - Log error here + return; } + //Check that port and core are not empty + //arduino-cli compile -b -p + break; case UPLOAD_SKETCH: - //Checks for config - file = new File(SKETCH_CONFIG_FILE); - if(!file.exists()) { - //TODO - CP - Report this error to the UI level (Message popup) - System.err.println( - "ACLI Manager file error: board config file not found."); + if(port.isEmpty()) { + //TODO - CP - Log error here return; } + + //arduino-cli upload -p break; default: } @@ -147,6 +152,66 @@ public void execute(ACLICommand command) { catch (Exception e) { System.err.println("ACLI Manager exec error: " + e); } + } + + /** + * Reads the board list file of attached arduino devices and parses + * out the required port name and core type for an APM module. These are + * requirements needed for arduino-cli to compile and upload a sketch. + * @return - Whether or not the operation was successful. + */ + public boolean parseInfo() { + //The APM shows up as an avr core. Here is a Regex target line example: + //"COM4 Serial Port (USB) Arduino Mega or Mega 2560 arduino:avr:mega arduino:avr" + Pattern pattern = Pattern.compile( + "^COM\\d+\\s\\w{0,6}\\s\\w{0,4}\\s\\(?\\w{0,5}\\)?\\sArduino"); + Matcher matcher; + File file; + boolean matchFound = false; + + //Checks for board list + file = new File(BOARD_LIST_FILE); + if(!file.exists()) { + System.err.println( + "ACLI Manager file error: Board list not found."); + return false; + } + try { + FileReader fileReader = new FileReader(BOARD_LIST_FILE); + BufferedReader bufferedReader = new BufferedReader(fileReader); + String temp; + + while((temp = bufferedReader.readLine()) != null) { + matcher = pattern.matcher(temp); + + //If a match was found + matchFound = matcher.find(); + if(matchFound) { + String[] substrings = temp.split(" "); + port = "serial://" + substrings[0]; + core = substrings[(substrings.length - 1)]; + break; + } + } + bufferedReader.close(); + + //If no match was found + if(!matchFound) { + System.err.println( + "ACLI Manager file error: Failed to find port."); + return false; + } + } + catch(Exception e) { + System.err.println("ACLI Manager file error: " + e); + return false; + } + + return true; + } + + public String getErrorStr() { + return errorString; } } From 568c7f6156dce2e443b3445242d8977fd737d6f7 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 11 Feb 2021 14:03:26 -0800 Subject: [PATCH 042/125] ACLIManager and UI Dialog updates Added file chooser and error messaging dialogs to UIConfigPanel. Added error reporting to command manager to be used by this. --- src/serial/SerialConnectPanel.java | 1 - src/ui/UIConfigPanel.java | 98 ++++++++++++++++++++++++++++++ src/util/ACLIManager.java | 76 ++++++++++++++++++----- 3 files changed, 158 insertions(+), 17 deletions(-) diff --git a/src/serial/SerialConnectPanel.java b/src/serial/SerialConnectPanel.java index 2dbce8b..4853b51 100644 --- a/src/serial/SerialConnectPanel.java +++ b/src/serial/SerialConnectPanel.java @@ -222,7 +222,6 @@ public void run() { // serialPort.setFlowControlMode( // SerialPort.FLOWCONTROL_RTSCTS_IN | // SerialPort.FLOWCONTROL_RTSCTS_OUT); - serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE); } diff --git a/src/ui/UIConfigPanel.java b/src/ui/UIConfigPanel.java index f1ebc98..b9b59e8 100644 --- a/src/ui/UIConfigPanel.java +++ b/src/ui/UIConfigPanel.java @@ -3,6 +3,7 @@ import com.Context; import com.map.*; +import java.io.*; import java.awt.Insets; import java.awt.Dimension; import java.awt.Component; @@ -12,7 +13,10 @@ import java.awt.geom.Point2D; import java.text.Format; import javax.swing.*; +import javax.swing.filechooser.FileFilter; +import javax.swing.filechooser.FileNameExtensionFilter; +import com.util.ACLIManager; /** * @@ -34,6 +38,7 @@ public class UIConfigPanel extends JPanel { private JPanel buttonPanel; private JButton toggleButton; private JButton driverButton; + private JButton sketchUploadButton; private JPanel homePanel; private JPanel lngPanel; private JLabel lngLabel; @@ -75,6 +80,13 @@ public UIConfigPanel(Context cxt, boolean isWindows) { this.add(driverButton, constraints); } + sketchUploadButton = new JButton(uploadSketchAction); + sketchUploadButton.setPreferredSize( + new Dimension(DEF_BUTTON_WIDTH, DEF_BUTTON_HEIGHT)); + constraints.gridx = 0; + constraints.gridy = 2; + this.add(sketchUploadButton, constraints); + Point2D homeCoords = context.getHomeProp(); latLabel = new JLabel("Latitiude:"); @@ -143,6 +155,92 @@ public void actionPerformed(ActionEvent e) { } }; + /** + * Action used to open a FileChooser dialog, and + * select and upload an Arduino sketch to the APM. + */ + private Action uploadSketchAction = new AbstractAction() { + { + String text = "Upload arduino sketch"; + putValue(Action.NAME, text); + } + public void actionPerformed(ActionEvent e) { + File selectedFile = null; + JFileChooser fileChooser = new JFileChooser(); + FileFilter filter = new FileNameExtensionFilter( + "Arduino Sketch (*.ino)", "ino"); + + fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + fileChooser.removeChoosableFileFilter(fileChooser.getFileFilter()); + fileChooser.addChoosableFileFilter(filter); + + int retVal = fileChooser.showOpenDialog(UIConfigPanel.this); + if(retVal == JFileChooser.APPROVE_OPTION) { + selectedFile = fileChooser.getSelectedFile(); + } + + if (selectedFile == null) { + System.err.println( + "UIConfigPanel - Upload error: No file was selected."); + return; + } + + boolean result = false; + + //Install avr core used by APM + result = ACLIManager.getInstance().execute( + ACLIManager.ACLICommand.INSTALL_AVR_CORE); + if (result == false) { + JOptionPane.showMessageDialog(UIConfigPanel.this, + ACLIManager.getInstance().getErrorStr(), + "Error", JOptionPane.ERROR_MESSAGE); + return; + } + + //Build list of available/connected boards + result = ACLIManager.getInstance().execute( + ACLIManager.ACLICommand.GENERATE_BOARD_LIST); + if (result == false) { + JOptionPane.showMessageDialog(UIConfigPanel.this, + ACLIManager.getInstance().getErrorStr(), + "Error", JOptionPane.ERROR_MESSAGE); + return; + } + + //Parse core and port info + result = ACLIManager.getInstance().execute( + ACLIManager.ACLICommand.PARSE_BOARD_INFO); + if (result == false) { + JOptionPane.showMessageDialog(UIConfigPanel.this, + ACLIManager.getInstance().getErrorStr(), + "Error", JOptionPane.ERROR_MESSAGE); + return; + } + + //Compile selected sketch + result = ACLIManager.getInstance().execute( + ACLIManager.ACLICommand.COMPILE_SKETCH, + selectedFile.getAbsolutePath()); + if (result == false) { + JOptionPane.showMessageDialog(UIConfigPanel.this, + ACLIManager.getInstance().getErrorStr(), + "Error", JOptionPane.ERROR_MESSAGE); + return; + } + + //Upload selected sketch + result = ACLIManager.getInstance().execute( + ACLIManager.ACLICommand.UPLOAD_SKETCH, + selectedFile.getAbsolutePath()); + if (result == false) { + JOptionPane.showMessageDialog(UIConfigPanel.this, + ACLIManager.getInstance().getErrorStr(), + "Error", JOptionPane.ERROR_MESSAGE); + return; + } + } + }; + /** * Action used to set the home longitude and latitude and save it to persistent * settings. diff --git a/src/util/ACLIManager.java b/src/util/ACLIManager.java index 506d621..102f3e8 100644 --- a/src/util/ACLIManager.java +++ b/src/util/ACLIManager.java @@ -19,6 +19,7 @@ public class ACLIManager { private static final String ACLI_PATH = ""; private static final String BOARD_LIST_FILE = "boardlist.txt"; private static final String SKETCH_CONFIG_FILE = "sketch.json"; + private ProcessBuilder processBuilder; private List params; private String port; @@ -36,8 +37,9 @@ public enum ACLICommand { GENERATE_BOARD_LIST (Arrays.asList("board", "list", ">", BOARD_LIST_FILE)), //Parses the port and core of the APM as it appears in the board list file PARSE_BOARD_INFO (Arrays.asList(BOARD_LIST_FILE)), - + //Compiles and prepares a sketch for upload COMPILE_SKETCH (Arrays.asList("compile")), + //Uploads a sketch to the APM UPLOAD_SKETCH (Arrays.asList("upload")); public final List params; @@ -53,7 +55,7 @@ public enum ACLICommand { private ACLIManager() { port = ""; core = ""; - errorString = "No error"; + errorString = "No error."; } /** @@ -73,7 +75,7 @@ public static ACLIManager getInstance() { * the Java ProcessBuilder class. * @param command - The action/command to execute */ - public void execute(ACLICommand command) { + public boolean execute(ACLICommand command) { params = new ArrayList(); params.addAll(0, Arrays.asList(CMD_EXEC, ACLI_EXEC, ACLI_PATH)); params.addAll(command.params); @@ -90,9 +92,10 @@ public void execute(ACLICommand command) { params.add(port); } else { - System.err.println("ACLI Manager board attach error: " + errorString = "Could not obtain port and core information."; + System.err.println("ACLI Manager parse error: " + "parsing operation unsuccessful"); - return; + return false; } break; default: @@ -102,12 +105,30 @@ public void execute(ACLICommand command) { processBuilder.command(params); try { - processBuilder.start(); + Process process = processBuilder.start(); + if(process.waitFor() == 0) { + errorString = "No error."; + } } catch (Exception e) { + switch(command) { + case INSTALL_AVR_CORE: + errorString = "Unable to install avr core."; + break; + case GENERATE_BOARD_LIST: + errorString = "Unable to generate board list."; + break; + case PARSE_BOARD_INFO: + errorString = "Unable to parse board info."; + break; + default: + errorString = "Unknown error"; + } System.err.println("ACLI Manager exec error: " + e); + return false; } + return true; } /** @@ -117,7 +138,7 @@ public void execute(ACLICommand command) { * @param command - The action/command to execute * @param sketchPath - The user selected path to the sketch being used. */ - public void execute(ACLICommand command, String sketchPath) { + public boolean execute(ACLICommand command, String sketchPath) { params = new ArrayList(); params.addAll(0, Arrays.asList(CMD_EXEC, ACLI_EXEC, ACLI_PATH)); params.addAll(command.params); @@ -125,20 +146,23 @@ public void execute(ACLICommand command, String sketchPath) { switch(command) { case COMPILE_SKETCH: if(core.isEmpty() || port.isEmpty()) { - //TODO - CP - Log error here - return; + errorString = "Necessary board information (port or core) missing. " + + "Unable to compile."; + return false; } - //Check that port and core are not empty - //arduino-cli compile -b -p - + params.add("-b " + core); + params.add("-p " + port); + params.add(sketchPath); break; case UPLOAD_SKETCH: if(port.isEmpty()) { - //TODO - CP - Log error here - return; + errorString = "port information missing. " + + "Unable to upload sketch."; + return false; } - //arduino-cli upload -p + params.add("-p " + port); + params.add(sketchPath); break; default: } @@ -147,11 +171,27 @@ public void execute(ACLICommand command, String sketchPath) { processBuilder.command(params); try { - processBuilder.start(); + Process process = processBuilder.start(); + if(process.waitFor() == 0) { + errorString = "No error."; + } } catch (Exception e) { + switch(command) { + case COMPILE_SKETCH: + errorString = "Unable to compile sketch."; + break; + case UPLOAD_SKETCH: + errorString = "Unable to upload sketch."; + break; + default: + errorString = "Unknown error"; + } System.err.println("ACLI Manager exec error: " + e); + return false; } + + return true; } /** @@ -211,6 +251,10 @@ public boolean parseInfo() { return true; } + /** + * Gets the most recent error string resulting from command executions. + * @return - The currently set error string or "No error" if there isn't one. + */ public String getErrorStr() { return errorString; } From dcec27856677f9c136245cbd14d914dd65f56b9c Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Tue, 16 Feb 2021 14:27:55 -0800 Subject: [PATCH 043/125] Sketch upload and persistence file updates - Fix command flags for sketch upload - Add persistence file relative path variation for non windows systems. --- .gitignore | 1 + resources/resources_en.properties | 2 +- src/Context.java | 25 +++++++++++++++++-------- src/ui/UIConfigPanel.java | 16 +++++++++++++--- src/util/ACLIManager.java | 7 ++++--- 5 files changed, 36 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index aec8ea3..e63201c 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ resources/tile_servers.properties .project .classpath .settings/org.eclipse.wst.sse.core.prefs +boardlist.txt diff --git a/resources/resources_en.properties b/resources/resources_en.properties index 1f6fbf1..1352896 100644 --- a/resources/resources_en.properties +++ b/resources/resources_en.properties @@ -1,5 +1,5 @@ version_id =1.0.2 -release_date =2020-12-03 +release_date =2021-02-16 image_folder =resources/images/ patch_folder =resources/images/nP/ font_folder =resources/fonts/ diff --git a/src/Context.java b/src/Context.java index 1580aad..4c89260 100644 --- a/src/Context.java +++ b/src/Context.java @@ -36,9 +36,7 @@ public class Context { private ResourceBundle resources; private Properties persist; - private static final File persistanceFile = - new File(System.getProperty("user.home") + "\\AppData\\Local\\MINDS-i Dashboard\\persist.properties"); - + private final File persistenceFile; private final String instanceLogName; private final Logger ioerr = Logger.getLogger("d.io"); @@ -52,13 +50,24 @@ public Context(Dashboard dashboard) { instanceLogName = sdf.format(cal.getTime()); persist = new Properties(); + + //Find out what OS type we are running under + String osname = System.getProperty("os.name"); + if(osname.toLowerCase().contains("windows")) { + persistenceFile = new File(System.getProperty("user.home") + + "\\AppData\\Local\\MINDS-i Dashboard\\persist.properties"); + } + else { //Assume Linux and default to relative directory structure + persistenceFile = new File("./resources/persist/persist.properties"); + } + try { - if(!persistanceFile.exists()) { - persistanceFile.getParentFile().mkdirs(); - persistanceFile.createNewFile(); + if(!persistenceFile.exists()) { + persistenceFile.getParentFile().mkdirs(); + persistenceFile.createNewFile(); } - InputStream is =new FileInputStream(persistanceFile); + InputStream is = new FileInputStream(persistenceFile); persist.load(is); is.close(); } @@ -181,7 +190,7 @@ public Point2D getHomeProp() { } private void saveProps() { - try(FileOutputStream file = new FileOutputStream(persistanceFile)) { + try(FileOutputStream file = new FileOutputStream(persistenceFile)) { persist.store(file, ""); } catch (Exception e) { ioerr.severe("Can't save persist props "+e); diff --git a/src/ui/UIConfigPanel.java b/src/ui/UIConfigPanel.java index b9b59e8..383619e 100644 --- a/src/ui/UIConfigPanel.java +++ b/src/ui/UIConfigPanel.java @@ -169,7 +169,7 @@ public void actionPerformed(ActionEvent e) { JFileChooser fileChooser = new JFileChooser(); FileFilter filter = new FileNameExtensionFilter( "Arduino Sketch (*.ino)", "ino"); - + fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); fileChooser.removeChoosableFileFilter(fileChooser.getFileFilter()); fileChooser.addChoosableFileFilter(filter); @@ -196,6 +196,8 @@ public void actionPerformed(ActionEvent e) { "Error", JOptionPane.ERROR_MESSAGE); return; } +// System.err.println("Successful Core Install"); + //Build list of available/connected boards result = ACLIManager.getInstance().execute( @@ -206,6 +208,7 @@ public void actionPerformed(ActionEvent e) { "Error", JOptionPane.ERROR_MESSAGE); return; } +// System.err.println("Successful Board List Generation"); //Parse core and port info result = ACLIManager.getInstance().execute( @@ -216,28 +219,35 @@ public void actionPerformed(ActionEvent e) { "Error", JOptionPane.ERROR_MESSAGE); return; } +// System.err.println("Successful Board List Parsing"); //Compile selected sketch result = ACLIManager.getInstance().execute( ACLIManager.ACLICommand.COMPILE_SKETCH, selectedFile.getAbsolutePath()); if (result == false) { - JOptionPane.showMessageDialog(UIConfigPanel.this, + JOptionPane.showMessageDialog(UIConfigPanel.this, ACLIManager.getInstance().getErrorStr(), "Error", JOptionPane.ERROR_MESSAGE); return; } +// System.err.println("Successful Sketch Compile"); //Upload selected sketch result = ACLIManager.getInstance().execute( ACLIManager.ACLICommand.UPLOAD_SKETCH, selectedFile.getAbsolutePath()); if (result == false) { - JOptionPane.showMessageDialog(UIConfigPanel.this, + JOptionPane.showMessageDialog(UIConfigPanel.this, ACLIManager.getInstance().getErrorStr(), "Error", JOptionPane.ERROR_MESSAGE); return; } +// System.err.println("Successful Sketch Upload"); + + JOptionPane.showMessageDialog(UIConfigPanel.this, + "Sketch uploaded successfully.", + "Info", JOptionPane.INFORMATION_MESSAGE); } }; diff --git a/src/util/ACLIManager.java b/src/util/ACLIManager.java index 102f3e8..08e73e5 100644 --- a/src/util/ACLIManager.java +++ b/src/util/ACLIManager.java @@ -14,7 +14,8 @@ public class ACLIManager { private static ACLIManager aclimInstance = null; - private static final String CMD_EXEC = "cmd /c"; + private static final String CMD_EXEC = "cmd.exe"; + private static final String TERM_FLAG = "/c"; private static final String ACLI_EXEC = "arduino-cli"; private static final String ACLI_PATH = ""; private static final String BOARD_LIST_FILE = "boardlist.txt"; @@ -77,7 +78,7 @@ public static ACLIManager getInstance() { */ public boolean execute(ACLICommand command) { params = new ArrayList(); - params.addAll(0, Arrays.asList(CMD_EXEC, ACLI_EXEC, ACLI_PATH)); + params.addAll(0, Arrays.asList(CMD_EXEC, TERM_FLAG, ACLI_EXEC, ACLI_PATH)); params.addAll(command.params); switch(command) { @@ -140,7 +141,7 @@ public boolean execute(ACLICommand command) { */ public boolean execute(ACLICommand command, String sketchPath) { params = new ArrayList(); - params.addAll(0, Arrays.asList(CMD_EXEC, ACLI_EXEC, ACLI_PATH)); + params.addAll(0, Arrays.asList(CMD_EXEC, TERM_FLAG, ACLI_EXEC, ACLI_PATH)); params.addAll(command.params); switch(command) { From f483c35d2c4575373b4daf111c35a0c930706223 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 18 Feb 2021 09:17:39 -0800 Subject: [PATCH 044/125] Cleanup Fix spelling, and remove unneeded comment --- resources/resources_en.properties | 2 +- src/ui/PingWidget.java | 6 ++---- src/util/ACLIManager.java | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/resources/resources_en.properties b/resources/resources_en.properties index 1352896..61e9856 100644 --- a/resources/resources_en.properties +++ b/resources/resources_en.properties @@ -1,5 +1,5 @@ version_id =1.0.2 -release_date =2021-02-16 +release_date =2021-02-17 image_folder =resources/images/ patch_folder =resources/images/nP/ font_folder =resources/fonts/ diff --git a/src/ui/PingWidget.java b/src/ui/PingWidget.java index 1ca1f16..78051d6 100644 --- a/src/ui/PingWidget.java +++ b/src/ui/PingWidget.java @@ -42,9 +42,9 @@ public class PingWidget extends UIWidget { * @param ctx - The application context */ public PingWidget(Context ctx) { - super(ctx, "Utrasound"); + super(ctx, "Ultrasound"); - curSensorVals = new int[5]; + curSensorVals = new int[] {0, 0, 0, 0, 0}; sensorOuterPanel = new JPanel(); sensorOuterPanel.setMinimumSize(new Dimension(100, 60)); @@ -138,8 +138,6 @@ public void update(int index, int data) { return; } -// System.err.println("Sensor Data - [Index: " + index + ", Data: " + data + "]"); - curSensorVals[index] = data; } diff --git a/src/util/ACLIManager.java b/src/util/ACLIManager.java index 08e73e5..a2ba396 100644 --- a/src/util/ACLIManager.java +++ b/src/util/ACLIManager.java @@ -9,7 +9,7 @@ * @author Chris Park @ Infinetix Corp * Date: 2-3-2021 * Description: Singleton class used to configure the arduino-cli and upload - * APM sketches from the dashboard. + * APM sketches from the dashboard using cmd call subprocesses. */ public class ACLIManager { private static ACLIManager aclimInstance = null; From f16e2ba04600e3d4e8734b55c38e9c8bed4a7a7e Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 18 Feb 2021 10:29:08 -0800 Subject: [PATCH 045/125] Add arduino-cli to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e63201c..41bd68e 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ resources/tile_servers.properties .classpath .settings/org.eclipse.wst.sse.core.prefs boardlist.txt +arduino-cli.exe From e2b56f1b6ee67403972d614213771486de7b3271 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Fri, 19 Feb 2021 12:38:12 -0800 Subject: [PATCH 046/125] Update build.properties --- build.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.properties b/build.properties index 6317a78..16913af 100644 --- a/build.properties +++ b/build.properties @@ -16,5 +16,6 @@ versionDateFile=resources/resources_en.properties MessageDbFile=resources/stateMessageDb.xml DroneLibsDir=../MINDS-i-Drone mainclass=com.Dashboard -pythonDir=C:/Program Files (x86)/Python38-32/python.exe +pythonDir=C:/Users/CPark/AppData/Local/Programs/Python/Python39/python.exe +pythonDirOld=C:/Program Files (x86)/Python38-32/python.exe powerShellDir=C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe From 5e6b97214701d7fbe0ab202a05f27b052b8b0b5b Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Mon, 22 Feb 2021 09:54:33 -0800 Subject: [PATCH 047/125] Update DashboardInstallerScript.iss --- DashboardInstallerScript.iss | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/DashboardInstallerScript.iss b/DashboardInstallerScript.iss index 37978cd..583ef7a 100644 --- a/DashboardInstallerScript.iss +++ b/DashboardInstallerScript.iss @@ -1,9 +1,11 @@ #define MyAppName "MINDS-i Dashboard" -#define MyAppVersion "1.0.2" +#define MyAppVersion "1.0.3" #define MyAppPublisher "MINDS-i Education" #define MyAppURL "https://mindsieducation.com/" #define MyAppExeName "Dashboard.exe" #define TelemDrivers "RadioDiversv2.12.06WHQL_Centified.exe" +#define ArduinoCLI "arduino-cli.exe" +#define ReleaseDir "C:\Archives\Working Directory\MINDS-i\Dashboard Release" [Setup] AppId={{C4B2ECC1-960A-4137-BFD2-23CD33DBC5B1} @@ -16,7 +18,7 @@ AppSupportURL={#MyAppURL} AppUpdatesURL={#MyAppURL} DefaultDirName={pf}\{#MyAppName} DefaultGroupName={#MyAppName} -OutputDir=C:\Archives\WorkingDirectory\MINDS-i\Dashboard Installer +OutputDir=C:\Archives\Working Directory\MINDS-i\Dashboard Installer OutputBaseFilename=DashboardSetup Compression=lzma SolidCompression=yes @@ -28,10 +30,11 @@ Name: "english"; MessagesFile: "compiler:Default.isl" Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; [Files] -Source: "C:\Archives\WorkingDirectory\MINDS-i\Dashboard Release\Dashboard.exe"; DestDir: "{app}"; Flags: ignoreversion -Source: "C:\Archives\WorkingDirectory\MINDS-i\Dashboard Release\DashBoard.jar"; DestDir: "{app}"; Flags: ignoreversion -Source: "C:\Archives\WorkingDirectory\MINDS-i\Dashboard Release\RadioDiversv2.12.06WHQL_Centified.exe"; DestDir: "{app}"; Flags: ignoreversion -Source: "C:\Archives\WorkingDirectory\MINDS-i\Dashboard Release\resources\*"; DestDir: "{app}\resources"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "{#ReleaseDir}\Dashboard.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#ReleaseDir}\DashBoard.jar"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#ReleaseDir}\RadioDiversv2.12.06WHQL_Centified.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#ReleaseDir}\resources\*"; DestDir: "{app}\resources"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "{#ReleaseDir}\arduino-cli.exe"; DestDir: "{app}"; Flags: ignoreversion [Icons] Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" From 3f5a8b7cf9ebe2c56123fe2a5a202420ecc5d3d6 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 25 Feb 2021 08:23:24 -0800 Subject: [PATCH 048/125] Updates to PingWidget. General Comment/Print Cleanup -Updated Warning level detection logic and target thresholds. -Removed the case where no meter is drawn, There will always be a green meter now. -Added error printing for ArduinoCLI Sketch upload process -Removed extraneous and solved TODOs --- src/Dashboard.java | 1 - src/serial/SerialParser.java | 2 +- src/ui/PingWidget.java | 47 ++++++++++++------------------------ src/ui/StateWidget.java | 8 +----- src/ui/UIConfigPanel.java | 10 ++++---- 5 files changed, 23 insertions(+), 45 deletions(-) diff --git a/src/Dashboard.java b/src/Dashboard.java index a50dfec..c40a4c3 100644 --- a/src/Dashboard.java +++ b/src/Dashboard.java @@ -250,7 +250,6 @@ private JPanel createRightPanel() { context, Serial.ROLL, context.theme.roverFront)); } - //TODO - CP - Relocate this watermark to a corner position //Watermark Image BufferedImage watermark = context.theme.logoWatermark; JLabel watermarkLabel = new JLabel(new ImageIcon(watermark)); diff --git a/src/serial/SerialParser.java b/src/serial/SerialParser.java index f5ec290..c835224 100644 --- a/src/serial/SerialParser.java +++ b/src/serial/SerialParser.java @@ -196,7 +196,7 @@ else if (a == Serial.SYNC_RESPOND) { //resync seen } break; - case Serial.STATE_WORD: //Subtype + case Serial.STATE_WORD: context.dash.stateWidget.update(a,b); break; } diff --git a/src/ui/PingWidget.java b/src/ui/PingWidget.java index 78051d6..88de99f 100644 --- a/src/ui/PingWidget.java +++ b/src/ui/PingWidget.java @@ -20,7 +20,7 @@ public class PingWidget extends UIWidget { //Constants protected static final int NUM_SENSORS = 5; protected static final int UPDATE_DELAY_MS = 500; - protected static final int[] WARN_LEVELS = {1500, 2400, 4500, 2400, 1500}; + protected static final int[] WARN_LEVELS = {2000, 3200, 6000, 3200, 2000}; protected static final int[] BLOCK_LEVELS = {1000, 1600, 3000, 1600, 1000}; //Ping Sensor Meters @@ -167,82 +167,67 @@ protected void updateMeters() { constraints.gridy = 0; constraints.gridx = 0; //Warning High - if(curSensorVals[0] <= 1500) { + if(curSensorVals[0] <= BLOCK_LEVELS[0]) { sensorOuterPanel.add(sensorMeters.get(0).get(3), constraints); } //Warning Medium - else if (curSensorVals[0] <= 3000) { + else if (curSensorVals[0] <= WARN_LEVELS[0]) { sensorOuterPanel.add(sensorMeters.get(0).get(2), constraints); } //Warning Low - else if (curSensorVals[0] <= 4500) { - sensorOuterPanel.add(sensorMeters.get(0).get(1), constraints); - } //Warning None else { - sensorOuterPanel.add(sensorMeters.get(0).get(0), constraints); + sensorOuterPanel.add(sensorMeters.get(0).get(1), constraints); } //Meter Two constraints.gridx = 1; //Warning High - if(curSensorVals[1] <= 2400) { + if(curSensorVals[1] <= BLOCK_LEVELS[1]) { sensorOuterPanel.add(sensorMeters.get(1).get(3), constraints); } //Warning Medium - else if (curSensorVals[1] <= 4800) { + else if (curSensorVals[1] <= WARN_LEVELS[1]) { sensorOuterPanel.add(sensorMeters.get(1).get(2), constraints); } //Warning Low - else if (curSensorVals[1] <= 9600) { - sensorOuterPanel.add(sensorMeters.get(1).get(1), constraints); - } //Warning None else { - sensorOuterPanel.add(sensorMeters.get(1).get(0), constraints); + sensorOuterPanel.add(sensorMeters.get(1).get(1), constraints); } //Meter Three constraints.gridx = 2; //Warning High - if(curSensorVals[2] <= 4500) { + if(curSensorVals[2] <= BLOCK_LEVELS[2]) { sensorOuterPanel.add(sensorMeters.get(2).get(3), constraints); } //Warning Medium - else if (curSensorVals[2] <= 9000) { + else if (curSensorVals[2] <= WARN_LEVELS[2]) { sensorOuterPanel.add(sensorMeters.get(2).get(2), constraints); } //Warning Low - else if (curSensorVals[2] <= 18000) { - sensorOuterPanel.add(sensorMeters.get(2).get(1), constraints); - } //Warning None else { - sensorOuterPanel.add(sensorMeters.get(2).get(0), constraints); + sensorOuterPanel.add(sensorMeters.get(2).get(1), constraints); } //Meter Four constraints.gridx = 3; //Warning High - if(curSensorVals[3] <= 2400) { + if(curSensorVals[3] <= BLOCK_LEVELS[3]) { sensorOuterPanel.add(sensorMeters.get(3).get(3), constraints); } //Warning Medium - else if (curSensorVals[3] <= 4800) { + else if (curSensorVals[3] <= WARN_LEVELS[3]) { sensorOuterPanel.add(sensorMeters.get(3).get(2), constraints); } //Warning Low - else if (curSensorVals[3] <= 9600) { - sensorOuterPanel.add(sensorMeters.get(3).get(1), constraints); - } //Warning None else { - sensorOuterPanel.add(sensorMeters.get(3).get(0), constraints); + sensorOuterPanel.add(sensorMeters.get(3).get(1), constraints); } //Meter Five constraints.gridx = 4; //Warning High - if(curSensorVals[4] <= 1500) { + if(curSensorVals[4] <= BLOCK_LEVELS[4]) { sensorOuterPanel.add(sensorMeters.get(4).get(3), constraints); } //Warning Medium - else if (curSensorVals[4] <= 3000) { + else if (curSensorVals[4] <= WARN_LEVELS[4]) { sensorOuterPanel.add(sensorMeters.get(4).get(2), constraints); } //Warning Low - else if (curSensorVals[4] <= 4500) { + else { sensorOuterPanel.add(sensorMeters.get(4).get(1), constraints); } //Warning None - else { - sensorOuterPanel.add(sensorMeters.get(4).get(0), constraints); - } } /** diff --git a/src/ui/StateWidget.java b/src/ui/StateWidget.java index c875e23..31231d7 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/StateWidget.java @@ -236,13 +236,7 @@ private void setAutoState(byte substate) { case Serial.AUTO_STATE_AVOID: fmt = String.format(fmtStr, "Avoid"); finalWidth = Math.min(fmt.length(), LINE_WIDTH); - - //TODO - CP - Currently the auto state flips between full and... - //avoid very quickly when a obstacle is detected by USound sensors. - //Need to talk with Ben about the best approach to smooth this out - //so the bar updates meaningfully and gives some time to be read. - //As is now it flip flops rapidly. Not sure if I should do this on - //the UI layer or not but I suspect so. + if(statusBar.getState() != StatusBarWidget.StatusType.CAUTION) { statusBar.update(StatusBarWidget.StatusType.CAUTION); } diff --git a/src/ui/UIConfigPanel.java b/src/ui/UIConfigPanel.java index 383619e..4318960 100644 --- a/src/ui/UIConfigPanel.java +++ b/src/ui/UIConfigPanel.java @@ -196,7 +196,7 @@ public void actionPerformed(ActionEvent e) { "Error", JOptionPane.ERROR_MESSAGE); return; } -// System.err.println("Successful Core Install"); + System.err.println("Successful Core Install"); //Build list of available/connected boards @@ -208,7 +208,7 @@ public void actionPerformed(ActionEvent e) { "Error", JOptionPane.ERROR_MESSAGE); return; } -// System.err.println("Successful Board List Generation"); + System.err.println("Successful Board List Generation"); //Parse core and port info result = ACLIManager.getInstance().execute( @@ -219,7 +219,7 @@ public void actionPerformed(ActionEvent e) { "Error", JOptionPane.ERROR_MESSAGE); return; } -// System.err.println("Successful Board List Parsing"); + System.err.println("Successful Board List Parsing"); //Compile selected sketch result = ACLIManager.getInstance().execute( @@ -231,7 +231,7 @@ public void actionPerformed(ActionEvent e) { "Error", JOptionPane.ERROR_MESSAGE); return; } -// System.err.println("Successful Sketch Compile"); + System.err.println("Successful Sketch Compile"); //Upload selected sketch result = ACLIManager.getInstance().execute( @@ -243,7 +243,7 @@ public void actionPerformed(ActionEvent e) { "Error", JOptionPane.ERROR_MESSAGE); return; } -// System.err.println("Successful Sketch Upload"); + System.err.println("Successful Sketch Upload"); JOptionPane.showMessageDialog(UIConfigPanel.this, "Sketch uploaded successfully.", From d69f8ea9439ca44a80289c3c60e7f8770f88fcf4 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Mon, 1 Mar 2021 15:54:35 -0800 Subject: [PATCH 049/125] Update gitignore -Removed ground/air settings files from gitignore for now. --- .gitignore | 2 -- resources/airSettings.xml | 34 +++++++++++++++++++++++++++++++ resources/groundSettings.xml | 27 ++++++++++++++++++++++++ resources/resources_en.properties | 4 ++-- src/ui/DataWindow.java | 27 +++++++++++++----------- 5 files changed, 78 insertions(+), 16 deletions(-) create mode 100644 resources/airSettings.xml create mode 100644 resources/groundSettings.xml diff --git a/.gitignore b/.gitignore index 41bd68e..0382d53 100644 --- a/.gitignore +++ b/.gitignore @@ -14,8 +14,6 @@ graph.jar Dashboard.zip test.png resources/stateMessageDb.xml -resources/airSettings.xml -resources/groundSettings.xml sourceExtraction/test/ sourceExtraction/__pycache__/ DashBoard.zip diff --git a/resources/airSettings.xml b/resources/airSettings.xml new file mode 100644 index 0000000..55e0c41 --- /dev/null +++ b/resources/airSettings.xml @@ -0,0 +1,34 @@ + Period in milliseconds between reading the imu, calculating orientation, and sending a signal to the ESC's<br> This value should be between 5000 (200Hz) and 10000(100Hz) <br> Higher speeds will decrease the processing time left for other tasks, but could lead to a more stable flight + Factor used during sensor update step. Should be between 0.0 and 1.0 A value of 0.0 flies completely based on the best estimate and gyroscope As the value approaches 1.0, the quad increasingly uses the accel measurement to inform pitch/roll. + Factor used during sensor update step. Should be between 0.0 and 1.0 The closer to 1 it is, the larger impact the magnetometer has on the aircraft's yaw estimate + Attitude Stabilization P term<br> Control proportional to current error.<br> Generally the main driver of PID control.<br> Higher P makes reaction quicker, but increases overshoot and degrades stability. + Attitude Stabilization I term<br> Used to eliminate steady state error, too much I can increase overshoot and degrade stability. + Attitude Stabilization D term<br> D Will dampen the output by predicting the future quadcopter position with linear extrapolation.<br> It will decrease overshoot and decrease settling time, but can cause new oscillations if set too high. + P term on attitude Velocity control loop <br> Higher values will make stabilization more aggressive. + I term on attitude Velocity control loop <br> Higher values increase response to drifting and unevent weight <br>\ Too high can cause instability and oscillations + D term on attitude Velocity control loop <br> Can be used to dampen oscillations and increase P's ceiling + Yaw Stabilization P term<br> + Yaw Stabilization I term<br> + Yaw Stabilization D term<br> + Yaw stabilization VP term<br> + Yaw stabilization VI term<br> + Yaw stabilization VD term<br> + Raw output throttle necessary to hover (used for throttle stick centering)<br> Used in the RC radio throttle stick curve equation. When the throttle stick in at 50%, This is what the quad's final output throttle will be at. + Affects radio throttle curve linearity<br> A Value of 0.5 is as linear as possible around the hover throttle<br> A Value of 0.0 is heavily curved for fine control around the hover point<br> A value of 1.0 is heavily curved for more sensitivity<br> A value slightly under 0.5 tends to work best + The maximum rate at which the yaw stick can adjust the yaw setpoint in half turns per second + The maximum rate at which the throttle stick can adjust the altitude hold setpoint in feet per second + The altitude estimate's sensitivity to changes in barometer readings 1.0 implies the altitude estimate is the exact barometer value 0.0 implies the altitude estimate is not effected by the barometer at all + The vertical velocity estimate's sensitivity 1.0 implies the vertical velocity estimate updates rapidly 0.0 implies the vertical velocity estimate never changes + How powerful the quadcopter's responses to unwanted changes in altitude are + How much the quadcopter's vertical velocity impacts its altitude hold control + How much the quadcopter's integrated altitude error contributes to its overall altitude corrections + Position hold P term<br> + Position hold I Term<br> + Position hold D term + Maximum over-ground travel velocity<br> + Minimum away from desired position that results in flying towards the target at the maximum allowed velocity + At what voltage to consider the quadcopter low on battery + The magnetic declination in degrees of the area the quad will be flying in + Set to 1 to enable gps loitering when flying in assisted mode with the pitch/roll commands centered. Set to 0 to disable gps loitering; the pilot retains complete control of pitch and roll when in assisted mode, with the processor only stabilizing the altitude autonomously + The desired descent rate in feet per second for the quadcopter to fall at when auto landing because of a radio signall loss + \ No newline at end of file diff --git a/resources/groundSettings.xml b/resources/groundSettings.xml new file mode 100644 index 0000000..3dcdd61 --- /dev/null +++ b/resources/groundSettings.xml @@ -0,0 +1,27 @@ + + Defines how strongly the rover should attempt to return to the original course between waypoints, verses the direct path from its current location to the target<br> + The number of degrees that rover will turn its wheels when it needs to to turn its most extreme amount + Switches between arctangent of error steering (0) <br> square of error steering (1) <br> and proportional to error steering (2) + Multiplier that determines how aggressively to steer + Minimum forward driving speed in MPH + Maximum forward driving speed in MPH + How far to turn the wheels when backing away from an obstacle + Speed in MPH to drive in reverse + Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles + Time in milliseconds to coast before reversing when an obstacle is encountered + Minimum time in milliseconds to reverse away from an obstacle + P term in cruise control PID loop + I term in cruise control PID loop + D term in cruise control PID loop + Tire Diameter in inches, used to calculate MPH + Center point in degrees corresponding to driving straight + Radius centered at the waypoint where target is determined to be met + Radius center at waypoint where the approach flag is set + + + + + + + + \ No newline at end of file diff --git a/resources/resources_en.properties b/resources/resources_en.properties index 61e9856..df3b630 100644 --- a/resources/resources_en.properties +++ b/resources/resources_en.properties @@ -1,5 +1,5 @@ -version_id =1.0.2 -release_date =2021-02-17 +version_id =1.0.3 +release_date =2021-03-01 image_folder =resources/images/ patch_folder =resources/images/nP/ font_folder =resources/fonts/ diff --git a/src/ui/DataWindow.java b/src/ui/DataWindow.java index aacec9b..41861df 100644 --- a/src/ui/DataWindow.java +++ b/src/ui/DataWindow.java @@ -32,6 +32,9 @@ public class DataWindow implements ActionListener { private static final Dimension descriptionMin = new Dimension(300, 80); private static final Dimension descriptionPref= new Dimension(300, 200); + private JFrame frame; + private JPanel mainPanel; + private JTable telTable, setTable; private ColumnTableModel setModel; private ColumnTableModel telModel; @@ -40,17 +43,12 @@ public class DataWindow implements ActionListener { private JPanel logPanel; private JTextField logInput; private JTextComponent descriptionBox; - - private JFrame frame; public DataWindow(Context cxt) { context = cxt; frame = new JFrame("Telemetry"); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.setSize(WINDOW_X,WINDOW_Y); - JPanel panel = new JPanel(); - panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); - frame.addWindowListener(new java.awt.event.WindowAdapter() { @Override public void windowClosing(java.awt.event.WindowEvent windowEvent) { @@ -58,6 +56,9 @@ public void windowClosing(java.awt.event.WindowEvent windowEvent) { onClose(); } }); + + mainPanel = new JPanel(); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS)); final SettingList settingList = context.settingList; @@ -126,6 +127,7 @@ public void setValueAt(String val, int row) { ; } }); + settings.add( new TableColumn() { public String getName() { return "Setting"; @@ -147,7 +149,8 @@ public void setValueAt(String val, int row) { Float newVal = Float.valueOf((String)val); if(settingList.get(row).outsideOfBounds(newVal)) { JFrame mf = new JFrame("Warning"); - JOptionPane.showMessageDialog(mf, "Caution: new value is outside of logical bounds"); + JOptionPane.showMessageDialog( + mf, "Caution: new value is outside of logical bounds"); } settingList.pushSetting(row,newVal); } @@ -203,13 +206,13 @@ public void valueChanged(ListSelectionEvent event) { descriptionBox = dBox; constructLogPane(); - panel.add(logPanel); - panel.add(telScroll); - panel.add(setScroll); - panel.add(descriptionBox); - panel.add(Box.createVerticalGlue()); + mainPanel.add(logPanel); + mainPanel.add(telScroll); + mainPanel.add(setScroll); + mainPanel.add(descriptionBox); + mainPanel.add(Box.createVerticalGlue()); - frame.add(panel); + frame.add(mainPanel); frame.pack(); frame.setVisible(true); startUpdateTimer(); From 378a36787a1eaac934e84ed93aac7a4310dead5d Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 3 Mar 2021 08:20:07 -0800 Subject: [PATCH 050/125] Minor readability/formatting update --- src/table/ColumnTableModel.java | 9 +++++++++ src/ui/DataWindow.java | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/table/ColumnTableModel.java b/src/table/ColumnTableModel.java index aa7e283..38e83b7 100644 --- a/src/table/ColumnTableModel.java +++ b/src/table/ColumnTableModel.java @@ -10,14 +10,18 @@ public class ColumnTableModel extends AbstractTableModel { private List> columns; + public ColumnTableModel(List> cList) { columns = cList; } + public int getColumnCount() { return columns.size(); } + public int getRowCount() { int rowCount = Integer.MAX_VALUE; + Iterator itr = columns.iterator(); while(itr.hasNext()) { TableColumn col = (TableColumn) itr.next(); @@ -25,18 +29,23 @@ public int getRowCount() { } return rowCount; } + public String getColumnName(int col) { return columns.get(col).getName(); } + public Object getValueAt(int row, int col) { return columns.get(col).getValueAt(row); } + public Class getColumnClass(int col) { return columns.get(col).getDataClass(); } + public boolean isCellEditable(int row, int col) { return columns.get(col).isRowEditable(row); } + public void setValueAt(Object value, int row, int col) { setValueAtHelper(columns.get(col), value, row); fireTableCellUpdated(row, col); diff --git a/src/ui/DataWindow.java b/src/ui/DataWindow.java index 41861df..741d352 100644 --- a/src/ui/DataWindow.java +++ b/src/ui/DataWindow.java @@ -170,6 +170,9 @@ public void setValueAt(String val, int row) { setTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); setTable.setFillsViewportHeight(true); + //setTable.setDefaultRenderer(Class type, new Renderer); + //setTable.setDefaultEditor(Class type, new Editor); + telScroll.setMaximumSize( telemBoxMax); telScroll.setPreferredSize(telemBoxPref); telScroll.setMinimumSize( telemBoxPref); @@ -217,9 +220,11 @@ public void valueChanged(ListSelectionEvent event) { frame.setVisible(true); startUpdateTimer(); } + private void onClose() { if(update != null) update.cancel(); } + private void constructLogPane() { logPanel = new JPanel(); logPanel.setLayout(new FlowLayout()); @@ -249,6 +254,7 @@ private void setDetail(int row) { } if(descriptionBox != null) descriptionBox.setText(detail.toString()); } + private void startUpdateTimer() { update = new java.util.Timer(); update.scheduleAtFixedRate(new TimerTask() { @@ -264,15 +270,19 @@ public void run() { } }, PERIOD, PERIOD); } + public void actionPerformed(ActionEvent evt) { if(logInput == null) return; - String inputText = logInput.getText(); + int input; + String inputText = logInput.getText(); + try { input = Integer.parseInt(inputText); logInput.setText(Integer.toString(input)); context.telemLog.setPeriod(input); - } catch (NumberFormatException e) { + } + catch (NumberFormatException e) { logInput.setText(Integer.toString(context.telemLog.getPeriod())); } } From 897c6a94add690f99f6f4561b9a1455c62251a54 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 3 Mar 2021 17:27:47 -0800 Subject: [PATCH 051/125] Add to package structures, added telemetry ui classes -Added ui.widgets and ui.telemetry package folders to help break up large collections of classes. -Added new telemetry classes (empty for now) for use in Data Window/Table rework --- src/ui/telemetry/TelemetryDataFieldPanel.java | 22 +++++++++++++++++++ src/ui/telemetry/TelemetryDataWindow.java | 14 ++++++++++++ src/ui/{ => widgets}/AngleWidget.java | 2 +- src/ui/{ => widgets}/HorizonWidgets.java | 2 +- src/ui/{ => widgets}/PingWidget.java | 2 +- src/ui/{ => widgets}/StateWidget.java | 2 +- src/ui/{ => widgets}/StatusBarWidget.java | 2 +- src/ui/{ => widgets}/TelemetryDataWidget.java | 2 +- src/ui/{ => widgets}/UIWidget.java | 2 +- 9 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 src/ui/telemetry/TelemetryDataFieldPanel.java create mode 100644 src/ui/telemetry/TelemetryDataWindow.java rename src/ui/{ => widgets}/AngleWidget.java (99%) rename src/ui/{ => widgets}/HorizonWidgets.java (98%) rename src/ui/{ => widgets}/PingWidget.java (99%) rename src/ui/{ => widgets}/StateWidget.java (99%) rename src/ui/{ => widgets}/StatusBarWidget.java (99%) rename src/ui/{ => widgets}/TelemetryDataWidget.java (99%) rename src/ui/{ => widgets}/UIWidget.java (98%) diff --git a/src/ui/telemetry/TelemetryDataFieldPanel.java b/src/ui/telemetry/TelemetryDataFieldPanel.java new file mode 100644 index 0000000..377584e --- /dev/null +++ b/src/ui/telemetry/TelemetryDataFieldPanel.java @@ -0,0 +1,22 @@ +package com.ui.telemetry; + +import javax.swing.*; + +/** + * @author Chris Park @ Infinetix Corp. + * Date: 3-3-21 + * Description: Custom JPanel designed to be used within a table view, allowing for control + * of configurable telemetry settings using JSliders. + */ +public class TelemetryDataFieldPanel extends JPanel { + + //Visual Components + public JPanel panel; + public JLabel telemName; + public JTextField telemValField; + public JSlider telemValSlider; + + public TelemetryDataFieldPanel() { + + } +} diff --git a/src/ui/telemetry/TelemetryDataWindow.java b/src/ui/telemetry/TelemetryDataWindow.java new file mode 100644 index 0000000..a1c127f --- /dev/null +++ b/src/ui/telemetry/TelemetryDataWindow.java @@ -0,0 +1,14 @@ +package com.ui.telemetry; + +/** + * @author Chris Park @ Infinetix Corp + * Date: 3-3-21 + * Description: + */ +public class TelemetryDataWindow { + + + public TelemetryDataWindow() { + + } +} diff --git a/src/ui/AngleWidget.java b/src/ui/widgets/AngleWidget.java similarity index 99% rename from src/ui/AngleWidget.java rename to src/ui/widgets/AngleWidget.java index 825818c..557aa66 100644 --- a/src/ui/AngleWidget.java +++ b/src/ui/widgets/AngleWidget.java @@ -1,4 +1,4 @@ -package com.ui; +package com.ui.widgets; import com.telemetry.TelemetryListener; import java.awt.*; diff --git a/src/ui/HorizonWidgets.java b/src/ui/widgets/HorizonWidgets.java similarity index 98% rename from src/ui/HorizonWidgets.java rename to src/ui/widgets/HorizonWidgets.java index 7494ac8..5f7d561 100644 --- a/src/ui/HorizonWidgets.java +++ b/src/ui/widgets/HorizonWidgets.java @@ -1,4 +1,4 @@ -package com.ui; +package com.ui.widgets; import com.telemetry.TelemetryListener; import com.ui.ninePatch.*; diff --git a/src/ui/PingWidget.java b/src/ui/widgets/PingWidget.java similarity index 99% rename from src/ui/PingWidget.java rename to src/ui/widgets/PingWidget.java index 88de99f..01147e9 100644 --- a/src/ui/PingWidget.java +++ b/src/ui/widgets/PingWidget.java @@ -1,4 +1,4 @@ -package com.ui; +package com.ui.widgets; import java.util.*; import java.awt.*; diff --git a/src/ui/StateWidget.java b/src/ui/widgets/StateWidget.java similarity index 99% rename from src/ui/StateWidget.java rename to src/ui/widgets/StateWidget.java index 31231d7..c843ee3 100644 --- a/src/ui/StateWidget.java +++ b/src/ui/widgets/StateWidget.java @@ -1,4 +1,4 @@ -package com.ui; +package com.ui.widgets; import java.util.*; import java.awt.*; diff --git a/src/ui/StatusBarWidget.java b/src/ui/widgets/StatusBarWidget.java similarity index 99% rename from src/ui/StatusBarWidget.java rename to src/ui/widgets/StatusBarWidget.java index b696c77..3a8ac1d 100644 --- a/src/ui/StatusBarWidget.java +++ b/src/ui/widgets/StatusBarWidget.java @@ -1,4 +1,4 @@ -package com.ui; +package com.ui.widgets; import java.util.*; import java.awt.*; diff --git a/src/ui/TelemetryDataWidget.java b/src/ui/widgets/TelemetryDataWidget.java similarity index 99% rename from src/ui/TelemetryDataWidget.java rename to src/ui/widgets/TelemetryDataWidget.java index cf0639f..9821ed6 100644 --- a/src/ui/TelemetryDataWidget.java +++ b/src/ui/widgets/TelemetryDataWidget.java @@ -1,4 +1,4 @@ -package com.ui; +package com.ui.widgets; import com.Context; import com.ui.UIWidget; diff --git a/src/ui/UIWidget.java b/src/ui/widgets/UIWidget.java similarity index 98% rename from src/ui/UIWidget.java rename to src/ui/widgets/UIWidget.java index d450166..c4c122c 100644 --- a/src/ui/UIWidget.java +++ b/src/ui/widgets/UIWidget.java @@ -1,4 +1,4 @@ -package com.ui; +package com.ui.widgets; import com.Context; From 6af14b84c3f0e3b4f83e66da499c7ed3a9f2f067 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 11 Mar 2021 08:48:57 -0800 Subject: [PATCH 052/125] Intermediate check in for rework of Data window Various threads of work happening concurrently: Added: - Custom slider control to use in updated tables. - New table factory class to handle the specifics of table creation to separate this logic from the data window and its UI rendering. - New rework of DataWindow (TelemetryDataWindow) with cleaner implementation and design. - Beginnings of a custom renderer and display widget for slider information. These are rough starts for now and may change drastically to fit the new table and data display design paradigms. --- resources/groundSettings.xml | 22 +- src/table/TableFactory.java | 191 ++++++++++++++++++ src/ui/DataWindow.java | 15 +- src/ui/FloatJSlider.java | 33 +++ src/ui/telemetry/TelemetryDataFieldPanel.java | 61 +++++- .../TelemetryDataFieldPanelRenderer.java | 27 +++ src/ui/telemetry/TelemetryDataWindow.java | 161 ++++++++++++++- src/ui/{ => widgets}/TelemetryWidget.java | 2 +- 8 files changed, 486 insertions(+), 26 deletions(-) create mode 100644 src/table/TableFactory.java create mode 100644 src/ui/FloatJSlider.java create mode 100644 src/ui/telemetry/TelemetryDataFieldPanelRenderer.java rename src/ui/{ => widgets}/TelemetryWidget.java (99%) diff --git a/resources/groundSettings.xml b/resources/groundSettings.xml index 3dcdd61..53e25ed 100644 --- a/resources/groundSettings.xml +++ b/resources/groundSettings.xml @@ -2,18 +2,18 @@ Defines how strongly the rover should attempt to return to the original course between waypoints, verses the direct path from its current location to the target<br> The number of degrees that rover will turn its wheels when it needs to to turn its most extreme amount Switches between arctangent of error steering (0) <br> square of error steering (1) <br> and proportional to error steering (2) - Multiplier that determines how aggressively to steer - Minimum forward driving speed in MPH - Maximum forward driving speed in MPH + Multiplier that determines how aggressively to steer + Minimum forward driving speed in MPH + Maximum forward driving speed in MPH How far to turn the wheels when backing away from an obstacle - Speed in MPH to drive in reverse - Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles - Time in milliseconds to coast before reversing when an obstacle is encountered - Minimum time in milliseconds to reverse away from an obstacle - P term in cruise control PID loop - I term in cruise control PID loop - D term in cruise control PID loop - Tire Diameter in inches, used to calculate MPH + Speed in MPH to drive in reverse + Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles + Time in milliseconds to coast before reversing when an obstacle is encountered + Minimum time in milliseconds to reverse away from an obstacle + P term in cruise control PID loop + I term in cruise control PID loop + D term in cruise control PID loop + Tire Diameter in inches, used to calculate MPH Center point in degrees corresponding to driving straight Radius centered at the waypoint where target is determined to be met Radius center at waypoint where the approach flag is set diff --git a/src/table/TableFactory.java b/src/table/TableFactory.java new file mode 100644 index 0000000..593d229 --- /dev/null +++ b/src/table/TableFactory.java @@ -0,0 +1,191 @@ +package com.table; + +import com.Context; +import com.remote.*; +import com.table.*; +import com.ui.telemetry.*; + +import java.io.*; +import java.util.*; +import javax.swing.*; +import javax.swing.table.*; + +/** + * @author Chris Park @ Infinetix Corp + * Date: 3-4-21 + * Description: Factory class used to encapsulate the creation of + * tables used for displaying telemetry and configurable vehicle settings. + */ +public class TableFactory { + + //Constructor is private here to prevent object instantiation. + private TableFactory() {} + + public enum TableType {Telemetry, Settings} + + public static JTable createTable(TableType type, Context context) { + switch(type) { + case Telemetry: + return buildTelemetryTable(context); + case Settings: + return buildSettingsTable(context); + default: + System.err.println("TableFactory Error - Unknown table type"); + } + + return null; + } + + /** + * Creates a vehicle settings table with column layout specified by + * the applied table model and sizing settings. + * @param context - The application context + * @return - A configured vehicle settings table + */ + private static JTable buildSettingsTable(Context context) { + JTable table; + ColumnTableModel model; + SettingList settingList = context.settingList; + ArrayList> columns = new ArrayList>(); + + columns.add( new TableColumn() { + public String getName() { + return "name"; + } + + public String getValueAt(int row) { + if(row < settingList.size()) + return settingList.get(row).getName(); + return "#"+row; + } + + public int getRowCount() { + return settingList.size(); + } + + public Class getDataClass() { + return String.class; + } + + public boolean isRowEditable(int row) { + return false; + } + + public void setValueAt(String val, int row) { + ; + } + }); + + columns.add( new TableColumn() { + public String getName() { + return "Setting"; + } + + public String getValueAt(int row) { + float val = settingList.get(row).getVal(); + return " "+val; + } + + public int getRowCount() { + return settingList.size(); + } + + public Class getDataClass() { + return String.class; + } + + public boolean isRowEditable(int row) { + return true; + } + + public void setValueAt(String val, int row) { + Float newVal = Float.valueOf((String)val); + if(settingList.get(row).outsideOfBounds(newVal)) { + JFrame mf = new JFrame("Warning"); + JOptionPane.showMessageDialog( + mf, "Caution: new value is outside of logical bounds"); + } + settingList.pushSetting(row,newVal); + } + }); + + model = new ColumnTableModel(columns); + table = new JTable(model); + + table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); + table.setFillsViewportHeight(true); + + return table; + } + + /** + * Creates a telemtry data table with column layout specified by + * the applied table model and sizing settings. + * @param context - The application context + * @return - A configured telemetry data table. + */ + private static JTable buildTelemetryTable(Context context) { + JTable table; + ColumnTableModel model; + + ArrayList> columns = new ArrayList>(); + columns.add( new TableColumn() { + public String getName() { + return "name"; + } + + public String getValueAt(int row) { + return context.getTelemetryName(row); + } + + public int getRowCount() { + return context.getTelemetryCount(); + } + + public Class getDataClass() { + return String.class; + } + + public boolean isRowEditable(int row) { + return false; + } + + public void setValueAt(String val, int row) { + } + }); + + columns.add( new TableColumn() { + public String getName() { + return "Value"; + } + + public String getValueAt(int row) { + return " "+context.getTelemetry(row); + } + + public int getRowCount() { + return context.getTelemetryCount(); + } + + public Class getDataClass() { + return String.class; + } + + public boolean isRowEditable(int row) { + return false; + } + + public void setValueAt(String val, int row) { + ; + } + }); + + model = new ColumnTableModel(columns); + table = new JTable(model); + + table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); + table.setFillsViewportHeight(true); + + return table; + } +} diff --git a/src/ui/DataWindow.java b/src/ui/DataWindow.java index 741d352..7cadf98 100644 --- a/src/ui/DataWindow.java +++ b/src/ui/DataWindow.java @@ -187,11 +187,14 @@ public void setValueAt(String val, int row) { setScroll.setBorder(tableBorders); telScroll.setBorder(tableBorders); - javax.swing.table.TableColumn col; - col = telTable.getColumn(telem.get(1).getName()); - col.setPreferredWidth(1); - col = setTable.getColumn(settings.get(1).getName()); - col.setPreferredWidth(1); + //TODO - CP - How is this variable ever used? This is the only place it appears... + //This is fully qualified because it creates a naming collision with the custom + //TableColumn interface in the code base... Bad. +// javax.swing.table.TableColumn col; +// col = telTable.getColumn(telem.get(1).getName()); +// col.setPreferredWidth(1); +// col = setTable.getColumn(settings.get(1).getName()); +// col.setPreferredWidth(1); setTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent event) { @@ -199,6 +202,8 @@ public void valueChanged(ListSelectionEvent event) { } }); + ///////// + JTextPane dBox = new JTextPane(); dBox.setBorder(BorderFactory.createLineBorder(Color.gray)); dBox.setContentType("text/html"); diff --git a/src/ui/FloatJSlider.java b/src/ui/FloatJSlider.java new file mode 100644 index 0000000..f57b5c6 --- /dev/null +++ b/src/ui/FloatJSlider.java @@ -0,0 +1,33 @@ +package com.ui; + +import javax.swing.*; + +/** + * @author Chris Park @ Infinetix.com + * Date: 3-5-21 + * Description: A standard JSlider works in integer values only. This + * custom JSlider class is intended for handling float values. + */ +public class FloatJSlider extends JSlider{ + private final int scale; + + /** + * Class constructor + * @param min - Minimum value for slider range + * @param max - Maximum value for slider range + * @param value - The initial value of the slider + * @param scale - The decimal scale of used for value conversion. + */ + public FloatJSlider(int min, int max, int value, int scale) { + super(min, max, value); + this.scale = scale; + } + + /** + * Returns the float value that corresponds to the sliders current position. + * @return - float + */ + public float getScaledValue() { + return ((float)super.getValue() / this.scale); + } +} diff --git a/src/ui/telemetry/TelemetryDataFieldPanel.java b/src/ui/telemetry/TelemetryDataFieldPanel.java index 377584e..483cbf7 100644 --- a/src/ui/telemetry/TelemetryDataFieldPanel.java +++ b/src/ui/telemetry/TelemetryDataFieldPanel.java @@ -1,6 +1,12 @@ package com.ui.telemetry; +import java.util.*; + import javax.swing.*; +import javax.swing.event.*; + +import com.remote.Setting; +import com.ui.FloatJSlider; /** * @author Chris Park @ Infinetix Corp. @@ -11,12 +17,55 @@ public class TelemetryDataFieldPanel extends JPanel { //Visual Components - public JPanel panel; - public JLabel telemName; - public JTextField telemValField; - public JSlider telemValSlider; + protected JPanel panel; + protected JLabel telemName; + protected JTextField telemValField; + protected FloatJSlider telemValSlider; + + protected Setting setting; + protected int sigFigs; + protected int conversionVal; + + public TelemetryDataFieldPanel(Setting setting) { + this.setting = setting; + + //Create label + telemName = new JLabel(setting.getDescription()); + + //Create/Set value in text field + telemValField = new JTextField(5); + telemValField.setEditable(false); + telemValField.setText(Float.toString(setting.getDefault())); + + //Creat slider and map values to it. + //Parse out the decimal places for JSlider mapping (Jslider uses ints for range). + String str = String.valueOf(setting.getDefault()); + String[] parsed = str.split("."); + + //NOTE: length vs length()... length returns the length of the array + //where as length() returns the number of characters in a string. + sigFigs = (parsed.length > 2) ? parsed[1].length() : 0; + conversionVal = (sigFigs < 0) ? (int) Math.pow(10, sigFigs) : 1; + + int min = (int) (setting.getMin() * conversionVal); + int max = (int) (setting.getMax() * conversionVal); + telemValSlider = new FloatJSlider( + min, max, (int) Math.floor(setting.getDefault()), conversionVal); + telemValSlider.setOpaque(false); + telemValSlider.setFocusable(false); + telemValSlider.getModel().addChangeListener(new ChangeListener() { + @Override public void stateChanged(ChangeEvent event) { + BoundedRangeModel model = (BoundedRangeModel) event.getSource(); + float updateVal = ((float) model.getValue() / (float) conversionVal); + + //Update text field value + telemValField.setText(Objects.toString(updateVal)); + } + }); + } - public TelemetryDataFieldPanel() { - + //TODO - CP - Update/push value change here. (this should fire from editor) + public void updateValue(Setting setting) { + } } diff --git a/src/ui/telemetry/TelemetryDataFieldPanelRenderer.java b/src/ui/telemetry/TelemetryDataFieldPanelRenderer.java new file mode 100644 index 0000000..52f225a --- /dev/null +++ b/src/ui/telemetry/TelemetryDataFieldPanelRenderer.java @@ -0,0 +1,27 @@ +package com.ui.telemetry; + +import com.remote.Setting; + +import javax.swing.JTable; +import javax.swing.table.TableCellRenderer; +import java.awt.*; + +public class TelemetryDataFieldPanelRenderer + extends TelemetryDataFieldPanel implements TableCellRenderer { + + public TelemetryDataFieldPanelRenderer(Setting setting) { + super(setting); + setName("Table.cellRenderer"); + } + + @Override + public Component getTableCellRendererComponent( + JTable table, Object value, boolean isSelected, boolean hasFocus, + int row, int col) { + if(value instanceof Setting) { + updateValue((Setting) value); + } + + return this; + } +} diff --git a/src/ui/telemetry/TelemetryDataWindow.java b/src/ui/telemetry/TelemetryDataWindow.java index a1c127f..c9d2360 100644 --- a/src/ui/telemetry/TelemetryDataWindow.java +++ b/src/ui/telemetry/TelemetryDataWindow.java @@ -1,14 +1,169 @@ package com.ui.telemetry; +import com.Context; +import com.remote.*; +import com.table.TableFactory; + +import javax.swing.*; +import javax.swing.text.*; +import javax.swing.event.ListSelectionListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.border.Border; +import javax.swing.BorderFactory; + +import java.awt.*; +import java.awt.event.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + /** * @author Chris Park @ Infinetix Corp * Date: 3-3-21 - * Description: + * Description: Application window used for displaying vehicle telemetry + * information and configurable settings. */ -public class TelemetryDataWindow { +public class TelemetryDataWindow implements ActionListener { + //Constants + private static final int UPDATE_PERIOD_MS = 200; + private static final int WINDOW_X = 300; + private static final int WINDOW_Y = 560; + + private static final Dimension TELEM_DIM_PREF = new Dimension(300, 140); + private static final Dimension TELEM_DIM_MIN = new Dimension(300, 140); + private static final Dimension TELEM_DIM_MAX = new Dimension(Integer.MAX_VALUE, 140); + private static final Dimension SETTINGS_DIM_PREF = new Dimension(300, 300); + private static final Dimension SETTINGS_DIM_MIN = new Dimension(300, 300); + private static final Dimension SETTINGS_DIM_MAX = new Dimension(Integer.MAX_VALUE, 300); + private static final Dimension DESC_DIM_MIN = new Dimension(300, 80); + private static final Dimension DESC_DIM_PREF = new Dimension(300, 200); + private static final Border TABLE_BORDERS = BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder(5, 5, 5, 5), + BorderFactory.createLineBorder(Color.BLACK)); - public TelemetryDataWindow() { + //UI Elements + private JFrame FRM_Window; + private JPanel PNL_Main; + private JTextPane TXP_Description; + private JTextComponent TXC_DescriptionBox; + private JTable TBL_Telemetry; + private JTable TBL_Settings; + private JScrollPane SCL_Telemetry; + private JScrollPane SCL_Settings; + + //Standard Vars + private Context context; + + /** + * Class constructor resposnible for intializing and creating required + * telemetry/settings tables using the factory and setting up all UI component + * visual layout. + * @param context - the application context + */ + public TelemetryDataWindow(Context context) { + this.context = context; + + //Set up window JFrame + FRM_Window = new JFrame("Telemetry"); + FRM_Window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + FRM_Window.setSize(WINDOW_X, WINDOW_Y); + FRM_Window.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent event) { + System.out.println("Data window closed"); + onClose(); + } + }); + + //Setup Description Box + TXP_Description = new JTextPane(); + TXP_Description.setBorder(BorderFactory.createLineBorder(Color.gray)); + TXP_Description.setContentType("text/html"); + TXP_Description.setMinimumSize(DESC_DIM_MIN); + TXP_Description.setPreferredSize(DESC_DIM_PREF); + TXP_Description.setOpaque(false); + TXC_DescriptionBox = TXP_Description; + + //Set up Telemetry Table and ScrollPane + /// + /// + /// + /// + + //Set up Settings Table and ScrollPane + TBL_Settings = TableFactory.createTable(TableFactory.TableType.Settings, + context); + TBL_Settings.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + public void valueChanged(ListSelectionEvent event) { + setSelectedDetails(TBL_Settings.getSelectedRow()); + } + }); + + SCL_Settings = new JScrollPane(TBL_Settings); + SCL_Settings.setMinimumSize(SETTINGS_DIM_MIN); + SCL_Settings.setMaximumSize(SETTINGS_DIM_MAX); + SCL_Settings.setPreferredSize(SETTINGS_DIM_PREF); + SCL_Settings.setBorder(TABLE_BORDERS); + + + //Set up Main JPanel + //Make sure all configuration for table elements is completed + PNL_Main = new JPanel(); + PNL_Main.setLayout(new BoxLayout(PNL_Main, BoxLayout.PAGE_AXIS)); + + //Add Finished Panel to Frame + } + + /** + * Sets the description details of the selected setting by row. + * @param row - The row of the setting that details will be displayed + * for. + */ + private void setSelectedDetails(int row) { + StringBuilder details = new StringBuilder(); + + if(row >= 0 && row < context.settingList.size()) { + Setting setting = context.settingList.get(row); + details.append("Min: "); + details.append(setting.getMin()); + details.append(" Max: "); + details.append(setting.getMax()); + details.append(" Default: "); + details.append(setting.getDefault()); + details.append("

"); + details.append(setting.getDescription()); + } + + if(TXC_DescriptionBox != null) { + TXC_DescriptionBox.setText(details.toString()); + } + } + + //TODO - CP - Finish Implementation here + public void actionPerformed(ActionEvent event) { + + } + + /** + * Brings the window frame to the front of the + * application window draw ordering. + */ + public void toFront() { + FRM_Window.toFront(); + } + + //TODO - CP - Finish Implementation here + public void onClose() { + + } + + /** + * Returns whether or not the window frame is currently + * visible. + * @return boolean + */ + public boolean getVisible() { + return FRM_Window.isVisible(); } } diff --git a/src/ui/TelemetryWidget.java b/src/ui/widgets/TelemetryWidget.java similarity index 99% rename from src/ui/TelemetryWidget.java rename to src/ui/widgets/TelemetryWidget.java index 17b6ef7..d07d37c 100644 --- a/src/ui/TelemetryWidget.java +++ b/src/ui/widgets/TelemetryWidget.java @@ -1,4 +1,4 @@ -package com.ui; +package com.ui.widgets; import com.telemetry.TelemetryListener; import com.ui.ninePatch.*; From 483038dc7f8172e9c017cbca0d63913dbcde263f Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 11 Mar 2021 11:27:30 -0800 Subject: [PATCH 053/125] Finish first pass rework of new TelemetryDataWindow Functionally complete, in testing. --- src/ui/DataWindow.java | 4 +- src/ui/telemetry/TelemetryDataWindow.java | 112 +++++++++++++++++++--- 2 files changed, 101 insertions(+), 15 deletions(-) diff --git a/src/ui/DataWindow.java b/src/ui/DataWindow.java index 7cadf98..b5bb87d 100644 --- a/src/ui/DataWindow.java +++ b/src/ui/DataWindow.java @@ -201,8 +201,6 @@ public void valueChanged(ListSelectionEvent event) { setDetail(setTable.getSelectedRow()); } }); - - ///////// JTextPane dBox = new JTextPane(); dBox.setBorder(BorderFactory.createLineBorder(Color.gray)); @@ -212,7 +210,7 @@ public void valueChanged(ListSelectionEvent event) { //dBox.setBorder(tableBorders); dBox.setOpaque(false); descriptionBox = dBox; - + constructLogPane(); mainPanel.add(logPanel); mainPanel.add(telScroll); diff --git a/src/ui/telemetry/TelemetryDataWindow.java b/src/ui/telemetry/TelemetryDataWindow.java index c9d2360..b93176f 100644 --- a/src/ui/telemetry/TelemetryDataWindow.java +++ b/src/ui/telemetry/TelemetryDataWindow.java @@ -2,14 +2,18 @@ import com.Context; import com.remote.*; +import com.serial.*; import com.table.TableFactory; +import java.util.TimerTask; + import javax.swing.*; import javax.swing.text.*; import javax.swing.event.ListSelectionListener; import javax.swing.event.ListSelectionEvent; import javax.swing.border.Border; import javax.swing.BorderFactory; +import javax.swing.table.AbstractTableModel; import java.awt.*; import java.awt.event.*; @@ -25,9 +29,10 @@ public class TelemetryDataWindow implements ActionListener { //Constants - private static final int UPDATE_PERIOD_MS = 200; - private static final int WINDOW_X = 300; - private static final int WINDOW_Y = 560; + private static final int UPDATE_PERIOD_MS = 200; + private static final int WINDOW_WIDTH = 300; + private static final int WINDOW_HEIGHT = 560; + private static final int LOG_FIELD_WIDTH = 8; private static final Dimension TELEM_DIM_PREF = new Dimension(300, 140); private static final Dimension TELEM_DIM_MIN = new Dimension(300, 140); @@ -44,6 +49,9 @@ public class TelemetryDataWindow implements ActionListener { //UI Elements private JFrame FRM_Window; private JPanel PNL_Main; + private JPanel PNL_Log; + private JLabel LBL_Log; + private JTextField TXF_Log; private JTextPane TXP_Description; private JTextComponent TXC_DescriptionBox; private JTable TBL_Telemetry; @@ -53,6 +61,7 @@ public class TelemetryDataWindow implements ActionListener { //Standard Vars private Context context; + private java.util.Timer updateTimer; /** * Class constructor resposnible for intializing and creating required @@ -66,7 +75,7 @@ public TelemetryDataWindow(Context context) { //Set up window JFrame FRM_Window = new JFrame("Telemetry"); FRM_Window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); - FRM_Window.setSize(WINDOW_X, WINDOW_Y); + FRM_Window.setSize(WINDOW_WIDTH, WINDOW_HEIGHT); FRM_Window.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent event) { @@ -85,10 +94,15 @@ public void windowClosing(WindowEvent event) { TXC_DescriptionBox = TXP_Description; //Set up Telemetry Table and ScrollPane - /// - /// - /// - /// + TBL_Telemetry = TableFactory.createTable(TableFactory.TableType.Telemetry, + context); + + SCL_Telemetry = new JScrollPane(TBL_Telemetry); + SCL_Telemetry.setMinimumSize(TELEM_DIM_MIN); + SCL_Telemetry.setMaximumSize(TELEM_DIM_MAX); + SCL_Telemetry.setPreferredSize(TELEM_DIM_PREF); + SCL_Telemetry.setBorder(TABLE_BORDERS); + //Set up Settings Table and ScrollPane TBL_Settings = TableFactory.createTable(TableFactory.TableType.Settings, @@ -104,15 +118,37 @@ public void valueChanged(ListSelectionEvent event) { SCL_Settings.setMaximumSize(SETTINGS_DIM_MAX); SCL_Settings.setPreferredSize(SETTINGS_DIM_PREF); SCL_Settings.setBorder(TABLE_BORDERS); + + //Build Log Panel + PNL_Log = new JPanel(); + PNL_Log.setLayout(new FlowLayout()); + LBL_Log = new JLabel("Set logging period (ms)"); + TXF_Log = new JTextField(); + TXF_Log.addActionListener(this); + TXF_Log.setText(Integer.toString(context.telemLog.getPeriod())); + TXF_Log.setColumns(LOG_FIELD_WIDTH); + PNL_Log.add(LBL_Log); + PNL_Log.add(TXF_Log); //Set up Main JPanel - //Make sure all configuration for table elements is completed PNL_Main = new JPanel(); PNL_Main.setLayout(new BoxLayout(PNL_Main, BoxLayout.PAGE_AXIS)); + //Add everything to main panel + PNL_Main.add(PNL_Log); + PNL_Main.add(SCL_Telemetry); + PNL_Main.add(SCL_Settings); + PNL_Main.add(TXP_Description); + PNL_Main.add(Box.createVerticalGlue()); //Add Finished Panel to Frame + FRM_Window.add(PNL_Main); + FRM_Window.pack(); + FRM_Window.setVisible(true); + + //Kick off the table updates + startTableUpdateTimer(); } /** @@ -140,9 +176,24 @@ private void setSelectedDetails(int row) { } } - //TODO - CP - Finish Implementation here + /** + * Updates the logging period in response to user driven + * change. + * @param event - The log period field update event + */ public void actionPerformed(ActionEvent event) { + if(TXF_Log == null) { + return; + } + try { + int input = Integer.parseInt(TXF_Log.getText()); + TXF_Log.setText(Integer.toString(input)); + context.telemLog.setPeriod(input); + } + catch(NumberFormatException e) { + TXF_Log.setText(Integer.toString(context.telemLog.getPeriod())); + } } /** @@ -153,9 +204,15 @@ public void toFront() { FRM_Window.toFront(); } - //TODO - CP - Finish Implementation here + /** + * Performs window closing operations such as + * stoping the update timer if it is running when + * the window is closed. + */ public void onClose() { - + if(updateTimer != null) { + updateTimer.cancel(); + } } /** @@ -166,4 +223,35 @@ public void onClose() { public boolean getVisible() { return FRM_Window.isVisible(); } + + /** + * Initializes a periodic table update timer using the UPDATE_PERIOD_MS + * variable. When the timer fires the table models will attempt to + * update the current values. + */ + private void startTableUpdateTimer() { + updateTimer = new java.util.Timer(); + updateTimer.scheduleAtFixedRate(new TimerTask() { + public void run() { + if(TBL_Telemetry.getModel() == null + || TBL_Settings.getModel() == null) { + return; + } + + if(context.connected) { + AbstractTableModel telemetryModel = + (AbstractTableModel) TBL_Telemetry.getModel(); + AbstractTableModel settingsModel = + (AbstractTableModel) TBL_Settings.getModel(); + telemetryModel.fireTableRowsUpdated( + 0, Serial.MAX_TELEMETRY); + settingsModel.fireTableRowsUpdated( + 0, Serial.MAX_TELEMETRY); + + TBL_Telemetry.invalidate(); + TBL_Settings.invalidate(); + } + } + }, UPDATE_PERIOD_MS, UPDATE_PERIOD_MS); + } } From 37c22eeabbe47ba97781feffe85a2d1f26530aca Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 17 Mar 2021 10:44:38 -0700 Subject: [PATCH 054/125] Table Slider impl. Intermediate checkin for branch switch Snapshot of current work while switching branches. --- resources/groundSettings.xml | 59 ++++++++++--------- src/Dashboard.java | 5 +- src/graph/GraphConfigWindow.java | 8 +-- src/map/WaypointPanel.java | 11 ++-- src/serial/Serial.java | 26 ++++---- src/table/ColumnTableModel.java | 8 +-- src/table/TableFactory.java | 52 +++++++++++++--- ...{TableColumn.java => TelemetryColumn.java} | 2 +- src/ui/DataWindow.java | 14 ++--- src/ui/FloatJSlider.java | 31 +++++++++- src/ui/RadioConfigScreen.java | 10 ++-- src/ui/WidgetPanel.java | 2 + src/ui/telemetry/TelemetryDataFieldPanel.java | 15 ++--- src/ui/telemetry/TelemetryDataWindow.java | 11 ++-- src/ui/widgets/TelemetryDataWidget.java | 2 +- 15 files changed, 164 insertions(+), 92 deletions(-) rename src/table/{TableColumn.java => TelemetryColumn.java} (93%) diff --git a/resources/groundSettings.xml b/resources/groundSettings.xml index 53e25ed..4336175 100644 --- a/resources/groundSettings.xml +++ b/resources/groundSettings.xml @@ -1,27 +1,32 @@ - - Defines how strongly the rover should attempt to return to the original course between waypoints, verses the direct path from its current location to the target<br> - The number of degrees that rover will turn its wheels when it needs to to turn its most extreme amount - Switches between arctangent of error steering (0) <br> square of error steering (1) <br> and proportional to error steering (2) - Multiplier that determines how aggressively to steer - Minimum forward driving speed in MPH - Maximum forward driving speed in MPH - How far to turn the wheels when backing away from an obstacle - Speed in MPH to drive in reverse - Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles - Time in milliseconds to coast before reversing when an obstacle is encountered - Minimum time in milliseconds to reverse away from an obstacle - P term in cruise control PID loop - I term in cruise control PID loop - D term in cruise control PID loop - Tire Diameter in inches, used to calculate MPH - Center point in degrees corresponding to driving straight - Radius centered at the waypoint where target is determined to be met - Radius center at waypoint where the approach flag is set - - - - - - - - \ No newline at end of file + Defines how strongly the rover should attempt to return to the original course between waypoints, verses the direct path from its current location to the target<br> + The number of degrees that rover will turn its wheels when it needs to to turn its most extreme amount + switches between arctangent of error steering (0) <br> square of error steering (1) <br> and proportional to error steering (2) + Multiplier that determines how aggressively to steer + minimum forward driving speed in MPH + maximum forward driving speed in MPH + How far to turn the wheels when backing away from an obstacle + speed in MPH to drive in reverse + Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles + Time in milliseconds to coast before reversing when an obstacle is encountered + minimum time in milliseconds to reverse away from an obstacle + P term in cruise control PID loop + I term in cruise control PID loop + D term in cruise control PID loop + Tire Diameter in inches, used to calculate MPH + Center point in degrees corresponding to driving straight + The number of degrees that rover will turn its wheels when it needs to to turn its most extreme amount + switches between arctangent of error steering (0) <br> square of error steering (1) <br> and proportional to error steering (2) + Multiplier that determines how aggressively to steer + minimum forward driving speed in MPH + maximum forward driving speed in MPH + How far to turn the wheels when backing away from an obstacle + speed in MPH to drive in reverse + Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles + Time in milliseconds to coast before reversing when an obstacle is encountered + minimum time in milliseconds to reverse away from an obstacle + P term in cruise control PID loop + I term in cruise control PID loop + D term in cruise control PID loop + Tire Diameter in inches, used to calculate MPH + Center point in degrees corresponding to driving straight + \ No newline at end of file diff --git a/src/Dashboard.java b/src/Dashboard.java index c40a4c3..ba52ce4 100644 --- a/src/Dashboard.java +++ b/src/Dashboard.java @@ -12,7 +12,10 @@ import com.ui.*; import com.ui.ArtificialHorizon.DataAxis; import com.ui.ninePatch.*; -import com.ui.PingWidget; +import com.ui.widgets.PingWidget; +import com.ui.telemetry.TelemetryDataWindow; +import com.ui.widgets.*; + import java.awt.*; import java.awt.Dimension; import java.awt.event.*; diff --git a/src/graph/GraphConfigWindow.java b/src/graph/GraphConfigWindow.java index a615262..a31876d 100644 --- a/src/graph/GraphConfigWindow.java +++ b/src/graph/GraphConfigWindow.java @@ -1,7 +1,7 @@ package com.graph; import com.table.ColumnTableModel; -import com.table.TableColumn; +import com.table.TelemetryColumn; import javax.swing.*; import java.awt.*; @@ -84,8 +84,8 @@ private JPanel buildSpinners() { private JComponent buildSourceTable() { List sources = subject.getSources(); - ArrayList> cols = new ArrayList>(); - cols.add( new TableColumn() { + ArrayList> cols = new ArrayList>(); + cols.add( new TelemetryColumn() { public String getName() { return "#"; } @@ -105,7 +105,7 @@ public void setValueAt(String val, int row) { ; } }); - cols.add( new TableColumn() { + cols.add( new TelemetryColumn() { public String getName() { return "Graph?"; } diff --git a/src/map/WaypointPanel.java b/src/map/WaypointPanel.java index 3cc56e3..341f550 100644 --- a/src/map/WaypointPanel.java +++ b/src/map/WaypointPanel.java @@ -10,7 +10,7 @@ import com.map.coordinateListener; import com.map.Dot; import com.serial.*; -import com.ui.DataWindow; +import com.ui.telemetry.TelemetryDataWindow; import com.ui.LogViewer; import com.ui.ninePatch.NinePatchPanel; import com.ui.SystemConfigWindow; @@ -38,6 +38,7 @@ class WaypointPanel extends NinePatchPanel { private MapPanel map; private WaypointList waypoints; private javax.swing.Timer zoomTimer; + private TelemetryDataWindow telemetryDataWindow; TelemField latitude; TelemField longitude; TelemField altitude; @@ -636,7 +637,6 @@ public void actionPerformed(ActionEvent e) { } }; - private DataWindow dataWindow; private Action openDataPanel = new AbstractAction() { { String text = "Telemetry"; @@ -644,12 +644,13 @@ public void actionPerformed(ActionEvent e) { } public void actionPerformed(ActionEvent e) { - if(dataWindow != null && dataWindow.getVisible() == true) { - dataWindow.toFront(); + if(telemetryDataWindow != null + && telemetryDataWindow.getVisible() == true) { + telemetryDataWindow.toFront(); return; } - dataWindow = new DataWindow(context); + telemetryDataWindow = new TelemetryDataWindow(context); } }; diff --git a/src/serial/Serial.java b/src/serial/Serial.java index a034dd8..017c522 100644 --- a/src/serial/Serial.java +++ b/src/serial/Serial.java @@ -8,29 +8,33 @@ public class Serial { public static final int WORD_TYPE = 0x2; public static final int STRING_TYPE = 0x3; - //waypoint type + //Waypoint type public static final int ADD_WAYPOINT = 0x0; public static final int ALTER_WAYPOINT = 0x1; - //data type + //Data type public static final int TELEMETRY_DATA = 0x0; public static final int SETTING_DATA = 0x1; public static final int SENSOR_DATA = 0x2; + public static final int INFO_DATA = 0x3; //Sensor Types public static final int OBJDETECT_SONIC= 0x0; - //word type + //Info Types + public static final int APM_VERSION = 0x0; + + //Word type public static final int CONFIRMATION = 0x0; public static final int SYNC_WORD = 0x1; public static final int COMMAND_WORD = 0x2; public static final int STATE_WORD = 0x3; - //string type + //String type public static final int ERROR_STRING = 0x0; public static final int STATE_STRING = 0x1; - //commands + //Commands public static final byte ESTOP_CMD = 0x0; public static final byte TARGET_CMD = 0x1; public static final byte LOOPING_CMD = 0x2; @@ -39,7 +43,7 @@ public class Serial { public static final byte STOP_CMD = 0x5; public static final byte START_CMD = 0x6; - //state types + //State types public static final byte APM_STATE = 0x0; public static final byte DRIVE_STATE = 0x1; public static final byte AUTO_STATE = 0x2; @@ -50,26 +54,26 @@ public class Serial { public static final byte APM_STATE_SELF_TEST = 0x2; public static final byte APM_STATE_DRIVE = 0x3; - //drive state sub values + //Drive state sub values public static final byte DRIVE_STATE_STOP = 0x1; public static final byte DRIVE_STATE_AUTO = 0x2; public static final byte DRIVE_STATE_RADIO = 0x3; - //auto state sub values + //Auto state sub values public static final byte AUTO_STATE_FULL = 0x1; public static final byte AUTO_STATE_AVOID = 0x2; public static final byte AUTO_STATE_STALLED = 0x3; - //auto flags sub values + //Auto flags sub values public static final byte AUTO_STATE_FLAGS_NONE = 0B00; public static final byte AUTO_STATE_FLAGS_CAUTION = 0B01; public static final byte AUTO_STATE_FLAGS_APPROACH = 0B10; - //sync + //Sync public static final byte SYNC_REQUEST = 0x00; public static final byte SYNC_RESPOND = 0x01; - //telemetry IDs + //Telemetry IDs public static final int LATITUDE = 0; public static final int LONGITUDE = 1; public static final int HEADING = 2; diff --git a/src/table/ColumnTableModel.java b/src/table/ColumnTableModel.java index 38e83b7..5dca633 100644 --- a/src/table/ColumnTableModel.java +++ b/src/table/ColumnTableModel.java @@ -9,9 +9,9 @@ import javax.swing.table.*; public class ColumnTableModel extends AbstractTableModel { - private List> columns; + private List> columns; - public ColumnTableModel(List> cList) { + public ColumnTableModel(List> cList) { columns = cList; } @@ -24,7 +24,7 @@ public int getRowCount() { Iterator itr = columns.iterator(); while(itr.hasNext()) { - TableColumn col = (TableColumn) itr.next(); + TelemetryColumn col = (TelemetryColumn) itr.next(); rowCount = Math.min(col.getRowCount(), rowCount); } return rowCount; @@ -50,7 +50,7 @@ public void setValueAt(Object value, int row, int col) { setValueAtHelper(columns.get(col), value, row); fireTableCellUpdated(row, col); } - private void setValueAtHelper(TableColumn tc, Object obj, int row){ + private void setValueAtHelper(TelemetryColumn tc, Object obj, int row) { T val = tc.getDataClass().cast(obj); tc.setValueAt(val, row); } diff --git a/src/table/TableFactory.java b/src/table/TableFactory.java index 593d229..ada0c55 100644 --- a/src/table/TableFactory.java +++ b/src/table/TableFactory.java @@ -4,6 +4,7 @@ import com.remote.*; import com.table.*; import com.ui.telemetry.*; +import com.ui.FloatJSlider; import java.io.*; import java.util.*; @@ -46,11 +47,11 @@ private static JTable buildSettingsTable(Context context) { JTable table; ColumnTableModel model; SettingList settingList = context.settingList; - ArrayList> columns = new ArrayList>(); + ArrayList> columns = new ArrayList>(); - columns.add( new TableColumn() { + columns.add(new TelemetryColumn() { public String getName() { - return "name"; + return "Name"; } public String getValueAt(int row) { @@ -76,7 +77,7 @@ public void setValueAt(String val, int row) { } }); - columns.add( new TableColumn() { + columns.add(new TelemetryColumn() { public String getName() { return "Setting"; } @@ -94,6 +95,7 @@ public Class getDataClass() { return String.class; } + //TODO - CP - Once sliders are functional, remove editing for this cell? public boolean isRowEditable(int row) { return true; } @@ -109,6 +111,40 @@ public void setValueAt(String val, int row) { } }); + columns.add(new TelemetryColumn() { + public String getName() { + return "Configuration"; + } + + public Integer getValueAt(int row) { + float val = settingList.get(row).getVal(); + + String str = String.valueOf(settingList.get(row).getDefault()); + String[] parsed = str.split("."); + + int sigFigs = (parsed.length > 2) ? parsed[1].length() : 0; + int conversionVal = (sigFigs < 0) ? (int) Math.pow(10, sigFigs) : 1; + + return (int) (val * conversionVal); + } + + public int getRowCount() { + return settingList.size(); + } + + public Class getDataClass() { + return Integer.class; + } + + public boolean isRowEditable(int row) { + return true; + } + + public void setValueAt(Integer val, int row) { + + } + }); + model = new ColumnTableModel(columns); table = new JTable(model); @@ -128,8 +164,8 @@ private static JTable buildTelemetryTable(Context context) { JTable table; ColumnTableModel model; - ArrayList> columns = new ArrayList>(); - columns.add( new TableColumn() { + ArrayList> columns = new ArrayList>(); + columns.add( new TelemetryColumn() { public String getName() { return "name"; } @@ -154,7 +190,7 @@ public void setValueAt(String val, int row) { } }); - columns.add( new TableColumn() { + columns.add( new TelemetryColumn() { public String getName() { return "Value"; } @@ -179,7 +215,7 @@ public void setValueAt(String val, int row) { ; } }); - + model = new ColumnTableModel(columns); table = new JTable(model); diff --git a/src/table/TableColumn.java b/src/table/TelemetryColumn.java similarity index 93% rename from src/table/TableColumn.java rename to src/table/TelemetryColumn.java index 1fc16a9..ff8e1ab 100644 --- a/src/table/TableColumn.java +++ b/src/table/TelemetryColumn.java @@ -1,6 +1,6 @@ package com.table; -public interface TableColumn { +public interface TelemetryColumn { /** Returns the name of this Column */ public String getName(); /** Returns the value for the coll on `row` */ diff --git a/src/ui/DataWindow.java b/src/ui/DataWindow.java index b5bb87d..c626322 100644 --- a/src/ui/DataWindow.java +++ b/src/ui/DataWindow.java @@ -3,7 +3,7 @@ import com.serial.*; import com.Context; import com.remote.*; -import com.table.TableColumn; +import com.table.TelemetryColumn; import com.table.ColumnTableModel; import java.awt.*; @@ -62,8 +62,8 @@ public void windowClosing(java.awt.event.WindowEvent windowEvent) { final SettingList settingList = context.settingList; - ArrayList> telem = new ArrayList>(); - telem.add( new TableColumn() { + ArrayList> telem = new ArrayList>(); + telem.add( new TelemetryColumn() { public String getName() { return "name"; } @@ -83,7 +83,7 @@ public void setValueAt(String val, int row) { } }); - telem.add( new TableColumn() { + telem.add( new TelemetryColumn() { public String getName() { return "Value"; } @@ -104,8 +104,8 @@ public void setValueAt(String val, int row) { } }); - ArrayList> settings = new ArrayList>(); - settings.add( new TableColumn() { + ArrayList> settings = new ArrayList>(); + settings.add( new TelemetryColumn() { public String getName() { return "name"; } @@ -128,7 +128,7 @@ public void setValueAt(String val, int row) { } }); - settings.add( new TableColumn() { + settings.add( new TelemetryColumn() { public String getName() { return "Setting"; } diff --git a/src/ui/FloatJSlider.java b/src/ui/FloatJSlider.java index f57b5c6..d7649d6 100644 --- a/src/ui/FloatJSlider.java +++ b/src/ui/FloatJSlider.java @@ -9,7 +9,15 @@ * custom JSlider class is intended for handling float values. */ public class FloatJSlider extends JSlider{ - private final int scale; + private int scale; + + /** + * Default Constructor + */ + public FloatJSlider() { + super(0, 100, 50); + this.scale = 1; + } /** * Class constructor @@ -30,4 +38,25 @@ public FloatJSlider(int min, int max, int value, int scale) { public float getScaledValue() { return ((float)super.getValue() / this.scale); } + + /** + * Sets the scale that determines decimal conversion when retrieving slider + * values. + * @return + */ + public int getScale() { + return scale; + } + + /** + * Sets the current decimal scale for this slider. + * @param value + */ + public void setScale(int value) { + scale = value; + } + + public void updateValue() { + //Update the slider value from an external source here. + } } diff --git a/src/ui/RadioConfigScreen.java b/src/ui/RadioConfigScreen.java index e53a86e..a970714 100644 --- a/src/ui/RadioConfigScreen.java +++ b/src/ui/RadioConfigScreen.java @@ -12,7 +12,7 @@ import java.util.*; import java.util.regex.*; import java.nio.charset.Charset; -import com.table.TableColumn; +import com.table.TelemetryColumn; import com.table.ColumnTableModel; public class RadioConfigScreen extends JPanel { @@ -263,9 +263,9 @@ public void actionPerformed(ActionEvent e) { }; private JTable makeSettingTable() { - java.util.List> setCols = new ArrayList>(); + java.util.List> setCols = new ArrayList>(); - setCols.add( new TableColumn() { + setCols.add( new TelemetryColumn() { public String getName() { return "ID"; } @@ -291,7 +291,7 @@ public void setValueAt(Integer val, int row) { } }); - setCols.add( new TableColumn() { + setCols.add( new TelemetryColumn() { public String getName() { return "Name"; } @@ -317,7 +317,7 @@ public void setValueAt(String val, int row) { } }); - setCols.add( new TableColumn() { + setCols.add( new TelemetryColumn() { public String getName() { return "Value"; } diff --git a/src/ui/WidgetPanel.java b/src/ui/WidgetPanel.java index 8e5f82e..4449b26 100644 --- a/src/ui/WidgetPanel.java +++ b/src/ui/WidgetPanel.java @@ -4,6 +4,8 @@ import com.Context; import com.ui.ninePatch.NinePatchPanel; +import com.ui.widgets.*; + import java.awt.*; import javax.swing.*; diff --git a/src/ui/telemetry/TelemetryDataFieldPanel.java b/src/ui/telemetry/TelemetryDataFieldPanel.java index 483cbf7..84a4aef 100644 --- a/src/ui/telemetry/TelemetryDataFieldPanel.java +++ b/src/ui/telemetry/TelemetryDataFieldPanel.java @@ -18,8 +18,8 @@ public class TelemetryDataFieldPanel extends JPanel { //Visual Components protected JPanel panel; - protected JLabel telemName; - protected JTextField telemValField; +// protected JLabel telemName; +// protected JTextField telemValField; protected FloatJSlider telemValSlider; protected Setting setting; @@ -28,14 +28,6 @@ public class TelemetryDataFieldPanel extends JPanel { public TelemetryDataFieldPanel(Setting setting) { this.setting = setting; - - //Create label - telemName = new JLabel(setting.getDescription()); - - //Create/Set value in text field - telemValField = new JTextField(5); - telemValField.setEditable(false); - telemValField.setText(Float.toString(setting.getDefault())); //Creat slider and map values to it. //Parse out the decimal places for JSlider mapping (Jslider uses ints for range). @@ -53,13 +45,14 @@ public TelemetryDataFieldPanel(Setting setting) { min, max, (int) Math.floor(setting.getDefault()), conversionVal); telemValSlider.setOpaque(false); telemValSlider.setFocusable(false); + telemValSlider.getModel().addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent event) { BoundedRangeModel model = (BoundedRangeModel) event.getSource(); float updateVal = ((float) model.getValue() / (float) conversionVal); //Update text field value - telemValField.setText(Objects.toString(updateVal)); +// telemValField.setText(Objects.toString(updateVal)); } }); } diff --git a/src/ui/telemetry/TelemetryDataWindow.java b/src/ui/telemetry/TelemetryDataWindow.java index b93176f..6a8c56d 100644 --- a/src/ui/telemetry/TelemetryDataWindow.java +++ b/src/ui/telemetry/TelemetryDataWindow.java @@ -65,8 +65,8 @@ public class TelemetryDataWindow implements ActionListener { /** * Class constructor resposnible for intializing and creating required - * telemetry/settings tables using the factory and setting up all UI component - * visual layout. + * telemetry/settings tables using the TableFactory class and setting + * up all UI component visual layout. * @param context - the application context */ public TelemetryDataWindow(Context context) { @@ -130,19 +130,18 @@ public void valueChanged(ListSelectionEvent event) { PNL_Log.add(LBL_Log); PNL_Log.add(TXF_Log); - - //Set up Main JPanel + //Set up main JPanel PNL_Main = new JPanel(); PNL_Main.setLayout(new BoxLayout(PNL_Main, BoxLayout.PAGE_AXIS)); - //Add everything to main panel + //Add everything to main JPanel PNL_Main.add(PNL_Log); PNL_Main.add(SCL_Telemetry); PNL_Main.add(SCL_Settings); PNL_Main.add(TXP_Description); PNL_Main.add(Box.createVerticalGlue()); - //Add Finished Panel to Frame + //Add finished panel setup to main JFrame FRM_Window.add(PNL_Main); FRM_Window.pack(); FRM_Window.setVisible(true); diff --git a/src/ui/widgets/TelemetryDataWidget.java b/src/ui/widgets/TelemetryDataWidget.java index 9821ed6..65a1769 100644 --- a/src/ui/widgets/TelemetryDataWidget.java +++ b/src/ui/widgets/TelemetryDataWidget.java @@ -1,7 +1,7 @@ package com.ui.widgets; import com.Context; -import com.ui.UIWidget; +import com.ui.widgets.UIWidget; import com.telemetry.TelemetryListener; From 757c6534827d2c6b699956879533bb7757377174 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 17 Mar 2021 14:17:49 -0700 Subject: [PATCH 055/125] Add APM Version Get/Set Serial Messaging --- resources/groundSettings.xml | 8 ++++++++ src/Context.java | 26 +++++++++++++++++++++++++- src/serial/Messages/DataMessage.java | 2 ++ src/serial/Messages/Message.java | 4 ++++ src/serial/SerialParser.java | 22 ++++++++++++++++++++++ src/ui/SystemConfigWindow.java | 4 +++- test/ColumnTableModel_test.java | 22 +++++++++++----------- 7 files changed, 75 insertions(+), 13 deletions(-) diff --git a/resources/groundSettings.xml b/resources/groundSettings.xml index 4336175..1c25e1c 100644 --- a/resources/groundSettings.xml +++ b/resources/groundSettings.xml @@ -14,6 +14,14 @@
D term in cruise control PID loop Tire Diameter in inches, used to calculate MPH Center point in degrees corresponding to driving straight + Radius centered at waypoint where target is determined to be meet + Radius cneter at waypoint where the approach flag is set + + + + + + The number of degrees that rover will turn its wheels when it needs to to turn its most extreme amount switches between arctangent of error steering (0) <br> square of error steering (1) <br> and proportional to error steering (2) Multiplier that determines how aggressively to steer diff --git a/src/Context.java b/src/Context.java index 4c89260..330c7ac 100644 --- a/src/Context.java +++ b/src/Context.java @@ -35,11 +35,13 @@ public class Context { private SerialPort port; private ResourceBundle resources; private Properties persist; - + private String APMVersion; + private final File persistenceFile; private final String instanceLogName; private final Logger ioerr = Logger.getLogger("d.io"); + public Context(Dashboard dashboard) { dash = dashboard; port = null; @@ -285,4 +287,26 @@ public List getTelemetryDataSources() { public void onConnection() { sender.sendWaypointList(); } + + /** + * Sets the current APM board version string. + * @param version + */ + public void setAPMVersion(String version) { + APMVersion = version; + } + + /** + * Gets the current AMP board version string if available, + * otherwise returns a placeholder. + * @return - String + */ + public String getAPMVersion() { + if(APMVersion == null || APMVersion.isEmpty()) { + APMVersion = "x.x.x"; + } + + sender.sendMessage(Message.requestAPMVersion()); + return APMVersion; + } } diff --git a/src/serial/Messages/DataMessage.java b/src/serial/Messages/DataMessage.java index 1d6f162..3b3d0d7 100644 --- a/src/serial/Messages/DataMessage.java +++ b/src/serial/Messages/DataMessage.java @@ -36,6 +36,8 @@ public String toString() { return "Settings Change"; case Serial.SENSOR_DATA: return "Sensor Message"; + case Serial.INFO_DATA: + return "Info Message"; } return "Data Message"; } diff --git a/src/serial/Messages/Message.java b/src/serial/Messages/Message.java index 9a01a28..cd1fb78 100644 --- a/src/serial/Messages/Message.java +++ b/src/serial/Messages/Message.java @@ -137,4 +137,8 @@ public static Message stopDriving() { public static Message startDriving() { return new WordMessage(Serial.COMMAND_WORD, Serial.START_CMD, (byte)0); } + + public static Message requestAPMVersion() { + return new DataMessage(Serial.INFO_DATA, (byte)Serial.APM_VERSION, (byte)0); + } } diff --git a/src/serial/SerialParser.java b/src/serial/SerialParser.java index c835224..3f6d3b0 100644 --- a/src/serial/SerialParser.java +++ b/src/serial/SerialParser.java @@ -116,6 +116,7 @@ public void handle(byte[] msg) { data = Float.intBitsToFloat(tempdata); context.setTelemetry(index, data); break; + case Serial.SETTING_DATA: tempdata = ( ((msg[2]&0xff)<<24)| ((msg[3]&0xff)<<16)| @@ -124,6 +125,7 @@ public void handle(byte[] msg) { data = Float.intBitsToFloat(tempdata); context.setSettingQuiet(index, data); break; + case Serial.SENSOR_DATA: int sensorVal = 0; int sensorSubtype = msg[1]; @@ -142,10 +144,30 @@ public void handle(byte[] msg) { context.dash.pingWidget.update(sensorIndex, sensorVal); break; + default: System.err.println("Unrecognized Sensor Subtype"); break; } + break; + case Serial.INFO_DATA: + int infoSubtype = msg[1]; + + switch(infoSubtype) { + case Serial.APM_VERSION: + int versionMajor = msg[2]; + int versionMinor = msg[3]; + int versionRev = msg[4]; + + context.setAPMVersion(String.format("%d.%d.%d", + versionMajor, versionMinor, versionRev)); + break; + + default: + System.err.println("Unrecognized Info Subtype"); + break; + } + break; } } diff --git a/src/ui/SystemConfigWindow.java b/src/ui/SystemConfigWindow.java index c856f1a..a292d96 100644 --- a/src/ui/SystemConfigWindow.java +++ b/src/ui/SystemConfigWindow.java @@ -41,9 +41,11 @@ public SystemConfigWindow(Context cxt) { // Add version numbers String versionString = String.format( - "MINDS-i Dashboard | Version %s | %s", + "MINDS-i Dashboard | Dashboard Version %s | APM Version %s | %s", context.getResource("version_id"), + context.getAPMVersion(), context.getResource("release_date")); + JLabel versionPane = new JLabel(versionString); versionPane.setAlignmentX(Component.CENTER_ALIGNMENT); container.add(versionPane); diff --git a/test/ColumnTableModel_test.java b/test/ColumnTableModel_test.java index 5ed35ce..c978139 100644 --- a/test/ColumnTableModel_test.java +++ b/test/ColumnTableModel_test.java @@ -10,9 +10,9 @@ import org.mockito.InOrder; public class ColumnTableModel_test { - List> mockTable(int cols) { + List> mockTable(int cols) { List list = new ArrayList(); - for(int i=0; i> table = mockTable(3); + List> table = mockTable(3); ColumnTableModel ctm = new ColumnTableModel(table); when(table.get(0).getRowCount()).thenReturn(999); @@ -35,7 +35,7 @@ public void testGetRowCount() { } @Test public void testGetColumnName() { - List> table = mockTable(3); + List> table = mockTable(3); ColumnTableModel ctm = new ColumnTableModel(table); when(table.get(0).getName()).thenReturn("Bob"); @@ -44,7 +44,7 @@ public void testGetColumnName() { } @Test public void testGetValue() { - List> table = mockTable(3); + List> table = mockTable(3); ColumnTableModel ctm = new ColumnTableModel(table); when(table.get(2).getValueAt(3)).thenReturn((Object)"Value"); @@ -53,17 +53,17 @@ public void testGetValue() { } @Test public void testGetColumnClass() { - List> table = mockTable(3); + List> table = mockTable(3); ColumnTableModel ctm = new ColumnTableModel(table); - when( ((TableColumn)table.get(0)).getDataClass() ) + when( ((TelemetryColumn)table.get(0)).getDataClass() ) .thenReturn(String.class); assertEquals(String.class, ctm.getColumnClass(0)); } @Test public void testIsCellEditable() { - List> table = mockTable(3); + List> table = mockTable(3); ColumnTableModel ctm = new ColumnTableModel(table); when(table.get(1).isRowEditable(0)).thenReturn(true); @@ -72,14 +72,14 @@ public void testIsCellEditable() { } @Test public void testSetValue() { - List> table = mockTable(3); + List> table = mockTable(3); ColumnTableModel ctm = new ColumnTableModel(table); - when( ((TableColumn)table.get(1)).getDataClass() ) + when( ((TelemetryColumn)table.get(1)).getDataClass() ) .thenReturn(String.class); when(table.get(1).isRowEditable(0)).thenReturn(true); ctm.setValueAt("Value", 0, 1); - verify((TableColumn)table.get(1)).setValueAt("Value", 0); + verify((TelemetryColumn)table.get(1)).setValueAt("Value", 0); } } From aa146a53ede5b905edb7befc6c27e41d74c3ca26 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Tue, 23 Mar 2021 14:47:14 -0700 Subject: [PATCH 056/125] Update Telem table config model Change from a single table with cell renderer to side by side tables with configuration sliders in their own table. --- src/table/SettingSliderModel.java | 102 ++++++++++++++++++ src/table/TableFactory.java | 59 ++++------ src/ui/telemetry/SettingPercentage.java | 64 +++++++++++ src/ui/telemetry/SliderRenderer.java | 30 ++++++ src/ui/telemetry/TableSlider.java | 12 +++ .../TelemetryDataFieldPanelRenderer.java | 27 ----- src/ui/telemetry/TelemetryDataWindow.java | 49 +++++++-- 7 files changed, 272 insertions(+), 71 deletions(-) create mode 100644 src/table/SettingSliderModel.java create mode 100644 src/ui/telemetry/SettingPercentage.java create mode 100644 src/ui/telemetry/SliderRenderer.java create mode 100644 src/ui/telemetry/TableSlider.java delete mode 100644 src/ui/telemetry/TelemetryDataFieldPanelRenderer.java diff --git a/src/table/SettingSliderModel.java b/src/table/SettingSliderModel.java new file mode 100644 index 0000000..cbf1539 --- /dev/null +++ b/src/table/SettingSliderModel.java @@ -0,0 +1,102 @@ +package com.table; + +import com.ui.telemetry.SettingPercentage; +import javax.swing.table.AbstractTableModel; + +/** + * @author Chris Park @ Infinetix Corp + * Date: 3-22-21 + * Description: Table model used to define the behavioral model for + * a settings slider. + */ +public class SettingSliderModel extends AbstractTableModel { + + String headers[] = {"Configure"}; + Object data[][]; + + /** + * Class Constructor + * @param context - The application context + */ + public SettingSliderModel(int size) { + //Init the data structure based on the size of the + //telemetry settings list. + data = new Object[size][1]; + + for(int i = 0; i < size; i++) { + data[i][0] = new SettingPercentage(); + } + } + + /** + * Get the number of rows in this table. + * @return - int + */ + @Override + public int getRowCount() { + return data.length; + } + + /** + * Get the number of columns in this table. + * @return - int + */ + @Override + public int getColumnCount() { + return headers.length; + } + + /** + * Get the class type from the specified column + * @param column - The column to retrieve the class from + * @return - Class + */ + @Override + public Class getColumnClass(int column) { + return SettingPercentage.class; + } + + /** + * Get the column name for the specified index. + * @param column - The column index + * @return - String + */ + @Override + public String getColumnName(int column) { + return headers[0]; + } + + /** + * Return whether or not the cell at the specified index + * is editable. + * @param row - The row index + * @param column - The column index + * @return - boolean + */ + @Override + public boolean isCellEditable(int row, int column) { + return true; + } + + /** + * Get the specified Object from the table. + * @param row - Table row index + * @param column - Table column index + * @return - Object + */ + @Override + public Object getValueAt(int row, int column) { + return data[row][column]; + } + + /** + * Set the value held at the specified index in the table. + * @param value - The value to be stored + * @param row - Table row index + * @param column - Table column index + */ + @Override + public void setValueAt(Object value, int row, int column) { + ((SettingPercentage) data[row][column]).setPercentage(value); + } +} diff --git a/src/table/TableFactory.java b/src/table/TableFactory.java index ada0c55..b6ea302 100644 --- a/src/table/TableFactory.java +++ b/src/table/TableFactory.java @@ -22,7 +22,7 @@ public class TableFactory { //Constructor is private here to prevent object instantiation. private TableFactory() {} - public enum TableType {Telemetry, Settings} + public enum TableType {Telemetry, Settings, Sliders} public static JTable createTable(TableType type, Context context) { switch(type) { @@ -30,6 +30,8 @@ public static JTable createTable(TableType type, Context context) { return buildTelemetryTable(context); case Settings: return buildSettingsTable(context); + case Sliders: + return buildSettingsSliderTable(context); default: System.err.println("TableFactory Error - Unknown table type"); } @@ -110,50 +112,35 @@ public void setValueAt(String val, int row) { settingList.pushSetting(row,newVal); } }); - - columns.add(new TelemetryColumn() { - public String getName() { - return "Configuration"; - } - - public Integer getValueAt(int row) { - float val = settingList.get(row).getVal(); - - String str = String.valueOf(settingList.get(row).getDefault()); - String[] parsed = str.split("."); - - int sigFigs = (parsed.length > 2) ? parsed[1].length() : 0; - int conversionVal = (sigFigs < 0) ? (int) Math.pow(10, sigFigs) : 1; - - return (int) (val * conversionVal); - } - - public int getRowCount() { - return settingList.size(); - } - - public Class getDataClass() { - return Integer.class; - } - - public boolean isRowEditable(int row) { - return true; - } - - public void setValueAt(Integer val, int row) { - - } - }); model = new ColumnTableModel(columns); table = new JTable(model); - + table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); table.setFillsViewportHeight(true); return table; } + private static JTable buildSettingsSliderTable(Context context) { + + JTable table; + SettingSliderModel model; + + + model = new SettingSliderModel(context.settingList.size()); + + table = new JTable(model); + + table.setDefaultRenderer(SettingPercentage.class, new SliderRenderer()); + //TODO - CP - Add SliderEditor set here. + + table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); + table.setFillsViewportHeight(true); + + return table; + } + /** * Creates a telemtry data table with column layout specified by * the applied table model and sizing settings. diff --git a/src/ui/telemetry/SettingPercentage.java b/src/ui/telemetry/SettingPercentage.java new file mode 100644 index 0000000..eb781a4 --- /dev/null +++ b/src/ui/telemetry/SettingPercentage.java @@ -0,0 +1,64 @@ +package com.ui.telemetry; + +/** + * @author Chris Park @ Infinetix Corp + * Date: 3-22-21 + * Description: Data structure class for maintaining a percentage value related + * to a telemetry setting. This is used to render a JSlider within a table to + * control Telemetry settings. + */ +public class SettingPercentage { + private int percentage; + + /** + * Class Constructor. Defaults value to 0 percent + */ + public SettingPercentage() { + setPercentage(0); + } + + /** + * Class Constructor. Sets percentage to specified + * value. + * @param value - the value to set the percentage to. + */ + public SettingPercentage(int value) { + setPercentage(value); + } + + /** + * Sets the percentage value. + * @param value - The new percentage value + */ + public void setPercentage(int value) { + percentage = value; + } + + /** + * Sets the percentage value from an existing SettingPercentage object. + * @param value - The new percentage value + */ + public void setPercentage(Object value) { + if(value instanceof SettingPercentage) { + setPercentage(((SettingPercentage) value).getPercentage()); + } + + } + + /** + * Returns the current percentage value + * @return - int + */ + public int getPercentage() { + return percentage; + } + + /** + * Returns the string representation of the current + * percentage value. + * @return - String + */ + public String toString() { + return String.valueOf(percentage); + } +} diff --git a/src/ui/telemetry/SliderRenderer.java b/src/ui/telemetry/SliderRenderer.java new file mode 100644 index 0000000..0b6c157 --- /dev/null +++ b/src/ui/telemetry/SliderRenderer.java @@ -0,0 +1,30 @@ +package com.ui.telemetry; + +import javax.swing.*; +import javax.swing.table.TableCellRenderer; + +import java.awt.Component; + +public class SliderRenderer extends JSlider implements TableCellRenderer { + public SliderRenderer() { + super(SwingConstants.HORIZONTAL); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, + boolean isSelected, boolean hasFocus, int row, int column) { + if(value == null) { + return this; + } + + if(value instanceof SettingPercentage) { + setValue(((SettingPercentage) value).getPercentage()); + } + else { + setValue(0); + } + + return this; + } + +} diff --git a/src/ui/telemetry/TableSlider.java b/src/ui/telemetry/TableSlider.java new file mode 100644 index 0000000..f78904a --- /dev/null +++ b/src/ui/telemetry/TableSlider.java @@ -0,0 +1,12 @@ +package com.ui.telemetry; + +import javax.swing.*; + +public class TableSlider extends JSlider { + + + public void updateValue(int value) { + setValue(value); + invalidate(); + } +} diff --git a/src/ui/telemetry/TelemetryDataFieldPanelRenderer.java b/src/ui/telemetry/TelemetryDataFieldPanelRenderer.java deleted file mode 100644 index 52f225a..0000000 --- a/src/ui/telemetry/TelemetryDataFieldPanelRenderer.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.ui.telemetry; - -import com.remote.Setting; - -import javax.swing.JTable; -import javax.swing.table.TableCellRenderer; -import java.awt.*; - -public class TelemetryDataFieldPanelRenderer - extends TelemetryDataFieldPanel implements TableCellRenderer { - - public TelemetryDataFieldPanelRenderer(Setting setting) { - super(setting); - setName("Table.cellRenderer"); - } - - @Override - public Component getTableCellRendererComponent( - JTable table, Object value, boolean isSelected, boolean hasFocus, - int row, int col) { - if(value instanceof Setting) { - updateValue((Setting) value); - } - - return this; - } -} diff --git a/src/ui/telemetry/TelemetryDataWindow.java b/src/ui/telemetry/TelemetryDataWindow.java index 6a8c56d..c1a4fac 100644 --- a/src/ui/telemetry/TelemetryDataWindow.java +++ b/src/ui/telemetry/TelemetryDataWindow.java @@ -34,14 +34,20 @@ public class TelemetryDataWindow implements ActionListener { private static final int WINDOW_HEIGHT = 560; private static final int LOG_FIELD_WIDTH = 8; - private static final Dimension TELEM_DIM_PREF = new Dimension(300, 140); - private static final Dimension TELEM_DIM_MIN = new Dimension(300, 140); + private static final Dimension TELEM_DIM_PREF = new Dimension(500, 140); + private static final Dimension TELEM_DIM_MIN = new Dimension(500, 140); private static final Dimension TELEM_DIM_MAX = new Dimension(Integer.MAX_VALUE, 140); + private static final Dimension SETTINGS_DIM_PREF = new Dimension(300, 300); private static final Dimension SETTINGS_DIM_MIN = new Dimension(300, 300); private static final Dimension SETTINGS_DIM_MAX = new Dimension(Integer.MAX_VALUE, 300); - private static final Dimension DESC_DIM_MIN = new Dimension(300, 80); - private static final Dimension DESC_DIM_PREF = new Dimension(300, 200); + + private static final Dimension SLIDERS_DIM_PREF = new Dimension(200, 300); + private static final Dimension SLIDERS_DIM_MIN = new Dimension(200, 300); + private static final Dimension SLIDERS_DIM_MAX = new Dimension(Integer.MAX_VALUE, 300); + + private static final Dimension DESC_DIM_MIN = new Dimension(500, 80); + private static final Dimension DESC_DIM_PREF = new Dimension(500, 200); private static final Border TABLE_BORDERS = BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(5, 5, 5, 5), BorderFactory.createLineBorder(Color.BLACK)); @@ -49,6 +55,10 @@ public class TelemetryDataWindow implements ActionListener { //UI Elements private JFrame FRM_Window; private JPanel PNL_Main; + + //New - To Vet + private JPanel PNL_Settings; + private JPanel PNL_Log; private JLabel LBL_Log; private JTextField TXF_Log; @@ -56,9 +66,16 @@ public class TelemetryDataWindow implements ActionListener { private JTextComponent TXC_DescriptionBox; private JTable TBL_Telemetry; private JTable TBL_Settings; + + //New - To Vet + private JTable TBL_SettingsSliders; + private JScrollPane SCL_Telemetry; private JScrollPane SCL_Settings; + //New - To Vet + private JScrollPane SCL_SettingsSliders; + //Standard Vars private Context context; private java.util.Timer updateTimer; @@ -103,7 +120,6 @@ public void windowClosing(WindowEvent event) { SCL_Telemetry.setPreferredSize(TELEM_DIM_PREF); SCL_Telemetry.setBorder(TABLE_BORDERS); - //Set up Settings Table and ScrollPane TBL_Settings = TableFactory.createTable(TableFactory.TableType.Settings, context); @@ -112,13 +128,29 @@ public void valueChanged(ListSelectionEvent event) { setSelectedDetails(TBL_Settings.getSelectedRow()); } }); - + SCL_Settings = new JScrollPane(TBL_Settings); SCL_Settings.setMinimumSize(SETTINGS_DIM_MIN); SCL_Settings.setMaximumSize(SETTINGS_DIM_MAX); SCL_Settings.setPreferredSize(SETTINGS_DIM_PREF); SCL_Settings.setBorder(TABLE_BORDERS); + //Set up SettingSlider Table and ScrollPane + TBL_SettingsSliders = TableFactory.createTable(TableFactory.TableType.Sliders, + context); + + SCL_SettingsSliders = new JScrollPane(TBL_SettingsSliders); + SCL_SettingsSliders.setMinimumSize(SLIDERS_DIM_MIN); + SCL_SettingsSliders.setMaximumSize(SLIDERS_DIM_MAX); + SCL_SettingsSliders.setPreferredSize(SLIDERS_DIM_PREF); + SCL_SettingsSliders.setBorder(TABLE_BORDERS); + + //Pack both settings tables in flow layout settings panel + PNL_Settings = new JPanel(); + PNL_Settings.setLayout(new FlowLayout()); + PNL_Settings.add(SCL_Settings); + PNL_Settings.add(SCL_SettingsSliders); + //Build Log Panel PNL_Log = new JPanel(); PNL_Log.setLayout(new FlowLayout()); @@ -137,7 +169,7 @@ public void valueChanged(ListSelectionEvent event) { //Add everything to main JPanel PNL_Main.add(PNL_Log); PNL_Main.add(SCL_Telemetry); - PNL_Main.add(SCL_Settings); + PNL_Main.add(PNL_Settings); PNL_Main.add(TXP_Description); PNL_Main.add(Box.createVerticalGlue()); @@ -146,7 +178,7 @@ public void valueChanged(ListSelectionEvent event) { FRM_Window.pack(); FRM_Window.setVisible(true); - //Kick off the table updates + //Kick off table updates startTableUpdateTimer(); } @@ -249,6 +281,7 @@ public void run() { TBL_Telemetry.invalidate(); TBL_Settings.invalidate(); + //TODO - CP - Does Slider table need to be invalidated here as well? } } }, UPDATE_PERIOD_MS, UPDATE_PERIOD_MS); From a67bcd0030302813e95722c8267c573364ebc949 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Tue, 30 Mar 2021 12:02:11 -0700 Subject: [PATCH 057/125] Impl Table slider editor -Add base visual render manipulation, -Handle edge cases for mouse event behavior as it corresponds to table cell events. --- src/table/SettingSliderModel.java | 10 +- src/table/TableFactory.java | 34 +++- src/ui/telemetry/SettingPercentage.java | 12 +- src/ui/telemetry/SliderEditor.java | 257 ++++++++++++++++++++++++ src/ui/telemetry/SliderRenderer.java | 19 +- src/ui/telemetry/TableSlider.java | 12 -- 6 files changed, 320 insertions(+), 24 deletions(-) create mode 100644 src/ui/telemetry/SliderEditor.java delete mode 100644 src/ui/telemetry/TableSlider.java diff --git a/src/table/SettingSliderModel.java b/src/table/SettingSliderModel.java index cbf1539..1fdd580 100644 --- a/src/table/SettingSliderModel.java +++ b/src/table/SettingSliderModel.java @@ -1,6 +1,7 @@ package com.table; import com.ui.telemetry.SettingPercentage; + import javax.swing.table.AbstractTableModel; /** @@ -13,6 +14,7 @@ public class SettingSliderModel extends AbstractTableModel { String headers[] = {"Configure"}; Object data[][]; + /** * Class Constructor @@ -20,12 +22,13 @@ public class SettingSliderModel extends AbstractTableModel { */ public SettingSliderModel(int size) { //Init the data structure based on the size of the - //telemetry settings list. + //telemetry settings list. data = new Object[size][1]; for(int i = 0; i < size; i++) { data[i][0] = new SettingPercentage(); } + } /** @@ -52,7 +55,7 @@ public int getColumnCount() { * @return - Class */ @Override - public Class getColumnClass(int column) { + public Class getColumnClass(int column) { return SettingPercentage.class; } @@ -97,6 +100,9 @@ public Object getValueAt(int row, int column) { */ @Override public void setValueAt(Object value, int row, int column) { +// System.err.println("SliderSettingModel - Setting value to model for row: " + row); ((SettingPercentage) data[row][column]).setPercentage(value); + + fireTableCellUpdated(row, column); } } diff --git a/src/table/TableFactory.java b/src/table/TableFactory.java index b6ea302..529081d 100644 --- a/src/table/TableFactory.java +++ b/src/table/TableFactory.java @@ -3,13 +3,17 @@ import com.Context; import com.remote.*; import com.table.*; -import com.ui.telemetry.*; import com.ui.FloatJSlider; +import com.ui.telemetry.SliderRenderer; +import com.ui.telemetry.SliderEditor; +import com.ui.telemetry.SettingPercentage; import java.io.*; import java.util.*; import javax.swing.*; import javax.swing.table.*; +//import javax.swing.event.TableModelListener; +//import javax.swing.event.TableModelEvent; /** * @author Chris Park @ Infinetix Corp @@ -122,6 +126,12 @@ public void setValueAt(String val, int row) { return table; } + /** + * Creates a custom table that renders slider controls for use in + * controlling vehicle telemetry settings values. + * @param context - The application context; + * @return - A configured slider table. + */ private static JTable buildSettingsSliderTable(Context context) { JTable table; @@ -130,10 +140,28 @@ private static JTable buildSettingsSliderTable(Context context) { model = new SettingSliderModel(context.settingList.size()); +// model.addTableModelListener(new TableModelListener() { +// public void tableChanged(TableModelEvent event) { +// System.err.println("Model listener fired a change event."); +// +// TableModel model = (TableModel) event.getSource(); +// +// int row = event.getFirstRow(); +// int column = event.getColumn(); +// +// Object data = model.getValueAt(row, column); +// +// if(data instanceof SettingPercentage) { +// System.err.println("Detected as setting percentage"); +// } +// } +// }); + table = new JTable(model); - table.setDefaultRenderer(SettingPercentage.class, new SliderRenderer()); - //TODO - CP - Add SliderEditor set here. + table.setDefaultRenderer(SettingPercentage.class, new SliderRenderer(context)); + table.setDefaultEditor(SettingPercentage.class, new SliderEditor(context, table)); + table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); table.setFillsViewportHeight(true); diff --git a/src/ui/telemetry/SettingPercentage.java b/src/ui/telemetry/SettingPercentage.java index eb781a4..ca44c96 100644 --- a/src/ui/telemetry/SettingPercentage.java +++ b/src/ui/telemetry/SettingPercentage.java @@ -14,7 +14,7 @@ public class SettingPercentage { * Class Constructor. Defaults value to 0 percent */ public SettingPercentage() { - setPercentage(0); + this(0); } /** @@ -39,10 +39,18 @@ public void setPercentage(int value) { * @param value - The new percentage value */ public void setPercentage(Object value) { +// System.err.println("Setting Percentage - Attempting to determine value for set"); if(value instanceof SettingPercentage) { +// System.err.println("Setting Percentage - Value identified as setting percentage class"); setPercentage(((SettingPercentage) value).getPercentage()); } - + else if (value instanceof String) { + setPercentage(Integer.parseInt((String) value)); + } + else { + System.err.println( + "SettingPercentage - Unrecognized value type set attempt"); + } } /** diff --git a/src/ui/telemetry/SliderEditor.java b/src/ui/telemetry/SliderEditor.java new file mode 100644 index 0000000..5746b6b --- /dev/null +++ b/src/ui/telemetry/SliderEditor.java @@ -0,0 +1,257 @@ +package com.ui.telemetry; + +import com.Context; +import com.remote.Setting; +import com.remote.SettingList; + +import java.util.*; + +import javax.swing.*; +import javax.swing.event.ChangeListener; +import javax.swing.event.ChangeEvent; +import javax.swing.event.CellEditorListener; +import javax.swing.table.TableCellEditor; + +import java.awt.Component; + +/** + * @author Chris Park @ Infinetix Corp + * Date: 3-22-21 + * Description: A custom editor that manipulates JSliders in table cells. + * This editor is responsible for the mapping and representation of the + * tables underlying SettingPercentge values (integers) and how they are + * displayed to the user. + */ +public class SliderEditor extends JSlider implements TableCellEditor { + Context context; + protected Vector listenerList; + + //Slider State Tracking Vars + protected int startingValue; + protected int previousChangeValue; + protected int targetRow; + + protected boolean editing; + + + + + public SliderEditor(Context context, JTable table) { + super(SwingConstants.HORIZONTAL); + this.context = context; + listenerList = new Vector(); + previousChangeValue = -1; + targetRow = -1; + + addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent event) { + int row = table.getSelectedRow(); + + //Don't use a row value that doesn't exist. (Table not init'd) + if(row < 0) { + return; + } + + //Edge Case: Ignore false change values of zero triggered when + //switching between sliders. + if(row != targetRow) { + System.err.println( + " SliderEditor - Previous slider false 0 value ignored."); + return; + } + + //Edge Case: Ignore duplicate slider values generated by + //mouse up events + if(previousChangeValue == getValue()) { + + System.err.println( + " SliderEditor - Duplicate value, should update now"); + return; + } + + + System.err.println(" Changing Row: " + row + + ", Current Percentage: " + getValue()); + Setting setting = context.settingList.get(row); + + + + //Edge cases: + + + //Releasing a slider sends a duplicate of the last value changed. + //Check that previous value doesn't equal current value before + //attempting to do anything with it. + + //**OR wait for that duplicate value if it's guaranteed (seems + //consistend so far), and then when we see it, we know we have + //our new percentage value to calculate the new setting for. + + //Click into a different slider sets slider model for previous slider + //one more time. Then one more change event is fired for that + //previous slider as a 0 value before picking up on the new ones. + //Will need a way to catch this last change event and discard + //that reset value. + + //Possible solution: + //Store a changes row number (need init value?) + //if the row on the next change event is not equal to + //the previous, don't use/set the percentage. + + + + + + //Update the previous value to account for duplication edge case + previousChangeValue = getValue(); + } + }); + + } + + /** + * Handles the standard functionality of the editor. Determining + * how the incoming value and position should be operated on. + * @param table - The table this editor is attached to + * @param value - The value to be manipulated/changed by this event. + * @param isSelected - Whether or not the active cell is selected + * @param row - The row location of this cell in the table. + * @param column - The column location of this cell in the table. + * @return - Component + */ + @Override + public Component getTableCellEditorComponent(JTable table, Object value, + boolean isSelected, int row, int column) { + targetRow = row; + + System.err.println("SliderEditor - in component editing row " + row); + +// if(this.getValueIsAdjusting()) { +// System.err.println("Adjusting..."); +// } +// this.setValueIsAdjusting(true); + + + if(value instanceof SettingPercentage) { + System.err.println("SliderEditor - Setting Percentage for row " + row); + setValue(((SettingPercentage) value).getPercentage()); + +// Setting setting = context.settingList.get(row); +// +// int percentage = ((SettingPercentage) value).getPercentage(); +// System.err.println("Percentage: " + percentage); +// +// float min = setting.getMin(); +// System.err.println("Min: " + min); +// +// float max = setting.getMax(); +// System.err.println("Max: " + max); +// +// float range = (max - min); +// System.err.println("Range: " + range); +// +// float settingValue = ((percentage * range) / 100) + min; +// System.err.println("New setting value is: " + settingValue); +// + } + else { + setValue(0); + } + + startingValue = getValue(); + editing = true; + + return this; + } + + /** + * Gets the value currently held by the cell editor. + * @return - Object + */ + @Override + public Object getCellEditorValue() { + return new SettingPercentage(getValue()); + } + + /** + * Returns whether or not a cell is editable. + * @param eventObj - The event that triggered this check + * @return - boolean + */ + @Override + public boolean isCellEditable(EventObject eventObj) { + return true; + } + + /** + * Returns whether or not a cell should be selected + * @param eventObj - The event that triggered this check + * @return - boolean + */ + @Override + public boolean shouldSelectCell(EventObject eventObj) { + return true; + } + + /** + * Stops cell editing and notifies all listeners + * @return - boolean + */ + @Override + public boolean stopCellEditing() { + fireEditingStopped(); + editing = false; + return true; + } + + /** + * Cancels cell editing and notifies all listeners. + */ + @Override + public void cancelCellEditing() { + fireEditingCanceled(); + editing = false; + } + + /** + * Adds a cell listener to the internally kept list + * @param listener - The listener to add + */ + @Override + public void addCellEditorListener(CellEditorListener listener) { + listenerList.addElement(listener); + } + + /** + * Removes a listener from the internally kept list. + * @param listener - The listener to remove. + */ + @Override + public void removeCellEditorListener(CellEditorListener listener) { + listenerList.removeElement(listener); + } + + /** + * Iterates through the list of listeners for this editors + * change events and notifies them that editing has been canceled. + */ + protected void fireEditingCanceled() { + setValue(startingValue); + ChangeEvent changeEvent = new ChangeEvent(this); + for (int i = listenerList.size() - 1; i >= 0; i--) { + ((CellEditorListener) listenerList.elementAt(i)).editingCanceled(changeEvent); + } + } + + /** + * Iterates through the list of listeners for this editors + * change events and notifies them that editing has stopped. + */ + protected void fireEditingStopped() { + ChangeEvent changeEvent = new ChangeEvent(this); + for (int i = listenerList.size() - 1; i >= 0; i--) { + ((CellEditorListener) listenerList.elementAt(i)).editingStopped(changeEvent); + } + } +} diff --git a/src/ui/telemetry/SliderRenderer.java b/src/ui/telemetry/SliderRenderer.java index 0b6c157..0a47d1c 100644 --- a/src/ui/telemetry/SliderRenderer.java +++ b/src/ui/telemetry/SliderRenderer.java @@ -1,23 +1,32 @@ package com.ui.telemetry; +import com.Context; + import javax.swing.*; import javax.swing.table.TableCellRenderer; import java.awt.Component; +/** + * @author Chris Park @ Infinetix Corp. + * Date: 3-22-21 + * Description: A custom renderer that display JSliders in table cells + * for SettingPercentage (integer) values. + */ public class SliderRenderer extends JSlider implements TableCellRenderer { - public SliderRenderer() { + Context context; + + public SliderRenderer(Context context) { super(SwingConstants.HORIZONTAL); + this.context = context; } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - if(value == null) { - return this; - } - + if(value instanceof SettingPercentage) { +// System.err.println("SliderRenderer - In renderer component"); setValue(((SettingPercentage) value).getPercentage()); } else { diff --git a/src/ui/telemetry/TableSlider.java b/src/ui/telemetry/TableSlider.java deleted file mode 100644 index f78904a..0000000 --- a/src/ui/telemetry/TableSlider.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.ui.telemetry; - -import javax.swing.*; - -public class TableSlider extends JSlider { - - - public void updateValue(int value) { - setValue(value); - invalidate(); - } -} From 7bccebf436a3100001369d0615a32879382212d5 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 31 Mar 2021 11:08:35 -0700 Subject: [PATCH 058/125] Settings Slider Updates -Slider functionality now operating on settings table values as expected. -Cleaned up debug post slider testing. -Set all +/- inf ground settings values to determined hard limits. (Needed to actually calculate new values using the slider) -Other small general clean up and documentation/comments. --- resources/groundSettings.xml | 40 ++++------ src/table/SettingSliderModel.java | 2 - src/table/TableFactory.java | 22 ------ src/ui/telemetry/SettingPercentage.java | 2 - src/ui/telemetry/SliderEditor.java | 98 ++++++------------------- src/ui/telemetry/SliderRenderer.java | 12 ++- 6 files changed, 48 insertions(+), 128 deletions(-) diff --git a/resources/groundSettings.xml b/resources/groundSettings.xml index 1c25e1c..ebc62c3 100644 --- a/resources/groundSettings.xml +++ b/resources/groundSettings.xml @@ -1,18 +1,18 @@ Defines how strongly the rover should attempt to return to the original course between waypoints, verses the direct path from its current location to the target<br> The number of degrees that rover will turn its wheels when it needs to to turn its most extreme amount switches between arctangent of error steering (0) <br> square of error steering (1) <br> and proportional to error steering (2) - Multiplier that determines how aggressively to steer - minimum forward driving speed in MPH - maximum forward driving speed in MPH + Multiplier that determines how aggressively to steer + minimum forward driving speed in MPH + maximum forward driving speed in MPH How far to turn the wheels when backing away from an obstacle - speed in MPH to drive in reverse - Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles - Time in milliseconds to coast before reversing when an obstacle is encountered - minimum time in milliseconds to reverse away from an obstacle - P term in cruise control PID loop - I term in cruise control PID loop - D term in cruise control PID loop - Tire Diameter in inches, used to calculate MPH + speed in MPH to drive in reverse + Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles + Time in milliseconds to coast before reversing when an obstacle is encountered + minimum time in milliseconds to reverse away from an obstacle + P term in cruise control PID loop + I term in cruise control PID loop + D term in cruise control PID loop + Tire Diameter in inches, used to calculate MPH Center point in degrees corresponding to driving straight Radius centered at waypoint where target is determined to be meet Radius cneter at waypoint where the approach flag is set @@ -22,19 +22,5 @@ - The number of degrees that rover will turn its wheels when it needs to to turn its most extreme amount - switches between arctangent of error steering (0) <br> square of error steering (1) <br> and proportional to error steering (2) - Multiplier that determines how aggressively to steer - minimum forward driving speed in MPH - maximum forward driving speed in MPH - How far to turn the wheels when backing away from an obstacle - speed in MPH to drive in reverse - Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles - Time in milliseconds to coast before reversing when an obstacle is encountered - minimum time in milliseconds to reverse away from an obstacle - P term in cruise control PID loop - I term in cruise control PID loop - D term in cruise control PID loop - Tire Diameter in inches, used to calculate MPH - Center point in degrees corresponding to driving straight - \ No newline at end of file + + \ No newline at end of file diff --git a/src/table/SettingSliderModel.java b/src/table/SettingSliderModel.java index 1fdd580..9c51178 100644 --- a/src/table/SettingSliderModel.java +++ b/src/table/SettingSliderModel.java @@ -100,9 +100,7 @@ public Object getValueAt(int row, int column) { */ @Override public void setValueAt(Object value, int row, int column) { -// System.err.println("SliderSettingModel - Setting value to model for row: " + row); ((SettingPercentage) data[row][column]).setPercentage(value); - fireTableCellUpdated(row, column); } } diff --git a/src/table/TableFactory.java b/src/table/TableFactory.java index 529081d..705869f 100644 --- a/src/table/TableFactory.java +++ b/src/table/TableFactory.java @@ -12,8 +12,6 @@ import java.util.*; import javax.swing.*; import javax.swing.table.*; -//import javax.swing.event.TableModelListener; -//import javax.swing.event.TableModelEvent; /** * @author Chris Park @ Infinetix Corp @@ -140,29 +138,9 @@ private static JTable buildSettingsSliderTable(Context context) { model = new SettingSliderModel(context.settingList.size()); -// model.addTableModelListener(new TableModelListener() { -// public void tableChanged(TableModelEvent event) { -// System.err.println("Model listener fired a change event."); -// -// TableModel model = (TableModel) event.getSource(); -// -// int row = event.getFirstRow(); -// int column = event.getColumn(); -// -// Object data = model.getValueAt(row, column); -// -// if(data instanceof SettingPercentage) { -// System.err.println("Detected as setting percentage"); -// } -// } -// }); - table = new JTable(model); - table.setDefaultRenderer(SettingPercentage.class, new SliderRenderer(context)); table.setDefaultEditor(SettingPercentage.class, new SliderEditor(context, table)); - - table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); table.setFillsViewportHeight(true); diff --git a/src/ui/telemetry/SettingPercentage.java b/src/ui/telemetry/SettingPercentage.java index ca44c96..8d744f0 100644 --- a/src/ui/telemetry/SettingPercentage.java +++ b/src/ui/telemetry/SettingPercentage.java @@ -39,9 +39,7 @@ public void setPercentage(int value) { * @param value - The new percentage value */ public void setPercentage(Object value) { -// System.err.println("Setting Percentage - Attempting to determine value for set"); if(value instanceof SettingPercentage) { -// System.err.println("Setting Percentage - Value identified as setting percentage class"); setPercentage(((SettingPercentage) value).getPercentage()); } else if (value instanceof String) { diff --git a/src/ui/telemetry/SliderEditor.java b/src/ui/telemetry/SliderEditor.java index 5746b6b..05087da 100644 --- a/src/ui/telemetry/SliderEditor.java +++ b/src/ui/telemetry/SliderEditor.java @@ -30,12 +30,16 @@ public class SliderEditor extends JSlider implements TableCellEditor { protected int startingValue; protected int previousChangeValue; protected int targetRow; - protected boolean editing; - - - + /** + * Class Constructor. Initializes global tracked values and defines + * the ChangeEvent listener responsible for calculating settings updates in + * response to slider movement. Final setting adjustments are pushed to the + * settings list on mouse up event. + * @param context + * @param table + */ public SliderEditor(Context context, JTable table) { super(SwingConstants.HORIZONTAL); this.context = context; @@ -46,6 +50,7 @@ public SliderEditor(Context context, JTable table) { addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent event) { + Setting setting; int row = table.getSelectedRow(); //Don't use a row value that doesn't exist. (Table not init'd) @@ -56,58 +61,30 @@ public void stateChanged(ChangeEvent event) { //Edge Case: Ignore false change values of zero triggered when //switching between sliders. if(row != targetRow) { - System.err.println( - " SliderEditor - Previous slider false 0 value ignored."); return; } - - //Edge Case: Ignore duplicate slider values generated by - //mouse up events - if(previousChangeValue == getValue()) { + + //Edge Case and Update Window: The last value generated on mouse + //up is a duplicate of the previous change event. Update the setting + //value here. + if(previousChangeValue == getValue()) { + setting = context.settingList.get(row); + + float min = setting.getMin(); + float max = setting.getMax(); + float range = (max - min); + float settingValue = ((getValue() * range) / 100) + min; + + //Set final updated value + context.settingList.pushSetting(row, settingValue); - System.err.println( - " SliderEditor - Duplicate value, should update now"); return; } - - System.err.println(" Changing Row: " + row - + ", Current Percentage: " + getValue()); - Setting setting = context.settingList.get(row); - - - - //Edge cases: - - - //Releasing a slider sends a duplicate of the last value changed. - //Check that previous value doesn't equal current value before - //attempting to do anything with it. - - //**OR wait for that duplicate value if it's guaranteed (seems - //consistend so far), and then when we see it, we know we have - //our new percentage value to calculate the new setting for. - - //Click into a different slider sets slider model for previous slider - //one more time. Then one more change event is fired for that - //previous slider as a 0 value before picking up on the new ones. - //Will need a way to catch this last change event and discard - //that reset value. - - //Possible solution: - //Store a changes row number (need init value?) - //if the row on the next change event is not equal to - //the previous, don't use/set the percentage. - - - - - //Update the previous value to account for duplication edge case previousChangeValue = getValue(); } }); - } /** @@ -124,36 +101,9 @@ public void stateChanged(ChangeEvent event) { public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { targetRow = row; - - System.err.println("SliderEditor - in component editing row " + row); - -// if(this.getValueIsAdjusting()) { -// System.err.println("Adjusting..."); -// } -// this.setValueIsAdjusting(true); - - + if(value instanceof SettingPercentage) { - System.err.println("SliderEditor - Setting Percentage for row " + row); setValue(((SettingPercentage) value).getPercentage()); - -// Setting setting = context.settingList.get(row); -// -// int percentage = ((SettingPercentage) value).getPercentage(); -// System.err.println("Percentage: " + percentage); -// -// float min = setting.getMin(); -// System.err.println("Min: " + min); -// -// float max = setting.getMax(); -// System.err.println("Max: " + max); -// -// float range = (max - min); -// System.err.println("Range: " + range); -// -// float settingValue = ((percentage * range) / 100) + min; -// System.err.println("New setting value is: " + settingValue); -// } else { setValue(0); diff --git a/src/ui/telemetry/SliderRenderer.java b/src/ui/telemetry/SliderRenderer.java index 0a47d1c..a0a059a 100644 --- a/src/ui/telemetry/SliderRenderer.java +++ b/src/ui/telemetry/SliderRenderer.java @@ -21,12 +21,22 @@ public SliderRenderer(Context context) { this.context = context; } + /** + * Handles the standard functionality of the renderer. Determining how + * values and positions are visually represented. + * @param table - The table this renderer is attached to + * @param value - The cell value manipulated by the renderer. + * @param isSelected - Whether or not the active cell is selected + * @param hasFocus - Whether or not the selected cell has focus. + * @param row - The row location of the cell in the table + * @param column - The column location of the cell in the table. + * @return - Component + */ @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if(value instanceof SettingPercentage) { -// System.err.println("SliderRenderer - In renderer component"); setValue(((SettingPercentage) value).getPercentage()); } else { From 2346d1f6e19ad7b806f4dac5c4280769c5a5b21a Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 31 Mar 2021 13:05:49 -0700 Subject: [PATCH 059/125] Add GPS status to State widget --- resources/groundSettings.xml | 40 +++++++++++++++++++++----------- src/serial/Serial.java | 1 + src/ui/widgets/StateWidget.java | 41 ++++++++++++++++++++++++++++++++- 3 files changed, 68 insertions(+), 14 deletions(-) diff --git a/resources/groundSettings.xml b/resources/groundSettings.xml index ebc62c3..1c25e1c 100644 --- a/resources/groundSettings.xml +++ b/resources/groundSettings.xml @@ -1,18 +1,18 @@ Defines how strongly the rover should attempt to return to the original course between waypoints, verses the direct path from its current location to the target<br> The number of degrees that rover will turn its wheels when it needs to to turn its most extreme amount switches between arctangent of error steering (0) <br> square of error steering (1) <br> and proportional to error steering (2) - Multiplier that determines how aggressively to steer - minimum forward driving speed in MPH - maximum forward driving speed in MPH + Multiplier that determines how aggressively to steer + minimum forward driving speed in MPH + maximum forward driving speed in MPH How far to turn the wheels when backing away from an obstacle - speed in MPH to drive in reverse - Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles - Time in milliseconds to coast before reversing when an obstacle is encountered - minimum time in milliseconds to reverse away from an obstacle - P term in cruise control PID loop - I term in cruise control PID loop - D term in cruise control PID loop - Tire Diameter in inches, used to calculate MPH + speed in MPH to drive in reverse + Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles + Time in milliseconds to coast before reversing when an obstacle is encountered + minimum time in milliseconds to reverse away from an obstacle + P term in cruise control PID loop + I term in cruise control PID loop + D term in cruise control PID loop + Tire Diameter in inches, used to calculate MPH Center point in degrees corresponding to driving straight Radius centered at waypoint where target is determined to be meet Radius cneter at waypoint where the approach flag is set @@ -22,5 +22,19 @@ - - \ No newline at end of file + The number of degrees that rover will turn its wheels when it needs to to turn its most extreme amount + switches between arctangent of error steering (0) <br> square of error steering (1) <br> and proportional to error steering (2) + Multiplier that determines how aggressively to steer + minimum forward driving speed in MPH + maximum forward driving speed in MPH + How far to turn the wheels when backing away from an obstacle + speed in MPH to drive in reverse + Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles + Time in milliseconds to coast before reversing when an obstacle is encountered + minimum time in milliseconds to reverse away from an obstacle + P term in cruise control PID loop + I term in cruise control PID loop + D term in cruise control PID loop + Tire Diameter in inches, used to calculate MPH + Center point in degrees corresponding to driving straight + \ No newline at end of file diff --git a/src/serial/Serial.java b/src/serial/Serial.java index 017c522..70180e3 100644 --- a/src/serial/Serial.java +++ b/src/serial/Serial.java @@ -48,6 +48,7 @@ public class Serial { public static final byte DRIVE_STATE = 0x1; public static final byte AUTO_STATE = 0x2; public static final byte AUTO_FLAGS = 0x3; + public static final byte GPS_STATE = 0x4; //APM state sub values public static final byte APM_STATE_INIT = 0x1; diff --git a/src/ui/widgets/StateWidget.java b/src/ui/widgets/StateWidget.java index c843ee3..e1248c2 100644 --- a/src/ui/widgets/StateWidget.java +++ b/src/ui/widgets/StateWidget.java @@ -35,6 +35,9 @@ public class StateWidget extends UIWidget { private JPanel flagPanel; private JLabel flagLabel; + private JPanel gpsPanel; + private JLabel gpsLabel; + private StatusBarWidget statusBar; /** @@ -84,8 +87,14 @@ private void initPanel() { flagLabel.setBackground(DEF_BACK_COLOR_B); flagLabel.setOpaque(true); + gpsLabel = new JLabel("GPS:--"); + gpsLabel.setFont(font); + gpsLabel.setForeground(DEF_FONT_COLOR); + gpsLabel.setBackground(DEF_BACK_COLOR_A); + gpsLabel.setOpaque(true); + JComponent[] labelList = new JComponent[] { - apmLabel, driveLabel, autoLabel, flagLabel + apmLabel, driveLabel, autoLabel, flagLabel, gpsLabel }; for(JComponent jc : labelList) { @@ -126,6 +135,14 @@ private void initPanel() { flagPanel.add(flagLabel); statePanels.add(flagPanel); + gpsPanel = new JPanel(); + gpsPanel.setBorder(insets); + gpsPanel.setLayout(new BoxLayout(gpsPanel, BoxLayout.LINE_AXIS)); + gpsPanel.setPreferredSize(labelSize); + gpsPanel.setOpaque(true); + gpsPanel.add(gpsLabel); + statePanels.add(gpsPanel); + //Add panels to widget for(JPanel panel : statePanels) { this.add(panel); @@ -153,6 +170,9 @@ public void update(byte state, byte substate) { case Serial.AUTO_FLAGS: setFlagState(substate); break; + case Serial.GPS_STATE: + setGPSState(substate); + break; default: System.err.println("Error - Unrecognized State"); } @@ -289,6 +309,25 @@ else if(caution) { flagLabel.setText(fmt.substring(0, finalWidth)); } + private void setGPSState(byte substate) { + int finalWidth; + String fmt; + String fmtStr = "GPS:%s"; + + boolean GPSSignalGood = (substate == 0x00) ? true : false; + + if(GPSSignalGood) { + fmt = String.format(fmtStr, "Good"); + finalWidth = Math.min(fmt.length(), LINE_WIDTH); + + } + else { + fmt = String.format(fmtStr, "Bad"); + finalWidth = Math.min(fmt.length(), LINE_WIDTH); + } + gpsLabel.setText(fmt.substring(0, finalWidth)); + } + /** * Generates an information panel on click describing any warnings, errors, * and details of the current state. From e9c0d01bd56da02618b38e6fb45222dffff9a32d Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Fri, 2 Apr 2021 13:31:15 -0700 Subject: [PATCH 060/125] Remove cell editing capability for raw settings table values This is now handled by the sliders --- .gitignore | 1 + resources/groundSettings.xml | 37 +++++++---------------- src/table/TableFactory.java | 2 +- src/ui/telemetry/TelemetryDataWindow.java | 1 - 4 files changed, 13 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index 0382d53..4af92e0 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ resources/tile_servers.properties .settings/org.eclipse.wst.sse.core.prefs boardlist.txt arduino-cli.exe +resources/groundSettings_edited.xml diff --git a/resources/groundSettings.xml b/resources/groundSettings.xml index 1c25e1c..a42cd65 100644 --- a/resources/groundSettings.xml +++ b/resources/groundSettings.xml @@ -1,18 +1,18 @@ Defines how strongly the rover should attempt to return to the original course between waypoints, verses the direct path from its current location to the target<br> The number of degrees that rover will turn its wheels when it needs to to turn its most extreme amount switches between arctangent of error steering (0) <br> square of error steering (1) <br> and proportional to error steering (2) - Multiplier that determines how aggressively to steer - minimum forward driving speed in MPH - maximum forward driving speed in MPH + Multiplier that determines how aggressively to steer + minimum forward driving speed in MPH + maximum forward driving speed in MPH How far to turn the wheels when backing away from an obstacle - speed in MPH to drive in reverse - Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles - Time in milliseconds to coast before reversing when an obstacle is encountered - minimum time in milliseconds to reverse away from an obstacle - P term in cruise control PID loop - I term in cruise control PID loop - D term in cruise control PID loop - Tire Diameter in inches, used to calculate MPH + speed in MPH to drive in reverse + Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles + Time in milliseconds to coast before reversing when an obstacle is encountered + minimum time in milliseconds to reverse away from an obstacle + P term in cruise control PID loop + I term in cruise control PID loop + D term in cruise control PID loop + Tire Diameter in inches, used to calculate MPH Center point in degrees corresponding to driving straight Radius centered at waypoint where target is determined to be meet Radius cneter at waypoint where the approach flag is set @@ -22,19 +22,4 @@ - The number of degrees that rover will turn its wheels when it needs to to turn its most extreme amount - switches between arctangent of error steering (0) <br> square of error steering (1) <br> and proportional to error steering (2) - Multiplier that determines how aggressively to steer - minimum forward driving speed in MPH - maximum forward driving speed in MPH - How far to turn the wheels when backing away from an obstacle - speed in MPH to drive in reverse - Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles - Time in milliseconds to coast before reversing when an obstacle is encountered - minimum time in milliseconds to reverse away from an obstacle - P term in cruise control PID loop - I term in cruise control PID loop - D term in cruise control PID loop - Tire Diameter in inches, used to calculate MPH - Center point in degrees corresponding to driving straight \ No newline at end of file diff --git a/src/table/TableFactory.java b/src/table/TableFactory.java index 705869f..fdaa33e 100644 --- a/src/table/TableFactory.java +++ b/src/table/TableFactory.java @@ -101,7 +101,7 @@ public Class getDataClass() { //TODO - CP - Once sliders are functional, remove editing for this cell? public boolean isRowEditable(int row) { - return true; + return false; } public void setValueAt(String val, int row) { diff --git a/src/ui/telemetry/TelemetryDataWindow.java b/src/ui/telemetry/TelemetryDataWindow.java index c1a4fac..15b1fee 100644 --- a/src/ui/telemetry/TelemetryDataWindow.java +++ b/src/ui/telemetry/TelemetryDataWindow.java @@ -281,7 +281,6 @@ public void run() { TBL_Telemetry.invalidate(); TBL_Settings.invalidate(); - //TODO - CP - Does Slider table need to be invalidated here as well? } } }, UPDATE_PERIOD_MS, UPDATE_PERIOD_MS); From 5a1fb017e51b261b8c82d5f52f3103ce54b708df Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Mon, 5 Apr 2021 10:49:04 -0700 Subject: [PATCH 061/125] Tie Settings and Settings Slider scrolling together --- build_infinetix.xml | 2 +- src/ui/telemetry/TelemetryDataWindow.java | 29 ++++++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/build_infinetix.xml b/build_infinetix.xml index 13f77bb..6ee51ba 100644 --- a/build_infinetix.xml +++ b/build_infinetix.xml @@ -35,7 +35,7 @@ - + diff --git a/src/ui/telemetry/TelemetryDataWindow.java b/src/ui/telemetry/TelemetryDataWindow.java index 15b1fee..3feac75 100644 --- a/src/ui/telemetry/TelemetryDataWindow.java +++ b/src/ui/telemetry/TelemetryDataWindow.java @@ -15,11 +15,19 @@ import javax.swing.BorderFactory; import javax.swing.table.AbstractTableModel; +import javax.swing.event.ChangeListener; +import javax.swing.event.ChangeEvent; + import java.awt.*; import java.awt.event.*; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; + +import java.awt.event.MouseListener; +import java.awt.event.MouseWheelListener; +import java.awt.event.MouseEvent; + /** * @author Chris Park @ Infinetix Corp * Date: 3-3-21 @@ -133,8 +141,18 @@ public void valueChanged(ListSelectionEvent event) { SCL_Settings.setMinimumSize(SETTINGS_DIM_MIN); SCL_Settings.setMaximumSize(SETTINGS_DIM_MAX); SCL_Settings.setPreferredSize(SETTINGS_DIM_PREF); - SCL_Settings.setBorder(TABLE_BORDERS); + SCL_Settings.setBorder(TABLE_BORDERS); + SCL_Settings.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER); + SCL_Settings.getViewport().addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent event) { + int scrollPosition = + SCL_Settings.getVerticalScrollBar().getValue(); + SCL_SettingsSliders.getVerticalScrollBar().setValue(scrollPosition); + } + }); + //Set up SettingSlider Table and ScrollPane TBL_SettingsSliders = TableFactory.createTable(TableFactory.TableType.Sliders, context); @@ -145,6 +163,15 @@ public void valueChanged(ListSelectionEvent event) { SCL_SettingsSliders.setPreferredSize(SLIDERS_DIM_PREF); SCL_SettingsSliders.setBorder(TABLE_BORDERS); + SCL_SettingsSliders.getViewport().addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent event) { + int scrollPosition = + SCL_SettingsSliders.getVerticalScrollBar().getValue(); + SCL_Settings.getVerticalScrollBar().setValue(scrollPosition); + } + }); + //Pack both settings tables in flow layout settings panel PNL_Settings = new JPanel(); PNL_Settings.setLayout(new FlowLayout()); From f1a86d073cff1789af504e8d6323d16abb2e7780 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Fri, 9 Apr 2021 16:31:28 -0700 Subject: [PATCH 062/125] Add slider init to Telem data window slider values are now populated based on current values when the window is created --- resources/groundSettings.xml | 2 +- src/Dashboard.java | 2 -- src/ui/telemetry/SettingPercentage.java | 3 +- src/ui/telemetry/SliderEditor.java | 2 +- src/ui/telemetry/TelemetryDataWindow.java | 36 +++++++++++++++++++---- 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/resources/groundSettings.xml b/resources/groundSettings.xml index a42cd65..f4f8ea1 100644 --- a/resources/groundSettings.xml +++ b/resources/groundSettings.xml @@ -15,7 +15,7 @@ Tire Diameter in inches, used to calculate MPH Center point in degrees corresponding to driving straight Radius centered at waypoint where target is determined to be meet - Radius cneter at waypoint where the approach flag is set + Radius center at waypoint where the approach flag is set diff --git a/src/Dashboard.java b/src/Dashboard.java index ba52ce4..5a7ca37 100644 --- a/src/Dashboard.java +++ b/src/Dashboard.java @@ -38,8 +38,6 @@ import jssc.SerialPortException; import jssc.SerialPortList; -//import com.ui.WidgetPanel; - public class Dashboard implements Runnable { //Standard References private Context context; diff --git a/src/ui/telemetry/SettingPercentage.java b/src/ui/telemetry/SettingPercentage.java index 8d744f0..2ac2906 100644 --- a/src/ui/telemetry/SettingPercentage.java +++ b/src/ui/telemetry/SettingPercentage.java @@ -46,8 +46,7 @@ else if (value instanceof String) { setPercentage(Integer.parseInt((String) value)); } else { - System.err.println( - "SettingPercentage - Unrecognized value type set attempt"); + setPercentage((int)value); } } diff --git a/src/ui/telemetry/SliderEditor.java b/src/ui/telemetry/SliderEditor.java index 05087da..92505b4 100644 --- a/src/ui/telemetry/SliderEditor.java +++ b/src/ui/telemetry/SliderEditor.java @@ -67,7 +67,7 @@ public void stateChanged(ChangeEvent event) { //Edge Case and Update Window: The last value generated on mouse //up is a duplicate of the previous change event. Update the setting //value here. - if(previousChangeValue == getValue()) { + if(previousChangeValue == getValue()) { setting = context.settingList.get(row); float min = setting.getMin(); diff --git a/src/ui/telemetry/TelemetryDataWindow.java b/src/ui/telemetry/TelemetryDataWindow.java index 3feac75..c341261 100644 --- a/src/ui/telemetry/TelemetryDataWindow.java +++ b/src/ui/telemetry/TelemetryDataWindow.java @@ -74,14 +74,10 @@ public class TelemetryDataWindow implements ActionListener { private JTextComponent TXC_DescriptionBox; private JTable TBL_Telemetry; private JTable TBL_Settings; - - //New - To Vet private JTable TBL_SettingsSliders; private JScrollPane SCL_Telemetry; private JScrollPane SCL_Settings; - - //New - To Vet private JScrollPane SCL_SettingsSliders; //Standard Vars @@ -104,7 +100,6 @@ public TelemetryDataWindow(Context context) { FRM_Window.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent event) { - System.out.println("Data window closed"); onClose(); } }); @@ -144,6 +139,7 @@ public void valueChanged(ListSelectionEvent event) { SCL_Settings.setBorder(TABLE_BORDERS); SCL_Settings.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER); + //Tie scrolling movement to slider table SCL_Settings.getViewport().addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent event) { @@ -163,6 +159,7 @@ public void stateChanged(ChangeEvent event) { SCL_SettingsSliders.setPreferredSize(SLIDERS_DIM_PREF); SCL_SettingsSliders.setBorder(TABLE_BORDERS); + //Tie scrolling movement to settings table SCL_SettingsSliders.getViewport().addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent event) { @@ -205,6 +202,9 @@ public void stateChanged(ChangeEvent event) { FRM_Window.pack(); FRM_Window.setVisible(true); + //Initialize slider values to match current telemetry data + initSliderPercentages(); + //Kick off table updates startTableUpdateTimer(); } @@ -312,4 +312,30 @@ public void run() { } }, UPDATE_PERIOD_MS, UPDATE_PERIOD_MS); } + + + /** + * Initializes the slider values for current telemetry values when the + * window is opened. + */ + public void initSliderPercentages() { + Setting setting; + float min; + float max; + float current; + int percentage; + + for(int i = 0; i < context.settingList.size(); i++) { + setting = context.settingList.get(i); + min = setting.getMin(); + max = setting.getMax(); + current = setting.getVal(); + + percentage = (int) Math.round(((current - min) * 100) / (max - min)); + percentage = (int) Math.floor(percentage); + + TBL_SettingsSliders.getModel().setValueAt(percentage, i, 0); + } + } + } From 6cf27aa7afdd5e1f8ee4405a8addf1ca59ff633d Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Fri, 9 Apr 2021 17:02:53 -0700 Subject: [PATCH 063/125] Give getAPMVersion command enough time for a serial response before returning a result --- src/Context.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Context.java b/src/Context.java index 330c7ac..b223b8d 100644 --- a/src/Context.java +++ b/src/Context.java @@ -302,11 +302,22 @@ public void setAPMVersion(String version) { * @return - String */ public String getAPMVersion() { - if(APMVersion == null || APMVersion.isEmpty()) { + sender.sendMessage(Message.requestAPMVersion()); + + try { + Thread.sleep(250); + } + catch(InterruptedException ex) { + Thread.currentThread().interrupt(); + System.err.println("Context - Wait for version interrupted with exception: " + + ex.toString()); + } + + + if(APMVersion == null || APMVersion.isEmpty()) { APMVersion = "x.x.x"; } - sender.sendMessage(Message.requestAPMVersion()); return APMVersion; } } From 97006e0679f39a6c9b43fb75320d2937a569877c Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 14 Apr 2021 14:27:10 -0700 Subject: [PATCH 064/125] Waypoint panel button access changes Exposing Waypoint Panel buttons to other parts of the program. -Used this to allow the changing of the Mission Start/Stop button state when a stop state is received on mission conclusion. Otherwise the button reads "Stop" even after stopping. --- src/Dashboard.java | 3 +- src/map/MapPanel.java | 2 +- src/map/WaypointPanel.java | 76 +++++++++++++++++++++++---------- src/ui/widgets/StateWidget.java | 15 ++++++- 4 files changed, 69 insertions(+), 27 deletions(-) diff --git a/src/Dashboard.java b/src/Dashboard.java index 5a7ca37..7634252 100644 --- a/src/Dashboard.java +++ b/src/Dashboard.java @@ -54,6 +54,7 @@ public class Dashboard implements Runnable { private TelemetryDataWidget dataWidget; public StateWidget stateWidget; public PingWidget pingWidget; + public MapPanel mapPanel; //Logging private final Logger seriallog = Logger.getLogger("d.serial"); @@ -146,7 +147,7 @@ public void disconnectRequest() { JPanel messageBox = createAlertBox(); - MapPanel mapPanel = new MapPanel(context, + mapPanel = new MapPanel(context, new Point((int)context.getHomeProp().getY(), (int)context.getHomeProp().getX()), 4, // default zoom level diff --git a/src/map/MapPanel.java b/src/map/MapPanel.java index 7d417cc..67f08e0 100644 --- a/src/map/MapPanel.java +++ b/src/map/MapPanel.java @@ -40,7 +40,7 @@ public class MapPanel extends JPanel implements CoordinateTransform { private BorderLayout border = new BorderLayout(); private DragListener mouseListener = new DragListener(); private LayerManager mll = new LayerManager(); - private WaypointPanel waypointPanel; + public WaypointPanel waypointPanel; private RoverPath roverPath; private final Logger iolog = Logger.getLogger("d.io"); diff --git a/src/map/WaypointPanel.java b/src/map/WaypointPanel.java index 341f550..af27a91 100644 --- a/src/map/WaypointPanel.java +++ b/src/map/WaypointPanel.java @@ -29,11 +29,13 @@ import javax.swing.border.EtchedBorder; import javax.xml.stream.XMLStreamException; -class WaypointPanel extends NinePatchPanel { - protected static final int MOVE_STEP = 32; +public class WaypointPanel extends NinePatchPanel { + //Constants + protected static final int MOVE_STEP = 32; protected static final int BDR_SIZE_TB = 20; protected static final int BDR_SIZE_LR = 15; protected static final String NO_WAYPOINT_MSG = "N / A"; + private Context context; private MapPanel map; private WaypointList waypoints; @@ -43,7 +45,31 @@ class WaypointPanel extends NinePatchPanel { TelemField longitude; TelemField altitude; JLabel waypointIndexDisplay; - + + //Panel Buttons + public JButton tileButton; + public JButton dataPanel; + public JButton graphButton; + public JButton reTarget; + public JButton looping; + public JButton config; + public JButton logPanelButton; + + //Map Zoom Options + public JButton zoomInButton; + public JButton zoomOutButton; + public JButton zoomFullButton; + //Waypoint Options + public JButton clearWaypoints; + public JButton newButton; + public JButton enterButton; + public JButton undoButton; + public JButton redoButton; + public JButton saveButton; + public JButton loadButton; + public JButton missionButton; + + private class TelemField extends JTextField{ float lastSetValue = Float.NaN; @Override @@ -163,30 +189,30 @@ private void buildPanel() { final Dimension buttonSize = new Dimension(140, 25); //Make all buttons - JButton tileButton = theme.makeButton(nextTileServer); - JButton dataPanel = theme.makeButton(openDataPanel); - JButton graphButton = theme.makeButton(buildGraph); - JButton reTarget = theme.makeButton(reTargetRover); - JButton looping = theme.makeButton(toggleLooping); - JButton config = theme.makeButton(openConfigWindow); - JButton logPanelButton = theme.makeButton(logPanelAction); + tileButton = theme.makeButton(nextTileServer); + dataPanel = theme.makeButton(openDataPanel); + graphButton = theme.makeButton(buildGraph); + reTarget = theme.makeButton(reTargetRover); + looping = theme.makeButton(toggleLooping); + config = theme.makeButton(openConfigWindow); + logPanelButton = theme.makeButton(logPanelAction); //Map Zoom Options - JButton zoomInButton = theme.makeButton(zoomInAction); - zoomInButton.addMouseListener(zoomInMouseAdapter); - JButton zoomOutButton = theme.makeButton(zoomOutAction); - zoomOutButton.addMouseListener(zoomOutMouseAdapter); - JButton zoomFullButton = theme.makeButton(zoomFullAction); + zoomInButton = theme.makeButton(zoomInAction); + zoomInButton.addMouseListener(zoomInMouseAdapter); + zoomOutButton = theme.makeButton(zoomOutAction); + zoomOutButton.addMouseListener(zoomOutMouseAdapter); + zoomFullButton = theme.makeButton(zoomFullAction); //Waypoint Options - JButton clearWaypoints = theme.makeButton(clearWaypointsAction); - JButton newButton = theme.makeButton(newWaypoint); - JButton enterButton = theme.makeButton(interpretLocationAction); - JButton undoButton = theme.makeButton(undoCommandAction); - JButton redoButton = theme.makeButton(redoCommandAction); - JButton saveButton = theme.makeButton(saveWaypoints); - JButton loadButton = theme.makeButton(loadWaypoints); - JButton missionButton = theme.makeButton(toggleMovement); + clearWaypoints = theme.makeButton(clearWaypointsAction); + newButton = theme.makeButton(newWaypoint); + enterButton = theme.makeButton(interpretLocationAction); + undoButton = theme.makeButton(undoCommandAction); + redoButton = theme.makeButton(redoCommandAction); + saveButton = theme.makeButton(saveWaypoints); + loadButton = theme.makeButton(loadWaypoints); + missionButton = theme.makeButton(toggleMovement); JComponent[] format = new JComponent[] { tileButton, dataPanel, graphButton, @@ -501,6 +527,10 @@ public void actionPerformed(ActionEvent e) { } }; + public boolean getIsMoving() { + return isUnitMoving; + } + private Action previousWaypoint = new AbstractAction() { { String text = "<"; diff --git a/src/ui/widgets/StateWidget.java b/src/ui/widgets/StateWidget.java index e1248c2..ca8792a 100644 --- a/src/ui/widgets/StateWidget.java +++ b/src/ui/widgets/StateWidget.java @@ -25,7 +25,7 @@ public class StateWidget extends UIWidget { private JFrame infoFrame; - //State readouts + //State Readouts Labels private JPanel apmPanel; private JLabel apmLabel; private JPanel drivePanel; @@ -40,6 +40,9 @@ public class StateWidget extends UIWidget { private StatusBarWidget statusBar; + //State Tracking Variables + protected byte lastDriveState = 0xF; + /** * Class constructor * @param ctx - The application context @@ -217,11 +220,18 @@ private void setDriveState(byte substate) { String fmt; String fmtStr = "Drv:%s"; -// System.err.println("StateWidget - Updating Drive State"); +// System.err.println("StateWidget - Updating Drive State"); switch(substate) { case Serial.DRIVE_STATE_STOP: fmt = String.format(fmtStr, "Stopped"); finalWidth = Math.min(fmt.length(), LINE_WIDTH); + + //If the mission finished, update the Start/Stop Mission button. + if(context.dash.mapPanel.waypointPanel.getIsMoving() + && lastDriveState != Serial.DRIVE_STATE_STOP) { + context.dash.mapPanel.waypointPanel.missionButton.doClick(); + } + break; case Serial.DRIVE_STATE_AUTO: fmt = String.format(fmtStr, "Auto"); @@ -236,6 +246,7 @@ private void setDriveState(byte substate) { finalWidth = Math.min(fmt.length(), LINE_WIDTH); } driveLabel.setText(fmt.substring(0, finalWidth)); + lastDriveState = substate; } /** From 09a70d4f1f4dc4d6b57926dbe0f2501b871ef07b Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 14 Apr 2021 14:27:20 -0700 Subject: [PATCH 065/125] Update WaypointPanel.java --- src/map/WaypointPanel.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/map/WaypointPanel.java b/src/map/WaypointPanel.java index af27a91..f9c2bf4 100644 --- a/src/map/WaypointPanel.java +++ b/src/map/WaypointPanel.java @@ -59,6 +59,7 @@ public class WaypointPanel extends NinePatchPanel { public JButton zoomInButton; public JButton zoomOutButton; public JButton zoomFullButton; + //Waypoint Options public JButton clearWaypoints; public JButton newButton; From 7307b2b6e0a6543887dc270bf22f8ad3529f166d Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Fri, 16 Apr 2021 10:48:49 -0700 Subject: [PATCH 066/125] Add GPS good/bad signal transition logging --- resources/groundSettings.xml | 22 +++++++++++----------- src/ui/widgets/StateWidget.java | 10 ++++++++++ src/ui/widgets/UIWidget.java | 6 +++++- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/resources/groundSettings.xml b/resources/groundSettings.xml index f4f8ea1..b51ecb9 100644 --- a/resources/groundSettings.xml +++ b/resources/groundSettings.xml @@ -1,14 +1,14 @@ - Defines how strongly the rover should attempt to return to the original course between waypoints, verses the direct path from its current location to the target<br> - The number of degrees that rover will turn its wheels when it needs to to turn its most extreme amount - switches between arctangent of error steering (0) <br> square of error steering (1) <br> and proportional to error steering (2) - Multiplier that determines how aggressively to steer - minimum forward driving speed in MPH - maximum forward driving speed in MPH - How far to turn the wheels when backing away from an obstacle - speed in MPH to drive in reverse - Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles - Time in milliseconds to coast before reversing when an obstacle is encountered - minimum time in milliseconds to reverse away from an obstacle + Defines how strongly the rover should attempt to return to the original course between waypoints, verses the direct path from its current location to the target<br> + The number of degrees that rover will turn its wheels when it needs to to turn its most extreme amount + switches between arctangent of error steering (0) <br> square of error steering (1) <br> and proportional to error steering (2) + Multiplier that determines how aggressively to steer + minimum forward driving speed in MPH + maximum forward driving speed in MPH + How far to turn the wheels when backing away from an obstacle + speed in MPH to drive in reverse + Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles + Time in milliseconds to coast before reversing when an obstacle is encountered + minimum time in milliseconds to reverse away from an obstacle P term in cruise control PID loop I term in cruise control PID loop D term in cruise control PID loop diff --git a/src/ui/widgets/StateWidget.java b/src/ui/widgets/StateWidget.java index ca8792a..5eb29fe 100644 --- a/src/ui/widgets/StateWidget.java +++ b/src/ui/widgets/StateWidget.java @@ -320,11 +320,14 @@ else if(caution) { flagLabel.setText(fmt.substring(0, finalWidth)); } + boolean previousGPSState; private void setGPSState(byte substate) { int finalWidth; String fmt; String fmtStr = "GPS:%s"; + + boolean GPSSignalGood = (substate == 0x00) ? true : false; if(GPSSignalGood) { @@ -337,6 +340,13 @@ private void setGPSState(byte substate) { finalWidth = Math.min(fmt.length(), LINE_WIDTH); } gpsLabel.setText(fmt.substring(0, finalWidth)); + + if(previousGPSState != GPSSignalGood) { + serialLog.warning( + "GPS Signal is: " + (GPSSignalGood ? "Good" : "Bad")); + } + + previousGPSState = GPSSignalGood; } /** diff --git a/src/ui/widgets/UIWidget.java b/src/ui/widgets/UIWidget.java index c4c122c..737cf0c 100644 --- a/src/ui/widgets/UIWidget.java +++ b/src/ui/widgets/UIWidget.java @@ -2,6 +2,8 @@ import com.Context; +import java.util.logging.Logger; + import javax.swing.*; import javax.swing.border.*; @@ -23,7 +25,7 @@ public class UIWidget extends JPanel { protected static final int BORDER_RHT = 0; protected static final int LABEL_HEIGHT = 25; protected static final int LABEL_WIDTH = 115; - + //Standard Variables protected Context context; protected Border insets; @@ -32,6 +34,8 @@ public class UIWidget extends JPanel { protected JPanel titlePanel; protected JLabel titleLabel; + //Logging support + protected final Logger serialLog = Logger.getLogger("d.serial"); /** * Class constructor From fb3bff31f494f53c00dfad91fc7eb1ce30542103 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Fri, 16 Apr 2021 10:51:52 -0700 Subject: [PATCH 067/125] Update groundSettings.xml Updated: -Approach -Waypoint Achieve -Max Forward Speed --- resources/groundSettings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/groundSettings.xml b/resources/groundSettings.xml index b51ecb9..c34a231 100644 --- a/resources/groundSettings.xml +++ b/resources/groundSettings.xml @@ -3,7 +3,7 @@ switches between arctangent of error steering (0) <br> square of error steering (1) <br> and proportional to error steering (2) Multiplier that determines how aggressively to steer minimum forward driving speed in MPH - maximum forward driving speed in MPH + maximum forward driving speed in MPH How far to turn the wheels when backing away from an obstacle speed in MPH to drive in reverse Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles @@ -14,8 +14,8 @@ D term in cruise control PID loop Tire Diameter in inches, used to calculate MPH Center point in degrees corresponding to driving straight - Radius centered at waypoint where target is determined to be meet - Radius center at waypoint where the approach flag is set + Radius centered at waypoint where target is determined to be meet + Radius center at waypoint where the approach flag is set From 843d08b26fb3800c925bc13c76c25dadf1858a47 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 21 Apr 2021 10:38:27 -0700 Subject: [PATCH 068/125] Create UtilHelper.java Add functions: -Calculate spherical distance between two coordinates -Convert km to feet --- src/util/UtilHelper.java | 72 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/util/UtilHelper.java diff --git a/src/util/UtilHelper.java b/src/util/UtilHelper.java new file mode 100644 index 0000000..8925b98 --- /dev/null +++ b/src/util/UtilHelper.java @@ -0,0 +1,72 @@ +package com.util; + +import java.util.*; + + +/** + * @author Chris Park @ Infinetix Corp + * Date: 4-21-21 + * Description: Singletone class containing useful utility functions for + * Minds-i Dashboard specific functionality and calculations. + */ +public class UtilHelper { + private static UtilHelper utilHelperInstance = null; + + private static final double EARTH_RADIUS = 6371.00; + private static final double FEET_PER_KM = 3280.84; + + /** + * Constructor (Private, accessed by getInstance + */ + private UtilHelper() { + + } + + /** + * Returns the singleton instance of this class to be used system wide. + * @return The UtilHelper instance. + */ + public static UtilHelper getInstance() { + if(utilHelperInstance == null) { + utilHelperInstance = new UtilHelper(); + } + + return utilHelperInstance; + } + + /** + * Computes the distance in km between to points on the surface of a sphere. + * @param lat1 - First latitude coordinate + * @param lon1 - First longitude coordinate + * @param lat2 - Second latitude coordinate + * @param lon2 - Second longitude coordinate + * @return - Distance between coordinates in km + */ + public double haversine(double lat1, double lon1, + double lat2, double lon2) { + //Calculate distance between Lats and Longs + double distLat = Math.toRadians(lat2 - lat1); + double distLon = Math.toRadians(lon2 - lon1); + + //Convert to Radians + lat1 = Math.toRadians(lat1); + lat2 = Math.toRadians(lat2); + + //Use Formula + double a = (Math.pow(Math.sin(distLat / 2), 2) + + (Math.pow(Math.sin(distLon / 2), 2) * + Math.cos(lat1) * + Math.cos(lat2))); + double c = (2 * Math.asin(Math.sqrt(a))); + return EARTH_RADIUS * c; + } + + /** + * Converts kilometer to feet. + * @param km - distance in km to convert + * @return - distance in feet. + */ + public double kmToFeet(double km) { + return (km * FEET_PER_KM); + } +} From 481b443554f1304330f6b57cf40bb50c69d7e430 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 21 Apr 2021 16:01:34 -0700 Subject: [PATCH 069/125] Add optimal distance message when adding waypoints When two or more waypoints are added, a minimum distance is now checked between the newest point and its previous counterpart. If the distance between them is less than the recommended minimum, a warning dialog is displayed to the user which must be dismissed before continuing. --- src/map/command/WaypointCommandAdd.java | 39 +++++++++++++++++++++++-- src/util/UtilHelper.java | 17 +++++++++-- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/map/command/WaypointCommandAdd.java b/src/map/command/WaypointCommandAdd.java index 6b3168d..d50024c 100644 --- a/src/map/command/WaypointCommandAdd.java +++ b/src/map/command/WaypointCommandAdd.java @@ -1,8 +1,13 @@ package com.map.command; +import javax.swing.*; + import com.map.WaypointList; import com.map.command.WaypointCommand.CommandType; import com.map.Dot; +import com.util.UtilHelper; + + /** * @author Chris Park @ Infinetix Corp. @@ -11,7 +16,10 @@ * active sessions list. */ public class WaypointCommandAdd extends WaypointCommand { - + private static final double MIN_DISTANCE_FT = 45.00; + private static final String WARN_STRING = "For optimal results, a minimum" + + " distance of " + MIN_DISTANCE_FT + " feet between waypoints" + + " is recommended."; /** * Constructor * @param waypoints - List of current navigational waypoints. @@ -39,7 +47,9 @@ public boolean execute() { waypoints.setTarget(index); } - waypoints.setSelected(index); + waypoints.setSelected(index); + warnMinimumDistance(index); + return true; } @@ -62,4 +72,29 @@ public boolean undo() { public boolean redo() { return execute(); } + + /** + * Check if the distance between the waypoint at index and the previous + * waypoint (if there is one) are greater than a minimum recommended distance + * (MIN_DIST_FT). If they are not, then provide a dialog box informing the user + * that they should increase the distance for best performance. + * @param index - the index of the most recent waypoint. + */ + public void warnMinimumDistance(int index) { + Dot waypointA; + Dot waypointB; + double distance; + + if(index > 0 ) { + waypointA = waypoints.get(index - 1).dot(); + waypointB = waypoints.get(index).dot(); + + distance = UtilHelper.getInstance().haversine(waypointA, waypointB); + distance = UtilHelper.getInstance().kmToFeet(distance); + + if(distance < MIN_DISTANCE_FT) { + JOptionPane.showMessageDialog(null, WARN_STRING); + } + } + } } diff --git a/src/util/UtilHelper.java b/src/util/UtilHelper.java index 8925b98..3175c84 100644 --- a/src/util/UtilHelper.java +++ b/src/util/UtilHelper.java @@ -2,6 +2,7 @@ import java.util.*; +import com.map.Dot; /** * @author Chris Park @ Infinetix Corp @@ -12,7 +13,7 @@ public class UtilHelper { private static UtilHelper utilHelperInstance = null; - private static final double EARTH_RADIUS = 6371.00; + private static final double EARTH_RADIUS_KM = 6371.00; private static final double FEET_PER_KM = 3280.84; /** @@ -34,6 +35,18 @@ public static UtilHelper getInstance() { return utilHelperInstance; } + + /** + * Computes the distance in km between to points on the surface of a sphere. + * @param pointA - First Waypoint (Dot) + * @param pointB - Second Waypoint (Dot) + * @return - Distance between coordinates in km + */ + public double haversine(Dot pointA, Dot pointB) { + return haversine(pointA.getLatitude(), pointA.getLongitude(), + pointB.getLatitude(), pointB.getLongitude()); + } + /** * Computes the distance in km between to points on the surface of a sphere. * @param lat1 - First latitude coordinate @@ -58,7 +71,7 @@ public double haversine(double lat1, double lon1, Math.cos(lat1) * Math.cos(lat2))); double c = (2 * Math.asin(Math.sqrt(a))); - return EARTH_RADIUS * c; + return EARTH_RADIUS_KM * c; } /** From 4e368fb5aff595e53aec7b00394bc66b68c62947 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Tue, 27 Apr 2021 09:38:56 -0700 Subject: [PATCH 070/125] Settings, status bar updates -Added logging on waypoint placement. -Adjusted groundSettings values for new defaults -Changed "Processing" text of status bar to white for readability. --- resources/groundSettings.xml | 6 +++--- resources/resources_en.properties | 2 +- src/map/command/WaypointCommandAdd.java | 12 ++++++++++-- src/ui/widgets/StatusBarWidget.java | 2 +- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/resources/groundSettings.xml b/resources/groundSettings.xml index c34a231..c147a7b 100644 --- a/resources/groundSettings.xml +++ b/resources/groundSettings.xml @@ -2,10 +2,10 @@ The number of degrees that rover will turn its wheels when it needs to to turn its most extreme amount switches between arctangent of error steering (0) <br> square of error steering (1) <br> and proportional to error steering (2) Multiplier that determines how aggressively to steer - minimum forward driving speed in MPH + minimum forward driving speed in MPH maximum forward driving speed in MPH How far to turn the wheels when backing away from an obstacle - speed in MPH to drive in reverse + speed in MPH to drive in reverse Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles Time in milliseconds to coast before reversing when an obstacle is encountered minimum time in milliseconds to reverse away from an obstacle @@ -14,7 +14,7 @@ D term in cruise control PID loop Tire Diameter in inches, used to calculate MPH Center point in degrees corresponding to driving straight - Radius centered at waypoint where target is determined to be meet + Radius centered at waypoint where target is determined to be meet Radius center at waypoint where the approach flag is set diff --git a/resources/resources_en.properties b/resources/resources_en.properties index df3b630..d4cc4bc 100644 --- a/resources/resources_en.properties +++ b/resources/resources_en.properties @@ -1,5 +1,5 @@ version_id =1.0.3 -release_date =2021-03-01 +release_date =2021-05-01 image_folder =resources/images/ patch_folder =resources/images/nP/ font_folder =resources/fonts/ diff --git a/src/map/command/WaypointCommandAdd.java b/src/map/command/WaypointCommandAdd.java index d50024c..9fb8a74 100644 --- a/src/map/command/WaypointCommandAdd.java +++ b/src/map/command/WaypointCommandAdd.java @@ -1,5 +1,7 @@ package com.map.command; +import java.util.logging.Logger; + import javax.swing.*; import com.map.WaypointList; @@ -7,8 +9,6 @@ import com.map.Dot; import com.util.UtilHelper; - - /** * @author Chris Park @ Infinetix Corp. * Date: 9-14-2020 @@ -20,6 +20,9 @@ public class WaypointCommandAdd extends WaypointCommand { private static final String WARN_STRING = "For optimal results, a minimum" + " distance of " + MIN_DISTANCE_FT + " feet between waypoints" + " is recommended."; + + private final Logger serialLog = Logger.getLogger("d.serial"); + /** * Constructor * @param waypoints - List of current navigational waypoints. @@ -93,7 +96,12 @@ public void warnMinimumDistance(int index) { distance = UtilHelper.getInstance().kmToFeet(distance); if(distance < MIN_DISTANCE_FT) { + serialLog.finer("Waypoint placement of " + + distance + + "feet is less than recommended minimum of " + + MIN_DISTANCE_FT + "feet."); JOptionPane.showMessageDialog(null, WARN_STRING); + } } } diff --git a/src/ui/widgets/StatusBarWidget.java b/src/ui/widgets/StatusBarWidget.java index 3a8ac1d..13bb5cd 100644 --- a/src/ui/widgets/StatusBarWidget.java +++ b/src/ui/widgets/StatusBarWidget.java @@ -37,7 +37,7 @@ public class StatusBarWidget extends JPanel { */ public enum StatusType { NORMAL ("Normal", Color.black, Color.white), - PROCESSING ("Processing", Color.black, Color.blue), + PROCESSING ("Processing", Color.white, Color.blue), CAUTION ("Caution", Color.black, Color.yellow), ERROR ("Error", Color.black, Color.red); From 3f2f72e679cc5b56b19978462a31ae316f5976dc Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Tue, 27 Apr 2021 11:18:02 -0700 Subject: [PATCH 071/125] Removing some extraneous/unused files --- src/table/TableFactory.java | 2 - src/ui/FloatJSlider.java | 62 ------------------ src/ui/telemetry/TelemetryDataFieldPanel.java | 64 ------------------- src/ui/widgets/TelemetryDataWidget.java | 7 ++ 4 files changed, 7 insertions(+), 128 deletions(-) delete mode 100644 src/ui/FloatJSlider.java delete mode 100644 src/ui/telemetry/TelemetryDataFieldPanel.java diff --git a/src/table/TableFactory.java b/src/table/TableFactory.java index fdaa33e..47b70a1 100644 --- a/src/table/TableFactory.java +++ b/src/table/TableFactory.java @@ -3,7 +3,6 @@ import com.Context; import com.remote.*; import com.table.*; -import com.ui.FloatJSlider; import com.ui.telemetry.SliderRenderer; import com.ui.telemetry.SliderEditor; import com.ui.telemetry.SettingPercentage; @@ -99,7 +98,6 @@ public Class getDataClass() { return String.class; } - //TODO - CP - Once sliders are functional, remove editing for this cell? public boolean isRowEditable(int row) { return false; } diff --git a/src/ui/FloatJSlider.java b/src/ui/FloatJSlider.java deleted file mode 100644 index d7649d6..0000000 --- a/src/ui/FloatJSlider.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.ui; - -import javax.swing.*; - -/** - * @author Chris Park @ Infinetix.com - * Date: 3-5-21 - * Description: A standard JSlider works in integer values only. This - * custom JSlider class is intended for handling float values. - */ -public class FloatJSlider extends JSlider{ - private int scale; - - /** - * Default Constructor - */ - public FloatJSlider() { - super(0, 100, 50); - this.scale = 1; - } - - /** - * Class constructor - * @param min - Minimum value for slider range - * @param max - Maximum value for slider range - * @param value - The initial value of the slider - * @param scale - The decimal scale of used for value conversion. - */ - public FloatJSlider(int min, int max, int value, int scale) { - super(min, max, value); - this.scale = scale; - } - - /** - * Returns the float value that corresponds to the sliders current position. - * @return - float - */ - public float getScaledValue() { - return ((float)super.getValue() / this.scale); - } - - /** - * Sets the scale that determines decimal conversion when retrieving slider - * values. - * @return - */ - public int getScale() { - return scale; - } - - /** - * Sets the current decimal scale for this slider. - * @param value - */ - public void setScale(int value) { - scale = value; - } - - public void updateValue() { - //Update the slider value from an external source here. - } -} diff --git a/src/ui/telemetry/TelemetryDataFieldPanel.java b/src/ui/telemetry/TelemetryDataFieldPanel.java deleted file mode 100644 index 84a4aef..0000000 --- a/src/ui/telemetry/TelemetryDataFieldPanel.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.ui.telemetry; - -import java.util.*; - -import javax.swing.*; -import javax.swing.event.*; - -import com.remote.Setting; -import com.ui.FloatJSlider; - -/** - * @author Chris Park @ Infinetix Corp. - * Date: 3-3-21 - * Description: Custom JPanel designed to be used within a table view, allowing for control - * of configurable telemetry settings using JSliders. - */ -public class TelemetryDataFieldPanel extends JPanel { - - //Visual Components - protected JPanel panel; -// protected JLabel telemName; -// protected JTextField telemValField; - protected FloatJSlider telemValSlider; - - protected Setting setting; - protected int sigFigs; - protected int conversionVal; - - public TelemetryDataFieldPanel(Setting setting) { - this.setting = setting; - - //Creat slider and map values to it. - //Parse out the decimal places for JSlider mapping (Jslider uses ints for range). - String str = String.valueOf(setting.getDefault()); - String[] parsed = str.split("."); - - //NOTE: length vs length()... length returns the length of the array - //where as length() returns the number of characters in a string. - sigFigs = (parsed.length > 2) ? parsed[1].length() : 0; - conversionVal = (sigFigs < 0) ? (int) Math.pow(10, sigFigs) : 1; - - int min = (int) (setting.getMin() * conversionVal); - int max = (int) (setting.getMax() * conversionVal); - telemValSlider = new FloatJSlider( - min, max, (int) Math.floor(setting.getDefault()), conversionVal); - telemValSlider.setOpaque(false); - telemValSlider.setFocusable(false); - - telemValSlider.getModel().addChangeListener(new ChangeListener() { - @Override public void stateChanged(ChangeEvent event) { - BoundedRangeModel model = (BoundedRangeModel) event.getSource(); - float updateVal = ((float) model.getValue() / (float) conversionVal); - - //Update text field value -// telemValField.setText(Objects.toString(updateVal)); - } - }); - } - - //TODO - CP - Update/push value change here. (this should fire from editor) - public void updateValue(Setting setting) { - - } -} diff --git a/src/ui/widgets/TelemetryDataWidget.java b/src/ui/widgets/TelemetryDataWidget.java index 65a1769..fcd7056 100644 --- a/src/ui/widgets/TelemetryDataWidget.java +++ b/src/ui/widgets/TelemetryDataWidget.java @@ -98,6 +98,13 @@ public Line(Context ctx, String format) { */ public void update(double data) { String format = String.format(formatStr, data); + + + //TODO - CP - Parse the Vcc string here and change text on low voltage (< 6.0v) +// if(format.contains("Vcc")) { +// System.out.println("Data: " + format); +// } + int finalWidth = Math.min(format.length(), lineWidth); setText(format.substring(0, finalWidth)); } From 7b3426ecbb8ddf2489a65bf8c6688bad8430f79c Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Tue, 27 Apr 2021 12:32:14 -0700 Subject: [PATCH 072/125] add scrolling to left telemetry table --- resources/groundSettings.xml | 4 ++-- src/ui/telemetry/TelemetryDataWindow.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/groundSettings.xml b/resources/groundSettings.xml index c147a7b..6dc223c 100644 --- a/resources/groundSettings.xml +++ b/resources/groundSettings.xml @@ -14,8 +14,8 @@ D term in cruise control PID loop Tire Diameter in inches, used to calculate MPH Center point in degrees corresponding to driving straight - Radius centered at waypoint where target is determined to be meet - Radius center at waypoint where the approach flag is set + Radius centered at waypoint where target is determined to be meet + Radius center at waypoint where the approach flag is set diff --git a/src/ui/telemetry/TelemetryDataWindow.java b/src/ui/telemetry/TelemetryDataWindow.java index c341261..699e918 100644 --- a/src/ui/telemetry/TelemetryDataWindow.java +++ b/src/ui/telemetry/TelemetryDataWindow.java @@ -137,7 +137,7 @@ public void valueChanged(ListSelectionEvent event) { SCL_Settings.setMaximumSize(SETTINGS_DIM_MAX); SCL_Settings.setPreferredSize(SETTINGS_DIM_PREF); SCL_Settings.setBorder(TABLE_BORDERS); - SCL_Settings.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER); +// SCL_Settings.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER); //Tie scrolling movement to slider table SCL_Settings.getViewport().addChangeListener(new ChangeListener() { From d668750e6ece41caad3afc9ee2ac6f72076cf848 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Tue, 27 Apr 2021 14:08:52 -0700 Subject: [PATCH 073/125] Update TelemetryDataWidget.java Add text color change to red when battery voltage reads less than or equal to 6.0v --- src/ui/widgets/TelemetryDataWidget.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/ui/widgets/TelemetryDataWidget.java b/src/ui/widgets/TelemetryDataWidget.java index fcd7056..5bb5c28 100644 --- a/src/ui/widgets/TelemetryDataWidget.java +++ b/src/ui/widgets/TelemetryDataWidget.java @@ -22,6 +22,8 @@ * Description: Widget child class used for displaying Telemetry data. */ public class TelemetryDataWidget extends UIWidget { + protected static final double BATTERY_LOW_THRESHOLD = 6.0; + protected JPanel panel; private int lineWidth; @@ -101,10 +103,17 @@ public void update(double data) { //TODO - CP - Parse the Vcc string here and change text on low voltage (< 6.0v) -// if(format.contains("Vcc")) { -// System.out.println("Data: " + format); -// } + if(format.contains("Vcc")) { + if(data <= BATTERY_LOW_THRESHOLD) { + this.setForeground(Color.red); + } + else { + this.setForeground(Color.decode("0xEA8300")); + } + } + + int finalWidth = Math.min(format.length(), lineWidth); setText(format.substring(0, finalWidth)); } From 0e461b0c30275242ec1cbbed788c2f0d11db77a4 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 28 Apr 2021 12:02:15 -0700 Subject: [PATCH 074/125] Update TelemetryDataWidget.java Minimal cleanup of comments in data widget --- src/ui/widgets/TelemetryDataWidget.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/ui/widgets/TelemetryDataWidget.java b/src/ui/widgets/TelemetryDataWidget.java index 5bb5c28..18c742b 100644 --- a/src/ui/widgets/TelemetryDataWidget.java +++ b/src/ui/widgets/TelemetryDataWidget.java @@ -101,8 +101,6 @@ public Line(Context ctx, String format) { public void update(double data) { String format = String.format(formatStr, data); - - //TODO - CP - Parse the Vcc string here and change text on low voltage (< 6.0v) if(format.contains("Vcc")) { if(data <= BATTERY_LOW_THRESHOLD) { this.setForeground(Color.red); @@ -112,8 +110,6 @@ public void update(double data) { } } - - int finalWidth = Math.min(format.length(), lineWidth); setText(format.substring(0, finalWidth)); } From a838338518ff2bca1678326e2a3d6915d81f426e Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 28 Apr 2021 15:26:08 -0700 Subject: [PATCH 075/125] Update default serial baud rate to 57600 This is still configurable via the available dropdown list --- resources/groundSettings.xml | 15 ++++++++------- src/serial/Serial.java | 3 ++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/resources/groundSettings.xml b/resources/groundSettings.xml index 6dc223c..e9b7c88 100644 --- a/resources/groundSettings.xml +++ b/resources/groundSettings.xml @@ -1,21 +1,22 @@ Defines how strongly the rover should attempt to return to the original course between waypoints, verses the direct path from its current location to the target<br> The number of degrees that rover will turn its wheels when it needs to to turn its most extreme amount - switches between arctangent of error steering (0) <br> square of error steering (1) <br> and proportional to error steering (2) - Multiplier that determines how aggressively to steer - minimum forward driving speed in MPH - maximum forward driving speed in MPH + Switches between arctangent of error steering (0) <br> square of error steering (1) <br> and proportional to error steering (2) + Multiplier that determines how aggressively to steer + Minimum forward driving speed in MPH + Maximum forward driving speed in MPH How far to turn the wheels when backing away from an obstacle - speed in MPH to drive in reverse + Speed in MPH to drive in reverse Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles Time in milliseconds to coast before reversing when an obstacle is encountered - minimum time in milliseconds to reverse away from an obstacle + Minimum time in milliseconds to reverse away from an obstacle P term in cruise control PID loop I term in cruise control PID loop D term in cruise control PID loop Tire Diameter in inches, used to calculate MPH Center point in degrees corresponding to driving straight Radius centered at waypoint where target is determined to be meet - Radius center at waypoint where the approach flag is set + Radius cneter at waypoint where the approach flag is set + diff --git a/src/serial/Serial.java b/src/serial/Serial.java index 70180e3..178da26 100644 --- a/src/serial/Serial.java +++ b/src/serial/Serial.java @@ -97,7 +97,8 @@ public class Serial { public static final int MAX_WAYPOINTS = 64; public static final int MAX_SETTINGS = 64; public static final int MAX_TELEMETRY = 256; - public static final int BAUD = SerialPort.BAUDRATE_9600; +// public static final int BAUD = SerialPort.BAUDRATE_9600; + public static final int BAUD = SerialPort.BAUDRATE_57600; public static final int U16_FIXED_POINT = 256; public static final int MAX_CONFIRM_WAIT = 2000; //in milliseconds From 2b490971df1c1617abf8cce8ff912b53656b7e37 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 6 May 2021 14:36:11 -0700 Subject: [PATCH 076/125] Renable manual settings value editing Values can be entered manually again. and will snap to the max/min for a setting as necessary. Slider values still need to be updated on manual table value change. --- src/table/SettingSliderModel.java | 1 + src/table/TableFactory.java | 15 +++++++++++++-- src/ui/telemetry/SliderEditor.java | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/table/SettingSliderModel.java b/src/table/SettingSliderModel.java index 9c51178..e5101b2 100644 --- a/src/table/SettingSliderModel.java +++ b/src/table/SettingSliderModel.java @@ -101,6 +101,7 @@ public Object getValueAt(int row, int column) { @Override public void setValueAt(Object value, int row, int column) { ((SettingPercentage) data[row][column]).setPercentage(value); + fireTableCellUpdated(row, column); } } diff --git a/src/table/TableFactory.java b/src/table/TableFactory.java index 47b70a1..be4112a 100644 --- a/src/table/TableFactory.java +++ b/src/table/TableFactory.java @@ -99,17 +99,28 @@ public Class getDataClass() { } public boolean isRowEditable(int row) { - return false; + return true; } public void setValueAt(String val, int row) { + Float newVal = Float.valueOf((String)val); + if(settingList.get(row).outsideOfBounds(newVal)) { JFrame mf = new JFrame("Warning"); JOptionPane.showMessageDialog( - mf, "Caution: new value is outside of logical bounds"); + mf, "Caution: new value is outside of allowable range. " + + "The min or max of that range will be used."); + + //Ensure we are only setting values at maximum or minimum + //if they have been exceeded. + newVal = ((newVal > settingList.get(row).getMax()) + ? settingList.get(row).getMax() + : settingList.get(row).getMin()); } settingList.pushSetting(row,newVal); + + //TODO - CP - Update the slider position based on the set value. } }); diff --git a/src/ui/telemetry/SliderEditor.java b/src/ui/telemetry/SliderEditor.java index 92505b4..838fe5d 100644 --- a/src/ui/telemetry/SliderEditor.java +++ b/src/ui/telemetry/SliderEditor.java @@ -75,6 +75,7 @@ public void stateChanged(ChangeEvent event) { float range = (max - min); float settingValue = ((getValue() * range) / 100) + min; + System.out.println("Pushing new setting value: " + settingValue); //Set final updated value context.settingList.pushSetting(row, settingValue); From 81e964c929fbe494552495109439977101a2bc53 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 27 May 2021 17:28:56 -0700 Subject: [PATCH 077/125] Settings Slider updates Added periodic update calls. Fixed a bug that was holding slider cells in editing mode. Editing is now stopped on mouse up. This ensures that values updated manually will show slider position updates immediately instead of having to wait for a new slider in the table to be selected to stop a previous edit. --- src/table/SettingSliderModel.java | 2 -- src/table/TableFactory.java | 15 +++++++---- src/ui/DataWindow.java | 14 ++++------ src/ui/telemetry/SliderEditor.java | 1 - src/ui/telemetry/SliderRenderer.java | 2 -- src/ui/telemetry/TelemetryDataWindow.java | 33 ++++++++++++++++------- 6 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/table/SettingSliderModel.java b/src/table/SettingSliderModel.java index e5101b2..6a8b142 100644 --- a/src/table/SettingSliderModel.java +++ b/src/table/SettingSliderModel.java @@ -28,7 +28,6 @@ public SettingSliderModel(int size) { for(int i = 0; i < size; i++) { data[i][0] = new SettingPercentage(); } - } /** @@ -101,7 +100,6 @@ public Object getValueAt(int row, int column) { @Override public void setValueAt(Object value, int row, int column) { ((SettingPercentage) data[row][column]).setPercentage(value); - fireTableCellUpdated(row, column); } } diff --git a/src/table/TableFactory.java b/src/table/TableFactory.java index be4112a..ae5ad2d 100644 --- a/src/table/TableFactory.java +++ b/src/table/TableFactory.java @@ -20,11 +20,18 @@ */ public class TableFactory { + //Table type to return from the factory. + public enum TableType {Telemetry, Settings, Sliders} + //Constructor is private here to prevent object instantiation. private TableFactory() {} - public enum TableType {Telemetry, Settings, Sliders} - + /** + * Create a table based on the Enum TableType provided. + * @param type - The TableType of the table to create + * @param context - The application context + * @return JTable - The generated table + */ public static JTable createTable(TableType type, Context context) { switch(type) { case Telemetry: @@ -118,9 +125,7 @@ public void setValueAt(String val, int row) { ? settingList.get(row).getMax() : settingList.get(row).getMin()); } - settingList.pushSetting(row,newVal); - - //TODO - CP - Update the slider position based on the set value. + settingList.pushSetting(row, newVal); } }); diff --git a/src/ui/DataWindow.java b/src/ui/DataWindow.java index c626322..2b69c4a 100644 --- a/src/ui/DataWindow.java +++ b/src/ui/DataWindow.java @@ -1,3 +1,8 @@ +/** + * DEPRICATED 4-21, Replaced by TelemetryDataWindow, with Table creation moved to + * separate class (TableFactory) + */ + package com.ui; import com.Dashboard; import com.serial.*; @@ -187,15 +192,6 @@ public void setValueAt(String val, int row) { setScroll.setBorder(tableBorders); telScroll.setBorder(tableBorders); - //TODO - CP - How is this variable ever used? This is the only place it appears... - //This is fully qualified because it creates a naming collision with the custom - //TableColumn interface in the code base... Bad. -// javax.swing.table.TableColumn col; -// col = telTable.getColumn(telem.get(1).getName()); -// col.setPreferredWidth(1); -// col = setTable.getColumn(settings.get(1).getName()); -// col.setPreferredWidth(1); - setTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent event) { setDetail(setTable.getSelectedRow()); diff --git a/src/ui/telemetry/SliderEditor.java b/src/ui/telemetry/SliderEditor.java index 838fe5d..92505b4 100644 --- a/src/ui/telemetry/SliderEditor.java +++ b/src/ui/telemetry/SliderEditor.java @@ -75,7 +75,6 @@ public void stateChanged(ChangeEvent event) { float range = (max - min); float settingValue = ((getValue() * range) / 100) + min; - System.out.println("Pushing new setting value: " + settingValue); //Set final updated value context.settingList.pushSetting(row, settingValue); diff --git a/src/ui/telemetry/SliderRenderer.java b/src/ui/telemetry/SliderRenderer.java index a0a059a..81e6ac5 100644 --- a/src/ui/telemetry/SliderRenderer.java +++ b/src/ui/telemetry/SliderRenderer.java @@ -35,7 +35,6 @@ public SliderRenderer(Context context) { @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - if(value instanceof SettingPercentage) { setValue(((SettingPercentage) value).getPercentage()); } @@ -45,5 +44,4 @@ public Component getTableCellRendererComponent(JTable table, Object value, return this; } - } diff --git a/src/ui/telemetry/TelemetryDataWindow.java b/src/ui/telemetry/TelemetryDataWindow.java index 699e918..68fbb0b 100644 --- a/src/ui/telemetry/TelemetryDataWindow.java +++ b/src/ui/telemetry/TelemetryDataWindow.java @@ -11,6 +11,8 @@ import javax.swing.text.*; import javax.swing.event.ListSelectionListener; import javax.swing.event.ListSelectionEvent; +import javax.swing.event.TableModelListener; +import javax.swing.event.TableModelEvent; import javax.swing.border.Border; import javax.swing.BorderFactory; import javax.swing.table.AbstractTableModel; @@ -63,10 +65,7 @@ public class TelemetryDataWindow implements ActionListener { //UI Elements private JFrame FRM_Window; private JPanel PNL_Main; - - //New - To Vet private JPanel PNL_Settings; - private JPanel PNL_Log; private JLabel LBL_Log; private JTextField TXF_Log; @@ -132,12 +131,19 @@ public void valueChanged(ListSelectionEvent event) { } }); - SCL_Settings = new JScrollPane(TBL_Settings); + //Set up slider visual updates when a value is entered manually. + TBL_Settings.getModel().addTableModelListener(new TableModelListener() { + @Override + public void tableChanged(TableModelEvent event) { + updateSliderPercentages(); + } + }); + + SCL_Settings = new JScrollPane(TBL_Settings); SCL_Settings.setMinimumSize(SETTINGS_DIM_MIN); SCL_Settings.setMaximumSize(SETTINGS_DIM_MAX); SCL_Settings.setPreferredSize(SETTINGS_DIM_PREF); SCL_Settings.setBorder(TABLE_BORDERS); -// SCL_Settings.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER); //Tie scrolling movement to slider table SCL_Settings.getViewport().addChangeListener(new ChangeListener() { @@ -153,6 +159,15 @@ public void stateChanged(ChangeEvent event) { TBL_SettingsSliders = TableFactory.createTable(TableFactory.TableType.Sliders, context); + //Release table cell editing on mouse up so that sliders update correctly + TBL_SettingsSliders.addMouseListener(new MouseAdapter() { + @Override + public void mouseReleased(MouseEvent e) { + TBL_SettingsSliders.getCellEditor().stopCellEditing(); + } + }); + + SCL_SettingsSliders = new JScrollPane(TBL_SettingsSliders); SCL_SettingsSliders.setMinimumSize(SLIDERS_DIM_MIN); SCL_SettingsSliders.setMaximumSize(SLIDERS_DIM_MAX); @@ -202,8 +217,8 @@ public void stateChanged(ChangeEvent event) { FRM_Window.pack(); FRM_Window.setVisible(true); - //Initialize slider values to match current telemetry data - initSliderPercentages(); + //Initialize/update slider values to match current telemetry data + updateSliderPercentages(); //Kick off table updates startTableUpdateTimer(); @@ -301,6 +316,7 @@ public void run() { (AbstractTableModel) TBL_Telemetry.getModel(); AbstractTableModel settingsModel = (AbstractTableModel) TBL_Settings.getModel(); + telemetryModel.fireTableRowsUpdated( 0, Serial.MAX_TELEMETRY); settingsModel.fireTableRowsUpdated( @@ -318,7 +334,7 @@ public void run() { * Initializes the slider values for current telemetry values when the * window is opened. */ - public void initSliderPercentages() { + public void updateSliderPercentages() { Setting setting; float min; float max; @@ -337,5 +353,4 @@ public void initSliderPercentages() { TBL_SettingsSliders.getModel().setValueAt(percentage, i, 0); } } - } From 490e4b9cb05ad0e1e95099c5c8f891b8c1dc86cf Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Tue, 15 Jun 2021 11:19:11 -0700 Subject: [PATCH 078/125] Gyro sync included in ground settings --- .gitignore | 2 ++ resources/groundSettings.xml | 1 + 2 files changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 4af92e0..6b3b288 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ resources/tile_servers.properties boardlist.txt arduino-cli.exe resources/groundSettings_edited.xml +resources/groundSettings_Old.xml +resources/groundSettings.xml diff --git a/resources/groundSettings.xml b/resources/groundSettings.xml index e9b7c88..418169f 100644 --- a/resources/groundSettings.xml +++ b/resources/groundSettings.xml @@ -16,6 +16,7 @@ Center point in degrees corresponding to driving straight Radius centered at waypoint where target is determined to be meet Radius cneter at waypoint where the approach flag is set + Change to init gyro sync to north From d6911bc920bf5989fc89d88cd107d6999a94c4af Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 24 Jun 2021 14:16:18 -0700 Subject: [PATCH 079/125] Add WaypointLock when mission in progress All waypoint manipulation from the WaypointPanel or map mouse events are now disabled during an active mission. To re-enable functionality, a mission must be stopped directly or stopped via a clear waypoints event. --- .gpx | 9 +++++ src/map/MapPanel.java | 4 ++ src/map/WaypointPanel.java | 70 ++++++++++++++++++++++++++++++--- src/ui/widgets/StateWidget.java | 2 - 4 files changed, 78 insertions(+), 7 deletions(-) diff --git a/.gpx b/.gpx index b486df2..de5e6be 100644 --- a/.gpx +++ b/.gpx @@ -1,4 +1,13 @@ + +0.0 + + +0.0 + + +0.0 + \ No newline at end of file diff --git a/src/map/MapPanel.java b/src/map/MapPanel.java index 67f08e0..0e59613 100644 --- a/src/map/MapPanel.java +++ b/src/map/MapPanel.java @@ -285,6 +285,10 @@ private Map importMapSources(Context ctx){ return sources; } + /** + * Name: Class - DragListener + * Desc: Handles mouse movement events (Click and Drag, Zoom by scrollwheel) for the map. + */ private class DragListener implements Layer, MouseWheelListener { private Point downCoords = null; private Point2D downPosition = null; diff --git a/src/map/WaypointPanel.java b/src/map/WaypointPanel.java index f9c2bf4..61146cf 100644 --- a/src/map/WaypointPanel.java +++ b/src/map/WaypointPanel.java @@ -36,11 +36,14 @@ public class WaypointPanel extends NinePatchPanel { protected static final int BDR_SIZE_LR = 15; protected static final String NO_WAYPOINT_MSG = "N / A"; + private Context context; private MapPanel map; private WaypointList waypoints; private javax.swing.Timer zoomTimer; private TelemetryDataWindow telemetryDataWindow; + + //Text Fields TelemField latitude; TelemField longitude; TelemField altitude; @@ -71,7 +74,7 @@ public class WaypointPanel extends NinePatchPanel { public JButton missionButton; - private class TelemField extends JTextField{ + private class TelemField extends JTextField { float lastSetValue = Float.NaN; @Override public void setText(String newString){ @@ -95,14 +98,18 @@ public WaypointPanel(Context cxt, MapPanel mapPanel) { super(cxt.theme.panelPatch); map = mapPanel; context = cxt; + makeActions(); + waypoints = context.getWaypointList(); setOpaque(false); LayoutManager layout = new BoxLayout(this, BoxLayout.PAGE_AXIS); setLayout(layout); setBorder(BorderFactory.createEmptyBorder(BDR_SIZE_TB, BDR_SIZE_LR, BDR_SIZE_TB, BDR_SIZE_LR)); + buildPanel(); + addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent me) { //do nothing here to prevent clicks from falling through @@ -172,16 +179,18 @@ public void updateDisplay() { latitude.setForeground(Color.BLACK); longitude.setForeground(Color.BLACK); altitude.setForeground(Color.BLACK); + if(selectedWaypoint < 0){ latitude.setEditable(false); longitude.setEditable(false); altitude.setEditable(false); } else { - latitude.setEditable(true); - longitude.setEditable(true); - altitude.setEditable(true); + if(!isUnitMoving) { + latitude.setEditable(true); + longitude.setEditable(true); + altitude.setEditable(true); + } } - } private void buildPanel() { @@ -259,6 +268,7 @@ public EditBoxSpec(JTextField ref, String label) { editorBoxes.add(new EditBoxSpec(latitude , "Lat: ")); editorBoxes.add(new EditBoxSpec(longitude, "Lng: ")); editorBoxes.add(new EditBoxSpec(altitude , context.getResource("waypointExtra")+" ")); + ArrayList editorPanels = new ArrayList(); for(EditBoxSpec box : editorBoxes) { //construct panel @@ -368,6 +378,54 @@ public void interpretLocationEntry() { } catch (NumberFormatException e) {} } + /** + * Disables all WaypointPanel buttons and mouse events related + * to the manipulation of waypoints. + */ + private void lockWaypoints() { + //Buttons + newButton.setEnabled(false); + enterButton.setEnabled(false); + undoButton.setEnabled(false); + redoButton.setEnabled(false); + saveButton.setEnabled(false); + loadButton.setEnabled(false); + reTarget.setEnabled(false); + looping.setEnabled(false); + + //Manual Fields + latitude.setEditable(false); + longitude.setEditable(false); + altitude.setEditable(false); + + //Mouse events + map.enablePathModifications(false); + } + + /** + * Enables all WaypointPanel buttons and mouse events related + * to the manipulation of waypoints. + */ + private void unlockWaypoints() { + //Buttons + newButton.setEnabled(true); + enterButton.setEnabled(true); + undoButton.setEnabled(true); + redoButton.setEnabled(true); + saveButton.setEnabled(true); + loadButton.setEnabled(true); + reTarget.setEnabled(true); + looping.setEnabled(true); + + //Manual Fields + latitude.setEditable(true); + longitude.setEditable(true); + altitude.setEditable(true); + + //Mouse events + map.enablePathModifications(true); + } + private Action logPanelAction = new AbstractAction() { LogViewer lv; Logger log; @@ -516,11 +574,13 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { if(isUnitMoving) { + unlockWaypoints(); context.sender.changeMovement(false); putValue(Action.NAME, "Start Mission"); isUnitMoving = false; } else { + lockWaypoints(); context.sender.changeMovement(true); putValue(Action.NAME, "Stop Mission"); isUnitMoving = true; diff --git a/src/ui/widgets/StateWidget.java b/src/ui/widgets/StateWidget.java index 5eb29fe..3a2710d 100644 --- a/src/ui/widgets/StateWidget.java +++ b/src/ui/widgets/StateWidget.java @@ -326,8 +326,6 @@ private void setGPSState(byte substate) { String fmt; String fmtStr = "GPS:%s"; - - boolean GPSSignalGood = (substate == 0x00) ? true : false; if(GPSSignalGood) { From edfb263346fde8e8f6d4c4e65b11c8633f1cd7c8 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Tue, 29 Jun 2021 16:19:07 -0700 Subject: [PATCH 080/125] Create/add gps widget image assets ... --- resources/images/satIconGPS.png | Bin 0 -> 414 bytes resources/images/verticalMeterGreen.png | Bin 0 -> 233 bytes resources/images/verticalMeterRed.png | Bin 0 -> 257 bytes resources/images/verticalMeterYellow.png | Bin 0 -> 260 bytes resources/images/veticalSpacer.png | Bin 0 -> 172 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/images/satIconGPS.png create mode 100644 resources/images/verticalMeterGreen.png create mode 100644 resources/images/verticalMeterRed.png create mode 100644 resources/images/verticalMeterYellow.png create mode 100644 resources/images/veticalSpacer.png diff --git a/resources/images/satIconGPS.png b/resources/images/satIconGPS.png new file mode 100644 index 0000000000000000000000000000000000000000..0cefe5672c8fb26a38322043a3a4271a7d327a93 GIT binary patch literal 414 zcmV;P0b%}$P)=&`g0*tF&&XQqkx50XV~f7weLd#sB~S07*qo IM6N<$f_Q+ar~m)} literal 0 HcmV?d00001 diff --git a/resources/images/verticalMeterGreen.png b/resources/images/verticalMeterGreen.png new file mode 100644 index 0000000000000000000000000000000000000000..c0c572aabb1bd51e00582636008352c266e7f337 GIT binary patch literal 233 zcmeAS@N?(olHy`uVBq!ia0vp^Ahrkx8<5=cZcP}FVoUONcVYMsf(!O8p9~b?Ebxdd zW?MX8A; zsVNHOnI#zt?w-B@DSD~wKyhDB7sn8d^T{a=4gYy=Fe=YUNJ#jx-fFhs>Q%Q_{+E;h z0?EX#8=@PfPpmw3pde~qV`FEdhrz0X5XGNd4>_-YQ7mI!{#5b-=kyh87#I@Ld6o00 Ry#5Wc(bLt>Wt~$(6976YNg)6L literal 0 HcmV?d00001 diff --git a/resources/images/verticalMeterRed.png b/resources/images/verticalMeterRed.png new file mode 100644 index 0000000000000000000000000000000000000000..82c62c3e5396215f1554948df7381bc4360d76fa GIT binary patch literal 257 zcmeAS@N?(olHy`uVBq!ia0vp^Ahrkx8<5=cZcP}FVoUONcVYMsf(!O8p9~b?Ebxdd zW?MX8A; zsVNHOnI#zt?w-B@DSD~wK=D*h7sn8d^KUON6m2lzVYtY>{-dZQV{p4`!iO^_8NRL& zJj`)-!6JI-i(`n!`L~xB@-{dKxLmYviDBwe^H{;OoAYb{_tO|& zk%HVchxD2R&TxL{sr%G_h?jxaa)r(vmJ9b*yeU4rP|Y3S&@<$kYhYu{an^LB{Ts5R1j3y literal 0 HcmV?d00001 diff --git a/resources/images/veticalSpacer.png b/resources/images/veticalSpacer.png new file mode 100644 index 0000000000000000000000000000000000000000..02746174b831d35e7edc3ce2cd6221440c6ea7c6 GIT binary patch literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^Ahrkx8<5=cZcP}FVoUONcVYMsf(!O8p9~b?Ebxdd zW?oJY4UXSb6Mw< G&;$S+*(rkn literal 0 HcmV?d00001 From c8ea33bef320544b6945522bf4038e5a36929ef4 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Tue, 6 Jul 2021 12:10:38 -0700 Subject: [PATCH 081/125] GPS Widget Updates -Added Serial IDs for GPS data -Added GPS meter resource references. -Added visual meter setup logic and initialization -Fixed a resource typo --- .../{veticalSpacer.png => verticalSpacer.png} | Bin resources/resources_en.properties | 4 + src/serial/Serial.java | 2 + src/ui/Theme.java | 8 + src/ui/widgets/GPSWidget.java | 256 ++++++++++++++++++ src/ui/widgets/StateWidget.java | 1 - src/ui/widgets/StatusBarWidget.java | 4 +- src/ui/widgets/UIWidget.java | 2 +- 8 files changed, 273 insertions(+), 4 deletions(-) rename resources/images/{veticalSpacer.png => verticalSpacer.png} (100%) create mode 100644 src/ui/widgets/GPSWidget.java diff --git a/resources/images/veticalSpacer.png b/resources/images/verticalSpacer.png similarity index 100% rename from resources/images/veticalSpacer.png rename to resources/images/verticalSpacer.png diff --git a/resources/resources_en.properties b/resources/resources_en.properties index d4cc4bc..940f635 100644 --- a/resources/resources_en.properties +++ b/resources/resources_en.properties @@ -35,3 +35,7 @@ ping_red =pingRed.png ping_yellow =pingYellow.png ping_green =pingGreen.png ping_spacer =pingSpacer.png +gps_red =verticalMeterRed.png +gps_yellow =verticalMeterYellow.png +gps_green =verticalMeterGreen.png +gps_spacer =verticalSpacer.png diff --git a/src/serial/Serial.java b/src/serial/Serial.java index 178da26..4956249 100644 --- a/src/serial/Serial.java +++ b/src/serial/Serial.java @@ -93,6 +93,8 @@ public class Serial { public static final int HOMELONGITUDE = 15; public static final int HOMEALTITUDE = 16; public static final int DELTAALTITUDE = 17; + public static final int GPSNUMSAT = 18; + public static final int GPSHDOP = 19; public static final int MAX_WAYPOINTS = 64; public static final int MAX_SETTINGS = 64; diff --git a/src/ui/Theme.java b/src/ui/Theme.java index 1c9cfd2..a4795d5 100644 --- a/src/ui/Theme.java +++ b/src/ui/Theme.java @@ -34,6 +34,10 @@ public class Theme { public BufferedImage pingYellow; public BufferedImage pingGreen; public BufferedImage pingSpacer; + public BufferedImage verticalMeterRed; + public BufferedImage verticalMeterYellow; + public BufferedImage verticalMeterGreen; + public BufferedImage verticalMeterSpacer; public Font number; public Font text; public Font alertFont; @@ -68,6 +72,10 @@ public Theme(Context ctx) { pingYellow = ImageIO.read(new File(img+ctx.getResource("ping_yellow"))); pingGreen = ImageIO.read(new File(img+ctx.getResource("ping_green"))); pingSpacer = ImageIO.read(new File(img+ctx.getResource("ping_spacer"))); + verticalMeterRed = ImageIO.read(new File(img+ctx.getResource("gps_red"))); + verticalMeterYellow = ImageIO.read(new File(img+ctx.getResource("gps_yellow"))); + verticalMeterGreen = ImageIO.read(new File(img+ctx.getResource("gps_green"))); + verticalMeterSpacer = ImageIO.read(new File(img+ctx.getResource("gps_spacer"))); buttonPatch = NinePatch.loadFrom(Paths.get(np+ctx.getResource("button"))); buttonHover = NinePatch.loadFrom(Paths.get(np+ctx.getResource("button_hovered"))); buttonPress = NinePatch.loadFrom(Paths.get(np+ctx.getResource("button_pressed"))); diff --git a/src/ui/widgets/GPSWidget.java b/src/ui/widgets/GPSWidget.java new file mode 100644 index 0000000..fd3ca86 --- /dev/null +++ b/src/ui/widgets/GPSWidget.java @@ -0,0 +1,256 @@ +package com.ui.widgets; + +import com.Context; +import com.serial.Serial; +import com.telemetry.TelemetryListener; + +import java.util.*; + +import javax.swing.*; + +import java.awt.*; +import java.awt.event.ActionListener; +import java.awt.event.ActionEvent; +import java.awt.image.BufferedImage; + +/** + * @author Chris Park @ Infinetix Corp. + * Date: 06-30-2021 + * Description: Dashbaord widget child class used to display the + * GPS signal strength of a unit in the field. This strength is determined + * using a mix of the number of available/visible GPS satellites, and the + * quality as determined from an HDOP signal. + */ +public class GPSWidget extends UIWidget { + //Constants + protected final static int UPDATE_DELAY_MS = 5000; + + //Meter Outer Panel + protected JPanel meterOuterPanel; + + //Meter + protected ArrayList gpsMeters; + + //Meter Update Frequency timer + protected javax.swing.Timer meterUpdateTimer; + + //Satellite Value/State + protected int satelliteValue; + protected SatelliteStrength currentSatelliteStrength; + + //HDOP Value/State + protected double hdopValue; + protected HDOPStrength currentHDOPStrength; + + /** + * Pre-defined strength enum used to track the GPS + * signal strength derived from HDOP. Used as a rough + * metric in conjunction with SatelliteStrength to make + * a better determination of signal strength. + */ + protected enum HDOPStrength { + EXCELLENT (1), + GOOD (2), + POOR (3), + UNKOWN (-1); + + private final int strength; + + HDOPStrength(int strength) { + this.strength = strength; + } + }; + + /** + * Pre-defined satellite strength enum based on the + * number of available satellites. Used as a rough + * metric in conjunction with HDOPStrength to make + * a better determination of signal strength. + */ + protected enum SatelliteStrength { + EXCELLENT (15), + GOOD (10), + POOR (5), + UNKOWN (-1); + + private final int numSats; + + SatelliteStrength(int numSats) { + this.numSats = numSats; + } + }; + + /** + * Class constructor + * @param ctx - The application context + */ + public GPSWidget(Context ctx) { + super(ctx, "GPS"); + + //Init member variables + satelliteValue = -1; + hdopValue = -1.0; + updateSatelliteStrength(SatelliteStrength.UNKOWN); + updateHDOPStrength(HDOPStrength.UNKOWN); + + //Setup telemetry update listeners + ctx.telemetry.registerListener(Serial.GPSNUMSAT, new TelemetryListener() { + //The number of satellites here is an integer sent as floating + //point, so we account for rounding error + public void update(double data) { + satelliteValue = (int)(data + 0.5); + } + }); + + ctx.telemetry.registerListener(Serial.GPSHDOP, new TelemetryListener() { + public void update(double data) { + hdopValue = data; + } + }); + + //Build Meter Widget + meterOuterPanel = new JPanel(); + meterOuterPanel.setMinimumSize(new Dimension(100, 60)); + meterOuterPanel.setLayout(new GridBagLayout()); + + GridBagConstraints constraints = new GridBagConstraints(); + constraints.insets = new Insets(2, 0, 2, 0); + constraints.anchor = GridBagConstraints.CENTER; + + gpsMeters = buildMeterSet(); + + constraints.gridx = 0; + constraints.gridy = 0; + meterOuterPanel.add(gpsMeters.get(0), constraints); + + this.add(meterOuterPanel); + + //Kick off the visual update timer. + meterUpdateTimer = new javax.swing.Timer(UPDATE_DELAY_MS, meterUpdateAction); + meterUpdateTimer.start(); + } + + /** + * Constructs a complete set of meter graphics to be used for a GPS signal + * strength meter. + * @return + */ + protected ArrayList buildMeterSet() { + ArrayList meterSet; + JPanel panel; + + meterSet = new ArrayList(); + + //No Signal + panel = new JPanel(); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); + meterSet.add(panel); + + //Poor Signal + panel = new JPanel(); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); + meterSet.add(panel); + + //Good Signal + panel = new JPanel(); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterYellow))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterYellow))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterYellow))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterYellow))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); + meterSet.add(panel); + + //Excellent Signal + panel = new JPanel(); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterYellow))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterYellow))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterYellow))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterYellow))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterGreen))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterGreen))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterGreen))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterGreen))); + panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); + meterSet.add(panel); + + return meterSet; + } + + /** + * Periodic update responsible for updating the GPS widget visual meter. + * This is fired by a predetermined timer value. See + * UPDATE_DELAY_MS for interrupt period. + */ + ActionListener meterUpdateAction = new ActionListener() { + public void actionPerformed(ActionEvent event) { + updateMeter(); + } + }; + + /** + * Updates the graphical representation of GPS signal strength for the + * widget. + */ + protected void updateMeter() { + GridBagConstraints constraints = new GridBagConstraints(); + + meterOuterPanel.removeAll(); + + //TODO - CP - Add strength determination algorithm here. + + //check levels to determine signal strength (enums) + //add corresponding meter set to outer panel. + } + + /** + * Updates the current satellite strength tracked by the widget. + * @param strength - The strength based on number of available satellites. + */ + public void updateSatelliteStrength(SatelliteStrength strength) { + currentSatelliteStrength = strength; + } + + /** + * Updates the current HDOP strength tracked by the widget. + * @param strength - The strength based on HDOP value. + */ + public void updateHDOPStrength(HDOPStrength strength) { + currentHDOPStrength = strength; + } +} \ No newline at end of file diff --git a/src/ui/widgets/StateWidget.java b/src/ui/widgets/StateWidget.java index 3a2710d..1ddff00 100644 --- a/src/ui/widgets/StateWidget.java +++ b/src/ui/widgets/StateWidget.java @@ -365,5 +365,4 @@ public void mousePressed(MouseEvent me) { } } }; - } diff --git a/src/ui/widgets/StatusBarWidget.java b/src/ui/widgets/StatusBarWidget.java index 13bb5cd..fb63768 100644 --- a/src/ui/widgets/StatusBarWidget.java +++ b/src/ui/widgets/StatusBarWidget.java @@ -10,7 +10,7 @@ /** * @author Chris Park @ Infinetix Corp. * Date: 1-26-21 - * Description: Dashboard Widget child class used to display contextual vehicle + * Description: Dashboard Widget class used to display contextual vehicle * information in an easy to interpret visual format. Example use cases would be * displaying a units current decision state, or perhaps a visual warning level * indication related to a vehicle sensor or other data processing part over @@ -32,7 +32,7 @@ public class StatusBarWidget extends JPanel { protected StatusType currentType; /** - * Pre-define status type enums used to more concisely track + * Pre-defined status type enums used to more concisely track * status bar parameters by type and allow for easier updating. */ public enum StatusType { diff --git a/src/ui/widgets/UIWidget.java b/src/ui/widgets/UIWidget.java index 737cf0c..f3270f9 100644 --- a/src/ui/widgets/UIWidget.java +++ b/src/ui/widgets/UIWidget.java @@ -13,7 +13,7 @@ * * @author Chris Park @ Infinetix Corp. * Date: 11-20-2020 - * Descriptions: Base class from which a UI widget derives its + * Description: Base class from which a UI widget derives its * general functionality. */ public class UIWidget extends JPanel { From c23ea2efd207fcb1fc30a65c93ac14bf8ed700df Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 8 Jul 2021 11:21:03 -0700 Subject: [PATCH 082/125] Update version --- resources/resources_en.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/resources_en.properties b/resources/resources_en.properties index d4cc4bc..12d6028 100644 --- a/resources/resources_en.properties +++ b/resources/resources_en.properties @@ -1,5 +1,5 @@ -version_id =1.0.3 -release_date =2021-05-01 +version_id =1.0.4 +release_date =2021-07-08 image_folder =resources/images/ patch_folder =resources/images/nP/ font_folder =resources/fonts/ From 66bd63e4fa94212cf4e353a78940ffe938743eca Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 8 Jul 2021 14:02:51 -0700 Subject: [PATCH 083/125] Finish initial GPS Widget implementation -Signal strength algorithm in place -Added new widget to widget panel -Tested static signal strength states and their corresponding visual updates. -To be confirmed with live data. --- resources/resources_en.properties | 4 +- src/Dashboard.java | 7 + src/ui/widgets/GPSWidget.java | 214 ++++++++++++++++++++---------- src/util/UtilHelper.java | 19 ++- 4 files changed, 170 insertions(+), 74 deletions(-) diff --git a/resources/resources_en.properties b/resources/resources_en.properties index 940f635..3e6b2c7 100644 --- a/resources/resources_en.properties +++ b/resources/resources_en.properties @@ -1,5 +1,5 @@ -version_id =1.0.3 -release_date =2021-05-01 +version_id =1.0.4 +release_date =2021-07-08 image_folder =resources/images/ patch_folder =resources/images/nP/ font_folder =resources/fonts/ diff --git a/src/Dashboard.java b/src/Dashboard.java index 7634252..1d03dd0 100644 --- a/src/Dashboard.java +++ b/src/Dashboard.java @@ -54,6 +54,7 @@ public class Dashboard implements Runnable { private TelemetryDataWidget dataWidget; public StateWidget stateWidget; public PingWidget pingWidget; + public GPSWidget gpsWidget; public MapPanel mapPanel; //Logging @@ -233,8 +234,14 @@ private JPanel createRightPanel() { pingWidget = new PingWidget(context); widgetPanel.add(pingWidget); + + //GPS Widget + gpsWidget = new GPSWidget(context); + widgetPanel.add(gpsWidget); + outerPanel.add(widgetPanel); + //Round Widgets outerPanel.add(AngleWidget.createDial( context, Serial.HEADING, context.theme.roverTop)); diff --git a/src/ui/widgets/GPSWidget.java b/src/ui/widgets/GPSWidget.java index fd3ca86..76ba49e 100644 --- a/src/ui/widgets/GPSWidget.java +++ b/src/ui/widgets/GPSWidget.java @@ -3,6 +3,7 @@ import com.Context; import com.serial.Serial; import com.telemetry.TelemetryListener; +import com.util.UtilHelper; import java.util.*; @@ -23,7 +24,14 @@ */ public class GPSWidget extends UIWidget { //Constants - protected final static int UPDATE_DELAY_MS = 5000; + protected final static int UPDATE_DELAY_MS = 5000; + protected final static int AVERAGE_WINDOW_SIZE = 10; + + protected final static int MIN_SATS_FOR_LOCK = 4; + + protected final static double HDOP_MAX_EXCELLENT= 1.0; + protected final static double HDOP_MAX_GOOD = 2.0; + protected final static double HDOP_MAX_FAIR = 5.0; //Meter Outer Panel protected JPanel meterOuterPanel; @@ -35,48 +43,36 @@ public class GPSWidget extends UIWidget { protected javax.swing.Timer meterUpdateTimer; //Satellite Value/State - protected int satelliteValue; - protected SatelliteStrength currentSatelliteStrength; + protected int satIdx; + protected int satAvgVal; + protected double[] satAvgArray; //HDOP Value/State - protected double hdopValue; - protected HDOPStrength currentHDOPStrength; + protected int hdopIdx; + protected double hdopAvgVal; + protected double[] hdopAvgArray; + + protected GPSStrength currGPSStrength; /** * Pre-defined strength enum used to track the GPS - * signal strength derived from HDOP. Used as a rough - * metric in conjunction with SatelliteStrength to make - * a better determination of signal strength. + * signal strength derived from HDOP and Satellite Data. */ - protected enum HDOPStrength { - EXCELLENT (1), - GOOD (2), - POOR (3), - UNKOWN (-1); + protected enum GPSStrength { + UNKOWN (0), + POOR (1), + FAIR (2), + GOOD (3), + EXCELLENT (4); private final int strength; - HDOPStrength(int strength) { + GPSStrength(int strength) { this.strength = strength; } - }; - - /** - * Pre-defined satellite strength enum based on the - * number of available satellites. Used as a rough - * metric in conjunction with HDOPStrength to make - * a better determination of signal strength. - */ - protected enum SatelliteStrength { - EXCELLENT (15), - GOOD (10), - POOR (5), - UNKOWN (-1); - private final int numSats; - - SatelliteStrength(int numSats) { - this.numSats = numSats; + public int getValue() { + return this.strength; } }; @@ -87,24 +83,57 @@ protected enum SatelliteStrength { public GPSWidget(Context ctx) { super(ctx, "GPS"); - //Init member variables - satelliteValue = -1; - hdopValue = -1.0; - updateSatelliteStrength(SatelliteStrength.UNKOWN); - updateHDOPStrength(HDOPStrength.UNKOWN); + //Init Satellite and HDOP averaging member vars + satIdx = 0; + satAvgVal = -1; + satAvgArray = new double[AVERAGE_WINDOW_SIZE]; + + hdopIdx = 0; + hdopAvgVal = -1.0; + hdopAvgArray = new double[AVERAGE_WINDOW_SIZE]; + + for(int i = 0; i < AVERAGE_WINDOW_SIZE; i++) { + satAvgArray[i] = 0.0; + hdopAvgArray[i] = 0.0; + } + + //Init current GPS strength state; + currGPSStrength = GPSStrength.UNKOWN; //Setup telemetry update listeners ctx.telemetry.registerListener(Serial.GPSNUMSAT, new TelemetryListener() { - //The number of satellites here is an integer sent as floating - //point, so we account for rounding error + /** + * Updates and computes a rolling average of the number of satellites. + * Satellite data is represented as an integer, so rounding error + * is accounted for in the final calculation. + * @param data - The most recent number of satellites + */ public void update(double data) { - satelliteValue = (int)(data + 0.5); + if(satIdx == AVERAGE_WINDOW_SIZE) { + satIdx = 0; + } + + satAvgArray[satIdx] = data; + satAvgVal = (int)(UtilHelper.getInstance().average( + satAvgArray, AVERAGE_WINDOW_SIZE) + 0.5); + satIdx++; } }); ctx.telemetry.registerListener(Serial.GPSHDOP, new TelemetryListener() { - public void update(double data) { - hdopValue = data; + /** + * Updates and computes a rolling average of HDOP values. + * @param data - The most recent HDOP value + */ + public void update(double data) { + if(hdopIdx == AVERAGE_WINDOW_SIZE) { + hdopIdx = 0; + } + + hdopAvgArray[hdopIdx] = data; + hdopAvgVal = UtilHelper.getInstance().average( + hdopAvgArray, AVERAGE_WINDOW_SIZE); + hdopIdx++; } }); @@ -121,7 +150,7 @@ public void update(double data) { constraints.gridx = 0; constraints.gridy = 0; - meterOuterPanel.add(gpsMeters.get(0), constraints); + meterOuterPanel.add(gpsMeters.get(GPSStrength.UNKOWN.getValue()), constraints); this.add(meterOuterPanel); @@ -141,7 +170,7 @@ protected ArrayList buildMeterSet() { meterSet = new ArrayList(); - //No Signal + //No Signal (Unknown) panel = new JPanel(); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); @@ -163,7 +192,7 @@ protected ArrayList buildMeterSet() { panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); - panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); @@ -175,13 +204,11 @@ protected ArrayList buildMeterSet() { panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); meterSet.add(panel); - //Good Signal + //Fair Signal panel = new JPanel(); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); - panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); - panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterYellow))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterYellow))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterYellow))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterYellow))); @@ -189,19 +216,38 @@ protected ArrayList buildMeterSet() { panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); meterSet.add(panel); - //Excellent Signal + //Good Signal panel = new JPanel(); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); - panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterYellow))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterYellow))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterYellow))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterGreen))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterGreen))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterGreen))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterSpacer))); + panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); + meterSet.add(panel); + + //Excellent Signal + panel = new JPanel(); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterRed))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterYellow))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterYellow))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterYellow))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterGreen))); + panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterGreen))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterGreen))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterGreen))); panel.add(new JLabel(new ImageIcon(context.theme.verticalMeterGreen))); @@ -219,10 +265,54 @@ protected ArrayList buildMeterSet() { */ ActionListener meterUpdateAction = new ActionListener() { public void actionPerformed(ActionEvent event) { - updateMeter(); + determineSignalStrength(); } }; + /** + * Determines the current GPS signal strength based on the number + * of satellites visible and HDOP values. + * + * The Algorithm: At least 4 GPS satellites are needed to get a reliable + * location fix on a position. If the number of satellites does not at least + * equal this (MIN_SATS_FOR_LOCK) then the signal is considered poor and HDOP + * values are not considered. If the minimum satellite requirement is met then + * HDOP values are evaluated to determine the strength of the signal. (See + * HDOP_MAX_[X] constants for range details) + * + * Once this is calculation is performed, the visual meter + * representation is updated. + */ + protected void determineSignalStrength() { + + //Determine the current GPS signal strength + if(satAvgVal < MIN_SATS_FOR_LOCK) { + currGPSStrength = GPSStrength.POOR; + } + else { + if(hdopAvgVal < HDOP_MAX_EXCELLENT) { + currGPSStrength = GPSStrength.EXCELLENT; + } + else if ((hdopAvgVal >= HDOP_MAX_EXCELLENT) + && (hdopAvgVal <= HDOP_MAX_GOOD)) { + currGPSStrength = GPSStrength.GOOD; + } + else if ((hdopAvgVal > HDOP_MAX_GOOD) + && (hdopAvgVal <= HDOP_MAX_FAIR )) { + currGPSStrength = GPSStrength.FAIR; + } + else if (hdopAvgVal > HDOP_MAX_FAIR) { + currGPSStrength = GPSStrength.POOR; + } + else { + currGPSStrength = GPSStrength.UNKOWN; + } + } + + //Update the visual meters accordingly + updateMeter(); + } + /** * Updates the graphical representation of GPS signal strength for the * widget. @@ -230,27 +320,11 @@ public void actionPerformed(ActionEvent event) { protected void updateMeter() { GridBagConstraints constraints = new GridBagConstraints(); - meterOuterPanel.removeAll(); + constraints.gridy = 0; + constraints.gridx = 0; - //TODO - CP - Add strength determination algorithm here. + meterOuterPanel.removeAll(); + meterOuterPanel.add(gpsMeters.get(currGPSStrength.getValue()), constraints); - //check levels to determine signal strength (enums) - //add corresponding meter set to outer panel. - } - - /** - * Updates the current satellite strength tracked by the widget. - * @param strength - The strength based on number of available satellites. - */ - public void updateSatelliteStrength(SatelliteStrength strength) { - currentSatelliteStrength = strength; - } - - /** - * Updates the current HDOP strength tracked by the widget. - * @param strength - The strength based on HDOP value. - */ - public void updateHDOPStrength(HDOPStrength strength) { - currentHDOPStrength = strength; } } \ No newline at end of file diff --git a/src/util/UtilHelper.java b/src/util/UtilHelper.java index 3175c84..aee8fec 100644 --- a/src/util/UtilHelper.java +++ b/src/util/UtilHelper.java @@ -35,9 +35,24 @@ public static UtilHelper getInstance() { return utilHelperInstance; } + /** + * Computes the avarage of the provided array values. + * @param array - The array to average + * @param length - The length of the array + * @return - the average of the array values. + */ + public double average(double[] array, int length) { + double sumOfData = 0; + + for(int i = 0; i < length; i++) { + sumOfData += array[i]; + } + + return (sumOfData / length); + } /** - * Computes the distance in km between to points on the surface of a sphere. + * Computes the distance in km between two points on the surface of a sphere. * @param pointA - First Waypoint (Dot) * @param pointB - Second Waypoint (Dot) * @return - Distance between coordinates in km @@ -48,7 +63,7 @@ public double haversine(Dot pointA, Dot pointB) { } /** - * Computes the distance in km between to points on the surface of a sphere. + * Computes the distance in km between two points on the surface of a sphere. * @param lat1 - First latitude coordinate * @param lon1 - First longitude coordinate * @param lat2 - Second latitude coordinate From 830cd7825362d5d5a79d685e7f4e12eb93a7e706 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Mon, 12 Jul 2021 09:29:30 -0700 Subject: [PATCH 084/125] Decrease GPS Visua update timer from 5 seconds to 1 second --- src/ui/widgets/GPSWidget.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ui/widgets/GPSWidget.java b/src/ui/widgets/GPSWidget.java index 76ba49e..c9e392d 100644 --- a/src/ui/widgets/GPSWidget.java +++ b/src/ui/widgets/GPSWidget.java @@ -24,11 +24,10 @@ */ public class GPSWidget extends UIWidget { //Constants - protected final static int UPDATE_DELAY_MS = 5000; + protected final static int UPDATE_DELAY_MS = 1000; protected final static int AVERAGE_WINDOW_SIZE = 10; - protected final static int MIN_SATS_FOR_LOCK = 4; - + protected final static int MIN_SATS_FOR_LOCK = 4; protected final static double HDOP_MAX_EXCELLENT= 1.0; protected final static double HDOP_MAX_GOOD = 2.0; protected final static double HDOP_MAX_FAIR = 5.0; From b25842fe8ea611ea035fb97957f2c26506828690 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Mon, 12 Jul 2021 12:05:25 -0700 Subject: [PATCH 085/125] Added waypoint selection wrapping If the end of the list is reached, the waypoint selection now wraps to the other end. --- src/map/RoverPath.java | 4 +-- src/map/WaypointList.java | 63 +++++++++++++++++++++++++++++++++------ 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/map/RoverPath.java b/src/map/RoverPath.java index 1dafd0a..ccfc165 100644 --- a/src/map/RoverPath.java +++ b/src/map/RoverPath.java @@ -224,7 +224,7 @@ private void drawLines(Graphics g) { private void drawPoints(Graphics g) { Point tmp; - for(int i = waypoints.extendedIndexStart(); i < waypoints.size(); i++) { + for(int i = waypoints.getExtendedIndexStart(); i < waypoints.size(); i++) { tmp = toPoint(drawnLocation(i)); ExtendedWaypoint w = waypoints.get(i); BufferedImage img = context.theme.waypointImage; @@ -262,7 +262,7 @@ private void drawImg(Graphics g, BufferedImage img, Point2D loc) { } public int isOverDot(Point2D click, BufferedImage image) { - for(int i = waypoints.extendedIndexStart(); i < waypoints.size(); i++) { + for(int i = waypoints.getExtendedIndexStart(); i < waypoints.size(); i++) { Dot d = waypoints.get(i).dot(); Point2D loc = mapTransform.screenPosition(d.getLocation()); if(Math.abs(click.getX()-loc.getX()-1) > image.getWidth() / 2.0) continue; diff --git a/src/map/WaypointList.java b/src/map/WaypointList.java index 7fe95c1..90aff2c 100644 --- a/src/map/WaypointList.java +++ b/src/map/WaypointList.java @@ -6,9 +6,14 @@ import java.util.*; public class WaypointList { - public static class WaypointListener{ + + /** + * Listener class for waypoint events. + */ + public static class WaypointListener { public enum Source { LOCAL, REMOTE } public enum Action { ADD, SET, DELETE } + /** * Override unusedEvent to have a method called from all ather handlers * that were not overriden @@ -22,7 +27,10 @@ public void unusedEvent() {} public void loopModeSet(WaypointListener.Source s, boolean isLooped) { unusedEvent(); } } - + //Constants + private final static int EXTENDED_INDEX_START = -2; + + //Vars private final List waypoints = new LinkedList(); private final List listeners = new LinkedList(); private Dot roverLocation = new Dot(); @@ -36,6 +44,7 @@ public WaypointList() {} public enum Type{ ROVER, HOME, WAYPOINT, SELECTED } + public interface ExtendedWaypoint{ Dot dot(); Type type(); @@ -45,7 +54,10 @@ public interface ExtendedWaypoint{ * The index location to start calling index at so that extended waypoints * like the rover's location and home position get added to the list */ - public int extendedIndexStart() { return -2; } + public int getExtendedIndexStart() { + return EXTENDED_INDEX_START; + } + /** * Return the waypoint at location `index` * Throws IndexOutOfBoundsException if there is no waypoint at that index @@ -73,12 +85,14 @@ public Type type(){ }; } } + /** * Return the current number of waypoints */ public int size() { return waypoints.size(); } + /** * Set the rover's location */ @@ -89,12 +103,14 @@ public void setRover(Dot p, WaypointListener.Source s){ roverLocation = p; listeners.stream().forEach(l -> l.roverMoved(s, p)); } + /** * Get the rover's location */ public Dot getRover(){ return new Dot(roverLocation); } + /** * Set the home's location */ @@ -105,12 +121,14 @@ public void setHome(Dot p, WaypointListener.Source s){ homeLocation = p; listeners.stream().forEach(l -> l.homeMoved(s, p)); } + /** * Get the home's location */ public Dot getHome(){ return new Dot(homeLocation); } + /** * Add a waypoint so it occures at position `index` in the Waypoint List * if index is greater than the current length of the list, an @@ -125,6 +143,7 @@ public void add(Dot p, int index, WaypointListener.Source s){ if(index <= targetIndex) setTarget(targetIndex + 1); listeners.stream().forEach(l -> l.changed(s, p, index, WaypointListener.Action.ADD)); } + /** * * Throws IndexOutOfBoundsException if there is no waypoint at that index @@ -136,6 +155,7 @@ public void set(Dot p, int index, WaypointListener.Source s){ waypoints.set(index, p); listeners.stream().forEach(l -> l.changed(s, p, index, WaypointListener.Action.SET)); } + /** * Return the waypoint at location `index` * Throws IndexOutOfBoundsException if there is no waypoint at that index @@ -150,6 +170,7 @@ public void remove(int index, WaypointListener.Source s){ if(index <= targetIndex) setTarget(targetIndex - 1); listeners.stream().forEach(l -> l.changed(s, p, index, WaypointListener.Action.DELETE)); } + /** * Set the waypoint looped status and inform listeners if it changed */ @@ -161,12 +182,14 @@ public void setLooped(boolean state, WaypointListener.Source s){ isLooped = state; listeners.stream().forEach(l -> l.loopModeSet(s, state)); } + /** * Return if waypoints are currently looped */ public boolean getLooped(){ return isLooped; } + /** * Set the target index and inform listeners if the target changed */ @@ -180,40 +203,62 @@ public void setTarget(int index, WaypointListener.Source s){ targetIndex = index; listeners.stream().forEach(l -> l.targetChanged(s, index)); } + /** * Return the current target index */ public int getTarget(){ return targetIndex; } + /** - * Set the Selected Waypoint index and inform listeners if the target changed + * Set the Selected Waypoint index and inform listeners if the target changed. + * If the end of the list is reached from either side, the selected index wraps + * around to the other end of the list. */ - public void setSelected(int index){ - if(index == selectedIndex || // only fire events when actually changed - index < extendedIndexStart() || - index >= waypoints.size()) return; - selectedIndex = index; + public void setSelected(int index) { + + //If this index is already selected, return + if(index == selectedIndex) { + return; + } + + //Case 1: If at start of list, wrap to end + if(index < EXTENDED_INDEX_START) { + selectedIndex = (waypoints.size() -1); + } //Case 2: If at end of index list, wrap to beginning + else if(index >= waypoints.size()) { + selectedIndex = EXTENDED_INDEX_START; + } //Case 3: Set the index normally + else { + selectedIndex = index; + } + + //Update listeners listeners.stream().forEach(l -> l.selectionChanged(index)); } + /** * Return the current target index */ public int getSelected(){ return selectedIndex; } + /** * Register a new WaypointListener */ public void addListener(WaypointListener l){ listeners.add(l); } + /** * Removes the first occurance of a listener .equals to `l` */ public void removeListener(WaypointListener l){ listeners.remove(l); } + /** * Cleares the waypoint list, passing source s through to listeners */ From 26c0b516a3de4db3d8e75288bb3e2bc25f6c8bc1 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Fri, 16 Jul 2021 09:24:10 -0700 Subject: [PATCH 086/125] Date and version updates --- DashboardInstallerScript.iss | 2 +- resources/resources_en.properties | 2 +- src/ui/widgets/GPSWidget.java | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/DashboardInstallerScript.iss b/DashboardInstallerScript.iss index 583ef7a..46a542d 100644 --- a/DashboardInstallerScript.iss +++ b/DashboardInstallerScript.iss @@ -1,5 +1,5 @@ #define MyAppName "MINDS-i Dashboard" -#define MyAppVersion "1.0.3" +#define MyAppVersion "1.0.4" #define MyAppPublisher "MINDS-i Education" #define MyAppURL "https://mindsieducation.com/" #define MyAppExeName "Dashboard.exe" diff --git a/resources/resources_en.properties b/resources/resources_en.properties index 3e6b2c7..bd1b19e 100644 --- a/resources/resources_en.properties +++ b/resources/resources_en.properties @@ -1,5 +1,5 @@ version_id =1.0.4 -release_date =2021-07-08 +release_date =2021-07-12 image_folder =resources/images/ patch_folder =resources/images/nP/ font_folder =resources/fonts/ diff --git a/src/ui/widgets/GPSWidget.java b/src/ui/widgets/GPSWidget.java index c9e392d..8ccd561 100644 --- a/src/ui/widgets/GPSWidget.java +++ b/src/ui/widgets/GPSWidget.java @@ -287,6 +287,7 @@ protected void determineSignalStrength() { //Determine the current GPS signal strength if(satAvgVal < MIN_SATS_FOR_LOCK) { currGPSStrength = GPSStrength.POOR; +// serialLog.warning("GPS: No good Sat lock!"); } else { if(hdopAvgVal < HDOP_MAX_EXCELLENT) { @@ -302,6 +303,7 @@ else if ((hdopAvgVal > HDOP_MAX_GOOD) } else if (hdopAvgVal > HDOP_MAX_FAIR) { currGPSStrength = GPSStrength.POOR; + serialLog.warning("GPS: HDOP lock Poor"); } else { currGPSStrength = GPSStrength.UNKOWN; From 3b8589b81aab3c76841a461e48404ba5c24bb84d Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Fri, 16 Jul 2021 14:01:32 -0700 Subject: [PATCH 087/125] GPS State Updates -Removed GPS State readings from State Widget -Improved logging of GPS signal in GPS Widget --- src/Dashboard.java | 2 +- src/ui/widgets/GPSWidget.java | 41 +++++++++++++++++-------- src/ui/widgets/StateWidget.java | 54 ++++----------------------------- src/util/UtilHelper.java | 1 - 4 files changed, 35 insertions(+), 63 deletions(-) diff --git a/src/Dashboard.java b/src/Dashboard.java index 1d03dd0..e8a3751 100644 --- a/src/Dashboard.java +++ b/src/Dashboard.java @@ -337,7 +337,7 @@ public static void main(String[] args) { launchOptions.load(optFile); openglProperty = launchOptions.getProperty("opengl", openglProperty); - } catch (Exception e){ + } catch (Exception e) { e.printStackTrace(); } diff --git a/src/ui/widgets/GPSWidget.java b/src/ui/widgets/GPSWidget.java index 8ccd561..69bc812 100644 --- a/src/ui/widgets/GPSWidget.java +++ b/src/ui/widgets/GPSWidget.java @@ -50,7 +50,7 @@ public class GPSWidget extends UIWidget { protected int hdopIdx; protected double hdopAvgVal; protected double[] hdopAvgArray; - + protected boolean wasBelowMinSats; protected GPSStrength currGPSStrength; /** @@ -95,7 +95,9 @@ public GPSWidget(Context ctx) { satAvgArray[i] = 0.0; hdopAvgArray[i] = 0.0; } - + + wasBelowMinSats = false; + //Init current GPS strength state; currGPSStrength = GPSStrength.UNKOWN; @@ -272,24 +274,37 @@ public void actionPerformed(ActionEvent event) { * Determines the current GPS signal strength based on the number * of satellites visible and HDOP values. * - * The Algorithm: At least 4 GPS satellites are needed to get a reliable - * location fix on a position. If the number of satellites does not at least - * equal this (MIN_SATS_FOR_LOCK) then the signal is considered poor and HDOP - * values are not considered. If the minimum satellite requirement is met then - * HDOP values are evaluated to determine the strength of the signal. (See - * HDOP_MAX_[X] constants for range details) + * The Algorithm: At least MIN_SATS_FOR_LOCK GPS satellites are needed to get + * a reliable location fix on a position. If the number of satellites does + * not at least equal this, then the signal is considered poor and HDOP + * values are not considered. If the minimum satellite requirement is met + * then HDOP values are evaluated to determine the strength of the signal. + * (See HDOP_MAX_[X] constants at the head of the class for range details) * - * Once this is calculation is performed, the visual meter - * representation is updated. + * Once this calculation is performed, the visual meter representation + * is updated. */ protected void determineSignalStrength() { + boolean roverIsMoving = context.dash.mapPanel.waypointPanel.getIsMoving(); - //Determine the current GPS signal strength + //Check for minimum # satellites to lock in GPS signal. if(satAvgVal < MIN_SATS_FOR_LOCK) { currGPSStrength = GPSStrength.POOR; -// serialLog.warning("GPS: No good Sat lock!"); + + //If there is an initial bad sat lock or the minimum sats + //cannot be met from a previously good HDOP state, send a warning. + if(!wasBelowMinSats && roverIsMoving) { + serialLog.warning("GPS: Unable to obtain satellite lock."); + wasBelowMinSats = true; + } } else { + //If a good satellite lock was just obtained, notify the user. + if(wasBelowMinSats) { + serialLog.warning("GPS: Minimum satellite lock obtained."); + wasBelowMinSats = false; + } + if(hdopAvgVal < HDOP_MAX_EXCELLENT) { currGPSStrength = GPSStrength.EXCELLENT; } @@ -303,7 +318,7 @@ else if ((hdopAvgVal > HDOP_MAX_GOOD) } else if (hdopAvgVal > HDOP_MAX_FAIR) { currGPSStrength = GPSStrength.POOR; - serialLog.warning("GPS: HDOP lock Poor"); + serialLog.warning("GPS: HDOP satellite lock is poor."); } else { currGPSStrength = GPSStrength.UNKOWN; diff --git a/src/ui/widgets/StateWidget.java b/src/ui/widgets/StateWidget.java index 1ddff00..129a831 100644 --- a/src/ui/widgets/StateWidget.java +++ b/src/ui/widgets/StateWidget.java @@ -35,9 +35,6 @@ public class StateWidget extends UIWidget { private JPanel flagPanel; private JLabel flagLabel; - private JPanel gpsPanel; - private JLabel gpsLabel; - private StatusBarWidget statusBar; //State Tracking Variables @@ -89,15 +86,9 @@ private void initPanel() { flagLabel.setForeground(DEF_FONT_COLOR); flagLabel.setBackground(DEF_BACK_COLOR_B); flagLabel.setOpaque(true); - - gpsLabel = new JLabel("GPS:--"); - gpsLabel.setFont(font); - gpsLabel.setForeground(DEF_FONT_COLOR); - gpsLabel.setBackground(DEF_BACK_COLOR_A); - gpsLabel.setOpaque(true); - + JComponent[] labelList = new JComponent[] { - apmLabel, driveLabel, autoLabel, flagLabel, gpsLabel + apmLabel, driveLabel, autoLabel, flagLabel //, gpsLabel }; for(JComponent jc : labelList) { @@ -138,19 +129,10 @@ private void initPanel() { flagPanel.add(flagLabel); statePanels.add(flagPanel); - gpsPanel = new JPanel(); - gpsPanel.setBorder(insets); - gpsPanel.setLayout(new BoxLayout(gpsPanel, BoxLayout.LINE_AXIS)); - gpsPanel.setPreferredSize(labelSize); - gpsPanel.setOpaque(true); - gpsPanel.add(gpsLabel); - statePanels.add(gpsPanel); - //Add panels to widget for(JPanel panel : statePanels) { this.add(panel); } - } /** @@ -174,7 +156,10 @@ public void update(byte state, byte substate) { setFlagState(substate); break; case Serial.GPS_STATE: - setGPSState(substate); + //(CP - Depricated as of 7-15-21, May be utilized for + //other functionality at another time so this case is left + //in place for now. If this turns out to not be the case, + //then this should be removed. break; default: System.err.println("Error - Unrecognized State"); @@ -320,33 +305,6 @@ else if(caution) { flagLabel.setText(fmt.substring(0, finalWidth)); } - boolean previousGPSState; - private void setGPSState(byte substate) { - int finalWidth; - String fmt; - String fmtStr = "GPS:%s"; - - boolean GPSSignalGood = (substate == 0x00) ? true : false; - - if(GPSSignalGood) { - fmt = String.format(fmtStr, "Good"); - finalWidth = Math.min(fmt.length(), LINE_WIDTH); - - } - else { - fmt = String.format(fmtStr, "Bad"); - finalWidth = Math.min(fmt.length(), LINE_WIDTH); - } - gpsLabel.setText(fmt.substring(0, finalWidth)); - - if(previousGPSState != GPSSignalGood) { - serialLog.warning( - "GPS Signal is: " + (GPSSignalGood ? "Good" : "Bad")); - } - - previousGPSState = GPSSignalGood; - } - /** * Generates an information panel on click describing any warnings, errors, * and details of the current state. diff --git a/src/util/UtilHelper.java b/src/util/UtilHelper.java index aee8fec..a973c69 100644 --- a/src/util/UtilHelper.java +++ b/src/util/UtilHelper.java @@ -20,7 +20,6 @@ public class UtilHelper { * Constructor (Private, accessed by getInstance */ private UtilHelper() { - } /** From 493e5f33ee133296a07ab9d8fafd403be13d7428 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Tue, 27 Jul 2021 13:42:02 -0700 Subject: [PATCH 088/125] Added serial handling for bumper buttons --- src/serial/Serial.java | 65 +++++++++++++++++++----------------- src/serial/SerialParser.java | 13 ++++++-- 2 files changed, 46 insertions(+), 32 deletions(-) diff --git a/src/serial/Serial.java b/src/serial/Serial.java index 4956249..8d6072c 100644 --- a/src/serial/Serial.java +++ b/src/serial/Serial.java @@ -3,52 +3,57 @@ import jssc.SerialPort; public class Serial { - public static final int WAYPOINT_TYPE = 0x0; - public static final int DATA_TYPE = 0x1; - public static final int WORD_TYPE = 0x2; - public static final int STRING_TYPE = 0x3; + public static final int WAYPOINT_TYPE = 0x0; + public static final int DATA_TYPE = 0x1; + public static final int WORD_TYPE = 0x2; + public static final int STRING_TYPE = 0x3; //Waypoint type - public static final int ADD_WAYPOINT = 0x0; - public static final int ALTER_WAYPOINT = 0x1; + public static final int ADD_WAYPOINT = 0x0; + public static final int ALTER_WAYPOINT = 0x1; //Data type - public static final int TELEMETRY_DATA = 0x0; - public static final int SETTING_DATA = 0x1; - public static final int SENSOR_DATA = 0x2; - public static final int INFO_DATA = 0x3; + public static final int TELEMETRY_DATA = 0x0; + public static final int SETTING_DATA = 0x1; + public static final int SENSOR_DATA = 0x2; + public static final int INFO_DATA = 0x3; //Sensor Types - public static final int OBJDETECT_SONIC= 0x0; + public static final int OBJDETECT_SONIC = 0x0; + public static final int OBJDETECT_BUMPER = 0x1; + + //Bumper Sub Types + public static final int BUMPER_BUTTON_LEFT = 0x0; + public static final int BUMPER_BUTTON_RIGHT = 0x1; //Info Types - public static final int APM_VERSION = 0x0; + public static final int APM_VERSION = 0x0; //Word type - public static final int CONFIRMATION = 0x0; - public static final int SYNC_WORD = 0x1; - public static final int COMMAND_WORD = 0x2; - public static final int STATE_WORD = 0x3; + public static final int CONFIRMATION = 0x0; + public static final int SYNC_WORD = 0x1; + public static final int COMMAND_WORD = 0x2; + public static final int STATE_WORD = 0x3; //String type - public static final int ERROR_STRING = 0x0; - public static final int STATE_STRING = 0x1; + public static final int ERROR_STRING = 0x0; + public static final int STATE_STRING = 0x1; //Commands - public static final byte ESTOP_CMD = 0x0; - public static final byte TARGET_CMD = 0x1; - public static final byte LOOPING_CMD = 0x2; - public static final byte CLEAR_CMD = 0x3; - public static final byte DELETE_CMD = 0x4; - public static final byte STOP_CMD = 0x5; - public static final byte START_CMD = 0x6; + public static final byte ESTOP_CMD = 0x0; + public static final byte TARGET_CMD = 0x1; + public static final byte LOOPING_CMD = 0x2; + public static final byte CLEAR_CMD = 0x3; + public static final byte DELETE_CMD = 0x4; + public static final byte STOP_CMD = 0x5; + public static final byte START_CMD = 0x6; //State types - public static final byte APM_STATE = 0x0; - public static final byte DRIVE_STATE = 0x1; - public static final byte AUTO_STATE = 0x2; - public static final byte AUTO_FLAGS = 0x3; - public static final byte GPS_STATE = 0x4; + public static final byte APM_STATE = 0x0; + public static final byte DRIVE_STATE = 0x1; + public static final byte AUTO_STATE = 0x2; + public static final byte AUTO_FLAGS = 0x3; + public static final byte GPS_STATE = 0x4; //APM state sub values public static final byte APM_STATE_INIT = 0x1; diff --git a/src/serial/SerialParser.java b/src/serial/SerialParser.java index 3f6d3b0..ed8822e 100644 --- a/src/serial/SerialParser.java +++ b/src/serial/SerialParser.java @@ -134,7 +134,7 @@ public void handle(byte[] msg) { switch(sensorSubtype) { case Serial.OBJDETECT_SONIC: - sensorIndex = msg[2]; +// sensorIndex = msg[2]; //[0]MSB [1]LSB sensorData[0] = (msg[3] & 0xff); sensorData[1] = (msg[4] & 0xff); @@ -144,7 +144,16 @@ public void handle(byte[] msg) { context.dash.pingWidget.update(sensorIndex, sensorVal); break; - + + case Serial.OBJDETECT_BUMPER: +// sensorIndex = msg[2]; + //0 = Off, 1 = On + sensorVal = sensorData[0]; + + //TODO - CP - Update a bumper widget here. + //Send: Index (left/right), State (on/off) + break; + default: System.err.println("Unrecognized Sensor Subtype"); break; From 05e9587c3886a8ceed3c4d6f1370a57952f59722 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 5 Aug 2021 13:45:46 -0700 Subject: [PATCH 089/125] Add back end driver logic for bumper widget Added the initial bumper widget class and back end logic. Added some new enum types to the status bar widget to accommodate the bumper widget states. --- src/serial/SerialParser.java | 6 +- src/ui/widgets/BumperWidget.java | 201 ++++++++++++++++++++++++++++ src/ui/widgets/GPSWidget.java | 2 +- src/ui/widgets/StateWidget.java | 5 +- src/ui/widgets/StatusBarWidget.java | 15 ++- 5 files changed, 221 insertions(+), 8 deletions(-) create mode 100644 src/ui/widgets/BumperWidget.java diff --git a/src/serial/SerialParser.java b/src/serial/SerialParser.java index ed8822e..4638306 100644 --- a/src/serial/SerialParser.java +++ b/src/serial/SerialParser.java @@ -134,7 +134,6 @@ public void handle(byte[] msg) { switch(sensorSubtype) { case Serial.OBJDETECT_SONIC: -// sensorIndex = msg[2]; //[0]MSB [1]LSB sensorData[0] = (msg[3] & 0xff); sensorData[1] = (msg[4] & 0xff); @@ -146,12 +145,11 @@ public void handle(byte[] msg) { break; case Serial.OBJDETECT_BUMPER: -// sensorIndex = msg[2]; //0 = Off, 1 = On sensorVal = sensorData[0]; - //TODO - CP - Update a bumper widget here. - //Send: Index (left/right), State (on/off) + //TODO - CP - VERIFY this. +// context.dash.bumperWidget.update(sensorIndex, sensorVal); break; default: diff --git a/src/ui/widgets/BumperWidget.java b/src/ui/widgets/BumperWidget.java new file mode 100644 index 0000000..26b74e5 --- /dev/null +++ b/src/ui/widgets/BumperWidget.java @@ -0,0 +1,201 @@ +package com.ui.widgets; + +import com.Context; +import com.serial.Serial; + +import java.awt.*; +import javax.swing.*; + +/** + * @author Chris Park @ Infinetix Corp. + * Date: 8-2-2021 + * Description: Dashboard Widget child class used to display the state of + * the push button bumbper. Left and right buttons are each displayed, + * along with a status bar the tracks the corresponding state and decision + * making in response to button events. + */ +public class BumperWidget extends UIWidget { + + //Constants + protected final static int LEFT_BUMPER = 0; + protected final static int RIGHT_BUMPER = 1; + + //Color Defaults + protected static final Color DEF_INACTIVE_COLOR = Color.decode("0x2CBC00"); + protected static final Color DEF_ACTIVE_COLOR = Color.decode("0xD70514"); + + //Visual Components + protected JPanel outerPanel; + protected JPanel leftPanel; + protected JLabel leftLabel; + protected JPanel rightPanel; + protected JLabel rightLabel; + protected StatusBarWidget statusBar; + + //State Tracking Variables + protected BumperStatus bumperStateLeft; + protected BumperStatus bumperStateRight; + + /** + * Pre-defined state enums used to keep track of and + * update the currently displayed status label based + * on bumper state. + */ + protected enum BumperStatus { + UNKOWN (0), + ACTIVATED (1), + CLEAR (2), + DEFER (3); + + private final int state; + + BumperStatus(int state) { + this.state = state; + } + + public int getValue() { + return this.state; + } + }; + + /** + * Class Constructor + * @param ctx - The application context + */ + public BumperWidget(Context ctx) { + super(ctx, "Bumper"); + + + Font font = context.theme.text.deriveFont(FONT_SIZE); + Dimension labelSize = new Dimension(50, 20); + + //Set initial bumper states + bumperStateLeft = BumperStatus.CLEAR; + bumperStateRight = BumperStatus.CLEAR; + + //Set up Status Bar + statusBar = new StatusBarWidget(context); + statusBar.update(StatusBarWidget.StatusType.CLEAR); + outerPanel.add(statusBar); + + //Set up left button label/panel + leftLabel = new JLabel("Left"); + leftLabel.setBackground(DEF_INACTIVE_COLOR); + leftLabel.setOpaque(true); + leftLabel.setMaximumSize(labelSize); + + leftPanel = new JPanel(); + leftPanel.setBorder(insets); + leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.LINE_AXIS)); + leftPanel.setPreferredSize(labelSize); + leftPanel.add(leftLabel); + outerPanel.add(leftPanel); + + //Set up right button label/panel + rightLabel = new JLabel("Right"); + rightLabel.setBackground(DEF_INACTIVE_COLOR); + rightLabel.setOpaque(true); + rightLabel.setMaximumSize(labelSize); + + rightPanel = new JPanel(); + rightPanel.setBorder(insets); + rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.LINE_AXIS)); + rightPanel.setPreferredSize(labelSize); + rightPanel.add(rightLabel); + outerPanel.add(rightPanel); + + this.add(outerPanel); + } + + /** + * Starts updates of the state, and color of the received target bumper. Once + * this operation is completed, the widget status label is evaluated for needed + * updates also. + * @param bumper - The bumper to be updated + * @param state - the received state of the bumper + */ + public void update(byte bumper, byte state) { + + switch(bumper) { + case LEFT_BUMPER: + setBumperState(bumperStateLeft, state); + break; + case RIGHT_BUMPER: + setBumperState(bumperStateRight, state); + break; + default: + serialLog.warning("BUMPER: Unkown bumper INDEX received on state update."); + } + + //Update Colors & Label + updateBumperColors(); + updateStatusLabel(); + } + + /** + * Sets the On/Off bumper state of the target bumper. + * @param target - Bumper to update + * @param state - State to set that bumper to. + */ + protected void setBumperState(BumperStatus target, byte state) { + + switch(state) { + case 0x00: + target = BumperStatus.CLEAR; + break; + case 0x01: + target = BumperStatus.ACTIVATED; + break; + default: + serialLog.warning("BUMPER: Unkown bumper STATE received on state update."); + } + } + + /** + * Updates the bumper button widget colors based on the + * current states of each button. + */ + protected void updateBumperColors() { + + //Update Left Bumper + switch(bumperStateLeft) { + case CLEAR: + leftLabel.setBackground(DEF_INACTIVE_COLOR); + break; + case ACTIVATED: + rightLabel.setBackground(DEF_ACTIVE_COLOR); + break; + default: + //TODO - CP - Log as unknown state? or use color to represent this? + } + + //Update Right Bumper + switch(bumperStateRight) { + case CLEAR: + rightLabel.setBackground(DEF_INACTIVE_COLOR); + break; + case ACTIVATED: + rightLabel.setBackground(DEF_ACTIVE_COLOR); + break; + default: + //TODO - CP - Log as unknown state? or use color to represent this? + } + } + + /** + * Updates the status label message with respect to + * the currently held bumper button states. + */ + protected void updateStatusLabel() { + //If either bumper has been triggered, update the label to reflect this. + if((bumperStateLeft == BumperStatus.ACTIVATED) + ||(bumperStateRight == BumperStatus.ACTIVATED)) { + statusBar.update(StatusBarWidget.StatusType.ACTIVATED); + return; + } + + //Otherwise assume the bumper is clear + statusBar.update(StatusBarWidget.StatusType.CLEAR); + } + +} diff --git a/src/ui/widgets/GPSWidget.java b/src/ui/widgets/GPSWidget.java index 69bc812..8aab060 100644 --- a/src/ui/widgets/GPSWidget.java +++ b/src/ui/widgets/GPSWidget.java @@ -76,7 +76,7 @@ public int getValue() { }; /** - * Class constructor + * Class Constructor * @param ctx - The application context */ public GPSWidget(Context ctx) { diff --git a/src/ui/widgets/StateWidget.java b/src/ui/widgets/StateWidget.java index 129a831..67d986c 100644 --- a/src/ui/widgets/StateWidget.java +++ b/src/ui/widgets/StateWidget.java @@ -35,7 +35,7 @@ public class StateWidget extends UIWidget { private JPanel flagPanel; private JLabel flagLabel; - private StatusBarWidget statusBar; + protected StatusBarWidget statusBar; //State Tracking Variables protected byte lastDriveState = 0xF; @@ -305,6 +305,9 @@ else if(caution) { flagLabel.setText(fmt.substring(0, finalWidth)); } + //TODO - CP - Add Set Wiggle Flag state or incorporate into existing + //flag structure. + /** * Generates an information panel on click describing any warnings, errors, * and details of the current state. diff --git a/src/ui/widgets/StatusBarWidget.java b/src/ui/widgets/StatusBarWidget.java index fb63768..fa5e705 100644 --- a/src/ui/widgets/StatusBarWidget.java +++ b/src/ui/widgets/StatusBarWidget.java @@ -36,10 +36,18 @@ public class StatusBarWidget extends JPanel { * status bar parameters by type and allow for easier updating. */ public enum StatusType { + + //General State Tracking NORMAL ("Normal", Color.black, Color.white), PROCESSING ("Processing", Color.white, Color.blue), CAUTION ("Caution", Color.black, Color.yellow), - ERROR ("Error", Color.black, Color.red); + ERROR ("Error", Color.black, Color.red), + UNKNOWN ("Unknown", Color.black, Color.white), + + //Bumper Specific State Tracking + DEFER ("Defer to US", Color.black, Color.yellow), + ACTIVATED ("Activated", Color.black, Color.yellow), + CLEAR ("Clear", Color.black, Color.white); private final String text; private final Color fgColor; @@ -52,7 +60,10 @@ public enum StatusType { } }; - //Class Constructor + /** + * Class Constructor + * @param ctx - The application context + */ public StatusBarWidget(Context ctx) { context = ctx; From ad473e309b3faac060f2a6445e1683581b6a9bfa Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Fri, 6 Aug 2021 09:58:05 -0700 Subject: [PATCH 090/125] Fixed widget component orientation Components for L/R buttons now sit along X axis, and below the status bar --- src/Dashboard.java | 5 +++++ src/ui/widgets/BumperWidget.java | 25 ++++++++++++++++++------- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/Dashboard.java b/src/Dashboard.java index e8a3751..c124b7c 100644 --- a/src/Dashboard.java +++ b/src/Dashboard.java @@ -55,6 +55,7 @@ public class Dashboard implements Runnable { public StateWidget stateWidget; public PingWidget pingWidget; public GPSWidget gpsWidget; + public BumperWidget bumperWidget; public MapPanel mapPanel; //Logging @@ -239,6 +240,10 @@ private JPanel createRightPanel() { gpsWidget = new GPSWidget(context); widgetPanel.add(gpsWidget); + //Bumper Widget + bumperWidget = new BumperWidget(context); + widgetPanel.add(bumperWidget); + outerPanel.add(widgetPanel); diff --git a/src/ui/widgets/BumperWidget.java b/src/ui/widgets/BumperWidget.java index 26b74e5..3f7ee0e 100644 --- a/src/ui/widgets/BumperWidget.java +++ b/src/ui/widgets/BumperWidget.java @@ -26,6 +26,7 @@ public class BumperWidget extends UIWidget { //Visual Components protected JPanel outerPanel; + protected JPanel lowerPanel; protected JPanel leftPanel; protected JLabel leftLabel; protected JPanel rightPanel; @@ -65,7 +66,6 @@ public int getValue() { public BumperWidget(Context ctx) { super(ctx, "Bumper"); - Font font = context.theme.text.deriveFont(FONT_SIZE); Dimension labelSize = new Dimension(50, 20); @@ -73,37 +73,48 @@ public BumperWidget(Context ctx) { bumperStateLeft = BumperStatus.CLEAR; bumperStateRight = BumperStatus.CLEAR; + //Set up outer panel + outerPanel = new JPanel(); + outerPanel.setLayout(new BoxLayout(outerPanel, BoxLayout.Y_AXIS)); + + //Set up lower panel (Houses Left/Right Displays) + lowerPanel = new JPanel(); + lowerPanel.setLayout(new BoxLayout(lowerPanel, BoxLayout.X_AXIS)); + //Set up Status Bar statusBar = new StatusBarWidget(context); statusBar.update(StatusBarWidget.StatusType.CLEAR); outerPanel.add(statusBar); //Set up left button label/panel - leftLabel = new JLabel("Left"); + leftLabel = new JLabel("Left", SwingConstants.CENTER); leftLabel.setBackground(DEF_INACTIVE_COLOR); leftLabel.setOpaque(true); leftLabel.setMaximumSize(labelSize); leftPanel = new JPanel(); leftPanel.setBorder(insets); - leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.LINE_AXIS)); + leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.X_AXIS)); + leftPanel.setBorder(BorderFactory.createLineBorder(Color.black)); leftPanel.setPreferredSize(labelSize); leftPanel.add(leftLabel); - outerPanel.add(leftPanel); + lowerPanel.add(leftPanel); //Set up right button label/panel - rightLabel = new JLabel("Right"); + rightLabel = new JLabel("Right", SwingConstants.CENTER); rightLabel.setBackground(DEF_INACTIVE_COLOR); rightLabel.setOpaque(true); rightLabel.setMaximumSize(labelSize); rightPanel = new JPanel(); rightPanel.setBorder(insets); - rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.LINE_AXIS)); + rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.X_AXIS)); + rightPanel.setBorder(BorderFactory.createLineBorder(Color.black)); rightPanel.setPreferredSize(labelSize); rightPanel.add(rightLabel); - outerPanel.add(rightPanel); + lowerPanel.add(rightPanel); + outerPanel.add(lowerPanel); this.add(outerPanel); } From 785263c6f5f0a3d69122153c0e392264bd60e8b4 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Fri, 6 Aug 2021 13:42:10 -0700 Subject: [PATCH 091/125] Add logging for unknown state comparisons --- src/ui/widgets/BumperWidget.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/widgets/BumperWidget.java b/src/ui/widgets/BumperWidget.java index 3f7ee0e..d6387d8 100644 --- a/src/ui/widgets/BumperWidget.java +++ b/src/ui/widgets/BumperWidget.java @@ -177,7 +177,7 @@ protected void updateBumperColors() { rightLabel.setBackground(DEF_ACTIVE_COLOR); break; default: - //TODO - CP - Log as unknown state? or use color to represent this? + serialLog.warning("BUMPER: Unknown left bumper STATE received on color update."); } //Update Right Bumper @@ -189,7 +189,7 @@ protected void updateBumperColors() { rightLabel.setBackground(DEF_ACTIVE_COLOR); break; default: - //TODO - CP - Log as unknown state? or use color to represent this? + serialLog.warning("BUMPER: Unknown right bumper STATE received on color update."); } } From b50156ef1d5a84b04a1b7496beb469a80eec205a Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Mon, 9 Aug 2021 15:22:59 -0700 Subject: [PATCH 092/125] Update Bumper serial receive Small changes in response to testing to ensure values are coming through from the rover. --- src/serial/SerialParser.java | 21 +++++++++------------ src/ui/widgets/BumperWidget.java | 9 ++++----- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/serial/SerialParser.java b/src/serial/SerialParser.java index 4638306..9e2a189 100644 --- a/src/serial/SerialParser.java +++ b/src/serial/SerialParser.java @@ -132,24 +132,21 @@ public void handle(byte[] msg) { int sensorIndex = msg[2]; int[] sensorData = new int[2]; + sensorData[0] = (msg[3] & 0xff); + sensorData[1] = (msg[4] & 0xff); + for(int val : sensorData) { + sensorVal = (sensorVal << 8) | val; + } + switch(sensorSubtype) { case Serial.OBJDETECT_SONIC: - //[0]MSB [1]LSB - sensorData[0] = (msg[3] & 0xff); - sensorData[1] = (msg[4] & 0xff); - for(int val : sensorData) { - sensorVal = (sensorVal << 8) | val; - } - + //SensorData: [0]MSB [1]LSB context.dash.pingWidget.update(sensorIndex, sensorVal); break; case Serial.OBJDETECT_BUMPER: - //0 = Off, 1 = On - sensorVal = sensorData[0]; - - //TODO - CP - VERIFY this. -// context.dash.bumperWidget.update(sensorIndex, sensorVal); + //SensorData: 0 = Off, 1 = On + context.dash.bumperWidget.update(sensorIndex, sensorVal); break; default: diff --git a/src/ui/widgets/BumperWidget.java b/src/ui/widgets/BumperWidget.java index d6387d8..7ab8d2e 100644 --- a/src/ui/widgets/BumperWidget.java +++ b/src/ui/widgets/BumperWidget.java @@ -125,8 +125,7 @@ public BumperWidget(Context ctx) { * @param bumper - The bumper to be updated * @param state - the received state of the bumper */ - public void update(byte bumper, byte state) { - + public void update(int bumper, int state) { switch(bumper) { case LEFT_BUMPER: setBumperState(bumperStateLeft, state); @@ -148,13 +147,13 @@ public void update(byte bumper, byte state) { * @param target - Bumper to update * @param state - State to set that bumper to. */ - protected void setBumperState(BumperStatus target, byte state) { + protected void setBumperState(BumperStatus target, int state) { switch(state) { - case 0x00: + case 0: target = BumperStatus.CLEAR; break; - case 0x01: + case 1: target = BumperStatus.ACTIVATED; break; default: From 507f266ec06ab86d59cd5e0e0701631f90ab300b Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Mon, 9 Aug 2021 16:34:35 -0700 Subject: [PATCH 093/125] Move setBumperState functionality to update main function Enum variable update attempts when passed as a param to a function where failing to update correctly, moved the logic into the main update function and set to target global state enum variables directly. --- src/ui/widgets/BumperWidget.java | 42 +++++++++++++++----------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/ui/widgets/BumperWidget.java b/src/ui/widgets/BumperWidget.java index 7ab8d2e..8e225b5 100644 --- a/src/ui/widgets/BumperWidget.java +++ b/src/ui/widgets/BumperWidget.java @@ -57,6 +57,7 @@ protected enum BumperStatus { public int getValue() { return this.state; } + }; /** @@ -126,12 +127,28 @@ public BumperWidget(Context ctx) { * @param state - the received state of the bumper */ public void update(int bumper, int state) { + BumperStatus newState; + + //Determine incoming state + switch(state) { + case 0: + newState = BumperStatus.CLEAR; + break; + case 1: + newState = BumperStatus.ACTIVATED; + break; + default: + newState = BumperStatus.UNKOWN; + serialLog.warning("BUMPER: Unrecognized bumper state, cannot update."); + } + + //Update target bumper switch(bumper) { case LEFT_BUMPER: - setBumperState(bumperStateLeft, state); + bumperStateLeft = newState; break; case RIGHT_BUMPER: - setBumperState(bumperStateRight, state); + bumperStateRight = newState; break; default: serialLog.warning("BUMPER: Unkown bumper INDEX received on state update."); @@ -141,25 +158,6 @@ public void update(int bumper, int state) { updateBumperColors(); updateStatusLabel(); } - - /** - * Sets the On/Off bumper state of the target bumper. - * @param target - Bumper to update - * @param state - State to set that bumper to. - */ - protected void setBumperState(BumperStatus target, int state) { - - switch(state) { - case 0: - target = BumperStatus.CLEAR; - break; - case 1: - target = BumperStatus.ACTIVATED; - break; - default: - serialLog.warning("BUMPER: Unkown bumper STATE received on state update."); - } - } /** * Updates the bumper button widget colors based on the @@ -173,7 +171,7 @@ protected void updateBumperColors() { leftLabel.setBackground(DEF_INACTIVE_COLOR); break; case ACTIVATED: - rightLabel.setBackground(DEF_ACTIVE_COLOR); + leftLabel.setBackground(DEF_ACTIVE_COLOR); break; default: serialLog.warning("BUMPER: Unknown left bumper STATE received on color update."); From 10e5ef46a6d2bd228d0f7f261eb2e2f15b39fba5 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 11 Aug 2021 13:08:38 -0700 Subject: [PATCH 094/125] Add bumper enable/disable support -Added configuration screen option for disabling push button bumper. -Added new serial constants, messages, and corresponding function call for enabling or disabling the push button bumper. --- src/serial/Messages/Message.java | 9 ++++ src/serial/Serial.java | 16 +++--- src/serial/SerialSender.java | 26 ++++++++++ src/ui/UIConfigPanel.java | 88 +++++++++++++++++++++++++++----- 4 files changed, 120 insertions(+), 19 deletions(-) diff --git a/src/serial/Messages/Message.java b/src/serial/Messages/Message.java index cd1fb78..1d326fa 100644 --- a/src/serial/Messages/Message.java +++ b/src/serial/Messages/Message.java @@ -141,4 +141,13 @@ public static Message startDriving() { public static Message requestAPMVersion() { return new DataMessage(Serial.INFO_DATA, (byte)Serial.APM_VERSION, (byte)0); } + + public static Message enableBumper() { + return new WordMessage(Serial.COMMAND_WORD, Serial.ENABLE_BUMPER_CMD, (byte)0); + } + + public static Message disableBumper() { + return new WordMessage(Serial.COMMAND_WORD, Serial.DISABLE_BUMPER_CMD, (byte)0); + } + } diff --git a/src/serial/Serial.java b/src/serial/Serial.java index 8d6072c..d9e5af8 100644 --- a/src/serial/Serial.java +++ b/src/serial/Serial.java @@ -40,13 +40,15 @@ public class Serial { public static final int STATE_STRING = 0x1; //Commands - public static final byte ESTOP_CMD = 0x0; - public static final byte TARGET_CMD = 0x1; - public static final byte LOOPING_CMD = 0x2; - public static final byte CLEAR_CMD = 0x3; - public static final byte DELETE_CMD = 0x4; - public static final byte STOP_CMD = 0x5; - public static final byte START_CMD = 0x6; + public static final byte ESTOP_CMD = 0x0; + public static final byte TARGET_CMD = 0x1; + public static final byte LOOPING_CMD = 0x2; + public static final byte CLEAR_CMD = 0x3; + public static final byte DELETE_CMD = 0x4; + public static final byte STOP_CMD = 0x5; + public static final byte START_CMD = 0x6; + public static final byte ENABLE_BUMPER_CMD = 0x7; + public static final byte DISABLE_BUMPER_CMD = 0x8; //State types public static final byte APM_STATE = 0x0; diff --git a/src/serial/SerialSender.java b/src/serial/SerialSender.java index a6fff09..a67a07f 100644 --- a/src/serial/SerialSender.java +++ b/src/serial/SerialSender.java @@ -153,6 +153,11 @@ private void advanceWaypointList(int confirm) { } } + /** + * Sends a message to a unit telling it whether it should start or stop + * moving. + * @param shouldMove - Whether movement should be started or stopped + */ public void changeMovement(boolean shouldMove) { Message msg; @@ -169,6 +174,27 @@ public void changeMovement(boolean shouldMove) { } } + /** + * Sends a message to a unit telling it whether to enable or + * disable its push button bumper. + * @param shouldEnable - Whether the bumper should be enabled or disabled. + */ + public void toggleBumper(boolean shouldEnable) { + Message msg; + + if(context.connected) { + if(shouldEnable) { + msg = Message.enableBumper(); + System.err.println("Sending bumper enable message."); + } + else { + msg = Message.disableBumper(); + System.err.println("Sending bumper disable message."); + } + sendMessage(msg); + } + } + public void sendSync() { Message msg = Message.syncMessage(Serial.SYNC_REQUEST); sendMessage(msg); diff --git a/src/ui/UIConfigPanel.java b/src/ui/UIConfigPanel.java index 4318960..d8cedff 100644 --- a/src/ui/UIConfigPanel.java +++ b/src/ui/UIConfigPanel.java @@ -4,14 +4,18 @@ import com.map.*; import java.io.*; +import java.text.Format; + import java.awt.Insets; import java.awt.Dimension; import java.awt.Component; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; -import java.awt.event.ActionEvent; import java.awt.geom.Point2D; -import java.text.Format; + +import java.awt.event.ActionEvent; +import java.awt.event.ItemEvent; + import javax.swing.*; import javax.swing.filechooser.FileFilter; import javax.swing.filechooser.FileNameExtensionFilter; @@ -28,13 +32,13 @@ */ public class UIConfigPanel extends JPanel { - private static final int DEF_TEXT_FIELD_WIDTH = 6; - private static final int DEF_BUTTON_WIDTH = 200; - private static final int DEF_BUTTON_HEIGHT = 30; - private static final String DEF_HOME_COORD = "0.0"; - - private Context context; + //Constants + private static final int DEF_TEXT_FIELD_WIDTH = 6; + private static final int DEF_BUTTON_WIDTH = 200; + private static final int DEF_BUTTON_HEIGHT = 30; + private static final String DEF_HOME_COORD = "0.0"; + //UI Componenets private JPanel buttonPanel; private JButton toggleButton; private JButton driverButton; @@ -48,6 +52,13 @@ public class UIConfigPanel extends JPanel { private JTextField latField; private JButton setHomeButton; + private JCheckBox bumperCheckBox; + private JButton applySettingsButton; + + //State and Reference vars + private Context context; + private boolean bumperIsEnabled; + /** * Class constructor * @param cxt - the application context @@ -57,13 +68,11 @@ public UIConfigPanel(Context cxt, boolean isWindows) { this.context = cxt; this.setLayout(new GridBagLayout()); - //Constraints persist between component applications, so any properties we - //don't want more than one component to share need to be explicitly defined - //before being applied to that a compoenent. GridBagConstraints constraints = new GridBagConstraints(); constraints.insets = new Insets(0,5,0,5); + //Unit type toggle button(Rover/Copter) toggleButton = new JButton(toggleLocaleAction); toggleButton.setPreferredSize( new Dimension(DEF_BUTTON_WIDTH, DEF_BUTTON_HEIGHT)); @@ -71,6 +80,10 @@ public UIConfigPanel(Context cxt, boolean isWindows) { constraints.gridy = 0; this.add(toggleButton, constraints); + //Driver installation button (Deprecated - Windows Only) + //Note - CP - 8-10-21: There is an updated version of the driver, + //and also an automatic installation of this driver from the + //installer executable now. This should be removed at some point. if(isWindows) { driverButton = new JButton(driverExecAction); driverButton.setPreferredSize( @@ -80,6 +93,7 @@ public UIConfigPanel(Context cxt, boolean isWindows) { this.add(driverButton, constraints); } + //Sketch upload button sketchUploadButton = new JButton(uploadSketchAction); sketchUploadButton.setPreferredSize( new Dimension(DEF_BUTTON_WIDTH, DEF_BUTTON_HEIGHT)); @@ -87,6 +101,7 @@ public UIConfigPanel(Context cxt, boolean isWindows) { constraints.gridy = 2; this.add(sketchUploadButton, constraints); + //Home coordinates fields and set button Point2D homeCoords = context.getHomeProp(); latLabel = new JLabel("Latitiude:"); @@ -117,8 +132,53 @@ public UIConfigPanel(Context cxt, boolean isWindows) { constraints.gridwidth = 2; constraints.fill = GridBagConstraints.HORIZONTAL; this.add(setHomeButton, constraints); + + //Bumper toggle checkbox + bumperCheckBox = new JCheckBox("Enable Bumper"); + bumperCheckBox.setSelected(true); + constraints.gridx = 3; + constraints.gridy = 1; + this.add(bumperCheckBox, constraints); + this.bumperIsEnabled = true; + + //Settings apply button (for Checkboxes/Radio Buttons) + applySettingsButton = new JButton(setSettingsAction); + constraints.gridx = 3; + constraints.gridy = 2; + this.add(applySettingsButton, constraints); } + /** + * Action used to update settings based on selectable radio buttons and + * check boxes. + */ + private Action setSettingsAction = new AbstractAction() { + { + String text = "Apply Settings"; + putValue(Action.NAME, text); + } + + //NOTE - CP - Message should be ack'd by rover yes? Need to update this. + public void actionPerformed(ActionEvent e) { + + //Bumper enable/disable toggle + if(bumperIsEnabled && !bumperCheckBox.isSelected()) { + bumperIsEnabled = false; + context.sender.toggleBumper(bumperIsEnabled); + + //TODO - Gray out/disable widget + + } + else if(!bumperIsEnabled && bumperCheckBox.isSelected()) { + bumperIsEnabled = true; + context.sender.toggleBumper(bumperIsEnabled); + + //TODO - Enable widget + } + + } + }; + /** * Action used to toggle the user interface between Air and Ground mode. * Requires a program restart for the setting to take effect. @@ -128,6 +188,7 @@ public UIConfigPanel(Context cxt, boolean isWindows) { String text = "Toggle ground/air mode"; putValue(Action.NAME, text); } + public void actionPerformed(ActionEvent e) { context.toggleLocale(); JFrame mf = new JFrame("message"); @@ -145,11 +206,13 @@ public void actionPerformed(ActionEvent e) { String text = "Launch driver installer"; putValue(Action.NAME, text); } + public void actionPerformed(ActionEvent e) { String[] cmd = { "RadioDiversv2.12.06WHQL_Centified.exe" }; try { Process p = Runtime.getRuntime().exec(cmd); - } catch (Exception ex) { + } + catch (Exception ex) { ex.printStackTrace(); } } @@ -164,6 +227,7 @@ public void actionPerformed(ActionEvent e) { String text = "Upload arduino sketch"; putValue(Action.NAME, text); } + public void actionPerformed(ActionEvent e) { File selectedFile = null; JFileChooser fileChooser = new JFileChooser(); From ab1c3b3b1aaafe3720ad1f3a97b1986a5ac76b5a Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 11 Aug 2021 14:37:41 -0700 Subject: [PATCH 095/125] Remove status label and corresponding functions from bumper widget --- src/ui/widgets/BumperWidget.java | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/ui/widgets/BumperWidget.java b/src/ui/widgets/BumperWidget.java index 8e225b5..621b67c 100644 --- a/src/ui/widgets/BumperWidget.java +++ b/src/ui/widgets/BumperWidget.java @@ -31,7 +31,6 @@ public class BumperWidget extends UIWidget { protected JLabel leftLabel; protected JPanel rightPanel; protected JLabel rightLabel; - protected StatusBarWidget statusBar; //State Tracking Variables protected BumperStatus bumperStateLeft; @@ -82,11 +81,6 @@ public BumperWidget(Context ctx) { lowerPanel = new JPanel(); lowerPanel.setLayout(new BoxLayout(lowerPanel, BoxLayout.X_AXIS)); - //Set up Status Bar - statusBar = new StatusBarWidget(context); - statusBar.update(StatusBarWidget.StatusType.CLEAR); - outerPanel.add(statusBar); - //Set up left button label/panel leftLabel = new JLabel("Left", SwingConstants.CENTER); leftLabel.setBackground(DEF_INACTIVE_COLOR); @@ -156,7 +150,6 @@ public void update(int bumper, int state) { //Update Colors & Label updateBumperColors(); - updateStatusLabel(); } /** @@ -189,21 +182,4 @@ protected void updateBumperColors() { serialLog.warning("BUMPER: Unknown right bumper STATE received on color update."); } } - - /** - * Updates the status label message with respect to - * the currently held bumper button states. - */ - protected void updateStatusLabel() { - //If either bumper has been triggered, update the label to reflect this. - if((bumperStateLeft == BumperStatus.ACTIVATED) - ||(bumperStateRight == BumperStatus.ACTIVATED)) { - statusBar.update(StatusBarWidget.StatusType.ACTIVATED); - return; - } - - //Otherwise assume the bumper is clear - statusBar.update(StatusBarWidget.StatusType.CLEAR); - } - } From 6cf2f40de08c5555c56ad792795631127f30ca0f Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 12 Aug 2021 14:32:22 -0700 Subject: [PATCH 096/125] Update rover top image orientation and directional line thickness --- .gitignore | 1 + resources/images/6x6-Top.png | Bin 7705 -> 7857 bytes 2 files changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6b3b288..24250fb 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ arduino-cli.exe resources/groundSettings_edited.xml resources/groundSettings_Old.xml resources/groundSettings.xml +resources/images/6x6-Top - Backup.png diff --git a/resources/images/6x6-Top.png b/resources/images/6x6-Top.png index 8f9b84297d6770fc42c4937ad9161dcc8fe0ed59..b054e5086179b0f5eb45497980be0cc625fc3723 100644 GIT binary patch literal 7857 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 EKYBKNBme*a literal 7705 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 From f7036fe475fdcca70279cbb6a6ae9157f6eb0b03 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Fri, 13 Aug 2021 13:31:06 -0700 Subject: [PATCH 097/125] Added turnaround flag readout to state widget and status bar --- src/serial/Serial.java | 7 ++++--- src/ui/widgets/StateWidget.java | 10 ++++++++-- src/ui/widgets/StatusBarWidget.java | 1 + 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/serial/Serial.java b/src/serial/Serial.java index d9e5af8..5612fcb 100644 --- a/src/serial/Serial.java +++ b/src/serial/Serial.java @@ -73,9 +73,10 @@ public class Serial { public static final byte AUTO_STATE_STALLED = 0x3; //Auto flags sub values - public static final byte AUTO_STATE_FLAGS_NONE = 0B00; - public static final byte AUTO_STATE_FLAGS_CAUTION = 0B01; - public static final byte AUTO_STATE_FLAGS_APPROACH = 0B10; + public static final byte AUTO_STATE_FLAGS_NONE = 0B00; + public static final byte AUTO_STATE_FLAGS_CAUTION = 0B01; + public static final byte AUTO_STATE_FLAGS_APPROACH = 0B10; + public static final byte AUTO_STATE_FLAGS_TURNAROUND = 0B100; //Sync public static final byte SYNC_REQUEST = 0x00; diff --git a/src/ui/widgets/StateWidget.java b/src/ui/widgets/StateWidget.java index 67d986c..2af164b 100644 --- a/src/ui/widgets/StateWidget.java +++ b/src/ui/widgets/StateWidget.java @@ -278,8 +278,9 @@ private void setFlagState(byte substate) { String fmt; String fmtStr = "Flg:%s"; - boolean caution = ((substate & Serial.AUTO_STATE_FLAGS_CAUTION) > 0 ) ? true : false; - boolean approach = ((substate & Serial.AUTO_STATE_FLAGS_APPROACH) > 0 ) ? true : false; + boolean caution = ((substate & Serial.AUTO_STATE_FLAGS_CAUTION) > 0) ? true : false; + boolean approach = ((substate & Serial.AUTO_STATE_FLAGS_APPROACH) > 0) ? true : false; + boolean turn = ((substate & Serial.AUTO_STATE_FLAGS_TURNAROUND) > 0) ? true : false; // System.out.println("StateWidget - Updating Flag State"); if(caution && approach) { @@ -297,6 +298,11 @@ else if(caution) { finalWidth = Math.min(fmt.length(), LINE_WIDTH); statusBar.update(StatusBarWidget.StatusType.CAUTION); } + else if(turn) { + fmt = String.format(fmtStr, "Turn"); + finalWidth = Math.min(fmt.length(), LINE_WIDTH); + statusBar.update(StatusBarWidget.StatusType.TURNAROUND); + } else { fmt = String.format(fmtStr, "None"); finalWidth = Math.min(fmt.length(), LINE_WIDTH); diff --git a/src/ui/widgets/StatusBarWidget.java b/src/ui/widgets/StatusBarWidget.java index fa5e705..e305eca 100644 --- a/src/ui/widgets/StatusBarWidget.java +++ b/src/ui/widgets/StatusBarWidget.java @@ -41,6 +41,7 @@ public enum StatusType { NORMAL ("Normal", Color.black, Color.white), PROCESSING ("Processing", Color.white, Color.blue), CAUTION ("Caution", Color.black, Color.yellow), + TURNAROUND ("Turn", Color.white, Color.blue), ERROR ("Error", Color.black, Color.red), UNKNOWN ("Unknown", Color.black, Color.white), From 62bd3913f8e1a9fa7c07382e88228f4ed888f285 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Fri, 13 Aug 2021 14:09:15 -0700 Subject: [PATCH 098/125] Added enable disable bumper widget function Applying bumper enable/disable from the configuration window now shows/hides and enables/disables the bumper widget. --- src/ui/UIConfigPanel.java | 9 ++------- src/ui/widgets/BumperWidget.java | 25 +++++++++++++++++++++++++ src/ui/widgets/StateWidget.java | 3 --- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/ui/UIConfigPanel.java b/src/ui/UIConfigPanel.java index d8cedff..018ea2e 100644 --- a/src/ui/UIConfigPanel.java +++ b/src/ui/UIConfigPanel.java @@ -158,24 +158,19 @@ public UIConfigPanel(Context cxt, boolean isWindows) { putValue(Action.NAME, text); } - //NOTE - CP - Message should be ack'd by rover yes? Need to update this. public void actionPerformed(ActionEvent e) { //Bumper enable/disable toggle if(bumperIsEnabled && !bumperCheckBox.isSelected()) { bumperIsEnabled = false; context.sender.toggleBumper(bumperIsEnabled); - - //TODO - Gray out/disable widget - + context.dash.bumperWidget.setEnabled(bumperIsEnabled); } else if(!bumperIsEnabled && bumperCheckBox.isSelected()) { bumperIsEnabled = true; context.sender.toggleBumper(bumperIsEnabled); - - //TODO - Enable widget + context.dash.bumperWidget.setEnabled(bumperIsEnabled); } - } }; diff --git a/src/ui/widgets/BumperWidget.java b/src/ui/widgets/BumperWidget.java index 621b67c..4c90900 100644 --- a/src/ui/widgets/BumperWidget.java +++ b/src/ui/widgets/BumperWidget.java @@ -182,4 +182,29 @@ protected void updateBumperColors() { serialLog.warning("BUMPER: Unknown right bumper STATE received on color update."); } } + + /** + * Sets the working state of the widget to either enabled or disabled. + * @param shouldEnabled - Whether or not to enable/disable the widget. + */ + public void setEnabled(boolean shouldEnable) { + if(shouldEnable) { + leftPanel.setEnabled(true); + leftLabel.setEnabled(true); + rightPanel.setEnabled(true); + rightLabel.setEnabled(true); + lowerPanel.setEnabled(true); + outerPanel.setEnabled(true); + outerPanel.setVisible(true); + } + else { + leftPanel.setEnabled(false); + leftLabel.setEnabled(false); + rightPanel.setEnabled(false); + rightLabel.setEnabled(false); + lowerPanel.setEnabled(false); + outerPanel.setEnabled(false); + outerPanel.setVisible(false); + } + } } diff --git a/src/ui/widgets/StateWidget.java b/src/ui/widgets/StateWidget.java index 2af164b..0b66030 100644 --- a/src/ui/widgets/StateWidget.java +++ b/src/ui/widgets/StateWidget.java @@ -311,9 +311,6 @@ else if(turn) { flagLabel.setText(fmt.substring(0, finalWidth)); } - //TODO - CP - Add Set Wiggle Flag state or incorporate into existing - //flag structure. - /** * Generates an information panel on click describing any warnings, errors, * and details of the current state. From 52f8312e86c4aee69047df395f39d6a290613914 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Fri, 13 Aug 2021 14:25:01 -0700 Subject: [PATCH 099/125] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6b3b288..24250fb 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ arduino-cli.exe resources/groundSettings_edited.xml resources/groundSettings_Old.xml resources/groundSettings.xml +resources/images/6x6-Top - Backup.png From b059e4b77784db1bda4ae2338f7d6667a4e0dcb0 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 18 Aug 2021 10:15:11 -0700 Subject: [PATCH 100/125] Warning logging update for state widget --- src/ui/widgets/StateWidget.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/widgets/StateWidget.java b/src/ui/widgets/StateWidget.java index 0b66030..3568211 100644 --- a/src/ui/widgets/StateWidget.java +++ b/src/ui/widgets/StateWidget.java @@ -162,7 +162,7 @@ public void update(byte state, byte substate) { //then this should be removed. break; default: - System.err.println("Error - Unrecognized State"); + serialLog.warning("State Widget: Unrecognized incoming State"); } } From 53207746b910e5d1da256324cebe51568c2997d0 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 19 Aug 2021 09:52:11 -0700 Subject: [PATCH 101/125] Update static values for dashboard init, turn on tile server debug --- src/Dashboard.java | 9 +++++---- src/map/TileServer.java | 5 +++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Dashboard.java b/src/Dashboard.java index c124b7c..60c6a5b 100644 --- a/src/Dashboard.java +++ b/src/Dashboard.java @@ -43,9 +43,10 @@ public class Dashboard implements Runnable { private Context context; //Static Values - private static final int START_WIDTH = 1200; //default window width - private static final int START_HEIGHT = 900; //default window height + private static final int DEF_WINDOW_WIDTH = 1200; + private static final int DEF_WINDOW_HEIGHT = 900; private static final int ROUND_WIDGET_SIZE = 105; + private static final int DEFAULT_ZOOM_LEVEL = 4; //UI Widget Frame WidgetPanel widgetPanel; @@ -152,14 +153,14 @@ public void disconnectRequest() { mapPanel = new MapPanel(context, new Point((int)context.getHomeProp().getY(), (int)context.getHomeProp().getX()), - 4, // default zoom level + DEFAULT_ZOOM_LEVEL, serialPanel, createRightPanel(), messageBox); f.add(mapPanel); f.pack(); - f.setSize(START_WIDTH, START_HEIGHT); + f.setSize(DEF_WINDOW_WIDTH, DEF_WINDOW_HEIGHT); f.setVisible(true); } diff --git a/src/map/TileServer.java b/src/map/TileServer.java index cb65cf3..4213f90 100644 --- a/src/map/TileServer.java +++ b/src/map/TileServer.java @@ -10,13 +10,13 @@ import javax.imageio.*; /** - * MapSource implementation for web-mecator maps loaded from external + * MapSource implementation for web-mercator maps loaded from external * tile servers */ class TileServer implements MapSource { //label tiles on screen for debugging - private static final boolean TILE_LABEL = false; + private static final boolean TILE_LABEL = true; //Number of pixels a tile takes up private static final int TILE_SIZE = 256; //Minimum index any tile can have on Z @@ -321,6 +321,7 @@ public int compare(TileTag a, TileTag b) { return dir * (int) Math.signum(xydiff + Z_PRIORITY * zdiff); } } + /** * TileTag contains refereces to x,y,z coordinates, defining equals * and hash code appropriately, and generating url's for standard From 6812122250fdb3d6ddcb8a9f7b9da7c5b3c6f340 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Fri, 20 Aug 2021 15:24:17 -0700 Subject: [PATCH 102/125] TileServer readability refactor and documentation --- src/Dashboard.java | 6 +- src/map/TileServer.java | 258 +++++++++++++++++++++++++++------------- 2 files changed, 181 insertions(+), 83 deletions(-) diff --git a/src/Dashboard.java b/src/Dashboard.java index 60c6a5b..34432c8 100644 --- a/src/Dashboard.java +++ b/src/Dashboard.java @@ -43,9 +43,9 @@ public class Dashboard implements Runnable { private Context context; //Static Values - private static final int DEF_WINDOW_WIDTH = 1200; - private static final int DEF_WINDOW_HEIGHT = 900; - private static final int ROUND_WIDGET_SIZE = 105; + private static final int DEF_WINDOW_WIDTH = 1200; + private static final int DEF_WINDOW_HEIGHT = 900; + private static final int ROUND_WIDGET_SIZE = 105; private static final int DEFAULT_ZOOM_LEVEL = 4; //UI Widget Frame diff --git a/src/map/TileServer.java b/src/map/TileServer.java index 4213f90..aa4a14d 100644 --- a/src/map/TileServer.java +++ b/src/map/TileServer.java @@ -13,62 +13,108 @@ * MapSource implementation for web-mercator maps loaded from external * tile servers */ - class TileServer implements MapSource { + //label tiles on screen for debugging private static final boolean TILE_LABEL = true; + //Number of pixels a tile takes up private static final int TILE_SIZE = 256; + //Minimum index any tile can have on Z + //QUESTION (CP) - Why min 2? private static final int MIN_Z = 2; + + //QUESTION (CP) - Why max 18? //Maximum index any tile can have on Z private static final int MAX_Z = 18; - //Maximum index any tile can have on X - private static final int MAX_X = (1 << MAX_Z) / TILE_SIZE; - //Maximum index any tile can have on Y + + //Maximum index any tile can have on X (CP - Not used anywhere?) +// private static final int MAX_X = (1 << MAX_Z) / TILE_SIZE; + + //Maximum index any tile can have on Y (Used in TileTag Hashcode calculation only) private static final int MAX_Y = (1 << MAX_Z) / TILE_SIZE; + //Maximum number of tiles to keep in the cache at any given moment private static final int CACHE_SIZE = 256; - //maximum number of concurrent tile load requests + + //maximum number of concurrent tile load requests (Threads) private static final int CC_REQUEST = 12; + //How many tiles to remove from the cache whenever a sweep is done private static final int CLEAN_NUM = 16; + //Ratio of lateral to vertical tile priority private static final int Z_PRIORITY = 6; + //rings of offscreen tiles around the margins to try and load private static final int CUR_ZOOM_MARGIN = 2; + //rings of offscreen tiles on the next zoom to try and load private static final int NEXT_ZOOM_MARGIN = -1; + //maximum percentage to zoom a tile before shrinking the layer below instead private static final float ZOOM_CROSSOVER = 1.30f; + //Dummy image to render when a tile that has not loaded is requested private static final Image dummyTile = new BufferedImage(TILE_SIZE,TILE_SIZE, BufferedImage.TYPE_INT_ARGB); + + //CP - List of listeners that should be triggered to repaint when a change + //to the map tiles occurs...Should be a satellite and elevation map for our + //purposes. private java.util.List repaintListeners = new LinkedList(); - private Map cache = new ConcurrentHashMap(CACHE_SIZE+1, 1.0f); + + //Key value pairings for currently cached map tile data. Contains the following: + //TileTag - XYZ and URL (Standard Web Mercator Protocol) + //Image - Corresponding Image to go along with tile tag information + private Map cache = + new ConcurrentHashMap(CACHE_SIZE + 1, 1.0f); + + //CP - Main URL of the tile server to be used (Root here because each TileTag has an + //additional URL piece that gets concatenated on to access it from the server. private final String rootURL; + // Keep track of the map position on the last draw to trigger new tile loads private TileTag centerTag = new TileTag(0,0,0); + /** + * Class Constructor + * @param url - The root url of the tile server. + */ TileServer(String url) { this.rootURL = url; } + /** + * Clears the current TileServer cache, interrupting tile loading threads if + * necessary. Once the cache is cleared, the center TileTag is also reset to its + * default/initial value in order to trigger tile loads immediately on the next draw. + */ public void clear() { if(tileLoader != null) { tileLoader.interrupt(); } + cache.clear(); - // reset the center tag to trigger tile loads immediatly on next draw centerTag = new TileTag(0,0,0); } + /** + * Calculates the ratio between crossover tile layers based on the current zoom + * level and then draws the current layer. + * @param gd - The Graphics2d class used for rendering and color management. + * @param center - Center Point2D position of the tiles in cache to paint. + * @param scale - Scale of the current tile layer being drawn. + * @param width - Width of the tile set in cache + * @param height - height of the tile set in cache + */ public void paint(Graphics2D gd, Point2D center, int scale, int width, int height) { Graphics2D g2d = (Graphics2D) gd.create(); //ratio between zoom and the tile layer above it double zfix = (double) scale / (double) Integer.highestOneBit(scale); - int zoom = 31 - Integer.numberOfLeadingZeros(scale/TILE_SIZE); + int zoom = (31 - Integer.numberOfLeadingZeros(scale / TILE_SIZE)); if(zfix >= ZOOM_CROSSOVER) { zoom += 1; @@ -76,8 +122,8 @@ public void paint(Graphics2D gd, Point2D center, int scale, int width, int heigh } //effective width/height after zoom correction - double ewidth = (width /zfix); - double eheight = (height/zfix); + double ewidth = (width / zfix); + double eheight = (height / zfix); g2d.scale(zfix, zfix); g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, @@ -90,25 +136,29 @@ public void paint(Graphics2D gd, Point2D center, int scale, int width, int heigh */ //pixel positions of top left point - double sLon = (center.getX()/zfix) - (ewidth /2.0); - double sLat = (center.getY()/zfix) - (eheight/2.0); + double sLon = (center.getX() / zfix) - (ewidth / 2.0); + double sLat = (center.getY() / zfix) - (eheight / 2.0); + //row/col Base index in the top left corner - int rowB = (int)(sLon/(double)TILE_SIZE); - int colB = (int)(sLat/(double)TILE_SIZE); + int rowB = (int)(sLon / (double)TILE_SIZE); + int colB = (int)(sLat / (double)TILE_SIZE); + //X,Y shifts to keep the view in alignment - int xalign = (int)-(sLon%TILE_SIZE); - int yalign = (int)-(sLat%TILE_SIZE); + int xalign = (int)-(sLon % TILE_SIZE); + int yalign = (int)-(sLat % TILE_SIZE); + //width/height in tiles - int wit = (((int)ewidth -xalign)/TILE_SIZE)+1; - int hit = (((int)eheight-yalign)/TILE_SIZE)+1; + int wit = (((int)ewidth - xalign) / TILE_SIZE) + 1; + int hit = (((int)eheight - yalign) / TILE_SIZE) + 1; for(int row = 0; row < wit; row++) { for(int col = 0; col < hit; col++) { - int x = xalign + row*TILE_SIZE; - int y = yalign + col*TILE_SIZE; - TileTag tile = new TileTag(row+rowB, col+colB, zoom); + int x = xalign + (row * TILE_SIZE); + int y = yalign + (col * TILE_SIZE); + TileTag tile = new TileTag((row + rowB), (col + colB), zoom); Image img = pollImage(tile); g2d.drawImage(img, x, y,null); + if(TILE_LABEL) { g2d.setColor(Color.YELLOW); g2d.drawString(tile.toString(), x, y); @@ -118,15 +168,15 @@ public void paint(Graphics2D gd, Point2D center, int scale, int width, int heigh g2d.dispose(); // Start loading new tiles if the center tag has changed - TileTag newCenterTag = new TileTag(rowB + wit/2, colB + hit/2, zoom); + TileTag newCenterTag = new TileTag(rowB + (wit / 2), colB + (hit / 2), zoom); if(!newCenterTag.equals(centerTag)) { centerTag = newCenterTag; - launchTileLoader(centerTag, (width/TILE_SIZE)+1, (height/TILE_SIZE)+1); + launchTileLoader(centerTag, (width / TILE_SIZE) + 1, (height / TILE_SIZE) + 1); } } public boolean isValidZoom(int zoomLevel) { - int zoom = 31 - Integer.numberOfLeadingZeros(zoomLevel/TILE_SIZE); + int zoom = 31 - Integer.numberOfLeadingZeros(zoomLevel / TILE_SIZE); return (zoom >= MIN_Z && zoom < MAX_Z); } @@ -152,7 +202,7 @@ private void launchTileLoader(TileTag center, int width, int height) { private void cleanTiles() { TileTag[] loaded = cache.keySet().toArray(new TileTag[] {}); Arrays.sort(loaded, new TileDistCmp(centerTag, TileDistCmp.FURTHEST)); - for(int i=0; i= numToLoad || TileLoader.this.stop) { finishedHelpers.incrementAndGet(); break; } + TileTag next = toLoad.get(index); loadTile(next); } } } } + /** * Method for fetching a given TileTag and putting it in the cache */ @@ -278,18 +354,23 @@ private void loadTile(TileTag target) { try { Image img = ImageIO.read(target.getURL(rootURL)); addTile(target, img); - } catch (Exception e) { - System.err.println("failed to load "+target); + + } + catch (Exception e) { + System.err.println("failed to load " + target); e.printStackTrace(); - } finally { + } + finally { //don't repaint for prefetched tiles - if(target.z == centerTag.z) - contentChanged(); + if(target.z == centerTag.z) { + contentChanged(); + } } } + /** - * Compares two TileTags based on which one is closer to the given tag - * Z axis is weighten using Z_PRIORITY + * Compares two TileTags based on which one is closer to the given tag. + * Z axis is weighted using Z_PRIORITY. * This should allow components to select the tags most/least important * to the viewer based on their current viewport into the map */ @@ -301,23 +382,26 @@ static class TileDistCmp implements Comparator { final double refy; final int refz; final int dir; + TileDistCmp(TileTag reference, int direction) { dir = direction; refz = reference.z; - refx = reference.x / (double)(1<= MIN_Z && z <= MAX_Z && x >= 0 && x < (1 << z) && y >= 0 && y < (1 << z) ; } + URL getURL(String rootURL) { - String url = rootURL.replaceAll("\\{[xX]\\}", ""+x) - .replaceAll("\\{[yY]\\}", ""+y) - .replaceAll("\\{[zZ]\\}", ""+z); + String url = rootURL.replaceAll("\\{[xX]\\}", "" + x) + .replaceAll("\\{[yY]\\}", "" + y) + .replaceAll("\\{[zZ]\\}", "" + z); URL res = null; + try { res = new URL(url); - } catch (Exception e) { + } + catch (Exception e) { e.printStackTrace(); } + return res; } + @Override public String toString() { StringBuilder sb = new StringBuilder("Tile @ "); + sb.append("x: "); sb.append(x); sb.append(" y: "); sb.append(y); sb.append(" z: "); sb.append(z); + return sb.toString(); } } + /** * small set of tests for TileDistCmp and TileTag */ @@ -390,28 +485,31 @@ private void testTileDist() { TileDistCmp cmp = new TileDistCmp(new TileTag(2,2,5), TileDistCmp.FURTHEST); - for(int z=0; z<12; z++) { - System.out.println(z+" dist: "+cmp.compare( - new TileTag((1<<6),(1<<6),6), - new TileTag((1< Date: Thu, 26 Aug 2021 13:26:59 -0700 Subject: [PATCH 103/125] Increase rover angle arrow visibility and add documentation to Map and Tile classes --- resources/images/6x6-Top.png | Bin 7857 -> 7865 bytes src/map/MapPanel.java | 27 ++++++----- src/map/RoverPath.java | 12 +++++ src/map/TileServer.java | 87 +++++++++++++++++++++++++---------- 4 files changed, 90 insertions(+), 36 deletions(-) diff --git a/resources/images/6x6-Top.png b/resources/images/6x6-Top.png index b054e5086179b0f5eb45497980be0cc625fc3723..667ffa5c338cd5a2645d392c321fcfbf40959bb2 100644 GIT binary patch literal 7865 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 literal 7857 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 EKYBKNBme*a diff --git a/src/map/MapPanel.java b/src/map/MapPanel.java index 0e59613..e6d1fcd 100644 --- a/src/map/MapPanel.java +++ b/src/map/MapPanel.java @@ -107,9 +107,10 @@ public void componentResized(ComponentEvent e){ public void enablePathModifications(boolean value){ roverPath.setWaypointsEnabled(value); } + //Code for CoordinateTransform interface /** - * Transforms a (lonitude,latitude) point to absolute (x,y) pixels + * Transforms a (longitude, latitude) point to absolute (x, y) pixels. * Will return an instance of the same class as the argument p */ public Point2D toPixels(Point2D p) { @@ -117,15 +118,13 @@ public Point2D toPixels(Point2D p) { double scale = zoom; double lon = p.getX(); double lat = Math.toRadians(p.getY()); - double x = (lon+180.0)/360.0 * scale; - double y = ((1 - - Math.log( - Math.tan(lat) + 1 / Math.cos(lat) - ) / Math.PI - )/2) * scale; + double x = ((lon + 180.0) / 360.0) * scale; + double y = ((1 - Math.log(Math.tan(lat) + 1 / Math.cos(lat)) / Math.PI) / 2) * scale; + f.setLocation(x,y); return f; } + /** * Transforms absolute (x,y) pixels to (lonitude,latitude) * Will return an instance of the same class as the argument p @@ -146,6 +145,7 @@ public Point2D toCoordinates(Point2D p) { f.setLocation(lon,lat); return f; } + /** * Transforms absolute (lon,lat) to the pixel position in the current screen */ @@ -157,14 +157,15 @@ public Point2D screenPosition(Point2D p) { click.getY() - center.getY() + getHeight()/2.0 ); return f; } + /** * Transforms pixel position relative current screen to absolute (lon,lat) */ public Point2D mapPosition(Point2D p) { Point2D f = (Point2D) p.clone(); Point2D center = getMapPosPixels(); - f.setLocation(p.getX() + center.getX() - getWidth()/2.0, - p.getY() + center.getY() - getHeight()/2.0); + f.setLocation(p.getX() + center.getX() - (getWidth() / 2.0), + p.getY() + center.getY() - (getHeight() / 2.0)); return toCoordinates(f); } //End Code for CoordinateTransform interface @@ -189,12 +190,14 @@ public int getZoom() { return zoom; } - /** - * Returns true if the new zoom is valid and the view has been changed - */ public boolean setZoom(int zoom) { boolean valid = currentTileServer.isValidZoom(zoom); if(valid) this.zoom = zoom; + + //CP - DEBUG + System.err.println("New zoom level: " + this.zoom + " Valid?: " + + (valid ? "Yes" : "No")); + return valid; } diff --git a/src/map/RoverPath.java b/src/map/RoverPath.java index ccfc165..6c48c66 100644 --- a/src/map/RoverPath.java +++ b/src/map/RoverPath.java @@ -17,6 +17,7 @@ class RoverPath implements Layer { + private static final Color ACTIVE_LINE_FILL = new Color(1.f, 1.f, 0.f, 1f); private static final Color PATH_LINE_FILL = new Color(0f, 0f, 0f, 1f); private static final Color LINE_BORDER = new Color(0.5f, 0.5f, 0.5f, 0.0f); @@ -181,6 +182,13 @@ private void paintDots(Graphics g) { drawPoints(g); } + /** + * Returns the screen coordinate (x, y) pixel position of + * the Lng/Lat point found at the specified index in the + * waypoint list. + * @param index - The waypoint list index + * @return - Point2D containing the pixel coordinates on the map. + */ private Point2D drawnLocation(int index){ Dot d = null; @@ -199,6 +207,7 @@ private Point2D roverLocation(){ } private void drawLines(Graphics g) { + if(waypoints.size() < 2) { return; } @@ -261,6 +270,8 @@ private void drawImg(Graphics g, BufferedImage img, Point2D loc) { g.translate( img.getWidth() / 2, img.getHeight() / 2); } + + //Mouse Action Handler public int isOverDot(Point2D click, BufferedImage image) { for(int i = waypoints.getExtendedIndexStart(); i < waypoints.size(); i++) { Dot d = waypoints.get(i).dot(); @@ -273,6 +284,7 @@ public int isOverDot(Point2D click, BufferedImage image) { return Integer.MAX_VALUE; } + //Mouse Action Handler public int isOverLine(Point p) { if(waypoints.size() <= 0) { return Integer.MAX_VALUE; diff --git a/src/map/TileServer.java b/src/map/TileServer.java index aa4a14d..6b5cf16 100644 --- a/src/map/TileServer.java +++ b/src/map/TileServer.java @@ -21,16 +21,13 @@ class TileServer implements MapSource { //Number of pixels a tile takes up private static final int TILE_SIZE = 256; + //QUESTION (CP) - Why min 2? (Two's Compliment leading zeros, checks out) //Minimum index any tile can have on Z - //QUESTION (CP) - Why min 2? private static final int MIN_Z = 2; - //QUESTION (CP) - Why max 18? + //QUESTION (CP) - Why max 18? (Two's Compliment of max zoom here ends up being 22. This doesn't line up...) //Maximum index any tile can have on Z - private static final int MAX_Z = 18; - - //Maximum index any tile can have on X (CP - Not used anywhere?) -// private static final int MAX_X = (1 << MAX_Z) / TILE_SIZE; + private static final int MAX_Z = 18; //Maximum index any tile can have on Y (Used in TileTag Hashcode calculation only) private static final int MAX_Y = (1 << MAX_Z) / TILE_SIZE; @@ -57,25 +54,24 @@ class TileServer implements MapSource { private static final float ZOOM_CROSSOVER = 1.30f; //Dummy image to render when a tile that has not loaded is requested - private static final Image dummyTile = new BufferedImage(TILE_SIZE,TILE_SIZE, + private static final Image dummyTile = new BufferedImage(TILE_SIZE, TILE_SIZE, BufferedImage.TYPE_INT_ARGB); - //CP - List of listeners that should be triggered to repaint when a change - //to the map tiles occurs...Should be a satellite and elevation map for our - //purposes. + //List of listeners triggered to repaint when a change to the map tiles occurs private java.util.List repaintListeners = new LinkedList(); + //Key value pairings for currently cached map tile data. Contains the following: - //TileTag - XYZ and URL (Standard Web Mercator Protocol) - //Image - Corresponding Image to go along with tile tag information + // TileTag - XYZ and URL (Standard Web Mercator Protocol) + // Image - that coincides with the tile tag information private Map cache = new ConcurrentHashMap(CACHE_SIZE + 1, 1.0f); - //CP - Main URL of the tile server to be used (Root here because each TileTag has an - //additional URL piece that gets concatenated on to access it from the server. + //Main URL of the tile server to be used. specific tile addresses are + //concatenated to this private final String rootURL; - // Keep track of the map position on the last draw to trigger new tile loads + // Keeps track of the map position on the last draw to trigger new tile loads private TileTag centerTag = new TileTag(0,0,0); /** @@ -171,15 +167,27 @@ public void paint(Graphics2D gd, Point2D center, int scale, int width, int heigh TileTag newCenterTag = new TileTag(rowB + (wit / 2), colB + (hit / 2), zoom); if(!newCenterTag.equals(centerTag)) { centerTag = newCenterTag; - launchTileLoader(centerTag, (width / TILE_SIZE) + 1, (height / TILE_SIZE) + 1); + launchTileLoader((width / TILE_SIZE) + 1, (height / TILE_SIZE) + 1); } } + /** + * Determines if the given zoom level is within the acceptable range + * determined by MIN_Z/MAX_Z. + * @param zoomLevel - The new new zoom level the verify + * @return - whether or not the zoom level is within range. + */ public boolean isValidZoom(int zoomLevel) { int zoom = 31 - Integer.numberOfLeadingZeros(zoomLevel / TILE_SIZE); return (zoom >= MIN_Z && zoom < MAX_Z); } + /** + * Retrieves a tile image from the cache. If no tile is available, + * a standard dummy tile is returned. + * @param target - the Images corresponding TileTag to index in the cache + * @return - The cached tile image or a dummy tile. + */ Image pollImage(TileTag target) { Image tile = cache.get(target); if(tile != null) return tile; @@ -187,17 +195,28 @@ Image pollImage(TileTag target) { return dummyTile; } + + + /** + * Instantiates and starts a new TileLoader thread. If one + * is already running it is interrupted. + * @param width - The width in map tiles of the map + * @param height - The Height in tiles of the map. + */ private Thread tileLoader = null; - private void launchTileLoader(TileTag center, int width, int height) { - if(tileLoader != null) { + private void launchTileLoader(int width, int height) { + + if(tileLoader != null) { tileLoader.interrupt(); } + tileLoader = new TileLoader(centerTag, width, height); tileLoader.start(); } /** * Remove the CLEAN_NUM furthest tiles from the cache to make space + * for new incoming tiles. */ private void cleanTiles() { TileTag[] loaded = cache.keySet().toArray(new TileTag[] {}); @@ -206,8 +225,11 @@ private void cleanTiles() { cache.remove(loaded[i]); } } + /** - * Add given tile as image to the cache + * Adds the TileTag and corresponding image to the cache + * @param tag + * @param tile */ private synchronized void addTile(TileTag tag, Image tile) { if(cache.size() >= CACHE_SIZE) { @@ -215,18 +237,23 @@ private synchronized void addTile(TileTag tag, Image tile) { } cache.put(tag, tile); } + /** - * Add listener to be repainted if the viewed map ever changes + * Add listener to be repainted if the viewed map ever changes + * @param c - The target component to be repaint on triggering. */ public void addRepaintListener(Component c) { repaintListeners.add(c); } + /** * Remove listener to be repainted if the viewed map ever changes + * @param c - the target component to be removed from the listener list */ public void removeRepaintListener(Component c) { repaintListeners.remove(c); } + /** * Notify listeners that the current map view may have changed */ @@ -235,26 +262,38 @@ private void contentChanged() { c.repaint(); } } + /** - * Tile loader thread - * This will load all the tiles around ref given width and height, as well - * as all the tiles one level below that, in the order specified by the - * TileDistCmp comparator + * Class: TileLoader + * Tile loader thread .This will load all the tiles around ref given width + * and height, as well as all the tiles one level below that, in the order + * specified by the TileDistCmp comparator */ private class TileLoader extends Thread { + final TileTag ref; final int width; final int height; + boolean stop = false; int numToLoad; + AtomicInteger loadIndex = new AtomicInteger(0); AtomicInteger finishedHelpers = new AtomicInteger(0); java.util.List toLoad = new ArrayList(CACHE_SIZE); + + /** + * Class Constructor + * @param ref - the map center reference point. + * @param width - Width in tiles of the map + * @param height - Height in tiles of the map + */ TileLoader(TileTag ref, int width, int height) { this.ref = ref; this.width = width; this.height = height; } + public void run() { try { //enqueue all the tiles on this zoom level by view+margin From 55828400c8dee57ed68244fc90736dc74773714e Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Fri, 27 Aug 2021 13:12:07 -0700 Subject: [PATCH 104/125] Remove some debug and a little comment cleanup --- src/Context.java | 1 + src/map/MapPanel.java | 4 ---- src/table/TableFactory.java | 3 ++- src/telemetry/TelemetryManager.java | 14 ++++++++++++++ src/ui/widgets/GPSWidget.java | 8 ++++---- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/Context.java b/src/Context.java index b223b8d..01e78d1 100644 --- a/src/Context.java +++ b/src/Context.java @@ -275,6 +275,7 @@ public void setTelemetry(int id, float value) { public float getTelemetry(int id) { return (float) telemetry.getTelemetry(id); } + public String getTelemetryName(int id) { return telemetry.getTelemetryName(id); } diff --git a/src/map/MapPanel.java b/src/map/MapPanel.java index e6d1fcd..633f133 100644 --- a/src/map/MapPanel.java +++ b/src/map/MapPanel.java @@ -194,10 +194,6 @@ public boolean setZoom(int zoom) { boolean valid = currentTileServer.isValidZoom(zoom); if(valid) this.zoom = zoom; - //CP - DEBUG - System.err.println("New zoom level: " + this.zoom + " Valid?: " - + (valid ? "Yes" : "No")); - return valid; } diff --git a/src/table/TableFactory.java b/src/table/TableFactory.java index ae5ad2d..c3b326c 100644 --- a/src/table/TableFactory.java +++ b/src/table/TableFactory.java @@ -16,7 +16,8 @@ * @author Chris Park @ Infinetix Corp * Date: 3-4-21 * Description: Factory class used to encapsulate the creation of - * tables used for displaying telemetry and configurable vehicle settings. + * tables used for displaying telemetry data and configurable + * vehicle settings. */ public class TableFactory { diff --git a/src/telemetry/TelemetryManager.java b/src/telemetry/TelemetryManager.java index e243b0c..8663f31 100644 --- a/src/telemetry/TelemetryManager.java +++ b/src/telemetry/TelemetryManager.java @@ -10,13 +10,19 @@ import java.util.regex.*; public class TelemetryManager { + private final List telemetry = new ArrayList(); private final Map> listenerMap = new HashMap>(); private final List streams = new ArrayList(); + private ResourceBundle labels = null; private int telemetryIndex = 0; + /** + * Class Constructor + * @param context - the application context + */ public TelemetryManager(Context context) { String labelResPath = context.getResource("telemetryLabels"); labels = context.loadResourceBundle(labelResPath); @@ -31,8 +37,16 @@ public TelemetryManager(Context context) { } } + /** + * Default Class COnstructor + */ public TelemetryManager() {} + /** + * Retrieves the name of the telemetry resource at the given index. + * @param index - Index of label found in telemetryLabels_en_US.properties + * @return - The label String + */ public String getTelemetryName(int index) { String resourceLabel = "t"+index; diff --git a/src/ui/widgets/GPSWidget.java b/src/ui/widgets/GPSWidget.java index 8aab060..b7c0e45 100644 --- a/src/ui/widgets/GPSWidget.java +++ b/src/ui/widgets/GPSWidget.java @@ -260,9 +260,9 @@ protected ArrayList buildMeterSet() { } /** - * Periodic update responsible for updating the GPS widget visual meter. - * This is fired by a predetermined timer value. See - * UPDATE_DELAY_MS for interrupt period. + * Periodic function responsible for updating the GPS widget visual meter. + * This is fired by a predetermined timer value. See UPDATE_DELAY_MS for + * the interrupt period. */ ActionListener meterUpdateAction = new ActionListener() { public void actionPerformed(ActionEvent event) { @@ -277,7 +277,7 @@ public void actionPerformed(ActionEvent event) { * The Algorithm: At least MIN_SATS_FOR_LOCK GPS satellites are needed to get * a reliable location fix on a position. If the number of satellites does * not at least equal this, then the signal is considered poor and HDOP - * values are not considered. If the minimum satellite requirement is met + * values are not evaluated. If the minimum satellite requirement is met * then HDOP values are evaluated to determine the strength of the signal. * (See HDOP_MAX_[X] constants at the head of the class for range details) * From ca8cf0e3beba0f7df7debca8c6d0f61683bb346d Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Mon, 30 Aug 2021 14:18:52 -0700 Subject: [PATCH 105/125] Switch Bumper Enable/Disable const values to match APM state values --- src/serial/Messages/Message.java | 1 - src/serial/Serial.java | 4 ++-- src/ui/SampleSource.java | 12 ++++++------ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/serial/Messages/Message.java b/src/serial/Messages/Message.java index 1d326fa..e246b61 100644 --- a/src/serial/Messages/Message.java +++ b/src/serial/Messages/Message.java @@ -149,5 +149,4 @@ public static Message enableBumper() { public static Message disableBumper() { return new WordMessage(Serial.COMMAND_WORD, Serial.DISABLE_BUMPER_CMD, (byte)0); } - } diff --git a/src/serial/Serial.java b/src/serial/Serial.java index 5612fcb..a19cfc8 100644 --- a/src/serial/Serial.java +++ b/src/serial/Serial.java @@ -47,8 +47,8 @@ public class Serial { public static final byte DELETE_CMD = 0x4; public static final byte STOP_CMD = 0x5; public static final byte START_CMD = 0x6; - public static final byte ENABLE_BUMPER_CMD = 0x7; - public static final byte DISABLE_BUMPER_CMD = 0x8; + public static final byte DISABLE_BUMPER_CMD = 0x7; + public static final byte ENABLE_BUMPER_CMD = 0x8; //State types public static final byte APM_STATE = 0x0; diff --git a/src/ui/SampleSource.java b/src/ui/SampleSource.java index 0844201..645d604 100644 --- a/src/ui/SampleSource.java +++ b/src/ui/SampleSource.java @@ -18,22 +18,22 @@ public class SampleSource implements DataSource, TelemetryListener { public SampleSource(String name) { this.name = name; data = new ArrayList(SAMPLES); - for(int i=0; i 1.0 || x < 0.0) return 0.0; - double xPoint = (x*((double)SAMPLES-1)); - int dataPos = ((int)Math.ceil(xPoint) +oldestPosition)%SAMPLES; - int dataPrv = ((int)Math.floor(xPoint)+oldestPosition)%SAMPLES; + double xPoint = (x * ((double)SAMPLES - 1)); + int dataPos = ((int)Math.ceil(xPoint) + oldestPosition) % SAMPLES; + int dataPrv = ((int)Math.floor(xPoint) + oldestPosition) % SAMPLES; double ratio = xPoint - Math.floor(xPoint); double rtn = data.get(dataPos) * ratio - +data.get(dataPrv) * (1.0d-ratio); + + data.get(dataPrv) * (1.0d - ratio); return rtn; } From cc33ffda39adf6c6d6b9cf4b6fad951808706949 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 1 Sep 2021 10:14:03 -0700 Subject: [PATCH 106/125] Added telemetry voltage monitoring Added voltage averaging and corresponding checks for an average below a minimum acceptable voltage before issuing a stop command to prevent unpredictable ground vehicle behavior. --- src/map/TileServer.java | 2 +- src/ui/widgets/TelemetryDataWidget.java | 80 +++++++++++++++++++++++-- src/util/UtilHelper.java | 2 +- 3 files changed, 76 insertions(+), 8 deletions(-) diff --git a/src/map/TileServer.java b/src/map/TileServer.java index 6b5cf16..ec873d5 100644 --- a/src/map/TileServer.java +++ b/src/map/TileServer.java @@ -16,7 +16,7 @@ class TileServer implements MapSource { //label tiles on screen for debugging - private static final boolean TILE_LABEL = true; + private static final boolean TILE_LABEL = false; //Number of pixels a tile takes up private static final int TILE_SIZE = 256; diff --git a/src/ui/widgets/TelemetryDataWidget.java b/src/ui/widgets/TelemetryDataWidget.java index 18c742b..d73721f 100644 --- a/src/ui/widgets/TelemetryDataWidget.java +++ b/src/ui/widgets/TelemetryDataWidget.java @@ -2,6 +2,7 @@ import com.Context; import com.ui.widgets.UIWidget; +import com.util.UtilHelper; import com.telemetry.TelemetryListener; @@ -22,13 +23,19 @@ * Description: Widget child class used for displaying Telemetry data. */ public class TelemetryDataWidget extends UIWidget { - protected static final double BATTERY_LOW_THRESHOLD = 6.0; + protected static final double BATTERY_LOW_WARNING_THRESHOLD = 6.5; + protected static final double BATTERY_LOW_CUTOFF_THRESHOLD = 6.0; + protected static final int VOLT_AVERAGING_SIZE = 20; protected JPanel panel; private int lineWidth; + private double[] voltArray; + private int voltArrayCount; + private Collection lines = new ArrayList(); + /** * @author Chris Park @ Infinetix Corp. * Date: 11-25-2020 @@ -96,18 +103,26 @@ public Line(Context ctx, String format) { } /** - * Updates the text and formatting help by this line + * Updates the value and text formatting for this line + * @param data - the value to update the line text to. */ - public void update(double data) { - String format = String.format(formatStr, data); + public void update(double data) { + String format = String.format(formatStr, data); - if(format.contains("Vcc")) { - if(data <= BATTERY_LOW_THRESHOLD) { + if(format.contains("Vcc")) { + if(data <= BATTERY_LOW_WARNING_THRESHOLD) { this.setForeground(Color.red); } else { this.setForeground(Color.decode("0xEA8300")); } + + voltArray[voltArrayCount] = data; + voltArrayCount++; + + if(voltArrayCount == VOLT_AVERAGING_SIZE) { + evaluateVoltage(); + } } int finalWidth = Math.min(format.length(), lineWidth); @@ -138,6 +153,9 @@ public TelemetryDataWidget(Context ctx, int lineWidth, float fontSize, super(ctx, "Telemetry"); this.lineWidth = lineWidth; + voltArray = new double[VOLT_AVERAGING_SIZE]; + initVoltAveraging(); + panel = new JPanel(); panel.setBorder(insets); panel.setPreferredSize(new Dimension(115, (25 * items.size()))); @@ -172,6 +190,56 @@ public void reset() { } } + /** + * Initializes the voltage averaing array to a zero value + * and resets the count. + */ + protected void initVoltAveraging() { + for(int i = 0; i < VOLT_AVERAGING_SIZE; i++) { + voltArray[i] = 0.0; + } + voltArrayCount = 0; + } + + /** + * Checks the average voltage value across a range of VOLT_AVERAGE_SIZE. + * If the average is below the BATTERY_LOW_CUTOFF_THRESHOLD, and the unit + * is a ground vehicle, a warning is issued and the unit is stopped to + * prevent abnormal running behavior. + * + * NOTE FOR FUTURE IMPROVEMENT: Initialization calls to Line.update() + * with a value of 0.0 will skew the average. This should be fine for + * a sufficiently large VOLT_AVERAGE_SIZE, but needs to be accounted + * for at some point. + */ + protected void evaluateVoltage() { + double average; + + average = UtilHelper.getInstance().average( + voltArray, VOLT_AVERAGING_SIZE); + + //Reset averaging for the next pass + initVoltAveraging(); + + if(average <= BATTERY_LOW_CUTOFF_THRESHOLD) { + //If this is not a ground vehicle, return. We don't want to crash. + if(context.getCurrentLocale() != "ground") { + return; + } + + //Otherwise given that we're initialized and running, + //Stop the ground vehicle. + if((context.dash.mapPanel != null) + && (context.dash.mapPanel.waypointPanel.getIsMoving())) { + serialLog.warning("Battery low. Stopping"); + serialLog.warning("to prevent unpredictable"); + serialLog.warning("vehicle behavior."); + + context.sender.changeMovement(false); + } + } + } + /** * Generates a data widget from an xml resource file. * @param ctx - The application context diff --git a/src/util/UtilHelper.java b/src/util/UtilHelper.java index a973c69..d632ba3 100644 --- a/src/util/UtilHelper.java +++ b/src/util/UtilHelper.java @@ -41,7 +41,7 @@ public static UtilHelper getInstance() { * @return - the average of the array values. */ public double average(double[] array, int length) { - double sumOfData = 0; + double sumOfData = 0.0; for(int i = 0; i < length; i++) { sumOfData += array[i]; From e34affa8f039f236de16365f11cf3fb64fcae794 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 9 Sep 2021 12:38:48 -0700 Subject: [PATCH 107/125] Swap Active/Inactive bumper widget coloring --- src/ui/widgets/BumperWidget.java | 4 ++-- src/ui/widgets/TelemetryDataWidget.java | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/ui/widgets/BumperWidget.java b/src/ui/widgets/BumperWidget.java index 4c90900..2c65f8c 100644 --- a/src/ui/widgets/BumperWidget.java +++ b/src/ui/widgets/BumperWidget.java @@ -21,8 +21,8 @@ public class BumperWidget extends UIWidget { protected final static int RIGHT_BUMPER = 1; //Color Defaults - protected static final Color DEF_INACTIVE_COLOR = Color.decode("0x2CBC00"); - protected static final Color DEF_ACTIVE_COLOR = Color.decode("0xD70514"); + protected static final Color DEF_ACTIVE_COLOR = Color.decode("0x2CBC00"); + protected static final Color DEF_INACTIVE_COLOR = Color.decode("0xD70514"); //Visual Components protected JPanel outerPanel; diff --git a/src/ui/widgets/TelemetryDataWidget.java b/src/ui/widgets/TelemetryDataWidget.java index d73721f..a245239 100644 --- a/src/ui/widgets/TelemetryDataWidget.java +++ b/src/ui/widgets/TelemetryDataWidget.java @@ -17,7 +17,8 @@ import java.awt.*; /** - * Adapted from source originally written by Brett Menzies (See deprecated TelemetryWidget class) + * Adapted from source originally written by Brett Menzies (See deprecated + * TelemetryWidget class) * @author Chris Park @ Infinetix Corp. * Date: 11-25-2020 * Description: Widget child class used for displaying Telemetry data. @@ -194,11 +195,14 @@ public void reset() { * Initializes the voltage averaing array to a zero value * and resets the count. */ + int DEBUG_RESET_COUNT = 0; protected void initVoltAveraging() { for(int i = 0; i < VOLT_AVERAGING_SIZE; i++) { voltArray[i] = 0.0; } voltArrayCount = 0; + + DEBUG_RESET_COUNT++; } /** @@ -231,11 +235,10 @@ protected void evaluateVoltage() { //Stop the ground vehicle. if((context.dash.mapPanel != null) && (context.dash.mapPanel.waypointPanel.getIsMoving())) { - serialLog.warning("Battery low. Stopping"); - serialLog.warning("to prevent unpredictable"); - serialLog.warning("vehicle behavior."); + serialLog.warning("Battery too low. Stopping"); context.sender.changeMovement(false); + System.err.println("Reset Count: " + DEBUG_RESET_COUNT); } } } From 1cefdb684a4f12bbc22d0b1d8dadd4409a18da90 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Tue, 14 Sep 2021 17:05:11 -0700 Subject: [PATCH 108/125] Create TelemetryMonitor Abstracts monitoring code out of the data widget and encapsulates monitoring of telemetry information within a subscriber model. --- src/telemetry/IMonitorListener.java | 5 + src/telemetry/TelemetryMonitor.java | 238 ++++++++++++++++++++++++ src/ui/widgets/TelemetryDataWidget.java | 80 +------- 3 files changed, 250 insertions(+), 73 deletions(-) create mode 100644 src/telemetry/IMonitorListener.java create mode 100644 src/telemetry/TelemetryMonitor.java diff --git a/src/telemetry/IMonitorListener.java b/src/telemetry/IMonitorListener.java new file mode 100644 index 0000000..d5630e4 --- /dev/null +++ b/src/telemetry/IMonitorListener.java @@ -0,0 +1,5 @@ +package com.telemetry; + +public interface IMonitorListener { + public void updateMonitor(TelemetryMonitor monitor); +} diff --git a/src/telemetry/TelemetryMonitor.java b/src/telemetry/TelemetryMonitor.java new file mode 100644 index 0000000..6dcfc0e --- /dev/null +++ b/src/telemetry/TelemetryMonitor.java @@ -0,0 +1,238 @@ +package com.telemetry; + +import java.util.*; +import java.awt.event.ActionListener; +import java.awt.event.ActionEvent; + +import com.Context; +import com.util.UtilHelper; + +import java.util.logging.Logger; + +import com.telemetry.TelemetryListener; + +/** + * @author Chris Park @ Infinetix Corp. + * Date: 9-14-21 + * Description: Class responsible for monitoring and responding to + * changes in various telemetry data types. This can sometimes include + * issuing commands to the greater UI system in response to those changes. + * + */ +public class TelemetryMonitor { + + //Constants + protected static final int UPDATE_CYCLE_MS = 100; + protected static final int VCC_EVAL_CYCLE_MS = 1000; + protected final Logger serialLog = Logger.getLogger("d.serial"); + + //Vars, Standard Refs + private Context context; + protected VCCMonitor vccMonitor; + + //Observer Update Timer + protected javax.swing.Timer updateTimer; + protected javax.swing.Timer vccEvalTimer; + + //List of all subscribed listeners + protected List listeners; + + /** + * Pre-defined strings for identify those telemetry + * value types that the monitor can service in some way. + */ + public enum TelemetryDataType { + VOLTAGE ("Vcc"); + + private final String text; + + TelemetryDataType(String text) { + this.text = text; + } + }; + + /** + * Class Constructor + * + * @param ctx + */ + public TelemetryMonitor(Context ctx) { + context = ctx; + vccMonitor = new VCCMonitor(); + updateTimer = new javax.swing.Timer(UPDATE_CYCLE_MS, cycleUpdateAction); + listeners = new LinkedList(); + } + + /** + * Stops the timers for this monitor. + */ + public void stop() { + updateTimer.stop(); + } + + /** + * Starts the timers for this monitor. + */ + public void start() { + updateTimer.start(); + } + + /** + * Adds a listener along with its corresponding type to the + * tracking list. + * @param toRegister - the listener to track + */ + public void register(IMonitorListener toRegister) { + + listeners.add(toRegister); + } + + /** + * Removes a lisener from the tracking list. + * @param toUnregister + */ + public void unregister(IMonitorListener toUnregister) { + listeners.remove(toUnregister); + } + + /** + * Action response that triggers listener updates. + */ + protected ActionListener cycleUpdateAction = new ActionListener() { + public void actionPerformed(ActionEvent event) { + signalUpdate(); + } + }; + + /** + * Notifies all listeners that its time to run their update + * services and send in new values. + */ + public void signalUpdate() { + for(IMonitorListener l : listeners) { + l.updateMonitor(this); + } + } + + /** + * Listener driven function call that updates tracked values for + * monitoring of specific telemetry data types, if the type is not + * recognized or handled, it is ignored. + * @param data - The telemetry data to store + * @param type - the type of telemetry data being sent + */ + public void storeData(double data, TelemetryDataType type) { + switch(type) { + case VOLTAGE: + vccMonitor.add(data); + break; + default: + System.err.println( + "ERROR - TelemetryMonitor - data storage" + + " attempted for unknown type"); + } + } + + /** + * @author Chris Park + * Date: 9-14-21 + * Description: Tracks a units voltage and responds to persistent + * low voltage events. + */ + protected class VCCMonitor { + + //Constants + protected static final double BATTERY_LOW_WARNING_THRESHOLD = 6.5; + protected static final double BATTERY_LOW_CUTOFF_THRESHOLD = 6.0; + protected static final int VCC_AVERAGING_SIZE = 20; + protected static final int MAX_SEC_BELOW_THRESHOLD = 20; + protected static final int LOW_VCC_SETTLING_TIME_MS = 20000; + + //Vars + protected int sampleIndex; + protected double average; + protected double[] sampleArray; + protected boolean isBelowThreshold; + protected int elapsedMS; + + /** + * Class Constructor + */ + public VCCMonitor() { + sampleIndex = 0; + average = 0.0; + sampleArray = new double[VCC_AVERAGING_SIZE]; + isBelowThreshold = false; + elapsedMS = 0; + + } + + /** + * Adds a value to the sample array and increments the count. + * Wraps to 0 once the end of the array is reached. + * @param val + */ + public void add(double val) { + if(sampleIndex == VCC_AVERAGING_SIZE) { + sampleIndex = 0; + } + + sampleArray[sampleIndex] = val; + sampleIndex++; + + elapsedMS += UPDATE_CYCLE_MS; + + //Check for acceptable voltage every VCC_EVAL_CYCLE_MS + if(elapsedMS % VCC_EVAL_CYCLE_MS == 0) { + calculateAverage(); + evaluateVoltage(); + } + } + + /** + * Updates the stored average using the the values currently stored in + * the sample array. + */ + public void calculateAverage() { + average = UtilHelper.getInstance().average(sampleArray, + VCC_AVERAGING_SIZE); + } + + /** + * Checks the current average against BATTERY_LOW_CUTOFF_THRESHOLD. + * If the average is above the cutoff, settling time is reset. If + * the settling time is met and the voltage remains low, the units + * operation is stopped (GROUND VEHICLES ONLY) + */ + public void evaluateVoltage() { + + //Voltage monitor reset + if(average > BATTERY_LOW_CUTOFF_THRESHOLD) { + isBelowThreshold = false; + elapsedMS = 0; + return; + } + + //LOW_VCC_SETTLING_TIME_MS time starts from here. + if(average <= BATTERY_LOW_CUTOFF_THRESHOLD) { + isBelowThreshold = true; + } + + //if voltage has remained low over settling time. stop the unit. + if(elapsedMS == LOW_VCC_SETTLING_TIME_MS) { + if(isBelowThreshold + && (context.getCurrentLocale() == "ground")) { + + if((context.dash.mapPanel != null) + && context.dash.mapPanel.waypointPanel.getIsMoving()) { + serialLog.warning("Battery voltage low. Stopping unit."); + context.sender.changeMovement(false); + } + + elapsedMS = 0; + } + } + } + + } +} diff --git a/src/ui/widgets/TelemetryDataWidget.java b/src/ui/widgets/TelemetryDataWidget.java index a245239..22afe5f 100644 --- a/src/ui/widgets/TelemetryDataWidget.java +++ b/src/ui/widgets/TelemetryDataWidget.java @@ -15,6 +15,8 @@ import javax.swing.*; import java.awt.*; +import java.awt.event.ActionListener; +import java.awt.event.ActionEvent; /** * Adapted from source originally written by Brett Menzies (See deprecated @@ -25,18 +27,12 @@ */ public class TelemetryDataWidget extends UIWidget { protected static final double BATTERY_LOW_WARNING_THRESHOLD = 6.5; - protected static final double BATTERY_LOW_CUTOFF_THRESHOLD = 6.0; - protected static final int VOLT_AVERAGING_SIZE = 20; protected JPanel panel; - +// protected TelemetryMonitor telemetryMonitor; private int lineWidth; - private double[] voltArray; - private int voltArrayCount; - private Collection lines = new ArrayList(); - /** * @author Chris Park @ Infinetix Corp. * Date: 11-25-2020 @@ -116,14 +112,7 @@ public void update(double data) { } else { this.setForeground(Color.decode("0xEA8300")); - } - - voltArray[voltArrayCount] = data; - voltArrayCount++; - - if(voltArrayCount == VOLT_AVERAGING_SIZE) { - evaluateVoltage(); - } + } } int finalWidth = Math.min(format.length(), lineWidth); @@ -132,7 +121,7 @@ public void update(double data) { /** * Repaints the parent widget to avoid a change in order layering - */ + */ @Override public void repaint() { super.repaint(); @@ -145,7 +134,7 @@ public void repaint() { * Class Constructor * @param ctx - The application context * @param lineWidth - Maximum Line width for a line of text - * @param fontSize - Font size of a line of + * @param fontSize - Font size of a line of text * @param textColor - Color of the displayed text * @param items - Collection of LineItems to be displayed */ @@ -153,10 +142,7 @@ public TelemetryDataWidget(Context ctx, int lineWidth, float fontSize, Color textColor, Collection items) { super(ctx, "Telemetry"); this.lineWidth = lineWidth; - - voltArray = new double[VOLT_AVERAGING_SIZE]; - initVoltAveraging(); - + panel = new JPanel(); panel.setBorder(insets); panel.setPreferredSize(new Dimension(115, (25 * items.size()))); @@ -190,58 +176,6 @@ public void reset() { l.update(0.0); } } - - /** - * Initializes the voltage averaing array to a zero value - * and resets the count. - */ - int DEBUG_RESET_COUNT = 0; - protected void initVoltAveraging() { - for(int i = 0; i < VOLT_AVERAGING_SIZE; i++) { - voltArray[i] = 0.0; - } - voltArrayCount = 0; - - DEBUG_RESET_COUNT++; - } - - /** - * Checks the average voltage value across a range of VOLT_AVERAGE_SIZE. - * If the average is below the BATTERY_LOW_CUTOFF_THRESHOLD, and the unit - * is a ground vehicle, a warning is issued and the unit is stopped to - * prevent abnormal running behavior. - * - * NOTE FOR FUTURE IMPROVEMENT: Initialization calls to Line.update() - * with a value of 0.0 will skew the average. This should be fine for - * a sufficiently large VOLT_AVERAGE_SIZE, but needs to be accounted - * for at some point. - */ - protected void evaluateVoltage() { - double average; - - average = UtilHelper.getInstance().average( - voltArray, VOLT_AVERAGING_SIZE); - - //Reset averaging for the next pass - initVoltAveraging(); - - if(average <= BATTERY_LOW_CUTOFF_THRESHOLD) { - //If this is not a ground vehicle, return. We don't want to crash. - if(context.getCurrentLocale() != "ground") { - return; - } - - //Otherwise given that we're initialized and running, - //Stop the ground vehicle. - if((context.dash.mapPanel != null) - && (context.dash.mapPanel.waypointPanel.getIsMoving())) { - serialLog.warning("Battery too low. Stopping"); - - context.sender.changeMovement(false); - System.err.println("Reset Count: " + DEBUG_RESET_COUNT); - } - } - } /** * Generates a data widget from an xml resource file. From 1f2c5edbe2ba22d8912ff60486454f7ca36230aa Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 15 Sep 2021 16:15:16 -0700 Subject: [PATCH 109/125] Monitoring work cont. Finish up basic implementation for Voltage monitoring. Set bumper to disabled by default --- src/telemetry/TelemetryMonitor.java | 36 ++++++++++++++++++++++--- src/ui/UIConfigPanel.java | 18 +++++++------ src/ui/widgets/BumperWidget.java | 3 +++ src/ui/widgets/TelemetryDataWidget.java | 32 +++++++++++++++++++--- 4 files changed, 75 insertions(+), 14 deletions(-) diff --git a/src/telemetry/TelemetryMonitor.java b/src/telemetry/TelemetryMonitor.java index 6dcfc0e..01b809d 100644 --- a/src/telemetry/TelemetryMonitor.java +++ b/src/telemetry/TelemetryMonitor.java @@ -67,14 +67,27 @@ public TelemetryMonitor(Context ctx) { * Stops the timers for this monitor. */ public void stop() { - updateTimer.stop(); + if(updateTimer.isRunning()) { + updateTimer.stop(); + } } /** * Starts the timers for this monitor. */ public void start() { - updateTimer.start(); + if(!updateTimer.isRunning()) { + + System.err.println("DEBUG - TelMon - Starting Timer"); + updateTimer.start(); + } + } + + /* + * Returns whether the update timer is currently running. + */ + public boolean isRunning() { + return updateTimer.isRunning(); } /** @@ -109,6 +122,12 @@ public void actionPerformed(ActionEvent event) { * services and send in new values. */ public void signalUpdate() { + + //If the rover is stopped, do not update. + if(!context.dash.mapPanel.waypointPanel.getIsMoving()) { + return; + } + for(IMonitorListener l : listeners) { l.updateMonitor(this); } @@ -124,6 +143,9 @@ public void signalUpdate() { public void storeData(double data, TelemetryDataType type) { switch(type) { case VOLTAGE: + +// System.err.println("DEBUG - TelMon - Receiving voltage Data: " + data); + vccMonitor.add(data); break; default: @@ -164,7 +186,6 @@ public VCCMonitor() { sampleArray = new double[VCC_AVERAGING_SIZE]; isBelowThreshold = false; elapsedMS = 0; - } /** @@ -208,6 +229,9 @@ public void evaluateVoltage() { //Voltage monitor reset if(average > BATTERY_LOW_CUTOFF_THRESHOLD) { + + System.err.println("DEBUG - VCCMon - Evaluation State: Reset"); + isBelowThreshold = false; elapsedMS = 0; return; @@ -215,11 +239,17 @@ public void evaluateVoltage() { //LOW_VCC_SETTLING_TIME_MS time starts from here. if(average <= BATTERY_LOW_CUTOFF_THRESHOLD) { + + System.err.println("DEBUG - VCCMon - Evaluation State: Cutoff Tick"); + isBelowThreshold = true; } //if voltage has remained low over settling time. stop the unit. if(elapsedMS == LOW_VCC_SETTLING_TIME_MS) { + + System.err.println("DEBUG - VCCMon - Evaluation State: Shutoff"); + if(isBelowThreshold && (context.getCurrentLocale() == "ground")) { diff --git a/src/ui/UIConfigPanel.java b/src/ui/UIConfigPanel.java index 018ea2e..fcd6460 100644 --- a/src/ui/UIConfigPanel.java +++ b/src/ui/UIConfigPanel.java @@ -135,11 +135,11 @@ public UIConfigPanel(Context cxt, boolean isWindows) { //Bumper toggle checkbox bumperCheckBox = new JCheckBox("Enable Bumper"); - bumperCheckBox.setSelected(true); + bumperCheckBox.setSelected(false); constraints.gridx = 3; constraints.gridy = 1; this.add(bumperCheckBox, constraints); - this.bumperIsEnabled = true; + toggleBumper(false); //Settings apply button (for Checkboxes/Radio Buttons) applySettingsButton = new JButton(setSettingsAction); @@ -162,18 +162,20 @@ public void actionPerformed(ActionEvent e) { //Bumper enable/disable toggle if(bumperIsEnabled && !bumperCheckBox.isSelected()) { - bumperIsEnabled = false; - context.sender.toggleBumper(bumperIsEnabled); - context.dash.bumperWidget.setEnabled(bumperIsEnabled); + toggleBumper(false); } else if(!bumperIsEnabled && bumperCheckBox.isSelected()) { - bumperIsEnabled = true; - context.sender.toggleBumper(bumperIsEnabled); - context.dash.bumperWidget.setEnabled(bumperIsEnabled); + toggleBumper(true); } } }; + private void toggleBumper(boolean isEnabled) { + bumperIsEnabled = isEnabled; + context.sender.toggleBumper(bumperIsEnabled); + context.dash.bumperWidget.setEnabled(bumperIsEnabled); + } + /** * Action used to toggle the user interface between Air and Ground mode. * Requires a program restart for the setting to take effect. diff --git a/src/ui/widgets/BumperWidget.java b/src/ui/widgets/BumperWidget.java index 2c65f8c..9c59fd9 100644 --- a/src/ui/widgets/BumperWidget.java +++ b/src/ui/widgets/BumperWidget.java @@ -111,6 +111,9 @@ public BumperWidget(Context ctx) { outerPanel.add(lowerPanel); this.add(outerPanel); + + //Default to off state + setEnabled(false); } /** diff --git a/src/ui/widgets/TelemetryDataWidget.java b/src/ui/widgets/TelemetryDataWidget.java index 22afe5f..06737df 100644 --- a/src/ui/widgets/TelemetryDataWidget.java +++ b/src/ui/widgets/TelemetryDataWidget.java @@ -5,6 +5,9 @@ import com.util.UtilHelper; import com.telemetry.TelemetryListener; +import com.telemetry.TelemetryMonitor; +import com.telemetry.TelemetryMonitor.TelemetryDataType; +import com.telemetry.IMonitorListener; import java.io.Reader; import java.io.FileReader; @@ -26,10 +29,12 @@ * Description: Widget child class used for displaying Telemetry data. */ public class TelemetryDataWidget extends UIWidget { + //Constants protected static final double BATTERY_LOW_WARNING_THRESHOLD = 6.5; + //Vars protected JPanel panel; -// protected TelemetryMonitor telemetryMonitor; + protected TelemetryMonitor telemetryMonitor; private int lineWidth; private Collection lines = new ArrayList(); @@ -85,8 +90,9 @@ public Color getBackgroundColor() { * Date: 11-25-2020 * Description: Nested internal class used to display telemetry data. */ - private class Line extends JLabel implements TelemetryListener { + private class Line extends JLabel implements TelemetryListener, IMonitorListener { private String formatStr; + private double currData; /** * Class Constructor @@ -97,13 +103,17 @@ public Line(Context ctx, String format) { formatStr = format; setPreferredSize(new Dimension(100, 20)); update(0.0); + + telemetryMonitor.register(this); + telemetryMonitor.start(); } /** * Updates the value and text formatting for this line * @param data - the value to update the line text to. */ - public void update(double data) { + public void update(double data) { + currData = data; String format = String.format(formatStr, data); if(format.contains("Vcc")) { @@ -119,6 +129,20 @@ public void update(double data) { setText(format.substring(0, finalWidth)); } + /** + * Sends the last received data value to the telemetry manager along + * with an identifier so that it can process the data appropriately. + * @param monitor - The reference to the telemetry monitor this object + * is registered/subscribed to. + */ + @Override + public void updateMonitor(TelemetryMonitor monitor) { + if(formatStr.contains("Vcc")) { + monitor.storeData(currData, TelemetryDataType.VOLTAGE); + } + + } + /** * Repaints the parent widget to avoid a change in order layering */ @@ -141,6 +165,8 @@ public void repaint() { public TelemetryDataWidget(Context ctx, int lineWidth, float fontSize, Color textColor, Collection items) { super(ctx, "Telemetry"); + + telemetryMonitor = new TelemetryMonitor(ctx); this.lineWidth = lineWidth; panel = new JPanel(); From 871afeb73b2e1ff6859e882e505e921c15bfb1d0 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 16 Sep 2021 14:33:57 -0700 Subject: [PATCH 110/125] Post Voltage Monitor Test cleanup --- src/telemetry/TelemetryMonitor.java | 15 ++------------- src/ui/UIConfigPanel.java | 3 ++- src/ui/widgets/BumperWidget.java | 11 +++++++++++ 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/telemetry/TelemetryMonitor.java b/src/telemetry/TelemetryMonitor.java index 01b809d..848c176 100644 --- a/src/telemetry/TelemetryMonitor.java +++ b/src/telemetry/TelemetryMonitor.java @@ -143,9 +143,6 @@ public void signalUpdate() { public void storeData(double data, TelemetryDataType type) { switch(type) { case VOLTAGE: - -// System.err.println("DEBUG - TelMon - Receiving voltage Data: " + data); - vccMonitor.add(data); break; default: @@ -229,9 +226,6 @@ public void evaluateVoltage() { //Voltage monitor reset if(average > BATTERY_LOW_CUTOFF_THRESHOLD) { - - System.err.println("DEBUG - VCCMon - Evaluation State: Reset"); - isBelowThreshold = false; elapsedMS = 0; return; @@ -239,24 +233,19 @@ public void evaluateVoltage() { //LOW_VCC_SETTLING_TIME_MS time starts from here. if(average <= BATTERY_LOW_CUTOFF_THRESHOLD) { - - System.err.println("DEBUG - VCCMon - Evaluation State: Cutoff Tick"); - isBelowThreshold = true; } //if voltage has remained low over settling time. stop the unit. if(elapsedMS == LOW_VCC_SETTLING_TIME_MS) { - System.err.println("DEBUG - VCCMon - Evaluation State: Shutoff"); - if(isBelowThreshold && (context.getCurrentLocale() == "ground")) { if((context.dash.mapPanel != null) && context.dash.mapPanel.waypointPanel.getIsMoving()) { - serialLog.warning("Battery voltage low. Stopping unit."); - context.sender.changeMovement(false); + serialLog.warning("VCC: Battery voltage low. Stopping unit."); + context.dash.mapPanel.waypointPanel.missionButton.doClick(); } elapsedMS = 0; diff --git a/src/ui/UIConfigPanel.java b/src/ui/UIConfigPanel.java index fcd6460..49699c9 100644 --- a/src/ui/UIConfigPanel.java +++ b/src/ui/UIConfigPanel.java @@ -139,7 +139,7 @@ public UIConfigPanel(Context cxt, boolean isWindows) { constraints.gridx = 3; constraints.gridy = 1; this.add(bumperCheckBox, constraints); - toggleBumper(false); + toggleBumper(context.dash.bumperWidget.isEnabled()); //Settings apply button (for Checkboxes/Radio Buttons) applySettingsButton = new JButton(setSettingsAction); @@ -172,6 +172,7 @@ else if(!bumperIsEnabled && bumperCheckBox.isSelected()) { private void toggleBumper(boolean isEnabled) { bumperIsEnabled = isEnabled; + bumperCheckBox.setSelected(bumperIsEnabled); context.sender.toggleBumper(bumperIsEnabled); context.dash.bumperWidget.setEnabled(bumperIsEnabled); } diff --git a/src/ui/widgets/BumperWidget.java b/src/ui/widgets/BumperWidget.java index 9c59fd9..0a44b81 100644 --- a/src/ui/widgets/BumperWidget.java +++ b/src/ui/widgets/BumperWidget.java @@ -36,6 +36,8 @@ public class BumperWidget extends UIWidget { protected BumperStatus bumperStateLeft; protected BumperStatus bumperStateRight; + protected boolean isCurrEnabled; + /** * Pre-defined state enums used to keep track of and * update the currently displayed status label based @@ -209,5 +211,14 @@ public void setEnabled(boolean shouldEnable) { outerPanel.setEnabled(false); outerPanel.setVisible(false); } + + isCurrEnabled = shouldEnable; + } + + /** + * Returns whether or not the bumpers are currently enabled. + */ + public boolean isEnabled() { + return isCurrEnabled; } } From 885f16914d72dc8d7425f5f07b09de104da290fa Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 29 Sep 2021 10:01:18 -0700 Subject: [PATCH 111/125] Update groundSettings file --- resources/groundSettings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/groundSettings.xml b/resources/groundSettings.xml index 418169f..11217fd 100644 --- a/resources/groundSettings.xml +++ b/resources/groundSettings.xml @@ -1,14 +1,14 @@ Defines how strongly the rover should attempt to return to the original course between waypoints, verses the direct path from its current location to the target<br> The number of degrees that rover will turn its wheels when it needs to to turn its most extreme amount Switches between arctangent of error steering (0) <br> square of error steering (1) <br> and proportional to error steering (2) - Multiplier that determines how aggressively to steer + Multiplier that determines how aggressively to steer Minimum forward driving speed in MPH Maximum forward driving speed in MPH How far to turn the wheels when backing away from an obstacle - Speed in MPH to drive in reverse + Speed in MPH to drive in reverse Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles - Time in milliseconds to coast before reversing when an obstacle is encountered - Minimum time in milliseconds to reverse away from an obstacle + Time in milliseconds to coast before reversing when an obstacle is encountered + Minimum time in seconds to reverse away from an obstacle P term in cruise control PID loop I term in cruise control PID loop D term in cruise control PID loop From f43b8975c26511af2c6cf72d82eb1faa151c8266 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Fri, 1 Oct 2021 14:35:54 -0700 Subject: [PATCH 112/125] Clean up error reporting and comments. --- src/serial/Serial.java | 1 - src/serial/SerialParser.java | 8 ++++++-- src/serial/SerialSender.java | 4 ---- src/telemetry/TelemetryMonitor.java | 4 ++-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/serial/Serial.java b/src/serial/Serial.java index a19cfc8..556c100 100644 --- a/src/serial/Serial.java +++ b/src/serial/Serial.java @@ -107,7 +107,6 @@ public class Serial { public static final int MAX_WAYPOINTS = 64; public static final int MAX_SETTINGS = 64; public static final int MAX_TELEMETRY = 256; -// public static final int BAUD = SerialPort.BAUDRATE_9600; public static final int BAUD = SerialPort.BAUDRATE_57600; public static final int U16_FIXED_POINT = 256; diff --git a/src/serial/SerialParser.java b/src/serial/SerialParser.java index 9e2a189..9a97cd7 100644 --- a/src/serial/SerialParser.java +++ b/src/serial/SerialParser.java @@ -150,7 +150,9 @@ public void handle(byte[] msg) { break; default: - System.err.println("Unrecognized Sensor Subtype"); + seriallog.severe( + "SerialParser - Sensor Data -" + + "Unrecognized Sensor Sbbtype"); break; } break; @@ -168,7 +170,9 @@ public void handle(byte[] msg) { break; default: - System.err.println("Unrecognized Info Subtype"); + seriallog.severe( + "SerialParser - Info Data - " + + "Unrecognized Info Subtype"); break; } diff --git a/src/serial/SerialSender.java b/src/serial/SerialSender.java index a67a07f..f7f60ba 100644 --- a/src/serial/SerialSender.java +++ b/src/serial/SerialSender.java @@ -164,11 +164,9 @@ public void changeMovement(boolean shouldMove) { if(context.connected) { if(shouldMove) { msg = Message.startDriving(); - System.err.println("Sending start driving message."); } else { msg = Message.stopDriving(); - System.err.println("Sending stop driving message."); } sendMessage(msg); } @@ -185,11 +183,9 @@ public void toggleBumper(boolean shouldEnable) { if(context.connected) { if(shouldEnable) { msg = Message.enableBumper(); - System.err.println("Sending bumper enable message."); } else { msg = Message.disableBumper(); - System.err.println("Sending bumper disable message."); } sendMessage(msg); } diff --git a/src/telemetry/TelemetryMonitor.java b/src/telemetry/TelemetryMonitor.java index 848c176..67fac72 100644 --- a/src/telemetry/TelemetryMonitor.java +++ b/src/telemetry/TelemetryMonitor.java @@ -23,7 +23,6 @@ public class TelemetryMonitor { //Constants protected static final int UPDATE_CYCLE_MS = 100; - protected static final int VCC_EVAL_CYCLE_MS = 1000; protected final Logger serialLog = Logger.getLogger("d.serial"); //Vars, Standard Refs @@ -102,7 +101,7 @@ public void register(IMonitorListener toRegister) { /** * Removes a lisener from the tracking list. - * @param toUnregister + * @param toUnregister - the listener to stop tracking */ public void unregister(IMonitorListener toUnregister) { listeners.remove(toUnregister); @@ -166,6 +165,7 @@ protected class VCCMonitor { protected static final int VCC_AVERAGING_SIZE = 20; protected static final int MAX_SEC_BELOW_THRESHOLD = 20; protected static final int LOW_VCC_SETTLING_TIME_MS = 20000; + protected static final int VCC_EVAL_CYCLE_MS = 1000; //Vars protected int sampleIndex; From 7e01a429df6310b9115017ca55dc4a1d3f5caed3 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Fri, 1 Oct 2021 14:59:15 -0700 Subject: [PATCH 113/125] Cruise P range adjustment --- resources/groundSettings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/groundSettings.xml b/resources/groundSettings.xml index 11217fd..2d2b017 100644 --- a/resources/groundSettings.xml +++ b/resources/groundSettings.xml @@ -9,7 +9,7 @@ Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles Time in milliseconds to coast before reversing when an obstacle is encountered Minimum time in seconds to reverse away from an obstacle - P term in cruise control PID loop + P term in cruise control PID loop I term in cruise control PID loop D term in cruise control PID loop Tire Diameter in inches, used to calculate MPH From 75fdeaee72dc5bb1120b6bb231e96dae812b74e0 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Tue, 5 Oct 2021 13:30:00 -0700 Subject: [PATCH 114/125] Readability Cleanup --- src/telemetry/TelemetryLogger.java | 4 ++-- src/telemetry/TelemetryManager.java | 2 +- src/telemetry/TelemetryMonitor.java | 13 ++++--------- src/ui/widgets/TelemetryDataWidget.java | 2 +- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/telemetry/TelemetryLogger.java b/src/telemetry/TelemetryLogger.java index c99a894..b77c2a1 100644 --- a/src/telemetry/TelemetryLogger.java +++ b/src/telemetry/TelemetryLogger.java @@ -9,7 +9,7 @@ import java.text.DecimalFormat; public class TelemetryLogger{ - private final static int DEFAULT_LOG_PERIOD = 250; + private final static int DEFAULT_LOG_PERIOD_MS = 250; static final DecimalFormat numberFormat = new DecimalFormat("#.########"); private BufferedWriter logFile; private java.util.Timer logTimer; @@ -27,7 +27,7 @@ public TelemetryLogger(Context ctx, TelemetryManager tm){ } catch (IOException ex) { System.err.println(ex); } - setPeriod(DEFAULT_LOG_PERIOD); + setPeriod(DEFAULT_LOG_PERIOD_MS); startTime = System.currentTimeMillis(); } diff --git a/src/telemetry/TelemetryManager.java b/src/telemetry/TelemetryManager.java index 8663f31..ec32f87 100644 --- a/src/telemetry/TelemetryManager.java +++ b/src/telemetry/TelemetryManager.java @@ -81,7 +81,7 @@ public void update(int id, double value) { } /** - * An index that can be used to see if a update has been made to any + * An index that can be used to see if an update has been made to any * telemetry values since the last time it was observed */ public int changeIndex() { diff --git a/src/telemetry/TelemetryMonitor.java b/src/telemetry/TelemetryMonitor.java index 67fac72..64db9d6 100644 --- a/src/telemetry/TelemetryMonitor.java +++ b/src/telemetry/TelemetryMonitor.java @@ -14,10 +14,9 @@ /** * @author Chris Park @ Infinetix Corp. * Date: 9-14-21 - * Description: Class responsible for monitoring and responding to + * Description: Class responsible for monitoring and handling * changes in various telemetry data types. This can sometimes include - * issuing commands to the greater UI system in response to those changes. - * + * issuing commands to in response to those changes. */ public class TelemetryMonitor { @@ -29,7 +28,7 @@ public class TelemetryMonitor { private Context context; protected VCCMonitor vccMonitor; - //Observer Update Timer + //Timers protected javax.swing.Timer updateTimer; protected javax.swing.Timer vccEvalTimer; @@ -37,7 +36,7 @@ public class TelemetryMonitor { protected List listeners; /** - * Pre-defined strings for identify those telemetry + * Pre-defined strings to identify those telemetry * value types that the monitor can service in some way. */ public enum TelemetryDataType { @@ -52,7 +51,6 @@ public enum TelemetryDataType { /** * Class Constructor - * * @param ctx */ public TelemetryMonitor(Context ctx) { @@ -76,8 +74,6 @@ public void stop() { */ public void start() { if(!updateTimer.isRunning()) { - - System.err.println("DEBUG - TelMon - Starting Timer"); updateTimer.start(); } } @@ -95,7 +91,6 @@ public boolean isRunning() { * @param toRegister - the listener to track */ public void register(IMonitorListener toRegister) { - listeners.add(toRegister); } diff --git a/src/ui/widgets/TelemetryDataWidget.java b/src/ui/widgets/TelemetryDataWidget.java index 06737df..380131b 100644 --- a/src/ui/widgets/TelemetryDataWidget.java +++ b/src/ui/widgets/TelemetryDataWidget.java @@ -41,7 +41,7 @@ public class TelemetryDataWidget extends UIWidget { /** * @author Chris Park @ Infinetix Corp. * Date: 11-25-2020 - * Description: Nested static internal class used to display telemetry data. + * Description: Nested static class used to display telemetry data. */ public static class LineItem { private String formatStr; From 9c8a80643205fe7c66d24b8125972d582107cd2e Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 7 Oct 2021 10:43:22 -0700 Subject: [PATCH 115/125] Minor syntax cleanup --- src/map/RoverPath.java | 4 ++-- src/map/Waypoint.java | 27 ++++++++++++++++++++++----- src/map/WaypointPanel.java | 11 ++++++++--- src/serial/SerialParser.java | 2 +- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/map/RoverPath.java b/src/map/RoverPath.java index 6c48c66..56a199b 100644 --- a/src/map/RoverPath.java +++ b/src/map/RoverPath.java @@ -178,7 +178,7 @@ public void paintLine(Graphics g, Point2D pointA, Point2D pointB, Color fill) { private void paintDots(Graphics g) { drawLines(g); - drowRoverLine(g); + drawRoverLine(g); drawPoints(g); } @@ -254,7 +254,7 @@ private void drawPoints(Graphics g) { } } - private void drowRoverLine(Graphics g) { + private void drawRoverLine(Graphics g) { if(waypoints.getTarget() >= waypoints.size()) { return; } diff --git a/src/map/Waypoint.java b/src/map/Waypoint.java index 3719d21..f82380c 100644 --- a/src/map/Waypoint.java +++ b/src/map/Waypoint.java @@ -1,25 +1,37 @@ +/** + * NOTE: - CP - 10-2021: I don't see any references to this class in the + * rest of the project...Waypoint list tracking uses the Dot.java class instead + * of this. This should be removed if no ties to the system can be found. + */ + package com.map; import static java.lang.Math.*; public class Waypoint{ + /** Earth's radius in miles */ private static final double EARTH_RAD = 3958.761; + /** Earth's circumference in miles */ - private static final double EARTH_CIRC = EARTH_RAD*Math.PI*2.0; + private static final double EARTH_CIRC = EARTH_RAD * Math.PI * 2.0; + /** Convert degrees to radians */ - private static double toRad(double degrees){ - return degrees*Math.PI/180.0; + private static double toRad(double degrees) { + return degrees * (Math.PI / 180.0); } + /** Convert radians to degrees */ - private static double toDeg(double radians){ - return radians*180.0/Math.PI; + private static double toDeg(double radians) { + return radians * (180.0 / Math.PI); } + /** * The waypoint's latitude and longitude in decimal degrees * northern and eastern hemisphere are positive lat, long respectively */ private final double latitude, longitude; + /** * Construct a new waypoint; arguments are in decimal degrees */ @@ -27,14 +39,17 @@ public Waypoint(double latitude, double longitude){ this.latitude = latitude; this.longitude = longitude; } + /** Return this Waypoint's latitude in decimal degrees */ public double getLatitude(){ return latitude; } + /** Return this Waypoint's longitude in decimal degrees */ public double getLongitude(){ return longitude; } + /** * Return the heading in degrees, north = 0, CW positive around UP from * this waypoint to target @@ -49,6 +64,7 @@ public double headingTo(Waypoint target){ - sin(aRlat)*cos(bRlat)*cos(bRlng - aRlng); return toDeg( atan2(y,x) ); } + /** * Return the distance in miles between this waypoint and the target */ @@ -62,6 +78,7 @@ public double distanceTo(Waypoint target){ double chord = sinlat*sinlat + sinlng*sinlng*cos(aRlat)*cos(bRlat); return 2.0 * EARTH_RAD * atan2( sqrt(chord), sqrt(1.-chord) ); } + /** * Return a waypoint that is `distance` miles away in the `heading` * direction from this waypoint's location. diff --git a/src/map/WaypointPanel.java b/src/map/WaypointPanel.java index 61146cf..731cab2 100644 --- a/src/map/WaypointPanel.java +++ b/src/map/WaypointPanel.java @@ -216,7 +216,7 @@ private void buildPanel() { //Waypoint Options clearWaypoints = theme.makeButton(clearWaypointsAction); - newButton = theme.makeButton(newWaypoint); + newButton = theme.makeButton(newWaypointAction); enterButton = theme.makeButton(interpretLocationAction); undoButton = theme.makeButton(undoCommandAction); redoButton = theme.makeButton(redoCommandAction); @@ -700,7 +700,7 @@ public void actionPerformed(ActionEvent e) { } }; - private Action newWaypoint = new AbstractAction() { + private Action newWaypointAction = new AbstractAction() { { String text = "New"; putValue(Action.NAME, text); @@ -708,6 +708,11 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { int selectedWaypoint = waypoints.getSelected(); + if(selectedWaypoint < 0) { + //TODO - CP - No waypoints found, so place at a default + //location (perhaps current map view center position?) + } + WaypointCommand command = new WaypointCommandAdd( waypoints, new Dot(waypoints.get(selectedWaypoint).dot()), @@ -725,7 +730,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { WaypointCommand command = new WaypointCommandClear(waypoints, context); CommandManager.getInstance().process(command); - } + } }; private Action openDataPanel = new AbstractAction() { diff --git a/src/serial/SerialParser.java b/src/serial/SerialParser.java index 9a97cd7..94e2ceb 100644 --- a/src/serial/SerialParser.java +++ b/src/serial/SerialParser.java @@ -152,7 +152,7 @@ public void handle(byte[] msg) { default: seriallog.severe( "SerialParser - Sensor Data -" - + "Unrecognized Sensor Sbbtype"); + + "Unrecognized Sensor Subtype"); break; } break; From 228d78a0d91d9ebb8c335e73117da10aac110234 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 7 Oct 2021 10:48:47 -0700 Subject: [PATCH 116/125] Turn Confirmation for Word messages (commands) on --- src/serial/Messages/WordMessage.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/serial/Messages/WordMessage.java b/src/serial/Messages/WordMessage.java index 3d75b6f..920f6dc 100644 --- a/src/serial/Messages/WordMessage.java +++ b/src/serial/Messages/WordMessage.java @@ -24,7 +24,7 @@ public WordMessage(int subtype, int ab) { @Override public boolean needsConfirm() { - return false; + return true; } @Override @@ -56,6 +56,10 @@ private String nameCommand(int command) { return "Stop Movement Command"; case Serial.START_CMD: return "Start Movement Command"; + case Serial.ENABLE_BUMPER_CMD: + return "Enable Bumper Command"; + case Serial.DISABLE_BUMPER_CMD: + return "Disable Bumper Command"; } return "Command Message"; } From f09a10290a6001e8ae0c948222eb41585a481fd5 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 7 Oct 2021 11:26:04 -0700 Subject: [PATCH 117/125] exclude comfirmation and sync type word messages from general packet confirmation --- src/serial/Messages/Message.java | 13 +++++++++++++ src/serial/Messages/WordMessage.java | 6 +++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/serial/Messages/Message.java b/src/serial/Messages/Message.java index e246b61..9e2bd0a 100644 --- a/src/serial/Messages/Message.java +++ b/src/serial/Messages/Message.java @@ -14,32 +14,41 @@ public class Message { protected int confirmSum; protected int failCount; protected Date sent; + protected Message() { //subclasses must call buildChecksums after making content failCount = 0; } + public Message(byte[] data) { failCount = 0; content = data.clone(); buildChecksum(); } + public void sendTime(Date date) { sent = (Date)date.clone(); } + public boolean isConfirmedBy(int confirmation) { return ((confirmation&0xFFFF) == (confirmSum&0xFFFF)); } + public boolean isPastExpiration(Date now) { return (now.getTime()-sent.getTime()) > Serial.MAX_CONFIRM_WAIT; } + public void addFailure() { failCount++; } + public int numberOfFailures() { return failCount; } + public int getConfirmSum() { return confirmSum; } + public void send(SerialPort port) throws SerialPortException { port.writeBytes(Serial.HEADER); port.writeBytes(content); @@ -47,6 +56,7 @@ public void send(SerialPort port) throws SerialPortException { port.writeBytes(Serial.FOOTER); sent = new Date(); } + private byte[] concat(byte[] a, byte[] b) { byte[] c = new byte[a.length+b.length]; for(int i=0; i< a.length; i++) { @@ -57,14 +67,17 @@ private byte[] concat(byte[] a, byte[] b) { } return c; } + protected void buildChecksum() { checkPair = Serial.fletcher16bytes(content); confirmSum = Serial.fletcher16( concat(content,checkPair) )&0xffff; } + //these should be overridden public boolean needsConfirm() { return false; } + @Override public String toString() { return "A message"; diff --git a/src/serial/Messages/WordMessage.java b/src/serial/Messages/WordMessage.java index 920f6dc..90867b4 100644 --- a/src/serial/Messages/WordMessage.java +++ b/src/serial/Messages/WordMessage.java @@ -24,7 +24,11 @@ public WordMessage(int subtype, int ab) { @Override public boolean needsConfirm() { - return true; + if(msgType == Serial.COMMAND_WORD) { + return true; + } + + return false; } @Override From 0553e7a411b0b08e9bccd141c77d39df2acf421c Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 7 Oct 2021 11:28:38 -0700 Subject: [PATCH 118/125] Readability cleanup --- src/serial/Messages/Message.java | 2 +- src/serial/Serial.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/serial/Messages/Message.java b/src/serial/Messages/Message.java index 9e2bd0a..000f51a 100644 --- a/src/serial/Messages/Message.java +++ b/src/serial/Messages/Message.java @@ -34,7 +34,7 @@ public boolean isConfirmedBy(int confirmation) { } public boolean isPastExpiration(Date now) { - return (now.getTime()-sent.getTime()) > Serial.MAX_CONFIRM_WAIT; + return (now.getTime()-sent.getTime()) > Serial.MAX_CONFIRM_WAIT_MS; } public void addFailure() { diff --git a/src/serial/Serial.java b/src/serial/Serial.java index 556c100..80e952a 100644 --- a/src/serial/Serial.java +++ b/src/serial/Serial.java @@ -110,7 +110,7 @@ public class Serial { public static final int BAUD = SerialPort.BAUDRATE_57600; public static final int U16_FIXED_POINT = 256; - public static final int MAX_CONFIRM_WAIT = 2000; //in milliseconds + public static final int MAX_CONFIRM_WAIT_MS = 2000; public static final int MAX_FAILURES = 6; public static final byte[] HEADER = {0x13, 0x37}; From 7b997f946494e288931c7e6f4dd0d6439f9e90cf Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 13 Oct 2021 10:11:18 -0700 Subject: [PATCH 119/125] Gps precision squash * Add precision GPS types to telemetry IDs * Update telemetry parsing to support mixed types Telemetry parsing now supports mixed type precision for lat/long gps values * rework bit shifting to be correct * Update SerialParser.java * adjust getTelemetry call to handle double instead of float * Increased the allowed range for settings to 72 * Convert telemetry float values to doubles across the code base convert telemetry focused float variables and structures to double variables and structures. This probably breaks the graph and the waypoint panel in some way... Testing will bear it out. * Reworked precision telemetry value parsing logic ... * Update SerialParser.java updates for ground settings Update groundSettings.xml Update groundSettings.xml --- .gitignore | 2 + doc/CommProtocol.txt | 6 +- resources/groundSettings.xml | 14 +-- resources/telemetryLabels_en_US.properties | 6 ++ src/Context.java | 15 ++- src/remote/Setting.java | 22 ++--- src/remote/SettingList.java | 26 +++-- src/serial/Messages/DataMessage.java | 4 +- src/serial/Messages/Message.java | 6 +- src/serial/Serial.java | 10 +- src/serial/SerialParser.java | 110 ++++++++++++--------- src/table/TableFactory.java | 4 +- src/ui/DataWindow.java | 2 +- src/ui/telemetry/SliderEditor.java | 8 +- src/ui/telemetry/TelemetryDataWindow.java | 6 +- 15 files changed, 145 insertions(+), 96 deletions(-) diff --git a/.gitignore b/.gitignore index 24250fb..80bc038 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ resources/groundSettings_edited.xml resources/groundSettings_Old.xml resources/groundSettings.xml resources/images/6x6-Top - Backup.png +resources/groundSettings.xml +resources/groundSettings.xml diff --git a/doc/CommProtocol.txt b/doc/CommProtocol.txt index d55552a..d343b48 100644 --- a/doc/CommProtocol.txt +++ b/doc/CommProtocol.txt @@ -1,3 +1,5 @@ +NOTE: 10-14-21 This document is out of date and does not reflect correct information. + 9600 baud fletcher16 checksum most significant byte on left @@ -24,9 +26,9 @@ Waypoint message: 0x0 data message: 0x1 1 byte index - 4 bytes float + 4 bytes double subtypes: - 0x0 telemety: Packet contains a telemetry float with label index + 0x0 telemetry: Packet contains a telemetry double with label index 0x1 set setting: Packet contains the value the indexed setting should be set to word messages: 0x2 diff --git a/resources/groundSettings.xml b/resources/groundSettings.xml index 2d2b017..139645b 100644 --- a/resources/groundSettings.xml +++ b/resources/groundSettings.xml @@ -17,11 +17,11 @@ Radius centered at waypoint where target is determined to be meet Radius cneter at waypoint where the approach flag is set Change to init gyro sync to north - - - - - - - + + + + + + + \ No newline at end of file diff --git a/resources/telemetryLabels_en_US.properties b/resources/telemetryLabels_en_US.properties index 76f4948..025b6b4 100644 --- a/resources/telemetryLabels_en_US.properties +++ b/resources/telemetryLabels_en_US.properties @@ -16,3 +16,9 @@ t14 =Home Latitude t15 =Home Longitude t16 =Home Altitude t17 =Relative Altitude +t18 =GPS Num Sat +t19 =GPS HDOP +t20 =Heading Lock +t64 =Latitude Precision +t65 =Longitude Precision + diff --git a/src/Context.java b/src/Context.java index 01e78d1..ca7280d 100644 --- a/src/Context.java +++ b/src/Context.java @@ -262,18 +262,23 @@ public WaypointList getWaypointList(){ public void sendSetting(int index) { settingList.pushSetting(index); } - public void setSetting(int index, float value) { + public void setSetting(int index, double value) { settingList.pushSetting(index, value); } - public void setSettingQuiet(int index, float value) { + public void setSettingQuiet(int index, double value) { settingList.updateSettingVal(index, value); } - + public void setTelemetry(int id, float value) { telemetry.updateTelemetry(id, (double)value); } - public float getTelemetry(int id) { - return (float) telemetry.getTelemetry(id); + + public void setTelemetry(int id, double value) { + telemetry.updateTelemetry(id, value); + } + + public double getTelemetry(int id) { + return telemetry.getTelemetry(id); } public String getTelemetryName(int id) { diff --git a/src/remote/Setting.java b/src/remote/Setting.java index eba906c..8e668b8 100644 --- a/src/remote/Setting.java +++ b/src/remote/Setting.java @@ -5,10 +5,10 @@ public class Setting { String name; String description; - float min; - float max; - float def; - float remoteVal; + double min; + double max; + double def; + double remoteVal; Setting() { this.name = ""; this.description = ""; @@ -16,17 +16,17 @@ public class Setting { this.max = 0; this.def = 0; } - Setting(String name, String description, float min, float max, float def) { + Setting(String name, String description, double min, double max, double def) { this.name = name; this.description = description; this.min = min; this.max = max; this.def = def; } - void setVal(float newVal) { + void setVal(double newVal) { remoteVal = newVal; } - public Boolean outsideOfBounds(float v) { + public Boolean outsideOfBounds(double v) { return (v < min) || (v > max); } public String getName() { @@ -35,16 +35,16 @@ public String getName() { public String getDescription() { return description; } - public float getMin() { + public double getMin() { return min; } - public float getMax() { + public double getMax() { return max; } - public float getDefault() { + public double getDefault() { return def; } - public float getVal() { + public double getVal() { return remoteVal; } } diff --git a/src/remote/SettingList.java b/src/remote/SettingList.java index 22fb5b0..ce52a67 100644 --- a/src/remote/SettingList.java +++ b/src/remote/SettingList.java @@ -18,36 +18,42 @@ public SettingList(Context cxt) { context = cxt; loadSettingData(); } + public int size() { return settingData.size(); } + public Setting get(int id) { return settingData.get(id); } - public void pushSetting(int id, float val) { + + public void pushSetting(int id, double val) { if(id < 0 || id >= settingData.size()) return; updateSettingVal(id, val); pushSetting(id); } + public void pushSetting(int id) { Message msg = Message.setSetting((byte)id, settingData.get(id).getVal()); context.sender.sendMessage(msg); } - public void updateSettingVal(int id, float val) { + + public void updateSettingVal(int id, double val) { if(id < 0 || id >= settingData.size()) return; settingData.get(id).setVal(val); } - - private float getFloat(XMLStreamReader reader, String label) { + + private double getDouble(XMLStreamReader reader, String label) { String raw = reader.getAttributeValue(null,label); if(raw == null) return 0.0f; //strip whitespace raw = raw.replaceAll("\\s",""); //parse - if(raw.equals("+inf")) return Float.POSITIVE_INFINITY; - else if (raw.equals("-inf")) return Float.NEGATIVE_INFINITY; - else return Float.valueOf(raw); + if(raw.equals("+inf")) return Double.POSITIVE_INFINITY; + else if (raw.equals("-inf")) return Double.NEGATIVE_INFINITY; + else return Double.valueOf(raw); } + private void loadSettingData() { settingData.clear(); for(int i=0; i details = sm.getFullDescription(data); @@ -254,10 +274,12 @@ private String format(String data) { d.getDescription(), d.getSourceFile()); } + public int claim(byte data) { if(Serial.getMsgType(data) == Serial.STRING_TYPE) return 255; else return -1; } + public void handle(byte[] msg) { int subtype = Serial.getSubtype(msg[0]); byte[] buff = new byte[msg.length-1]; diff --git a/src/table/TableFactory.java b/src/table/TableFactory.java index c3b326c..16bd820 100644 --- a/src/table/TableFactory.java +++ b/src/table/TableFactory.java @@ -94,7 +94,7 @@ public String getName() { } public String getValueAt(int row) { - float val = settingList.get(row).getVal(); + double val = settingList.get(row).getVal(); return " "+val; } @@ -112,7 +112,7 @@ public boolean isRowEditable(int row) { public void setValueAt(String val, int row) { - Float newVal = Float.valueOf((String)val); + double newVal = Double.valueOf((String)val); if(settingList.get(row).outsideOfBounds(newVal)) { JFrame mf = new JFrame("Warning"); diff --git a/src/ui/DataWindow.java b/src/ui/DataWindow.java index 2b69c4a..e290212 100644 --- a/src/ui/DataWindow.java +++ b/src/ui/DataWindow.java @@ -138,7 +138,7 @@ public String getName() { return "Setting"; } public String getValueAt(int row) { - float val = settingList.get(row).getVal(); + double val = settingList.get(row).getVal(); return " "+val; } public int getRowCount() { diff --git a/src/ui/telemetry/SliderEditor.java b/src/ui/telemetry/SliderEditor.java index 92505b4..edc8810 100644 --- a/src/ui/telemetry/SliderEditor.java +++ b/src/ui/telemetry/SliderEditor.java @@ -70,10 +70,10 @@ public void stateChanged(ChangeEvent event) { if(previousChangeValue == getValue()) { setting = context.settingList.get(row); - float min = setting.getMin(); - float max = setting.getMax(); - float range = (max - min); - float settingValue = ((getValue() * range) / 100) + min; + double min = setting.getMin(); + double max = setting.getMax(); + double range = (max - min); + double settingValue = ((getValue() * range) / 100) + min; //Set final updated value context.settingList.pushSetting(row, settingValue); diff --git a/src/ui/telemetry/TelemetryDataWindow.java b/src/ui/telemetry/TelemetryDataWindow.java index 68fbb0b..52064f3 100644 --- a/src/ui/telemetry/TelemetryDataWindow.java +++ b/src/ui/telemetry/TelemetryDataWindow.java @@ -336,9 +336,9 @@ public void run() { */ public void updateSliderPercentages() { Setting setting; - float min; - float max; - float current; + double min; + double max; + double current; int percentage; for(int i = 0; i < context.settingList.size(); i++) { From c3b1f3c6c18f2b7e6c721f091ff5120f5d895fb7 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 27 Oct 2021 16:04:18 -0700 Subject: [PATCH 120/125] Revert "Gps precision squash" This reverts commit 7b997f946494e288931c7e6f4dd0d6439f9e90cf. --- .gitignore | 2 - doc/CommProtocol.txt | 6 +- resources/groundSettings.xml | 14 +-- resources/telemetryLabels_en_US.properties | 6 -- src/Context.java | 15 +-- src/remote/Setting.java | 22 ++--- src/remote/SettingList.java | 26 ++--- src/serial/Messages/DataMessage.java | 4 +- src/serial/Messages/Message.java | 6 +- src/serial/Serial.java | 10 +- src/serial/SerialParser.java | 110 +++++++++------------ src/table/TableFactory.java | 4 +- src/ui/DataWindow.java | 2 +- src/ui/telemetry/SliderEditor.java | 8 +- src/ui/telemetry/TelemetryDataWindow.java | 6 +- 15 files changed, 96 insertions(+), 145 deletions(-) diff --git a/.gitignore b/.gitignore index 80bc038..24250fb 100644 --- a/.gitignore +++ b/.gitignore @@ -28,5 +28,3 @@ resources/groundSettings_edited.xml resources/groundSettings_Old.xml resources/groundSettings.xml resources/images/6x6-Top - Backup.png -resources/groundSettings.xml -resources/groundSettings.xml diff --git a/doc/CommProtocol.txt b/doc/CommProtocol.txt index d343b48..d55552a 100644 --- a/doc/CommProtocol.txt +++ b/doc/CommProtocol.txt @@ -1,5 +1,3 @@ -NOTE: 10-14-21 This document is out of date and does not reflect correct information. - 9600 baud fletcher16 checksum most significant byte on left @@ -26,9 +24,9 @@ Waypoint message: 0x0 data message: 0x1 1 byte index - 4 bytes double + 4 bytes float subtypes: - 0x0 telemetry: Packet contains a telemetry double with label index + 0x0 telemety: Packet contains a telemetry float with label index 0x1 set setting: Packet contains the value the indexed setting should be set to word messages: 0x2 diff --git a/resources/groundSettings.xml b/resources/groundSettings.xml index 139645b..2d2b017 100644 --- a/resources/groundSettings.xml +++ b/resources/groundSettings.xml @@ -17,11 +17,11 @@ Radius centered at waypoint where target is determined to be meet Radius cneter at waypoint where the approach flag is set Change to init gyro sync to north - - - - - - - + + + + + + + \ No newline at end of file diff --git a/resources/telemetryLabels_en_US.properties b/resources/telemetryLabels_en_US.properties index 025b6b4..76f4948 100644 --- a/resources/telemetryLabels_en_US.properties +++ b/resources/telemetryLabels_en_US.properties @@ -16,9 +16,3 @@ t14 =Home Latitude t15 =Home Longitude t16 =Home Altitude t17 =Relative Altitude -t18 =GPS Num Sat -t19 =GPS HDOP -t20 =Heading Lock -t64 =Latitude Precision -t65 =Longitude Precision - diff --git a/src/Context.java b/src/Context.java index ca7280d..01e78d1 100644 --- a/src/Context.java +++ b/src/Context.java @@ -262,23 +262,18 @@ public WaypointList getWaypointList(){ public void sendSetting(int index) { settingList.pushSetting(index); } - public void setSetting(int index, double value) { + public void setSetting(int index, float value) { settingList.pushSetting(index, value); } - public void setSettingQuiet(int index, double value) { + public void setSettingQuiet(int index, float value) { settingList.updateSettingVal(index, value); } - + public void setTelemetry(int id, float value) { telemetry.updateTelemetry(id, (double)value); } - - public void setTelemetry(int id, double value) { - telemetry.updateTelemetry(id, value); - } - - public double getTelemetry(int id) { - return telemetry.getTelemetry(id); + public float getTelemetry(int id) { + return (float) telemetry.getTelemetry(id); } public String getTelemetryName(int id) { diff --git a/src/remote/Setting.java b/src/remote/Setting.java index 8e668b8..eba906c 100644 --- a/src/remote/Setting.java +++ b/src/remote/Setting.java @@ -5,10 +5,10 @@ public class Setting { String name; String description; - double min; - double max; - double def; - double remoteVal; + float min; + float max; + float def; + float remoteVal; Setting() { this.name = ""; this.description = ""; @@ -16,17 +16,17 @@ public class Setting { this.max = 0; this.def = 0; } - Setting(String name, String description, double min, double max, double def) { + Setting(String name, String description, float min, float max, float def) { this.name = name; this.description = description; this.min = min; this.max = max; this.def = def; } - void setVal(double newVal) { + void setVal(float newVal) { remoteVal = newVal; } - public Boolean outsideOfBounds(double v) { + public Boolean outsideOfBounds(float v) { return (v < min) || (v > max); } public String getName() { @@ -35,16 +35,16 @@ public String getName() { public String getDescription() { return description; } - public double getMin() { + public float getMin() { return min; } - public double getMax() { + public float getMax() { return max; } - public double getDefault() { + public float getDefault() { return def; } - public double getVal() { + public float getVal() { return remoteVal; } } diff --git a/src/remote/SettingList.java b/src/remote/SettingList.java index ce52a67..22fb5b0 100644 --- a/src/remote/SettingList.java +++ b/src/remote/SettingList.java @@ -18,42 +18,36 @@ public SettingList(Context cxt) { context = cxt; loadSettingData(); } - public int size() { return settingData.size(); } - public Setting get(int id) { return settingData.get(id); } - - public void pushSetting(int id, double val) { + public void pushSetting(int id, float val) { if(id < 0 || id >= settingData.size()) return; updateSettingVal(id, val); pushSetting(id); } - public void pushSetting(int id) { Message msg = Message.setSetting((byte)id, settingData.get(id).getVal()); context.sender.sendMessage(msg); } - - public void updateSettingVal(int id, double val) { + public void updateSettingVal(int id, float val) { if(id < 0 || id >= settingData.size()) return; settingData.get(id).setVal(val); } - - private double getDouble(XMLStreamReader reader, String label) { + + private float getFloat(XMLStreamReader reader, String label) { String raw = reader.getAttributeValue(null,label); if(raw == null) return 0.0f; //strip whitespace raw = raw.replaceAll("\\s",""); //parse - if(raw.equals("+inf")) return Double.POSITIVE_INFINITY; - else if (raw.equals("-inf")) return Double.NEGATIVE_INFINITY; - else return Double.valueOf(raw); + if(raw.equals("+inf")) return Float.POSITIVE_INFINITY; + else if (raw.equals("-inf")) return Float.NEGATIVE_INFINITY; + else return Float.valueOf(raw); } - private void loadSettingData() { settingData.clear(); for(int i=0; i details = sm.getFullDescription(data); @@ -274,12 +254,10 @@ private String format(String data) { d.getDescription(), d.getSourceFile()); } - public int claim(byte data) { if(Serial.getMsgType(data) == Serial.STRING_TYPE) return 255; else return -1; } - public void handle(byte[] msg) { int subtype = Serial.getSubtype(msg[0]); byte[] buff = new byte[msg.length-1]; diff --git a/src/table/TableFactory.java b/src/table/TableFactory.java index 16bd820..c3b326c 100644 --- a/src/table/TableFactory.java +++ b/src/table/TableFactory.java @@ -94,7 +94,7 @@ public String getName() { } public String getValueAt(int row) { - double val = settingList.get(row).getVal(); + float val = settingList.get(row).getVal(); return " "+val; } @@ -112,7 +112,7 @@ public boolean isRowEditable(int row) { public void setValueAt(String val, int row) { - double newVal = Double.valueOf((String)val); + Float newVal = Float.valueOf((String)val); if(settingList.get(row).outsideOfBounds(newVal)) { JFrame mf = new JFrame("Warning"); diff --git a/src/ui/DataWindow.java b/src/ui/DataWindow.java index e290212..2b69c4a 100644 --- a/src/ui/DataWindow.java +++ b/src/ui/DataWindow.java @@ -138,7 +138,7 @@ public String getName() { return "Setting"; } public String getValueAt(int row) { - double val = settingList.get(row).getVal(); + float val = settingList.get(row).getVal(); return " "+val; } public int getRowCount() { diff --git a/src/ui/telemetry/SliderEditor.java b/src/ui/telemetry/SliderEditor.java index edc8810..92505b4 100644 --- a/src/ui/telemetry/SliderEditor.java +++ b/src/ui/telemetry/SliderEditor.java @@ -70,10 +70,10 @@ public void stateChanged(ChangeEvent event) { if(previousChangeValue == getValue()) { setting = context.settingList.get(row); - double min = setting.getMin(); - double max = setting.getMax(); - double range = (max - min); - double settingValue = ((getValue() * range) / 100) + min; + float min = setting.getMin(); + float max = setting.getMax(); + float range = (max - min); + float settingValue = ((getValue() * range) / 100) + min; //Set final updated value context.settingList.pushSetting(row, settingValue); diff --git a/src/ui/telemetry/TelemetryDataWindow.java b/src/ui/telemetry/TelemetryDataWindow.java index 52064f3..68fbb0b 100644 --- a/src/ui/telemetry/TelemetryDataWindow.java +++ b/src/ui/telemetry/TelemetryDataWindow.java @@ -336,9 +336,9 @@ public void run() { */ public void updateSliderPercentages() { Setting setting; - double min; - double max; - double current; + float min; + float max; + float current; int percentage; for(int i = 0; i < context.settingList.size(); i++) { From a02b97c7ecbb2360ad35115a5c0c817333c1b167 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Wed, 27 Oct 2021 16:48:17 -0700 Subject: [PATCH 121/125] Take abs value of slider range for settings --- src/ui/telemetry/SliderEditor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui/telemetry/SliderEditor.java b/src/ui/telemetry/SliderEditor.java index 92505b4..3804b90 100644 --- a/src/ui/telemetry/SliderEditor.java +++ b/src/ui/telemetry/SliderEditor.java @@ -5,6 +5,7 @@ import com.remote.SettingList; import java.util.*; +import java.lang.Math; import javax.swing.*; import javax.swing.event.ChangeListener; @@ -72,7 +73,7 @@ public void stateChanged(ChangeEvent event) { float min = setting.getMin(); float max = setting.getMax(); - float range = (max - min); + float range = Math.abs(max - min); float settingValue = ((getValue() * range) / 100) + min; //Set final updated value From 8c779d7c862121aec5ef387231a3b3220989c1f5 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 28 Oct 2021 09:39:47 -0700 Subject: [PATCH 122/125] Delete groundSettings.xml Should not be tracked in the repository. --- resources/groundSettings.xml | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 resources/groundSettings.xml diff --git a/resources/groundSettings.xml b/resources/groundSettings.xml deleted file mode 100644 index 2d2b017..0000000 --- a/resources/groundSettings.xml +++ /dev/null @@ -1,27 +0,0 @@ - Defines how strongly the rover should attempt to return to the original course between waypoints, verses the direct path from its current location to the target<br> - The number of degrees that rover will turn its wheels when it needs to to turn its most extreme amount - Switches between arctangent of error steering (0) <br> square of error steering (1) <br> and proportional to error steering (2) - Multiplier that determines how aggressively to steer - Minimum forward driving speed in MPH - Maximum forward driving speed in MPH - How far to turn the wheels when backing away from an obstacle - Speed in MPH to drive in reverse - Factor to determine how strongly obstacles effect the rover's course <br> Larger numbers correspond to larger effects from obstacles - Time in milliseconds to coast before reversing when an obstacle is encountered - Minimum time in seconds to reverse away from an obstacle - P term in cruise control PID loop - I term in cruise control PID loop - D term in cruise control PID loop - Tire Diameter in inches, used to calculate MPH - Center point in degrees corresponding to driving straight - Radius centered at waypoint where target is determined to be meet - Radius cneter at waypoint where the approach flag is set - Change to init gyro sync to north - - - - - - - - \ No newline at end of file From 0d68892eae728e9e6e5eb4fb3ed88d0409ead7a9 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Thu, 28 Oct 2021 12:50:55 -0700 Subject: [PATCH 123/125] Add GPS and heading lock labels back into props --- resources/telemetryLabels_en_US.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resources/telemetryLabels_en_US.properties b/resources/telemetryLabels_en_US.properties index 76f4948..c771a54 100644 --- a/resources/telemetryLabels_en_US.properties +++ b/resources/telemetryLabels_en_US.properties @@ -16,3 +16,6 @@ t14 =Home Latitude t15 =Home Longitude t16 =Home Altitude t17 =Relative Altitude +t18 =GPS Num Sat +t19 =GPS HDOP +t20 =Heading Lock From cec5f55631dc3154443370c8a6d377114e18fcb5 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Mon, 1 Nov 2021 10:37:47 -0700 Subject: [PATCH 124/125] Change branch format --- resources/resources_en.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/resources_en.properties b/resources/resources_en.properties index bd1b19e..c781a52 100644 --- a/resources/resources_en.properties +++ b/resources/resources_en.properties @@ -1,4 +1,4 @@ -version_id =1.0.4 +version_id =1.4.1 release_date =2021-07-12 image_folder =resources/images/ patch_folder =resources/images/nP/ From db25f8eda27917587ff5bf1d32dfd73eccd49bc5 Mon Sep 17 00:00:00 2001 From: Chris Park <68874725+ChrisParkInfinetix@users.noreply.github.com> Date: Mon, 1 Nov 2021 10:42:50 -0700 Subject: [PATCH 125/125] Update resources_en.properties --- resources/resources_en.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/resources_en.properties b/resources/resources_en.properties index c781a52..58eab84 100644 --- a/resources/resources_en.properties +++ b/resources/resources_en.properties @@ -1,5 +1,5 @@ version_id =1.4.1 -release_date =2021-07-12 +release_date =2021-11-1 image_folder =resources/images/ patch_folder =resources/images/nP/ font_folder =resources/fonts/