From 890abc303d368b536cee7d95c3ac80dbcc6b644c Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Tue, 10 Jun 2025 19:47:40 +0200 Subject: [PATCH] shit --- .gitignore | 4 + src/main/java/com/puchdyno/AnalogMeter.java | 11 +- src/main/java/com/puchdyno/CSVLogger.java | 53 ++++++++++ src/main/java/com/puchdyno/DynoGUI.java | 93 ++++++++++++++++- src/main/java/com/puchdyno/Main.java | 110 ++++++++++++++++++++ 5 files changed, 262 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/puchdyno/CSVLogger.java diff --git a/.gitignore b/.gitignore index ed12cf4..4845726 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/src/main/java/com/puchdyno/AnalogMeter.java b/src/main/java/com/puchdyno/AnalogMeter.java index 68fd1d9..ed1954f 100644 --- a/src/main/java/com/puchdyno/AnalogMeter.java +++ b/src/main/java/com/puchdyno/AnalogMeter.java @@ -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 } } \ No newline at end of file diff --git a/src/main/java/com/puchdyno/CSVLogger.java b/src/main/java/com/puchdyno/CSVLogger.java new file mode 100644 index 0000000..ac7d4ed --- /dev/null +++ b/src/main/java/com/puchdyno/CSVLogger.java @@ -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(); + } +} \ No newline at end of file diff --git a/src/main/java/com/puchdyno/DynoGUI.java b/src/main/java/com/puchdyno/DynoGUI.java index eb7a81f..eddf250 100644 --- a/src/main/java/com/puchdyno/DynoGUI.java +++ b/src/main/java/com/puchdyno/DynoGUI.java @@ -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() { diff --git a/src/main/java/com/puchdyno/Main.java b/src/main/java/com/puchdyno/Main.java index 390be14..1dea028 100644 --- a/src/main/java/com/puchdyno/Main.java +++ b/src/main/java/com/puchdyno/Main.java @@ -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); + } } \ No newline at end of file