yessssssssss!

This commit is contained in:
rattatwinko
2025-06-09 20:55:31 +02:00
parent cbbe868c8c
commit e80a94ff94
10 changed files with 773 additions and 0 deletions

6
.gitignore vendored
View File

@@ -58,3 +58,9 @@ replay_pid*
*.out
*.app
<<<<<<< HEAD
=======
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!)

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "interactive"
}

9
LICENCE Normal file
View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) 2025 rattatwinko
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

10
arduinosrc/main.ino Normal file
View File

@@ -0,0 +1,10 @@
void setup() {
Serial.begin(9600); // Ensure this matches the baud rate in Main.java
}
void loop() {
// Replace this with your actual RPM measurement logic
double currentRPM = 1500.0; // Example RPM value
Serial.println(currentRPM);
delay(100); // Send data every 100ms, adjust as needed
}

83
pom.xml Normal file
View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.puchdyno</groupId>
<artifactId>PuchDyno</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- jSerialComm for serial port communication -->
<dependency>
<groupId>com.fazecast</groupId>
<artifactId>jSerialComm</artifactId>
<version>2.10.0</version>
</dependency>
<!-- JFreeChart for plotting -->
<dependency>
<groupId>org.jfree</groupId>
<artifactId>jfreechart</artifactId>
<version>1.5.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>com.puchdyno.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<archive>
<manifest>
<mainClass>
com.puchdyno.Main
</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

2
run.sh Executable file
View File

@@ -0,0 +1,2 @@
mvn clean install
java -jar target/PuchDyno-1.0-SNAPSHOT-jar-with-dependencies.jar

View File

@@ -0,0 +1,87 @@
package com.puchdyno;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
public class AnalogMeter extends JPanel {
private String label;
private double maxValue;
private double currentValue;
private Color needleColor;
public AnalogMeter(String label, double maxValue, Color needleColor) {
this.label = label;
this.maxValue = maxValue;
this.currentValue = 0.0;
this.needleColor = needleColor;
setPreferredSize(new Dimension(200, 200));
}
public void setValue(double value) {
if (value < 0) value = 0;
if (value > maxValue) value = maxValue;
this.currentValue = value;
repaint();
}
public void setNeedleColor(Color color) {
this.needleColor = color;
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = Math.min(centerX, centerY) - 10;
// Draw meter background
g2d.setColor(Color.LIGHT_GRAY);
g2d.fillOval(centerX - radius, centerY - radius, radius * 2, radius * 2);
// Draw scale and ticks
g2d.setColor(Color.BLACK);
g2d.drawOval(centerX - radius, centerY - radius, radius * 2, radius * 2);
for (int i = 0; i <= 10; i++) {
double angle = Math.toRadians(210 - (i * 24)); // Start at 210 degrees, end at -30 degrees
int x1 = (int) (centerX + radius * 0.8 * Math.cos(angle));
int y1 = (int) (centerY - radius * 0.8 * Math.sin(angle));
int x2 = (int) (centerX + radius * 0.9 * Math.cos(angle));
int y2 = (int) (centerY - radius * 0.9 * Math.sin(angle));
g2d.drawLine(x1, y1, x2, y2);
String tickLabel = String.valueOf((int) (maxValue / 10 * i));
FontMetrics fm = g2d.getFontMetrics();
int labelWidth = fm.stringWidth(tickLabel);
int labelHeight = fm.getHeight();
int labelX = (int) (centerX + radius * 0.7 * Math.cos(angle) - labelWidth / 2);
int labelY = (int) (centerY - radius * 0.7 * Math.sin(angle) + labelHeight / 4);
g2d.drawString(tickLabel, labelX, labelY);
}
// Draw needle
g2d.setColor(needleColor);
double angle = Math.toRadians(210 - (currentValue / maxValue) * 240); // 240 degrees for the full sweep (210 to -30)
int needleLength = (int) (radius * 0.75);
int needleX = (int) (centerX + needleLength * Math.cos(angle));
int needleY = (int) (centerY - needleLength * Math.sin(angle));
g2d.setStroke(new BasicStroke(3));
g2d.drawLine(centerX, centerY, needleX, needleY);
g2d.fillOval(centerX - 5, centerY - 5, 10, 10);
// Draw label
g2d.setColor(Color.BLACK);
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);
}
}

