From 45d6d3802fcc387c7ef5dcbbb9c52987e6b6c7e1 Mon Sep 17 00:00:00 2001 From: Bdale Garbee Date: Mon, 15 Jan 2018 14:58:05 -0300 Subject: [PATCH] max pressure and max thrust now available as test stats --- teststand/AltosGraphUI.java | 10 +- teststand/Makefile.am | 2 + teststand/TestStand.java | 4 +- teststand/TestStandGraph.java | 6 +- teststand/TestStats.java | 256 ++++++++++++++++++++++++++++++++++ teststand/TestStatsTable.java | 200 ++++++++++++++++++++++++++ 6 files changed, 468 insertions(+), 10 deletions(-) create mode 100644 teststand/TestStats.java create mode 100644 teststand/TestStatsTable.java diff --git a/teststand/AltosGraphUI.java b/teststand/AltosGraphUI.java index ed77bdef..6fd05b26 100644 --- a/teststand/AltosGraphUI.java +++ b/teststand/AltosGraphUI.java @@ -36,8 +36,8 @@ public class AltosGraphUI extends AltosUIFrame implements AltosFontListener, Alt JTabbedPane pane; TestStandGraph graph; AltosUIEnable enable; - AltosFlightStats stats; - AltosFlightStatsTable statsTable; + TestStats stats; + TestStatsTable statsTable; AltosGPS gps; boolean has_gps; @@ -56,7 +56,7 @@ public class AltosGraphUI extends AltosUIFrame implements AltosFontListener, Alt public void filter_changed(double speed_filter, double accel_filter) { flight_series.set_filter(speed_filter, accel_filter); graph.filter_changed(); - stats = new AltosFlightStats(flight_series); + stats = new TestStats(flight_series); statsTable.filter_changed(stats); } @@ -83,11 +83,11 @@ public class AltosGraphUI extends AltosUIFrame implements AltosFontListener, Alt flight_series.finish(); - stats = new AltosFlightStats(flight_series); + stats = new TestStats(flight_series); graph = new TestStandGraph(enable, stats, flight_series); - statsTable = new AltosFlightStatsTable(stats); + statsTable = new TestStatsTable(stats); pane.add("Test Graph", graph.panel); pane.add("Configure Graph", enable); diff --git a/teststand/Makefile.am b/teststand/Makefile.am index 8e9ab3f5..dccb0070 100644 --- a/teststand/Makefile.am +++ b/teststand/Makefile.am @@ -27,6 +27,8 @@ teststand_JAVA = \ AltosPad.java \ TestStand.java \ TestStandGraph.java \ + TestStats.java \ + TestStatsTable.java \ AltosGraphUI.java JFREECHART_CLASS= \ diff --git a/teststand/TestStand.java b/teststand/TestStand.java index 213e69c8..35ed67e1 100644 --- a/teststand/TestStand.java +++ b/teststand/TestStand.java @@ -412,7 +412,7 @@ public class TestStand extends AltosUIFrame implements AltosEepromGrapher { return false; System.out.printf("%s:\n", file.toString()); AltosFlightSeries series = make_series(set); - AltosFlightStats stats = new AltosFlightStats(series); + TestStats stats = new TestStats(series); if (stats.serial != AltosLib.MISSING) System.out.printf("Serial: %5d\n", stats.serial); if (stats.flight != AltosLib.MISSING) @@ -462,7 +462,7 @@ public class TestStand extends AltosUIFrame implements AltosEepromGrapher { return false; System.out.printf("%s", file.toString()); AltosFlightSeries series = make_series(set); - AltosFlightStats stats = new AltosFlightStats(series); + TestStats stats = new TestStats(series); if (stats.max_height != AltosLib.MISSING) System.out.printf(" height %6.0f m", stats.max_height); if (stats.max_speed != AltosLib.MISSING) diff --git a/teststand/TestStandGraph.java b/teststand/TestStandGraph.java index 14774c51..87c59389 100644 --- a/teststand/TestStandGraph.java +++ b/teststand/TestStandGraph.java @@ -92,7 +92,7 @@ public class TestStandGraph extends AltosUIGraph { AltosUIFlightSeries flight_series; - AltosUITimeSeries[] setup(AltosFlightStats stats, AltosUIFlightSeries flight_series) { + AltosUITimeSeries[] setup(TestStats stats, AltosUIFlightSeries flight_series) { AltosCalData cal_data = flight_series.cal_data(); AltosUIAxis height_axis, speed_axis, accel_axis, voltage_axis, temperature_axis, nsat_axis, dbm_axis; @@ -342,7 +342,7 @@ public class TestStandGraph extends AltosUIGraph { return flight_series.series(cal_data); } - public void set_data(AltosFlightStats stats, AltosUIFlightSeries flight_series) { + public void set_data(TestStats stats, AltosUIFlightSeries flight_series) { set_series(setup(stats, flight_series)); } @@ -350,7 +350,7 @@ public class TestStandGraph extends AltosUIGraph { super(enable, "Flight"); } - public TestStandGraph(AltosUIEnable enable, AltosFlightStats stats, AltosUIFlightSeries flight_series) { + public TestStandGraph(AltosUIEnable enable, TestStats stats, AltosUIFlightSeries flight_series) { this(enable); this.flight_series = flight_series; set_series(setup(stats, flight_series)); diff --git a/teststand/TestStats.java b/teststand/TestStats.java new file mode 100644 index 00000000..57636bd9 --- /dev/null +++ b/teststand/TestStats.java @@ -0,0 +1,256 @@ +/* + * Copyright © 2018 Bdale Garbee + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package teststand; + +import java.io.*; +import org.altusmetrum.altoslib_12.*; + +public class TestStats { + public double max_pressure; + public double max_thrust; + public double max_height; + public double max_gps_height; + public double max_speed; + public double max_acceleration; + public double[] state_speed = new double[AltosLib.ao_flight_invalid + 1]; + public double[] state_enter_speed = new double[AltosLib.ao_flight_invalid + 1]; + public double[] state_enter_height = new double[AltosLib.ao_flight_invalid + 1]; + public double[] state_enter_gps_height = new double[AltosLib.ao_flight_invalid + 1]; + public double[] state_accel = new double[AltosLib.ao_flight_invalid + 1]; + public double[] state_time = new double[AltosLib.ao_flight_invalid + 1]; + public String product; + public String firmware_version; + public int serial; + public int flight; + public int year, month, day; + public int hour, minute, second; + public double boost_time; + public double landed_time; + public double lat, lon; + public double pad_lat, pad_lon; + public boolean has_flight_data; + public boolean has_gps; + public boolean has_gps_sats; + public boolean has_gps_detail; + public boolean has_flight_adc; + public boolean has_battery; + public boolean has_rssi; + public boolean has_imu; + public boolean has_mag; + public boolean has_orient; + public int num_igniter; + + double landed_time(AltosFlightSeries series) { + double landed_state_time = AltosLib.MISSING; + + double prev_state_time = AltosLib.MISSING; + if (series.state_series != null) { + for (AltosTimeValue state : series.state_series) { + if (state.value == AltosLib.ao_flight_landed) { + landed_state_time = state.time; + break; + } else { + prev_state_time = state.time; + } + } + } + + if (landed_state_time == AltosLib.MISSING && series.height_series != null) + landed_state_time = series.height_series.get(series.height_series.size()-1).time; + + double landed_height = AltosLib.MISSING; + + if (series.height_series != null) { + for (AltosTimeValue height : series.height_series) { + landed_height = height.value; + if (height.time >= landed_state_time) + break; + } + } + + if (landed_height == AltosLib.MISSING) + return AltosLib.MISSING; + + boolean above = true; + + double landed_time = AltosLib.MISSING; + + if (series.height_series != null) { + for (AltosTimeValue height : series.height_series) { + if (height.value > landed_height + 10) { + above = true; + } else { + if (above && Math.abs(height.value - landed_height) < 2) { + above = false; + landed_time = height.time; + } + } + } + } + + if (landed_time == AltosLib.MISSING || (prev_state_time != AltosLib.MISSING && landed_time < prev_state_time)) + landed_time = landed_state_time; + return landed_time; + } + + double boost_time(AltosFlightSeries series) { + double boost_time = AltosLib.MISSING; + double boost_state_time = AltosLib.MISSING; + + if (series.state_series != null) { + for (AltosTimeValue state : series.state_series) { + if (state.value >= AltosLib.ao_flight_boost && state.value <= AltosLib.ao_flight_landed) { + boost_state_time = state.time; + break; + } + } + } + if (series.accel_series != null) { + for (AltosTimeValue accel : series.accel_series) { + if (accel.value < 1) + boost_time = accel.time; + if (boost_state_time != AltosLib.MISSING && accel.time >= boost_state_time) + break; + } + } + if (boost_time == AltosLib.MISSING) + boost_time = boost_state_time; + return boost_time; + } + + private void add_times(AltosFlightSeries series, int state, double start_time, double end_time) { + double delta_time = end_time - start_time; + if (0 <= state && state <= AltosLib.ao_flight_invalid && delta_time > 0) { + if (state_enter_speed[state] == AltosLib.MISSING) + state_enter_speed[state] = series.speed_series.value(start_time); + if (state_enter_height[state] == AltosLib.MISSING) + state_enter_height[state] = series.height_series.value(start_time); + if (state_enter_gps_height[state] == AltosLib.MISSING) + if (series.gps_height != null) + state_enter_gps_height[state] = series.gps_height.value(start_time); + speeds[state].value += series.speed_series.average(start_time, end_time) * delta_time; + speeds[state].time += delta_time; + accels[state].value += series.accel_series.average(start_time, end_time) * delta_time; + accels[state].time += delta_time; + state_time[state] += delta_time; + + if (state == AltosLib.ao_flight_boost) { + AltosTimeValue tv_speed = series.speed_series.max(start_time, end_time); + if (tv_speed != null && (max_speed == AltosLib.MISSING || tv_speed.value > max_speed)) + max_speed = tv_speed.value; + AltosTimeValue tv_accel = series.accel_series.max(start_time, end_time); + if (tv_accel != null && (max_acceleration == AltosLib.MISSING || tv_accel.value > max_acceleration)) + max_acceleration = tv_accel.value; + } + } + } + + AltosTimeValue[] speeds = new AltosTimeValue[AltosLib.ao_flight_invalid + 1]; + AltosTimeValue[] accels = new AltosTimeValue[AltosLib.ao_flight_invalid + 1]; + + public TestStats(AltosFlightSeries series) { + AltosCalData cal_data = series.cal_data(); + + series.finish(); + + boost_time = boost_time(series); + landed_time = landed_time(series); + + if (series.state_series != null){ + boolean fixed_boost = false; + boolean fixed_landed = false; + for (AltosTimeValue state : series.state_series) { + if ((int) state.value == AltosLib.ao_flight_boost) + if (boost_time != AltosLib.MISSING && !fixed_boost) { + state.time = boost_time; + fixed_boost = true; + } + if ((int) state.value == AltosLib.ao_flight_landed) + if (landed_time != AltosLib.MISSING && !fixed_landed) { + state.time = landed_time; + fixed_landed = true; + } + } + } + + year = month = day = AltosLib.MISSING; + hour = minute = second = AltosLib.MISSING; + serial = flight = AltosLib.MISSING; + lat = lon = AltosLib.MISSING; + has_flight_data = false; + has_gps = false; + has_gps_sats = false; + has_flight_adc = false; + has_battery = false; + has_rssi = false; + has_imu = false; + has_mag = false; + has_orient = false; + + for (int s = 0; s < AltosLib.ao_flight_invalid + 1; s++) { + state_speed[s] = AltosLib.MISSING; + state_enter_speed[s] = AltosLib.MISSING; + state_accel[s] = AltosLib.MISSING; + state_time[s] = 0; + speeds[s] = new AltosTimeValue(0, 0); + accels[s] = new AltosTimeValue(0, 0); + } + + max_speed = AltosLib.MISSING; + max_acceleration = AltosLib.MISSING; + + if (series.state_series != null) { + AltosTimeValue prev = null; + for (AltosTimeValue state : series.state_series) { + if (prev != null) + add_times(series, (int) prev.value, prev.time, state.time); + prev = state; + } + if (prev != null) { + AltosTimeValue last_accel = series.accel_series.last(); + if (last_accel != null) + add_times(series, (int) prev.value, prev.time, last_accel.time); + } + } + + for (int s = 0; s <= AltosLib.ao_flight_invalid; s++) { + if (speeds[s].time > 0) + state_speed[s] = speeds[s].value / speeds[s].time; + if (accels[s].time > 0) + state_accel[s] = accels[s].value / accels[s].time; + } + + product = cal_data.product; + firmware_version = cal_data.firmware_version; + serial = cal_data.serial; + flight = cal_data.flight; + + has_battery = series.battery_voltage_series != null; + has_flight_adc = series.main_voltage_series != null; + has_rssi = series.rssi_series != null; + has_flight_data = series.pressure_series != null; + + max_pressure = AltosLib.MISSING; + if (series.pressure_series != null) + max_pressure = series.pressure_series.max().value; + max_thrust = AltosLib.MISSING; + if (series.thrust_series != null) + max_thrust = series.thrust_series.max().value; + } +} diff --git a/teststand/TestStatsTable.java b/teststand/TestStatsTable.java new file mode 100644 index 00000000..e6070f51 --- /dev/null +++ b/teststand/TestStatsTable.java @@ -0,0 +1,200 @@ +/* + * Copyright © 2018 Bdale Garbee + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package teststand; + +import java.awt.*; +import javax.swing.*; +import java.util.*; +import org.altusmetrum.altoslib_12.*; +import org.altusmetrum.altosuilib_12.*; + +public class TestStatsTable extends JComponent implements AltosFontListener { + GridBagLayout layout; + + LinkedList flight_stats = new LinkedList(); + + class FlightStat implements AltosFontListener { + JLabel label; + JTextField[] value; + + public void font_size_changed(int font_size) { + label.setFont(AltosUILib.label_font); + for (int i = 0; i < value.length; i++) + value[i].setFont(AltosUILib.value_font); + } + + public void set(String ... values) { + for (int j = 0; j < values.length; j++) + value[j].setText(values[j]); + } + + public FlightStat(GridBagLayout layout, int y, String label_text, String ... values) { + GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets(AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad); + c.weighty = 1; + + label = new JLabel(label_text); + label.setFont(AltosUILib.label_font); + label.setHorizontalAlignment(SwingConstants.LEFT); + c.gridx = 0; c.gridy = y; + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.VERTICAL; + c.weightx = 0; + layout.setConstraints(label, c); + add(label); + + value = new JTextField[values.length]; + for (int j = 0; j < values.length; j++) { + value[j] = new JTextField(values[j]); + value[j].setEditable(false); + value[j].setFont(AltosUILib.value_font); + value[j].setHorizontalAlignment(SwingConstants.RIGHT); + c.gridx = j+1; c.gridy = y; + c.anchor = GridBagConstraints.EAST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + layout.setConstraints(value[j], c); + add(value[j]); + } + flight_stats.add(this); + } + + } + + public void font_size_changed(int font_size) { + for (FlightStat f : flight_stats) + f.font_size_changed(font_size); + } + + static String pos(double p, String pos, String neg) { + String h = pos; + if (p < 0) { + h = neg; + p = -p; + } + int deg = (int) Math.floor(p); + double min = (p - Math.floor(p)) * 60.0; + return String.format("%s %4d° %9.6f'", h, deg, min); + } + + private FlightStat max_pressure_stat; + private FlightStat max_thrust_stat; + + private FlightStat max_height_stat; + private FlightStat max_speed_stat; + private FlightStat max_accel_stat; + private FlightStat boost_accel_stat; + private FlightStat drogue_descent_stat; + private FlightStat main_descent_stat; + + public void set_values(TestStats stats) { + if (max_thrust_stat != null && stats.max_thrust != AltosLib.MISSING) { + max_thrust_stat.set(String.format("%6.1f N", stats.max_thrust), + String.format("%5.0f lbs", AltosConvert.n_to_lb(stats.max_thrust))); + } + if (max_height_stat != null && stats.max_height != AltosLib.MISSING) { + max_height_stat.set(String.format("%6.1f m", stats.max_height), + String.format("%5.0f ft", AltosConvert.meters_to_feet(stats.max_height))); + } + if (max_speed_stat != null && stats.max_speed != AltosLib.MISSING) { + max_speed_stat.set(String.format("%6.1f m/s", stats.max_speed), + String.format("%5.0f fps", AltosConvert.mps_to_fps(stats.max_speed)), + String.format("Mach %4.1f", AltosConvert.meters_to_mach(stats.max_speed))); + } + if (max_accel_stat != null && stats.max_acceleration != AltosLib.MISSING) { + max_accel_stat.set(String.format("%6.1f m/s²", stats.max_acceleration), + String.format("%5.0f ft/s²", AltosConvert.meters_to_feet(stats.max_acceleration)), + String.format("%6.2f G", AltosConvert.meters_to_g(stats.max_acceleration))); + } + if (boost_accel_stat != null && stats.state_accel[AltosLib.ao_flight_boost] != AltosLib.MISSING) { + boost_accel_stat.set(String.format("%6.1f m/s²", stats.state_accel[AltosLib.ao_flight_boost]), + String.format("%5.0f ft/s²", AltosConvert.meters_to_feet(stats.state_accel[AltosLib.ao_flight_boost])), + String.format("%6.2f G", AltosConvert.meters_to_g(stats.state_accel[AltosLib.ao_flight_boost]))); + } + if (drogue_descent_stat != null && stats.state_speed[AltosLib.ao_flight_drogue] != AltosLib.MISSING) { + drogue_descent_stat.set(String.format("%6.1f m/s", -stats.state_speed[AltosLib.ao_flight_drogue]), + String.format("%5.0f ft/s", -AltosConvert.meters_to_feet(stats.state_speed[AltosLib.ao_flight_drogue]))); + } + if (main_descent_stat != null && stats.state_speed[AltosLib.ao_flight_main] != AltosLib.MISSING) { + main_descent_stat.set(String.format("%6.1f m/s", -stats.state_speed[AltosLib.ao_flight_main]), + String.format("%5.0f ft/s", -AltosConvert.meters_to_feet(stats.state_speed[AltosLib.ao_flight_main]))); + } + } + + public void set_stats(TestStats stats) { + int y = 0; + if (stats.serial != AltosLib.MISSING) { + if (stats.product != null && stats.firmware_version != null) + new FlightStat(layout, y++, "Device", + stats.product, + String.format("version %s", stats.firmware_version), + String.format("serial %d", stats.serial)); + else + new FlightStat(layout, y++, "Serial", String.format("%d", stats.serial)); + } + if (stats.flight != AltosLib.MISSING) + new FlightStat(layout, y++, "Test", String.format("%d", stats.flight)); + if (stats.year != AltosLib.MISSING && stats.hour != AltosLib.MISSING) + new FlightStat(layout, y++, "Date/Time", + String.format("%04d-%02d-%02d", stats.year, stats.month, stats.day), + String.format("%02d:%02d:%02d UTC", stats.hour, stats.minute, stats.second)); + else { + if (stats.year != AltosLib.MISSING) + new FlightStat(layout, y++, "Date", + String.format("%04d-%02d-%02d", stats.year, stats.month, stats.day)); + if (stats.hour != AltosLib.MISSING) + new FlightStat(layout, y++, "Time", + String.format("%02d:%02d:%02d UTC", stats.hour, stats.minute, stats.second)); + } + if (stats.max_pressure != AltosLib.MISSING) { + max_pressure_stat = new FlightStat(layout, y++, "Maximum pressure", + String.format("%6.1f kpa", stats.max_pressure / 1000.0), + String.format("%5.0f psi", AltosConvert.pa_to_psi(stats.max_pressure))); + } + if (stats.max_thrust != AltosLib.MISSING) { + max_thrust_stat = new FlightStat(layout, y++, "Maximum thrust", + String.format("%6.1f N", stats.max_thrust), + String.format("%5.0f lbs", AltosConvert.n_to_lb(stats.max_thrust))); + } + if (stats.landed_time > stats.boost_time) + new FlightStat(layout, y++, "Flight time", + String.format("%6.1f s", stats.landed_time - stats.boost_time)); + } + + public void tell_closing() { + AltosUIPreferences.unregister_font_listener(this); + } + + public void filter_changed(TestStats stats) { + set_values(stats); + } + + public TestStatsTable() { + layout = new GridBagLayout(); + + setLayout(layout); + + AltosUIPreferences.register_font_listener(this); + } + + public TestStatsTable(TestStats stats) { + this(); + set_stats(stats); + } +} -- 2.30.2