This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -2,6 +2,9 @@
|
||||
# Compiled class file
|
||||
*.class
|
||||
|
||||
# ---> CSV
|
||||
*.csv
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
@@ -64,3 +67,4 @@ target/maven-archiver/pom.properties
|
||||
target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
|
||||
target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
|
||||
>>>>>>> f462d53 (yessssssssss!)
|
||||
logs/dyno_2025-06-10_19-41-50.csv
|
||||
|
||||
@@ -16,7 +16,7 @@ public class AnalogMeter extends JPanel {
|
||||
this.maxValue = maxValue;
|
||||
this.currentValue = 0.0;
|
||||
this.needleColor = needleColor;
|
||||
setPreferredSize(new Dimension(200, 200));
|
||||
setPreferredSize(new Dimension(250, 250));
|
||||
}
|
||||
|
||||
public void setValue(double value) {
|
||||
@@ -41,8 +41,9 @@ public class AnalogMeter extends JPanel {
|
||||
int centerY = getHeight() / 2;
|
||||
int radius = Math.min(centerX, centerY) - 10;
|
||||
|
||||
// Draw meter background
|
||||
g2d.setColor(Color.LIGHT_GRAY);
|
||||
// Draw meter background with gradient
|
||||
GradientPaint gradient = new GradientPaint(0, 0, Color.LIGHT_GRAY, 0, getHeight(), Color.WHITE);
|
||||
g2d.setPaint(gradient);
|
||||
g2d.fillOval(centerX - radius, centerY - radius, radius * 2, radius * 2);
|
||||
|
||||
// Draw scale and ticks
|
||||
@@ -73,7 +74,7 @@ public class AnalogMeter extends JPanel {
|
||||
int needleX = (int) (centerX + needleLength * Math.cos(angle));
|
||||
int needleY = (int) (centerY - needleLength * Math.sin(angle));
|
||||
|
||||
g2d.setStroke(new BasicStroke(3));
|
||||
g2d.setStroke(new BasicStroke(4)); // Thicker needle
|
||||
g2d.drawLine(centerX, centerY, needleX, needleY);
|
||||
g2d.fillOval(centerX - 5, centerY - 5, 10, 10);
|
||||
|
||||
@@ -82,6 +83,6 @@ public class AnalogMeter extends JPanel {
|
||||
g2d.setFont(new Font("Arial", Font.BOLD, 16));
|
||||
FontMetrics fm = g2d.getFontMetrics();
|
||||
int labelWidth = fm.stringWidth(label);
|
||||
g2d.drawString(label, centerX - labelWidth / 2, centerY + radius + 20);
|
||||
g2d.drawString(label, centerX - labelWidth / 2, centerY + radius + 30); // Adjusted position
|
||||
}
|
||||
}
|
||||
53
src/main/java/com/puchdyno/CSVLogger.java
Normal file
53
src/main/java/com/puchdyno/CSVLogger.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package com.puchdyno;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* Utility class that appends each sample coming from the dyno run to a csv file.
|
||||
* The file will be created the first time it is needed and automatically closed
|
||||
* when the logger is closed.
|
||||
*/
|
||||
public class CSVLogger implements Closeable {
|
||||
private static final DateTimeFormatter FILE_TS = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss");
|
||||
private static final DateTimeFormatter ROW_TS = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
||||
|
||||
private final BufferedWriter writer;
|
||||
|
||||
/**
|
||||
* Creates a new logger that writes to the given directory using the current timestamp as file name.
|
||||
*/
|
||||
public CSVLogger(File directory) throws IOException {
|
||||
if (!directory.exists() && !directory.mkdirs()) {
|
||||
throw new IOException("Unable to create log directory " + directory);
|
||||
}
|
||||
File file = new File(directory, "dyno_" + LocalDateTime.now().format(FILE_TS) + ".csv");
|
||||
boolean created = file.createNewFile();
|
||||
writer = new BufferedWriter(new FileWriter(file, true));
|
||||
if (created) {
|
||||
writer.write("timestamp,rpm,ps,nm");
|
||||
writer.newLine();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void log(double rpm, double ps, double nm) {
|
||||
try {
|
||||
writer.write(String.format("%s,%.2f,%.2f,%.2f", LocalDateTime.now().format(ROW_TS), rpm, ps, nm));
|
||||
writer.newLine();
|
||||
} catch (IOException e) {
|
||||
// we can't do much more than print the stacktrace at this point
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
writer.flush();
|
||||
writer.close();
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,16 @@ public class DynoGUI extends JFrame {
|
||||
private final AnalogMeter hpMeter;
|
||||
private final XYSeries powerSeries;
|
||||
private final XYSeries torqueSeries;
|
||||
private final XYSeries efficiencySeries;
|
||||
private final ChartPanel chartPanel;
|
||||
private final JLabel maxLeistungLabel;
|
||||
private final JLabel maxDrehmomentLabel;
|
||||
private double maxLeistung = 0.0;
|
||||
private double maxDrehmoment = 0.0;
|
||||
private double maxLeistungRpm = 0.0;
|
||||
private double maxLeistungNmAtHp = 0.0;
|
||||
private double maxDrehmomentRpm = 0.0;
|
||||
private double maxDrehmomentPsAtNm = 0.0;
|
||||
|
||||
public DynoGUI() {
|
||||
setTitle("Puch Maxi Dyno");
|
||||
@@ -27,12 +36,24 @@ public class DynoGUI extends JFrame {
|
||||
setLayout(new BorderLayout(10, 10));
|
||||
|
||||
// Top panel: analog meters
|
||||
JPanel meterPanel = new JPanel(new GridLayout(1, 2, 10, 10));
|
||||
JPanel meterPanel = new JPanel(new GridLayout(1, 2, 20, 20));
|
||||
meterPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
rpmMeter = new AnalogMeter("RPM", 12000, Color.BLUE);
|
||||
hpMeter = new AnalogMeter("Horse Power", 15, Color.RED);
|
||||
meterPanel.add(rpmMeter);
|
||||
meterPanel.add(hpMeter);
|
||||
|
||||
rpmMeter.setToolTipText("Motordrehzahl in U/min");
|
||||
hpMeter.setToolTipText("Motorleistung in PS");
|
||||
|
||||
JPanel rpmWrapper = new JPanel(new BorderLayout());
|
||||
rpmWrapper.setBorder(BorderFactory.createTitledBorder("Drehzahl [RPM]"));
|
||||
rpmWrapper.add(rpmMeter, BorderLayout.CENTER);
|
||||
|
||||
JPanel hpWrapper = new JPanel(new BorderLayout());
|
||||
hpWrapper.setBorder(BorderFactory.createTitledBorder("Leistung [PS]"));
|
||||
hpWrapper.add(hpMeter, BorderLayout.CENTER);
|
||||
|
||||
meterPanel.add(rpmWrapper);
|
||||
meterPanel.add(hpWrapper);
|
||||
add(meterPanel, BorderLayout.NORTH);
|
||||
|
||||
// Center panel: chart and labels
|
||||
@@ -46,24 +67,35 @@ public class DynoGUI extends JFrame {
|
||||
|
||||
leistungLabel = new JLabel("Leistung: 0.00 PS");
|
||||
drehmomentLabel = new JLabel("Drehmoment: 0.00 Nm");
|
||||
maxLeistungLabel = new JLabel("Max Leistung: 0.00 PS");
|
||||
maxDrehmomentLabel = new JLabel("Max Drehmoment: 0.00 Nm");
|
||||
|
||||
leistungLabel.setFont(new Font("SansSerif", Font.BOLD, 24));
|
||||
drehmomentLabel.setFont(new Font("SansSerif", Font.BOLD, 24));
|
||||
maxLeistungLabel.setFont(new Font("SansSerif", Font.PLAIN, 14));
|
||||
maxDrehmomentLabel.setFont(new Font("SansSerif", Font.PLAIN, 14));
|
||||
|
||||
leistungLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
drehmomentLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
maxLeistungLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
maxDrehmomentLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
|
||||
labelPanel.add(leistungLabel);
|
||||
labelPanel.add(Box.createVerticalStrut(10));
|
||||
labelPanel.add(drehmomentLabel);
|
||||
labelPanel.add(Box.createVerticalStrut(5));
|
||||
labelPanel.add(maxLeistungLabel);
|
||||
labelPanel.add(Box.createVerticalStrut(5));
|
||||
labelPanel.add(maxDrehmomentLabel);
|
||||
centerPanel.add(labelPanel, BorderLayout.NORTH);
|
||||
|
||||
// Data series
|
||||
powerSeries = new XYSeries("Power (PS)");
|
||||
torqueSeries = new XYSeries("Torque (Nm)");
|
||||
efficiencySeries = new XYSeries("Efficiency");
|
||||
|
||||
XYSeriesCollection powerDataset = new XYSeriesCollection(powerSeries);
|
||||
XYSeriesCollection torqueDataset = new XYSeriesCollection(torqueSeries);
|
||||
XYSeriesCollection efficiencyDataset = new XYSeriesCollection(efficiencySeries);
|
||||
|
||||
// Date for chart title
|
||||
String dateStr = LocalDate.now().format(DateTimeFormatter.ofPattern("dd.MM.yyyy"));
|
||||
@@ -107,6 +139,15 @@ public class DynoGUI extends JFrame {
|
||||
plot.setDataset(1, torqueDataset);
|
||||
plot.mapDatasetToRangeAxis(1, 1);
|
||||
|
||||
// Efficiency axis (additional, right side 2)
|
||||
NumberAxis effAxis = new NumberAxis("Effizienz [arb.u.]");
|
||||
effAxis.setAutoRangeIncludesZero(false);
|
||||
effAxis.setTickLabelFont(new Font("SansSerif", Font.PLAIN, 12));
|
||||
effAxis.setLabelFont(new Font("SansSerif", Font.BOLD, 14));
|
||||
plot.setRangeAxis(2, effAxis);
|
||||
plot.setDataset(2, efficiencyDataset);
|
||||
plot.mapDatasetToRangeAxis(2, 2);
|
||||
|
||||
// X axis
|
||||
NumberAxis xAxis = (NumberAxis) plot.getDomainAxis();
|
||||
xAxis.setRange(0.0, 12000.0);
|
||||
@@ -123,6 +164,12 @@ public class DynoGUI extends JFrame {
|
||||
torqueRenderer.setSeriesPaint(0, new Color(38, 139, 210)); // Blue
|
||||
plot.setRenderer(1, torqueRenderer);
|
||||
|
||||
XYLineAndShapeRenderer effRenderer = new XYLineAndShapeRenderer(false, true);
|
||||
effRenderer.setSeriesPaint(0, new Color(133, 153, 0)); // green
|
||||
effRenderer.setSeriesShapesVisible(0, true);
|
||||
effRenderer.setSeriesShape(0, new java.awt.geom.Ellipse2D.Double(-4, -4, 8, 8));
|
||||
plot.setRenderer(2, effRenderer);
|
||||
|
||||
// Chart panel setup
|
||||
chartPanel = new ChartPanel(chart);
|
||||
chartPanel.setPreferredSize(new Dimension(800, 400));
|
||||
@@ -149,6 +196,36 @@ public class DynoGUI extends JFrame {
|
||||
leistungLabel.setText(String.format("Leistung: %.2f PS", leistung));
|
||||
drehmomentLabel.setText(String.format("Drehmoment: %.2f Nm", drehmoment));
|
||||
|
||||
// update max hold values
|
||||
boolean effUpdated = false;
|
||||
if (leistung > maxLeistung) {
|
||||
maxLeistung = leistung;
|
||||
maxLeistungLabel.setText(String.format("Max Leistung: %.2f PS", maxLeistung));
|
||||
maxLeistungRpm = rpm;
|
||||
maxLeistungNmAtHp = drehmoment;
|
||||
effUpdated = true;
|
||||
}
|
||||
if (drehmoment > maxDrehmoment) {
|
||||
maxDrehmoment = drehmoment;
|
||||
maxDrehmomentLabel.setText(String.format("Max Drehmoment: %.2f Nm", maxDrehmoment));
|
||||
maxDrehmomentRpm = rpm;
|
||||
maxDrehmomentPsAtNm = leistung;
|
||||
effUpdated = true;
|
||||
}
|
||||
|
||||
if (effUpdated) {
|
||||
efficiencySeries.clear();
|
||||
if (maxLeistungNmAtHp > 0) {
|
||||
efficiencySeries.add(maxLeistungRpm, maxLeistung / maxLeistungNmAtHp);
|
||||
}
|
||||
if (maxDrehmoment > 0) {
|
||||
double effNm = maxDrehmomentPsAtNm == 0 ? 0.0 : maxDrehmomentPsAtNm / maxDrehmoment;
|
||||
// avoid duplicate point if same rpm
|
||||
if (maxDrehmomentRpm != maxLeistungRpm)
|
||||
efficiencySeries.add(maxDrehmomentRpm, effNm);
|
||||
}
|
||||
}
|
||||
|
||||
powerSeries.add(rpm, leistung);
|
||||
torqueSeries.add(rpm, drehmoment);
|
||||
}
|
||||
@@ -156,6 +233,14 @@ public class DynoGUI extends JFrame {
|
||||
public void resetChart() {
|
||||
powerSeries.clear();
|
||||
torqueSeries.clear();
|
||||
efficiencySeries.clear();
|
||||
}
|
||||
|
||||
public void resetMaxHold() {
|
||||
maxLeistung = 0.0;
|
||||
maxDrehmoment = 0.0;
|
||||
maxLeistungLabel.setText("Max Leistung: 0.00 PS");
|
||||
maxDrehmomentLabel.setText("Max Drehmoment: 0.00 Nm");
|
||||
}
|
||||
|
||||
public JFreeChart getChart() {
|
||||
|
||||
@@ -39,6 +39,9 @@ public class Main {
|
||||
private static ScheduledExecutorService serialExecutor;
|
||||
private static BufferedReader serialReader;
|
||||
|
||||
private static CSVLogger csvLogger;
|
||||
private static boolean measurementActive = false;
|
||||
|
||||
public static void main(String[] args) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
dynoGUI = new DynoGUI();
|
||||
@@ -67,10 +70,22 @@ public class Main {
|
||||
resetChartButton.addActionListener(e -> dynoGUI.resetChart());
|
||||
controlPanel.add(resetChartButton);
|
||||
|
||||
JButton resetMaxButton = new JButton("Reset Max-Hold");
|
||||
resetMaxButton.addActionListener(e -> dynoGUI.resetMaxHold());
|
||||
controlPanel.add(resetMaxButton);
|
||||
|
||||
JButton reconnectButton = new JButton("Reconnect Serial");
|
||||
reconnectButton.addActionListener(e -> reconnectSerial());
|
||||
controlPanel.add(reconnectButton);
|
||||
|
||||
JButton startMeasButton = new JButton("Start Messung");
|
||||
startMeasButton.addActionListener(e -> startMeasurement());
|
||||
controlPanel.add(startMeasButton);
|
||||
|
||||
JButton stopMeasButton = new JButton("Stop Messung");
|
||||
stopMeasButton.addActionListener(e -> stopMeasurement());
|
||||
controlPanel.add(stopMeasButton);
|
||||
|
||||
dynoGUI.add(controlPanel, BorderLayout.SOUTH);
|
||||
dynoGUI.revalidate();
|
||||
dynoGUI.repaint();
|
||||
@@ -144,6 +159,10 @@ public class Main {
|
||||
testDataExecutor = null;
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
dynoGUI.updateWerte(0, 0, 0); // Reset gauges
|
||||
if (csvLogger != null) {
|
||||
try { csvLogger.close(); } catch (Exception ex) { ex.printStackTrace(); }
|
||||
csvLogger = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -262,6 +281,11 @@ public class Main {
|
||||
currentSerialPort = null;
|
||||
}
|
||||
|
||||
if (csvLogger != null) {
|
||||
try { csvLogger.close(); } catch (Exception ex) { ex.printStackTrace(); }
|
||||
csvLogger = null;
|
||||
}
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
dynoGUI.updateWerte(0, 0, 0); // Reset gauges
|
||||
});
|
||||
@@ -292,6 +316,19 @@ public class Main {
|
||||
|
||||
lastTimestamp = currentTimestamp;
|
||||
lastOmega = currentOmega;
|
||||
|
||||
if (measurementActive) {
|
||||
if (csvLogger == null) {
|
||||
try {
|
||||
csvLogger = new CSVLogger(new java.io.File("logs"));
|
||||
} catch (Exception ex) {
|
||||
System.err.println("Could not create CSV logger: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
if (csvLogger != null) {
|
||||
csvLogger.log(rpm, leistung, drehmoment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void processRPMData(double rpm, double forcedDeltaTime) {
|
||||
@@ -306,6 +343,19 @@ public class Main {
|
||||
|
||||
lastOmega = currentOmega;
|
||||
lastTimestamp = System.currentTimeMillis();
|
||||
|
||||
if (measurementActive) {
|
||||
if (csvLogger == null) {
|
||||
try {
|
||||
csvLogger = new CSVLogger(new java.io.File("logs"));
|
||||
} catch (Exception ex) {
|
||||
System.err.println("Could not create CSV logger: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
if (csvLogger != null) {
|
||||
csvLogger.log(rpm, leistung, drehmoment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static double rpmToOmega(double rpm) {
|
||||
@@ -340,9 +390,13 @@ public class Main {
|
||||
JButton savePdfButton = new JButton("Save as PDF");
|
||||
savePdfButton.addActionListener(e -> saveChartAsPdf(printChartPanel));
|
||||
|
||||
JButton savePngButton = new JButton("Save as PNG");
|
||||
savePngButton.addActionListener(e -> saveChartAsPng(printChartPanel));
|
||||
|
||||
JPanel controlPanel = new JPanel();
|
||||
controlPanel.add(printButton);
|
||||
controlPanel.add(savePdfButton);
|
||||
controlPanel.add(savePngButton);
|
||||
chartFrame.add(controlPanel, BorderLayout.SOUTH);
|
||||
|
||||
chartFrame.setLocationRelativeTo(dynoGUI);
|
||||
@@ -391,4 +445,60 @@ public class Main {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void saveChartAsPng(ChartPanel chartPanel) {
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
fileChooser.setDialogTitle("Save Chart as PNG");
|
||||
fileChooser.setFileFilter(new FileNameExtensionFilter("PNG Image (*.png)", "png"));
|
||||
|
||||
int userSelection = fileChooser.showSaveDialog(null);
|
||||
|
||||
if (userSelection == JFileChooser.APPROVE_OPTION) {
|
||||
File fileToSave = fileChooser.getSelectedFile();
|
||||
String filePath = fileToSave.getAbsolutePath();
|
||||
if (!filePath.toLowerCase().endsWith(".png")) {
|
||||
filePath += ".png";
|
||||
}
|
||||
File finalFile = new File(filePath);
|
||||
try {
|
||||
org.jfree.chart.ChartUtils.saveChartAsPNG(finalFile, chartPanel.getChart(), chartPanel.getWidth(), chartPanel.getHeight());
|
||||
JOptionPane.showMessageDialog(null, "Chart saved as PNG successfully!\n" + finalFile.getAbsolutePath(), "Save Successful", JOptionPane.INFORMATION_MESSAGE);
|
||||
} catch (Exception ex) {
|
||||
JOptionPane.showMessageDialog(null, "Error saving chart as PNG: " + ex.getMessage(), "Save Error", JOptionPane.ERROR_MESSAGE);
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void startMeasurement() {
|
||||
if (measurementActive) {
|
||||
JOptionPane.showMessageDialog(null, "Messung läuft bereits.", "Info", JOptionPane.INFORMATION_MESSAGE);
|
||||
return;
|
||||
}
|
||||
measurementActive = true;
|
||||
dynoGUI.resetChart();
|
||||
dynoGUI.resetMaxHold();
|
||||
if (csvLogger != null) {
|
||||
try { csvLogger.close(); } catch (Exception ex) { ex.printStackTrace(); }
|
||||
}
|
||||
try {
|
||||
csvLogger = new CSVLogger(new java.io.File("logs"));
|
||||
} catch (Exception ex) {
|
||||
csvLogger = null;
|
||||
System.err.println("CSV Logger konnte nicht erstellt werden: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void stopMeasurement() {
|
||||
if (!measurementActive) {
|
||||
JOptionPane.showMessageDialog(null, "Keine laufende Messung.", "Info", JOptionPane.INFORMATION_MESSAGE);
|
||||
return;
|
||||
}
|
||||
measurementActive = false;
|
||||
if (csvLogger != null) {
|
||||
try { csvLogger.close(); } catch (Exception ex) { ex.printStackTrace(); }
|
||||
csvLogger = null;
|
||||
}
|
||||
JOptionPane.showMessageDialog(null, "Messung gestoppt.", "Info", JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user