View File

@@ -0,0 +1,130 @@
package com.puchdyno;
import javax.swing.*;
import java.awt.*;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
public class DynoGUI extends JFrame {
private JLabel leistungLabel;
private JLabel drehmomentLabel;
private AnalogMeter rpmMeter;
private AnalogMeter hpMeter;
private XYSeries powerSeries;
private XYSeries torqueSeries;
private ChartPanel chartPanel;
public DynoGUI() {
setTitle("Puch Maxi Dyno");
setSize(1000, 700); // Increased size for more elements
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
// Top panel for analog meters
JPanel meterPanel = new JPanel(new GridLayout(1, 2));
rpmMeter = new AnalogMeter("RPM", 12000, Color.BLUE); // Max RPM, adjusted for new test range
hpMeter = new AnalogMeter("Horse Power", 15, Color.BLUE); // Max HP, e.g., 15
meterPanel.add(rpmMeter);
meterPanel.add(hpMeter);
add(meterPanel, BorderLayout.NORTH);
// Center panel for labels and chart
JPanel centerPanel = new JPanel(new BorderLayout());
JPanel labelPanel = new JPanel(new GridLayout(2, 1));
leistungLabel = new JLabel("Leistung: 0.00 PS", SwingConstants.CENTER);
drehmomentLabel = new JLabel("Drehmoment: 0.00 Nm", SwingConstants.CENTER);
leistungLabel.setFont(new Font("Arial", Font.BOLD, 24));
drehmomentLabel.setFont(new Font("Arial", Font.BOLD, 24));
labelPanel.add(leistungLabel);
labelPanel.add(drehmomentLabel);
centerPanel.add(labelPanel, BorderLayout.NORTH);
// Chart setup
powerSeries = new XYSeries("Power (PS)");
torqueSeries = new XYSeries("Torque (Nm)");
XYSeriesCollection dataset = new XYSeriesCollection();
dataset.addSeries(powerSeries);
dataset.addSeries(torqueSeries);
JFreeChart chart = ChartFactory.createXYLineChart(
"Leistungsdiagramm - Motorleistung",
"U/min",
"Leistung [PS]",
dataset,
PlotOrientation.VERTICAL,
true,
true,
false
);
XYPlot plot = chart.getXYPlot();
NumberAxis powerAxis = (NumberAxis) plot.getRangeAxis();
powerAxis.setRange(0.0, 10.0);
powerAxis.setLabel("Leistung [PS]");
NumberAxis torqueAxis = new NumberAxis("Drehmoment [Nm]");
torqueAxis.setRange(0.0, 15.0);
plot.setRangeAxis(1, torqueAxis);
plot.mapDatasetToRangeAxis(1, 1);
NumberAxis xAxis = (NumberAxis) plot.getDomainAxis();
xAxis.setRange(4000.0, 12000.0);
xAxis.setLabel("U/min");
XYLineAndShapeRenderer renderer1 = new XYLineAndShapeRenderer();
renderer1.setSeriesPaint(0, Color.RED);
renderer1.setSeriesShapesVisible(0, false);
plot.setRenderer(0, renderer1);
XYLineAndShapeRenderer renderer2 = new XYLineAndShapeRenderer();
renderer2.setSeriesPaint(0, Color.BLUE);
renderer2.setSeriesShapesVisible(0, false);
plot.setRenderer(1, renderer2);
chartPanel = new ChartPanel(chart);
chartPanel.setPreferredSize(new Dimension(600, 400));
centerPanel.add(chartPanel, BorderLayout.CENTER);
add(centerPanel, BorderLayout.CENTER);
setVisible(true);
}
public void updateWerte(double leistung, double drehmoment, double rpm) {
rpmMeter.setValue(rpm);
hpMeter.setValue(leistung);
// Change HP meter needle color based on performance
if (leistung >= 15.0) {
hpMeter.setNeedleColor(Color.RED);
} else if (leistung >= 10.0) {
hpMeter.setNeedleColor(Color.ORANGE);
} else {
hpMeter.setNeedleColor(Color.BLUE);
}
leistungLabel.setText(String.format("Leistung: %.2f PS", leistung));
drehmomentLabel.setText(String.format("Drehmoment: %.2f Nm", drehmoment));
// Add data to chart
powerSeries.add(rpm, leistung);
torqueSeries.add(rpm, drehmoment);
}
public void resetChart() {
powerSeries.clear();
torqueSeries.clear();
}
public JFreeChart getChart() {
return chartPanel.getChart();
}
}

