very good very nice, C++ working now! so you can test the app with a ESP32's serial port
All checks were successful
Maven Build / build (push) Successful in 10m13s
All checks were successful
Maven Build / build (push) Successful in 10m13s
This commit is contained in:
@@ -1,10 +1,201 @@
|
|||||||
|
#include <WiFi.h>
|
||||||
|
|
||||||
|
// Mock dyno simulation parameters
|
||||||
|
const int SERIAL_BAUD_RATE = 9600;
|
||||||
|
const int DATA_SEND_INTERVAL_MS = 100; // Send data every 100ms
|
||||||
|
const int SIMULATION_STEP_MS = 50; // Update simulation every 50ms
|
||||||
|
|
||||||
|
// Simulation state
|
||||||
|
enum SimulationState {
|
||||||
|
IDLE,
|
||||||
|
ACCELERATING,
|
||||||
|
HOLDING,
|
||||||
|
DECELERATING
|
||||||
|
};
|
||||||
|
|
||||||
|
SimulationState currentState = IDLE;
|
||||||
|
unsigned long lastDataSend = 0;
|
||||||
|
unsigned long lastSimulationUpdate = 0;
|
||||||
|
unsigned long stateStartTime = 0;
|
||||||
|
|
||||||
|
// RPM simulation variables
|
||||||
|
float currentRPM = 0.0;
|
||||||
|
float targetRPM = 0.0;
|
||||||
|
float rpmAcceleration = 0.0;
|
||||||
|
float maxRPM = 11000.0;
|
||||||
|
float minRPM = 800.0; // Idle RPM
|
||||||
|
float idleRPM = 800.0;
|
||||||
|
|
||||||
|
// Dyno run parameters
|
||||||
|
const float ACCELERATION_RATE = 25.0; // RPM per 100ms during acceleration
|
||||||
|
const float DECELERATION_RATE = 30.0; // RPM per 100ms during deceleration
|
||||||
|
const int HOLD_DURATION_MS = 2000; // Hold at max RPM for 2 seconds
|
||||||
|
const int IDLE_DURATION_MS = 3000; // Stay at idle for 3 seconds
|
||||||
|
|
||||||
|
// Add some realistic noise and variations
|
||||||
|
float rpmNoise = 0.0;
|
||||||
|
int noiseCounter = 0;
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(9600); // Ensure this matches the baud rate in Main.java
|
Serial.begin(SERIAL_BAUD_RATE);
|
||||||
|
|
||||||
|
// Initialize random seed
|
||||||
|
randomSeed(analogRead(0));
|
||||||
|
|
||||||
|
// Wait for serial to initialize
|
||||||
|
delay(2000);
|
||||||
|
|
||||||
|
Serial.println("ESP32 Mock Dyno Data Generator");
|
||||||
|
Serial.println("Sending RPM data for dyno simulation...");
|
||||||
|
Serial.println("==================================");
|
||||||
|
|
||||||
|
// Start at idle
|
||||||
|
currentRPM = idleRPM;
|
||||||
|
targetRPM = idleRPM;
|
||||||
|
currentState = IDLE;
|
||||||
|
stateStartTime = millis();
|
||||||
|
|
||||||
|
delay(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
// Replace this with your actual RPM measurement logic
|
unsigned long currentTime = millis();
|
||||||
double currentRPM = 1500.0; // Example RPM value
|
|
||||||
Serial.println(currentRPM);
|
// Update simulation state
|
||||||
delay(100); // Send data every 100ms, adjust as needed
|
if (currentTime - lastSimulationUpdate >= SIMULATION_STEP_MS) {
|
||||||
|
updateSimulation();
|
||||||
|
lastSimulationUpdate = currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send data to Java application
|
||||||
|
if (currentTime - lastDataSend >= DATA_SEND_INTERVAL_MS) {
|
||||||
|
sendRPMData();
|
||||||
|
lastDataSend = currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(10); // Small delay to prevent excessive CPU usage
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateSimulation() {
|
||||||
|
unsigned long currentTime = millis();
|
||||||
|
unsigned long stateElapsed = currentTime - stateStartTime;
|
||||||
|
|
||||||
|
switch (currentState) {
|
||||||
|
case IDLE:
|
||||||
|
// Stay at idle RPM for a while, then start accelerating
|
||||||
|
targetRPM = idleRPM + random(-50, 51); // Add some idle variation
|
||||||
|
if (stateElapsed > IDLE_DURATION_MS) {
|
||||||
|
currentState = ACCELERATING;
|
||||||
|
targetRPM = maxRPM;
|
||||||
|
stateStartTime = currentTime;
|
||||||
|
Serial.println("Starting dyno run - ACCELERATING");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACCELERATING:
|
||||||
|
// Accelerate towards max RPM
|
||||||
|
if (currentRPM < maxRPM - 100) {
|
||||||
|
currentRPM += ACCELERATION_RATE * (SIMULATION_STEP_MS / 100.0);
|
||||||
|
// Add some realistic acceleration curve (not perfectly linear)
|
||||||
|
float accelerationFactor = 1.0 - (currentRPM / maxRPM) * 0.3;
|
||||||
|
currentRPM = currentRPM * accelerationFactor + currentRPM * (1.0 - accelerationFactor);
|
||||||
|
} else {
|
||||||
|
currentState = HOLDING;
|
||||||
|
stateStartTime = currentTime;
|
||||||
|
Serial.println("Reached max RPM - HOLDING");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HOLDING:
|
||||||
|
// Hold at max RPM with some variation
|
||||||
|
targetRPM = maxRPM + random(-100, 101);
|
||||||
|
if (stateElapsed > HOLD_DURATION_MS) {
|
||||||
|
currentState = DECELERATING;
|
||||||
|
targetRPM = idleRPM;
|
||||||
|
stateStartTime = currentTime;
|
||||||
|
Serial.println("Starting deceleration - DECELERATING");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DECELERATING:
|
||||||
|
// Decelerate back to idle
|
||||||
|
if (currentRPM > idleRPM + 100) {
|
||||||
|
currentRPM -= DECELERATION_RATE * (SIMULATION_STEP_MS / 100.0);
|
||||||
|
// Add engine braking effect (faster deceleration at higher RPM)
|
||||||
|
float brakingFactor = 1.0 + (currentRPM / maxRPM) * 0.5;
|
||||||
|
currentRPM -= (DECELERATION_RATE * brakingFactor - DECELERATION_RATE) * (SIMULATION_STEP_MS / 100.0);
|
||||||
|
} else {
|
||||||
|
currentRPM = idleRPM;
|
||||||
|
currentState = IDLE;
|
||||||
|
stateStartTime = currentTime;
|
||||||
|
Serial.println("Returned to idle - IDLE");
|
||||||
|
Serial.println("==================================");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add realistic noise and fluctuations
|
||||||
|
addRealisticNoise();
|
||||||
|
|
||||||
|
// Ensure RPM doesn't go below 0 or above reasonable limits
|
||||||
|
currentRPM = constrain(currentRPM, 0, maxRPM + 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addRealisticNoise() {
|
||||||
|
// Add different types of noise based on RPM range
|
||||||
|
float baseNoise = random(-10, 11); // Basic noise
|
||||||
|
|
||||||
|
// Engine vibration increases with RPM
|
||||||
|
float vibrationNoise = (currentRPM / 1000.0) * random(-5, 6);
|
||||||
|
|
||||||
|
// Periodic fluctuations (simulating engine cycles)
|
||||||
|
noiseCounter++;
|
||||||
|
float periodicNoise = sin(noiseCounter * 0.1) * (currentRPM / 2000.0) * 3.0;
|
||||||
|
|
||||||
|
// Combine all noise sources
|
||||||
|
rpmNoise = baseNoise + vibrationNoise + periodicNoise;
|
||||||
|
|
||||||
|
// Apply noise with some smoothing
|
||||||
|
currentRPM += rpmNoise * 0.3; // Reduce noise impact
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendRPMData() {
|
||||||
|
// Send just the RPM value as your Java code expects
|
||||||
|
Serial.println(currentRPM, 0); // Send as integer (no decimal places)
|
||||||
|
|
||||||
|
// Optional: Send debug info to Serial Monitor (comment out for production)
|
||||||
|
// Serial.print("DEBUG - State: ");
|
||||||
|
// Serial.print(getStateString());
|
||||||
|
// Serial.print(", RPM: ");
|
||||||
|
// Serial.print(currentRPM, 1);
|
||||||
|
// Serial.print(", Noise: ");
|
||||||
|
// Serial.println(rpmNoise, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getStateString() {
|
||||||
|
switch (currentState) {
|
||||||
|
case IDLE: return "IDLE";
|
||||||
|
case ACCELERATING: return "ACCELERATING";
|
||||||
|
case HOLDING: return "HOLDING";
|
||||||
|
case DECELERATING: return "DECELERATING";
|
||||||
|
default: return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to manually trigger a dyno run (call this if you want manual control)
|
||||||
|
void triggerDynoRun() {
|
||||||
|
if (currentState == IDLE) {
|
||||||
|
currentState = ACCELERATING;
|
||||||
|
stateStartTime = millis();
|
||||||
|
Serial.println("Manual dyno run triggered!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to reset to idle (emergency stop)
|
||||||
|
void resetToIdle() {
|
||||||
|
currentState = IDLE;
|
||||||
|
currentRPM = idleRPM;
|
||||||
|
targetRPM = idleRPM;
|
||||||
|
stateStartTime = millis();
|
||||||
|
Serial.println("Reset to idle!");
|
||||||
}
|
}
|
||||||
@@ -29,66 +29,28 @@ public class Main {
|
|||||||
private static LeistungBerechner leistungBerechner;
|
private static LeistungBerechner leistungBerechner;
|
||||||
private static long lastTimestamp = 0;
|
private static long lastTimestamp = 0;
|
||||||
private static double lastOmega = 0.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 final double WHEEL_RADIUS_METERS = 0.25;
|
||||||
|
|
||||||
private static boolean testMode = false;
|
private static boolean testMode = false;
|
||||||
private static ScheduledExecutorService testDataExecutor;
|
private static ScheduledExecutorService testDataExecutor;
|
||||||
private static int testRpmCounter = 1000;
|
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 SerialPort currentSerialPort;
|
||||||
private static double testRpmCurrentValue = 0.0;
|
private static ScheduledExecutorService serialExecutor;
|
||||||
private static final double TEST_RPM_INCREMENT_STEP = 100.0; // Increased for faster simulation
|
private static BufferedReader serialReader;
|
||||||
|
|
||||||
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) {
|
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(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
dynoGUI = new DynoGUI();
|
dynoGUI = new DynoGUI();
|
||||||
leistungBerechner = new LeistungBerechner();
|
leistungBerechner = new LeistungBerechner();
|
||||||
addControlPanel();
|
addControlPanel();
|
||||||
// Optionally start in test mode for development
|
startSerialPortListener(); // Start listening for serial data immediately
|
||||||
// toggleTestMode();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addControlPanel() {
|
private static void addControlPanel() {
|
||||||
JPanel controlPanel = new JPanel();
|
JPanel controlPanel = new JPanel();
|
||||||
|
|
||||||
JButton toggleModeButton = new JButton("Toggle Test Mode");
|
JButton toggleModeButton = new JButton("Toggle Test Mode");
|
||||||
toggleModeButton.addActionListener(e -> toggleTestMode());
|
toggleModeButton.addActionListener(e -> toggleTestMode());
|
||||||
controlPanel.add(toggleModeButton);
|
controlPanel.add(toggleModeButton);
|
||||||
@@ -105,11 +67,20 @@ public class Main {
|
|||||||
resetChartButton.addActionListener(e -> dynoGUI.resetChart());
|
resetChartButton.addActionListener(e -> dynoGUI.resetChart());
|
||||||
controlPanel.add(resetChartButton);
|
controlPanel.add(resetChartButton);
|
||||||
|
|
||||||
|
JButton reconnectButton = new JButton("Reconnect Serial");
|
||||||
|
reconnectButton.addActionListener(e -> reconnectSerial());
|
||||||
|
controlPanel.add(reconnectButton);
|
||||||
|
|
||||||
dynoGUI.add(controlPanel, BorderLayout.SOUTH);
|
dynoGUI.add(controlPanel, BorderLayout.SOUTH);
|
||||||
dynoGUI.revalidate();
|
dynoGUI.revalidate();
|
||||||
dynoGUI.repaint();
|
dynoGUI.repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void reconnectSerial() {
|
||||||
|
stopSerialPortListener();
|
||||||
|
startSerialPortListener();
|
||||||
|
}
|
||||||
|
|
||||||
private static void toggleTestMode() {
|
private static void toggleTestMode() {
|
||||||
testMode = !testMode;
|
testMode = !testMode;
|
||||||
if (testMode) {
|
if (testMode) {
|
||||||
@@ -133,69 +104,38 @@ public class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dynoGUI.resetChart();
|
dynoGUI.resetChart();
|
||||||
lastTimestamp = 0; // Reset for a new run
|
lastTimestamp = 0;
|
||||||
lastOmega = 0.0;
|
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 = Executors.newSingleThreadScheduledExecutor();
|
||||||
testDataExecutor.scheduleAtFixedRate(() -> {
|
testDataExecutor.scheduleAtFixedRate(() -> {
|
||||||
simulateSmoothRpmRun();
|
// Simulate a realistic dyno run pattern
|
||||||
}, 0, 1, TimeUnit.MILLISECONDS); // Update RPM every 10ms for faster animation
|
long currentTime = System.currentTimeMillis();
|
||||||
}
|
double elapsedSeconds = (currentTime - lastTimestamp) / 1000.0;
|
||||||
|
|
||||||
private static void simulateSmoothRpmRun() {
|
// Simulate acceleration, hold, then deceleration
|
||||||
if (smoothRpmDataIndex >= smoothRpmData.size()) {
|
double simulatedRpm;
|
||||||
|
if (elapsedSeconds < 5.0) {
|
||||||
|
// Acceleration phase (0-5 seconds)
|
||||||
|
simulatedRpm = 800 + (11000 - 800) * (elapsedSeconds / 5.0);
|
||||||
|
} else if (elapsedSeconds < 7.0) {
|
||||||
|
// Hold at max RPM (5-7 seconds)
|
||||||
|
simulatedRpm = 11000;
|
||||||
|
} else if (elapsedSeconds < 12.0) {
|
||||||
|
// Deceleration phase (7-12 seconds)
|
||||||
|
simulatedRpm = 11000 - (11000 - 800) * ((elapsedSeconds - 7.0) / 5.0);
|
||||||
|
} else {
|
||||||
|
// Return to idle after 12 seconds
|
||||||
|
simulatedRpm = 800;
|
||||||
stopTestRun();
|
stopTestRun();
|
||||||
JOptionPane.showMessageDialog(null, "Test run finished.", "Info", JOptionPane.INFORMATION_MESSAGE);
|
|
||||||
showPrintableChart();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double currentSimulatedRpm = smoothRpmData.get(smoothRpmDataIndex);
|
// Add some realistic noise
|
||||||
processRPMData(currentSimulatedRpm, TEST_SIMULATION_DELTA_TIME);
|
simulatedRpm += (Math.random() * 200 - 100);
|
||||||
smoothRpmDataIndex++;
|
simulatedRpm = Math.max(0, simulatedRpm);
|
||||||
|
|
||||||
|
processRPMData(simulatedRpm, 0.05); // Use 50ms delta time for test mode
|
||||||
|
}, 0, 50, TimeUnit.MILLISECONDS); // Update every 50ms
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void stopTestRun() {
|
private static void stopTestRun() {
|
||||||
@@ -208,9 +148,6 @@ public class Main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SerialPort currentSerialPort;
|
|
||||||
private static ScheduledExecutorService serialExecutor;
|
|
||||||
|
|
||||||
private static void startSerialPortListener() {
|
private static void startSerialPortListener() {
|
||||||
SerialPort[] comPorts = SerialPort.getCommPorts();
|
SerialPort[] comPorts = SerialPort.getCommPorts();
|
||||||
if (comPorts.length == 0) {
|
if (comPorts.length == 0) {
|
||||||
@@ -250,23 +187,29 @@ public class Main {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final SerialPort finalChosenPort = chosenPort;
|
currentSerialPort = chosenPort;
|
||||||
currentSerialPort = finalChosenPort; // Store for later closing
|
chosenPort.setBaudRate(9600);
|
||||||
|
chosenPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 100, 0);
|
||||||
|
|
||||||
finalChosenPort.setBaudRate(9600); // Common baud rate, can be adjusted
|
if (chosenPort.openPort()) {
|
||||||
if (finalChosenPort.openPort()) {
|
System.out.println("Serial port " + chosenPort.getSystemPortName() + " opened successfully.");
|
||||||
System.out.println("Serial port " + finalChosenPort.getSystemPortName() + " opened successfully.");
|
serialReader = new BufferedReader(new InputStreamReader(chosenPort.getInputStream()));
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(finalChosenPort.getInputStream()));
|
|
||||||
|
|
||||||
serialExecutor = Executors.newSingleThreadScheduledExecutor();
|
serialExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||||
serialExecutor.scheduleAtFixedRate(() -> {
|
serialExecutor.scheduleAtFixedRate(() -> {
|
||||||
try {
|
try {
|
||||||
if (reader.ready()) {
|
if (serialReader.ready()) {
|
||||||
String line = reader.readLine();
|
String line = serialReader.readLine();
|
||||||
if (line != null && !line.trim().isEmpty()) {
|
if (line != null && !line.trim().isEmpty()) {
|
||||||
try {
|
try {
|
||||||
double rpm = Double.parseDouble(line.trim());
|
// Handle potential incomplete lines or multiple values
|
||||||
processRPMData(rpm); // Call the main processRPMData for live data
|
String[] values = line.trim().split("[\\s,]+");
|
||||||
|
for (String value : values) {
|
||||||
|
if (!value.isEmpty()) {
|
||||||
|
double rpm = Double.parseDouble(value);
|
||||||
|
processRPMData(rpm); // Process each valid RPM value
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
System.err.println("Received non-numeric data: " + line);
|
System.err.println("Received non-numeric data: " + line);
|
||||||
}
|
}
|
||||||
@@ -276,89 +219,72 @@ public class Main {
|
|||||||
System.err.println("Error reading from serial port: " + e.getMessage());
|
System.err.println("Error reading from serial port: " + e.getMessage());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
stopSerialPortListener();
|
stopSerialPortListener();
|
||||||
JOptionPane.showMessageDialog(null, "Serial port error: " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
JOptionPane.showMessageDialog(null,
|
||||||
|
"Serial port error: " + e.getMessage(),
|
||||||
|
"Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, 0, 100, TimeUnit.MILLISECONDS); // Read every 100ms
|
}, 0, 10, TimeUnit.MILLISECONDS); // Check more frequently for data
|
||||||
} else {
|
} else {
|
||||||
JOptionPane.showMessageDialog(null, "Failed to open serial port " + finalChosenPort.getSystemPortName(), "Error", JOptionPane.ERROR_MESSAGE);
|
JOptionPane.showMessageDialog(null,
|
||||||
|
"Failed to open serial port " + chosenPort.getSystemPortName(),
|
||||||
|
"Error", JOptionPane.ERROR_MESSAGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void stopSerialPortListener() {
|
private static void stopSerialPortListener() {
|
||||||
if (serialExecutor != null) {
|
if (serialExecutor != null) {
|
||||||
serialExecutor.shutdownNow();
|
serialExecutor.shutdownNow();
|
||||||
|
try {
|
||||||
|
if (!serialExecutor.awaitTermination(500, TimeUnit.MILLISECONDS)) {
|
||||||
|
serialExecutor.shutdownNow();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
serialExecutor.shutdownNow();
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
serialExecutor = null;
|
serialExecutor = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (serialReader != null) {
|
||||||
|
try {
|
||||||
|
serialReader.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Error closing serial reader: " + e.getMessage());
|
||||||
|
}
|
||||||
|
serialReader = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (currentSerialPort != null && currentSerialPort.isOpen()) {
|
if (currentSerialPort != null && currentSerialPort.isOpen()) {
|
||||||
currentSerialPort.closePort();
|
currentSerialPort.closePort();
|
||||||
System.out.println("Serial port " + currentSerialPort.getSystemPortName() + " closed.");
|
System.out.println("Serial port " + currentSerialPort.getSystemPortName() + " closed.");
|
||||||
currentSerialPort = null;
|
currentSerialPort = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
dynoGUI.updateWerte(0, 0, 0); // Reset gauges
|
dynoGUI.updateWerte(0, 0, 0); // Reset gauges
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overloaded processRPMData for live mode, calculates deltaZeit from system time
|
|
||||||
private static void processRPMData(double rpm) {
|
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();
|
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) {
|
if (lastTimestamp == 0) {
|
||||||
lastTimestamp = currentTimestamp;
|
lastTimestamp = currentTimestamp;
|
||||||
lastOmega = rpmToOmega(rpm);
|
lastOmega = rpmToOmega(rpm);
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
dynoGUI.updateWerte(0, 0, rpm); // Update RPM, but no power/torque yet for the first point
|
dynoGUI.updateWerte(0, 0, rpm);
|
||||||
});
|
});
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
drehmoment = leistungBerechner.berechneDrehmoment(lastOmega, currentOmega, effectiveDeltaZeit);
|
double deltaTime = (currentTimestamp - lastTimestamp) / 1000.0;
|
||||||
leistung = leistungBerechner.berechneLeistung(drehmoment, currentOmega);
|
if (deltaTime <= 0) deltaTime = 0.001;
|
||||||
|
|
||||||
|
double currentOmega = rpmToOmega(rpm);
|
||||||
|
double drehmoment = leistungBerechner.berechneDrehmoment(lastOmega, currentOmega, deltaTime);
|
||||||
|
double leistung = leistungBerechner.berechneLeistung(drehmoment, currentOmega);
|
||||||
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
dynoGUI.updateWerte(leistung, drehmoment, rpm);
|
dynoGUI.updateWerte(leistung, drehmoment, rpm);
|
||||||
@@ -367,33 +293,19 @@ public class Main {
|
|||||||
lastTimestamp = currentTimestamp;
|
lastTimestamp = currentTimestamp;
|
||||||
lastOmega = currentOmega;
|
lastOmega = currentOmega;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Linear interpolation method
|
private static void processRPMData(double rpm, double forcedDeltaTime) {
|
||||||
private static double interpolate(TreeMap<Double, Double> curve, double rpm) {
|
// For test mode with forced delta time
|
||||||
if (curve.containsKey(rpm)) {
|
double currentOmega = rpmToOmega(rpm);
|
||||||
return curve.get(rpm);
|
double drehmoment = leistungBerechner.berechneDrehmoment(lastOmega, currentOmega, forcedDeltaTime);
|
||||||
}
|
double leistung = leistungBerechner.berechneLeistung(drehmoment, currentOmega);
|
||||||
Map.Entry<Double, Double> lower = curve.floorEntry(rpm);
|
|
||||||
Map.Entry<Double, Double> upper = curve.ceilingEntry(rpm);
|
|
||||||
|
|
||||||
if (lower == null) {
|
SwingUtilities.invokeLater(() -> {
|
||||||
return upper.getValue(); // RPM is below lowest defined point, return lowest point's value
|
dynoGUI.updateWerte(leistung, drehmoment, rpm);
|
||||||
}
|
});
|
||||||
if (upper == null) {
|
|
||||||
return lower.getValue(); // RPM is above highest defined point, return highest point's value
|
|
||||||
}
|
|
||||||
|
|
||||||
double x1 = lower.getKey();
|
lastOmega = currentOmega;
|
||||||
double y1 = lower.getValue();
|
lastTimestamp = System.currentTimeMillis();
|
||||||
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) {
|
private static double rpmToOmega(double rpm) {
|
||||||
@@ -418,7 +330,9 @@ public class Main {
|
|||||||
try {
|
try {
|
||||||
job.print();
|
job.print();
|
||||||
} catch (PrinterException ex) {
|
} catch (PrinterException ex) {
|
||||||
JOptionPane.showMessageDialog(chartFrame, "Printing error: " + ex.getMessage(), "Print Error", JOptionPane.ERROR_MESSAGE);
|
JOptionPane.showMessageDialog(chartFrame,
|
||||||
|
"Printing error: " + ex.getMessage(),
|
||||||
|
"Print Error", JOptionPane.ERROR_MESSAGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -451,25 +365,28 @@ public class Main {
|
|||||||
File finalFile = new File(filePath);
|
File finalFile = new File(filePath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Use the dimensions of the ChartPanel to create the PDF document
|
|
||||||
Document document = new Document(new Rectangle(chartPanel.getWidth(), chartPanel.getHeight()));
|
Document document = new Document(new Rectangle(chartPanel.getWidth(), chartPanel.getHeight()));
|
||||||
PdfWriter.getInstance(document, new FileOutputStream(finalFile));
|
PdfWriter.getInstance(document, new FileOutputStream(finalFile));
|
||||||
document.open();
|
document.open();
|
||||||
|
|
||||||
// Create BufferedImage from the ChartPanel's chart
|
java.awt.Image awtImage = chartPanel.getChart().createBufferedImage(
|
||||||
java.awt.Image awtImage = chartPanel.getChart().createBufferedImage(chartPanel.getWidth(), chartPanel.getHeight());
|
chartPanel.getWidth(), chartPanel.getHeight());
|
||||||
Image pdfImage = Image.getInstance(awtImage, null);
|
Image pdfImage = Image.getInstance(awtImage, null);
|
||||||
|
|
||||||
// Scale image to fit document
|
pdfImage.scaleToFit(document.getPageSize().getWidth() - 20,
|
||||||
pdfImage.scaleToFit(document.getPageSize().getWidth() - 20, document.getPageSize().getHeight() - 20); // Add some padding
|
document.getPageSize().getHeight() - 20);
|
||||||
pdfImage.setAlignment(Image.ALIGN_CENTER);
|
pdfImage.setAlignment(Image.ALIGN_CENTER);
|
||||||
|
|
||||||
document.add(pdfImage);
|
document.add(pdfImage);
|
||||||
document.close();
|
document.close();
|
||||||
|
|
||||||
JOptionPane.showMessageDialog(null, "Chart saved as PDF successfully!\n" + finalFile.getAbsolutePath(), "Save Successful", JOptionPane.INFORMATION_MESSAGE);
|
JOptionPane.showMessageDialog(null,
|
||||||
|
"Chart saved as PDF successfully!\n" + finalFile.getAbsolutePath(),
|
||||||
|
"Save Successful", JOptionPane.INFORMATION_MESSAGE);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
JOptionPane.showMessageDialog(null, "Error saving chart as PDF: " + ex.getMessage(), "Save Error", JOptionPane.ERROR_MESSAGE);
|
JOptionPane.showMessageDialog(null,
|
||||||
|
"Error saving chart as PDF: " + ex.getMessage(),
|
||||||
|
"Save Error", JOptionPane.ERROR_MESSAGE);
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user