View File

@@ -0,0 +1,15 @@
package com.puchdyno;
public class LeistungBerechner {
private double J = 0.0285; // Trägheitsmoment in kg*m², adjusted for realistic values
public double berechneDrehmoment(double omegaAlt, double omegaNeu, double deltaZeit) {
double alpha = (omegaNeu - omegaAlt) / deltaZeit;
return J * Math.abs(alpha);
}
public double berechneLeistung(double drehmoment, double omega) {
double leistungWatt = drehmoment * omega;
return leistungWatt / 735.5; // in PS
}
}

View File

@@ -0,0 +1,428 @@
package com.puchdyno;
import com.fazecast.jSerialComm.SerialPort;
import javax.swing.*;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.awt.BorderLayout;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import java.util.ArrayList;
import java.util.TreeMap;
import java.util.Map;
public class Main {
private static DynoGUI dynoGUI;
private static LeistungBerechner leistungBerechner;
private static long lastTimestamp = 0;
private static double lastOmega = 0.0;
private static final double WHEEL_RADIUS_METERS = 0.25; // Example: 25 cm radius for the dyno wheel
private static boolean testMode = false;
private static ScheduledExecutorService testDataExecutor;
private static int testRpmCounter = 1000;
private static final double[] TEST_RPM_VALUES = {
0, 500, 1000, 1500, 2000, 2500, 3000, 3500,
4000, 4500, 5000, 5500, 6000, 6500, 7000, 7500, 8000, 8500,
9000, 9500, 10000, 10500, 11000, 11500, 11000, 10500, 10000, 9500,
9000, 8500, 8000, 7500, 7000, 6500, 6000, 5500, 5000, 4500,
4000, 3500, 3000, 2500, 2000, 1500, 1000, 500, 0
};
private static int testRpmCurrentIndex = 0;
private static double testRpmCurrentValue = 0.0;
private static final double TEST_RPM_INCREMENT_STEP = 1.0; // Very small steps for extremely smooth simulation
private static java.util.List<Double> smoothRpmData;
private static int smoothRpmDataIndex = 0;
private static final double TEST_SIMULATION_DELTA_TIME = 0.05; // 50 milliseconds
private static TreeMap<Double, Double> simulatedPowerCurve;
private static TreeMap<Double, Double> simulatedTorqueCurve;
public static void main(String[] args) {
// Initialize simulated curves
simulatedPowerCurve = new TreeMap<>();
simulatedPowerCurve.put(0.0, 0.0);
simulatedPowerCurve.put(4200.0, 2.0);
simulatedPowerCurve.put(6000.0, 5.0);
simulatedPowerCurve.put(7000.0, 6.0);
simulatedPowerCurve.put(8000.0, 7.5);
simulatedPowerCurve.put(8945.0, 8.5); // Peak Power
simulatedPowerCurve.put(9500.0, 8.0);
simulatedPowerCurve.put(10500.0, 7.0);
simulatedPowerCurve.put(11400.0, 6.5);
simulatedPowerCurve.put(12000.0, 6.0);
simulatedTorqueCurve = new TreeMap<>();
simulatedTorqueCurve.put(0.0, 0.0);
simulatedTorqueCurve.put(4200.0, 3.0);
simulatedTorqueCurve.put(5000.0, 4.0);
simulatedTorqueCurve.put(6000.0, 5.0);
simulatedTorqueCurve.put(8379.0, 6.8); // Peak Torque
simulatedTorqueCurve.put(9000.0, 6.0);
simulatedTorqueCurve.put(10000.0, 5.0);
simulatedTorqueCurve.put(11400.0, 4.0);
simulatedTorqueCurve.put(12000.0, 3.5);
SwingUtilities.invokeLater(() -> {
dynoGUI = new DynoGUI();
leistungBerechner = new LeistungBerechner();
addControlPanel();
// Optionally start in test mode for development
// toggleTestMode();
});
}
private static void addControlPanel() {
JPanel controlPanel = new JPanel();
JButton toggleModeButton = new JButton("Toggle Test Mode");
toggleModeButton.addActionListener(e -> toggleTestMode());
controlPanel.add(toggleModeButton);
JButton startTestButton = new JButton("Start Test Run");
startTestButton.addActionListener(e -> startTestRun());
controlPanel.add(startTestButton);
JButton stopTestButton = new JButton("Stop Test Run");
stopTestButton.addActionListener(e -> stopTestRun());
controlPanel.add(stopTestButton);
JButton resetChartButton = new JButton("Reset Chart");
resetChartButton.addActionListener(e -> dynoGUI.resetChart());
controlPanel.add(resetChartButton);
dynoGUI.add(controlPanel, BorderLayout.SOUTH);
dynoGUI.revalidate();
dynoGUI.repaint();
}
private static void toggleTestMode() {
testMode = !testMode;
if (testMode) {
stopSerialPortListener();
JOptionPane.showMessageDialog(null, "Switched to Test Mode. Click 'Start Test Run' to begin.", "Mode Changed", JOptionPane.INFORMATION_MESSAGE);
} else {
stopTestRun();
startSerialPortListener();
JOptionPane.showMessageDialog(null, "Switched to Live Mode. Attempting to connect to serial port.", "Mode Changed", JOptionPane.INFORMATION_MESSAGE);
}
}
private static void startTestRun() {
if (!testMode) {
JOptionPane.showMessageDialog(null, "Please switch to Test Mode first.", "Error", JOptionPane.ERROR_MESSAGE);
return;
}
if (testDataExecutor != null && !testDataExecutor.isShutdown()) {
JOptionPane.showMessageDialog(null, "Test run already in progress.", "Info", JOptionPane.INFORMATION_MESSAGE);
return;
}
dynoGUI.resetChart();
lastTimestamp = 0; // Reset for a new run
lastOmega = 0.0;
testRpmCurrentIndex = 0;
testRpmCurrentValue = TEST_RPM_VALUES[0]; // Start at the first RPM value
// Pre-generate smooth RPM data
smoothRpmData = new ArrayList<>();
if (TEST_RPM_VALUES.length > 0) {
smoothRpmData.add(TEST_RPM_VALUES[0]); // Start with the first point
}
for (int i = 0; i < TEST_RPM_VALUES.length - 1; i++) {
double startRpm = TEST_RPM_VALUES[i];
double endRpm = TEST_RPM_VALUES[i+1];
if (startRpm == endRpm) {
// If start and end RPM are the same, just add it if it's not already the last element
if (smoothRpmData.isEmpty() || smoothRpmData.get(smoothRpmData.size() - 1).doubleValue() != endRpm) {
smoothRpmData.add(endRpm);
}
continue; // Move to the next segment
}
// Determine the direction of change
int direction = (endRpm > startRpm) ? 1 : -1;
double currentRpm = startRpm;
// Generate intermediate points
while (true) {
double nextRpm = currentRpm + direction * TEST_RPM_INCREMENT_STEP;
// Check if we would overshoot the endRpm
if ((direction > 0 && nextRpm > endRpm) || (direction < 0 && nextRpm < endRpm)) {
// Add the exact endRpm if it's not already the last one
if (smoothRpmData.isEmpty() || smoothRpmData.get(smoothRpmData.size() - 1).doubleValue() != endRpm) {
smoothRpmData.add(endRpm);
}
break; // Segment finished
} else {
smoothRpmData.add(nextRpm);
currentRpm = nextRpm;
}
}
}
smoothRpmDataIndex = 0;
testDataExecutor = Executors.newSingleThreadScheduledExecutor();
testDataExecutor.scheduleAtFixedRate(() -> {
simulateSmoothRpmRun();
}, 0, 50, TimeUnit.MILLISECONDS); // Update RPM every 50ms
}
private static void simulateSmoothRpmRun() {
if (smoothRpmDataIndex >= smoothRpmData.size()) {
stopTestRun();
JOptionPane.showMessageDialog(null, "Test run finished.", "Info", JOptionPane.INFORMATION_MESSAGE);
showPrintableChart();
return;
}
double currentSimulatedRpm = smoothRpmData.get(smoothRpmDataIndex);
processRPMData(currentSimulatedRpm, TEST_SIMULATION_DELTA_TIME);
smoothRpmDataIndex++;
}
private static void stopTestRun() {
if (testDataExecutor != null) {
testDataExecutor.shutdownNow();
testDataExecutor = null;
SwingUtilities.invokeLater(() -> {
dynoGUI.updateWerte(0, 0, 0); // Reset gauges
dynoGUI.resetChart(); // Clear chart on stop
});
}
}
private static SerialPort currentSerialPort;
private static ScheduledExecutorService serialExecutor;
private static void startSerialPortListener() {
SerialPort[] comPorts = SerialPort.getCommPorts();
if (comPorts.length == 0) {
JOptionPane.showMessageDialog(null, "No serial ports found!", "Error", JOptionPane.ERROR_MESSAGE);
return;
}
String[] portNames = new String[comPorts.length];
for (int i = 0; i < comPorts.length; i++) {
portNames[i] = comPorts[i].getSystemPortName();
}
String selectedPortName = (String) JOptionPane.showInputDialog(
null,
"Choose a serial port:",
"Serial Port Selection",
JOptionPane.QUESTION_MESSAGE,
null,
portNames,
portNames[0]);
if (selectedPortName == null) {
JOptionPane.showMessageDialog(null, "No serial port selected. Exiting.", "Info", JOptionPane.INFORMATION_MESSAGE);
return;
}
SerialPort chosenPort = null;
for (SerialPort port : comPorts) {
if (port.getSystemPortName().equals(selectedPortName)) {
chosenPort = port;
break;
}
}
if (chosenPort == null) {
JOptionPane.showMessageDialog(null, "Invalid serial port selected.", "Error", JOptionPane.ERROR_MESSAGE);
return;
}
final SerialPort finalChosenPort = chosenPort;
currentSerialPort = finalChosenPort; // Store for later closing
finalChosenPort.setBaudRate(9600); // Common baud rate, can be adjusted
if (finalChosenPort.openPort()) {
System.out.println("Serial port " + finalChosenPort.getSystemPortName() + " opened successfully.");
BufferedReader reader = new BufferedReader(new InputStreamReader(finalChosenPort.getInputStream()));
serialExecutor = Executors.newSingleThreadScheduledExecutor();
serialExecutor.scheduleAtFixedRate(() -> {
try {
if (reader.ready()) {
String line = reader.readLine();
if (line != null && !line.trim().isEmpty()) {
try {
double rpm = Double.parseDouble(line.trim());
processRPMData(rpm); // Call the main processRPMData for live data
} catch (NumberFormatException e) {
System.err.println("Received non-numeric data: " + line);
}
}
}
} catch (Exception e) {
System.err.println("Error reading from serial port: " + e.getMessage());
e.printStackTrace();
stopSerialPortListener();
JOptionPane.showMessageDialog(null, "Serial port error: " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
}, 0, 100, TimeUnit.MILLISECONDS); // Read every 100ms
} else {
JOptionPane.showMessageDialog(null, "Failed to open serial port " + finalChosenPort.getSystemPortName(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
private static void stopSerialPortListener() {
if (serialExecutor != null) {
serialExecutor.shutdownNow();
serialExecutor = null;
}
if (currentSerialPort != null && currentSerialPort.isOpen()) {
currentSerialPort.closePort();
System.out.println("Serial port " + currentSerialPort.getSystemPortName() + " closed.");
currentSerialPort = null;
}
SwingUtilities.invokeLater(() -> {
dynoGUI.updateWerte(0, 0, 0); // Reset gauges
dynoGUI.resetChart(); // Clear chart on stop
});
}
// Overloaded processRPMData for live mode, calculates deltaZeit from system time
private static void processRPMData(double rpm) {
processRPMData(rpm, 0.0); // Pass 0.0 to indicate no forced delta time, use system time
}
// Main processRPMData for both live and test mode, uses forcedDeltaTime if provided
private static void processRPMData(double rpm, double forcedDeltaTime) {
long currentTimestamp = System.currentTimeMillis();
double effectiveDeltaZeit;
double leistung;
double drehmoment;
if (forcedDeltaTime > 0) {
// Test mode: Use simulated curves with linear interpolation
effectiveDeltaZeit = forcedDeltaTime;
leistung = interpolate(simulatedPowerCurve, rpm);
drehmoment = interpolate(simulatedTorqueCurve, rpm);
SwingUtilities.invokeLater(() -> {
dynoGUI.updateWerte(leistung, drehmoment, rpm);
});
// For test mode, we still need to update lastOmega for the next iteration
// in case the next simulated RPM is used for an actual calculation (e.g. if we switch modes).
lastOmega = rpmToOmega(rpm);
lastTimestamp = currentTimestamp; // Also update timestamp for consistency, though not used in test mode calculation
return;
} else {
// Live mode: Calculate from actual sensor data
if (lastTimestamp == 0) {
lastTimestamp = currentTimestamp;
lastOmega = rpmToOmega(rpm);
SwingUtilities.invokeLater(() -> {
dynoGUI.updateWerte(0, 0, rpm); // Update RPM, but no power/torque yet for the first point
});
return; // Skip calculations for the very first data point
}
effectiveDeltaZeit = (currentTimestamp - lastTimestamp) / 1000.0;
// Ensure effectiveDeltaZeit is always positive and non-zero for live mode
if (effectiveDeltaZeit <= 0) {
effectiveDeltaZeit = 0.001; // Assign a small positive value to avoid division by zero
}
double currentOmega = rpmToOmega(rpm);
// If there's no change in RPM, then acceleration is zero. Display zero power/torque.
if (currentOmega == lastOmega) {
SwingUtilities.invokeLater(() -> {
dynoGUI.updateWerte(0, 0, rpm); // Update RPM, but output zero power/torque
});
lastTimestamp = currentTimestamp; // Crucially update for the next iteration
lastOmega = currentOmega; // Crucially update for the next iteration
return;
}
drehmoment = leistungBerechner.berechneDrehmoment(lastOmega, currentOmega, effectiveDeltaZeit);
leistung = leistungBerechner.berechneLeistung(drehmoment, currentOmega);
SwingUtilities.invokeLater(() -> {
dynoGUI.updateWerte(leistung, drehmoment, rpm);
});
lastTimestamp = currentTimestamp;
lastOmega = currentOmega;
}
}
// Linear interpolation method
private static double interpolate(TreeMap<Double, Double> curve, double rpm) {
if (curve.containsKey(rpm)) {
return curve.get(rpm);
}
Map.Entry<Double, Double> lower = curve.floorEntry(rpm);
Map.Entry<Double, Double> upper = curve.ceilingEntry(rpm);
if (lower == null) {
return upper.getValue(); // RPM is below lowest defined point, return lowest point's value
}
if (upper == null) {
return lower.getValue(); // RPM is above highest defined point, return highest point's value
}
double x1 = lower.getKey();
double y1 = lower.getValue();
double x2 = upper.getKey();
double y2 = upper.getValue();
// Linear interpolation formula: y = y1 + ((y2 - y1) / (x2 - x1)) * (x - x1)
if (x2 == x1) { // Avoid division by zero if two points have the same RPM (shouldn't happen with proper data)
return y1;
}
return y1 + ((y2 - y1) / (x2 - x1)) * (rpm - x1);
}
private static double rpmToOmega(double rpm) {
return (rpm * 2 * Math.PI) / 60;
}
private static void showPrintableChart() {
JFrame chartFrame = new JFrame("Dyno Run Chart - Printable");
chartFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
chartFrame.setSize(800, 600);
JFreeChart chart = dynoGUI.getChart();
ChartPanel printChartPanel = new ChartPanel(chart);
printChartPanel.setMouseWheelEnabled(true);
chartFrame.add(printChartPanel, BorderLayout.CENTER);
JButton printButton = new JButton("Print Chart");
printButton.addActionListener(e -> {
PrinterJob job = PrinterJob.getPrinterJob();
job.setPrintable(printChartPanel);
if (job.printDialog()) {
try {
job.print();
} catch (PrinterException ex) {
JOptionPane.showMessageDialog(chartFrame, "Printing error: " + ex.getMessage(), "Print Error", JOptionPane.ERROR_MESSAGE);
}
}
});
JPanel controlPanel = new JPanel();
controlPanel.add(printButton);
chartFrame.add(controlPanel, BorderLayout.SOUTH);
chartFrame.setLocationRelativeTo(dynoGUI);
chartFrame.setVisible(true);
}
}