Compare commits
4 Commits
64cc14239d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
420fb93a05 | ||
|
|
890abc303d | ||
|
|
67e707eac8 | ||
|
|
c6602315ba |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.py linguist-vendored
|
||||
55
.gitea/workflows/maven_build.yaml
Normal file
55
.gitea/workflows/maven_build.yaml
Normal file
@@ -0,0 +1,55 @@
|
||||
name: Maven Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master, dev ]
|
||||
pull_request:
|
||||
branches: [ main, master ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
cache: 'maven'
|
||||
|
||||
- name: Install Maven
|
||||
run: |
|
||||
if ! command -v mvn &> /dev/null; then
|
||||
echo "Maven not found, installing..."
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y maven
|
||||
fi
|
||||
mvn --version
|
||||
|
||||
- name: Debug Info
|
||||
run: |
|
||||
echo "Current workspace directory: $GITHUB_WORKSPACE"
|
||||
echo "Current directory: $(pwd)"
|
||||
echo "Project structure:"
|
||||
find . -type f -name "*.kt" | sort
|
||||
find . -type f -name "pom.xml"
|
||||
echo "Maven version: $(mvn --version)"
|
||||
|
||||
- name: Build PuchDyno (PuchDyno)
|
||||
run: |
|
||||
echo "Building PuchDyno"
|
||||
echo "Current directory: $(pwd)"
|
||||
# Run Maven build directly using the POM file path
|
||||
mvn -B clean package -f "$GITHUB_WORKSPACE/pom.xml" -Dmaven.compiler.failOnError=true
|
||||
|
||||
- name: Upload PuchDyno artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: PuchDyno
|
||||
path: target/PuchDyno-*.jar
|
||||
if-no-files-found: error
|
||||
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
|
||||
|
||||
@@ -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() {
|
||||
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() {
|
||||
// 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
|
||||
unsigned long currentTime = millis();
|
||||
|
||||
// Update simulation state
|
||||
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!");
|
||||
}
|
||||
10
build.sh
Executable file
10
build.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
# Direct build script for Maven project
|
||||
|
||||
echo "Current directory: $(pwd)"
|
||||
echo "Building project with Maven..."
|
||||
|
||||
# Run Maven build using the exact pom.xml location
|
||||
mvn clean package -f "$(pwd)/pom.xml"
|
||||
|
||||
echo "Build complete. JAR file should be in target/ directory."
|
||||
291
pommer.py
vendored
Normal file
291
pommer.py
vendored
Normal file
@@ -0,0 +1,291 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
POMMER.PY
|
||||
|
||||
THIS IS PROPRIETARY SOFTWARE DO NOT DISTRIBUTE TO OUTSIDERS!
|
||||
|
||||
This Python File is distributed with every Kotlin Plugin Repository!
|
||||
If you find this to be confusing to use look at the Documentation in "rattatwinko/pommer"
|
||||
|
||||
Run this Script with Python 3.11 ; 3.9
|
||||
|
||||
This YET only works with Maven!
|
||||
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
import xml.etree.ElementTree as ET
|
||||
import re
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import glob
|
||||
|
||||
|
||||
def parse_pom_xml(pom_path):
|
||||
"""
|
||||
Parse a pom.xml file and extract relevant information
|
||||
"""
|
||||
try:
|
||||
print(f"Parsing POM file: {pom_path}")
|
||||
|
||||
# Register the default namespace
|
||||
ET.register_namespace('', "http://maven.apache.org/POM/4.0.0")
|
||||
|
||||
# Parse the XML file
|
||||
tree = ET.parse(pom_path)
|
||||
root = tree.getroot()
|
||||
|
||||
# Define namespace for easier XPath queries
|
||||
ns = {'mvn': "http://maven.apache.org/POM/4.0.0"}
|
||||
|
||||
# Extract project info
|
||||
artifact_id = root.find('./mvn:artifactId', ns).text
|
||||
group_id = root.find('./mvn:groupId', ns).text if root.find('./mvn:groupId', ns) is not None else "unknown"
|
||||
version = root.find('./mvn:version', ns).text if root.find('./mvn:version', ns) is not None else "unknown"
|
||||
name = root.find('./mvn:name', ns).text if root.find('./mvn:name', ns) is not None else artifact_id
|
||||
|
||||
# Extract Java version
|
||||
java_version_elem = root.find('./mvn:properties/mvn:java.version', ns)
|
||||
java_version = java_version_elem.text if java_version_elem is not None else "17" # Default to Java 17 if not specified
|
||||
|
||||
# Extract packaging type (default to jar if not specified)
|
||||
packaging = root.find('./mvn:packaging', ns)
|
||||
packaging = packaging.text if packaging is not None else "jar"
|
||||
|
||||
# Check if Kotlin is used
|
||||
kotlin_version_elem = root.find('./mvn:properties/mvn:kotlin.version', ns)
|
||||
kotlin_version = kotlin_version_elem.text if kotlin_version_elem is not None else None
|
||||
|
||||
# Check for Kotlin plugin or dependency
|
||||
kotlin_plugin = None
|
||||
kotlin_dep = None
|
||||
|
||||
# Check for Kotlin plugin
|
||||
plugins = root.findall('.//mvn:plugin', ns)
|
||||
for plugin in plugins:
|
||||
plugin_group = plugin.find('./mvn:groupId', ns)
|
||||
if plugin_group is not None and plugin_group.text == 'org.jetbrains.kotlin':
|
||||
kotlin_plugin = plugin
|
||||
break
|
||||
|
||||
# Check for Kotlin dependency
|
||||
deps = root.findall('.//mvn:dependency', ns)
|
||||
for dep in deps:
|
||||
dep_group = dep.find('./mvn:groupId', ns)
|
||||
dep_artifact = dep.find('./mvn:artifactId', ns)
|
||||
if (dep_group is not None and dep_group.text == 'org.jetbrains.kotlin' and
|
||||
dep_artifact is not None and 'kotlin-stdlib' in dep_artifact.text):
|
||||
kotlin_dep = dep
|
||||
break
|
||||
|
||||
# Determine if this is a Kotlin project
|
||||
is_kotlin = kotlin_version is not None or kotlin_plugin is not None or kotlin_dep is not None
|
||||
|
||||
# Check for source directories
|
||||
source_dir = None
|
||||
source_dirs = root.findall('.//mvn:sourceDirectory', ns)
|
||||
if source_dirs:
|
||||
source_dir = source_dirs[0].text
|
||||
|
||||
# Check for default goal (to use the same command as IntelliJ)
|
||||
default_goal = None
|
||||
default_goal_elem = root.find('./mvn:build/mvn:defaultGoal', ns)
|
||||
if default_goal_elem is not None:
|
||||
default_goal = default_goal_elem.text
|
||||
|
||||
return {
|
||||
"artifact_id": artifact_id,
|
||||
"group_id": group_id,
|
||||
"version": version,
|
||||
"name": name,
|
||||
"java_version": java_version,
|
||||
"packaging": packaging,
|
||||
"is_kotlin": is_kotlin,
|
||||
"kotlin_version": kotlin_version,
|
||||
"source_dir": source_dir,
|
||||
"default_goal": default_goal,
|
||||
"pom_path": pom_path
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"Error parsing {pom_path}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def generate_gitea_workflow(pom_infos):
|
||||
"""
|
||||
Generate a Gitea workflow YAML file based on multiple POM information
|
||||
"""
|
||||
if not pom_infos:
|
||||
print("No valid POM files found")
|
||||
return None
|
||||
|
||||
# Get the highest Java version required
|
||||
java_version = max([info["java_version"] for info in pom_infos])
|
||||
|
||||
# Check if any project uses Kotlin
|
||||
uses_kotlin = any(info["is_kotlin"] for info in pom_infos)
|
||||
|
||||
# Kotlin version (if any)
|
||||
kotlin_version = None
|
||||
for info in pom_infos:
|
||||
if info["kotlin_version"]:
|
||||
kotlin_version = info["kotlin_version"]
|
||||
break
|
||||
|
||||
# Construct the workflow content
|
||||
workflow_content = f"""name: Maven Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master, dev ]
|
||||
pull_request:
|
||||
branches: [ main, master ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK {java_version}
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '{java_version}'
|
||||
cache: 'maven'
|
||||
|
||||
- name: Install Maven
|
||||
run: |
|
||||
if ! command -v mvn &> /dev/null; then
|
||||
echo "Maven not found, installing..."
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y maven
|
||||
fi
|
||||
mvn --version
|
||||
|
||||
- name: Debug Info
|
||||
run: |
|
||||
echo "Current workspace directory: $GITHUB_WORKSPACE"
|
||||
echo "Current directory: $(pwd)"
|
||||
echo "Project structure:"
|
||||
find . -type f -name "*.kt" | sort
|
||||
find . -type f -name "pom.xml"
|
||||
echo "Maven version: $(mvn --version)"
|
||||
"""
|
||||
|
||||
# Add individual build steps for each POM
|
||||
for i, info in enumerate(pom_infos):
|
||||
# Determine the Maven command to use (same as the default goal if specified)
|
||||
maven_command = "clean package"
|
||||
if info["default_goal"]:
|
||||
maven_command = info["default_goal"]
|
||||
|
||||
workflow_content += f"""
|
||||
- name: Build {info["name"]} ({info["artifact_id"]})
|
||||
run: |
|
||||
echo "Building {info["artifact_id"]}"
|
||||
echo "Current directory: $(pwd)"
|
||||
# Run Maven build directly using the POM file path
|
||||
mvn -B {maven_command} -f "$GITHUB_WORKSPACE/pom.xml" -Dmaven.compiler.failOnError=true
|
||||
"""
|
||||
|
||||
# Add artifact upload step
|
||||
workflow_content += f"""
|
||||
- name: Upload {info["artifact_id"]} artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: {info["artifact_id"]}
|
||||
path: target/{info['artifact_id']}-*.jar
|
||||
if-no-files-found: error
|
||||
"""
|
||||
|
||||
return workflow_content
|
||||
|
||||
|
||||
def find_pom_files(base_dir="."):
|
||||
"""Find all pom.xml files in the given directory and subdirectories"""
|
||||
return glob.glob(f"{base_dir}/**/pom.xml", recursive=True)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Generate Gitea workflow for Maven/Kotlin projects')
|
||||
parser.add_argument('--dir', '-d', default='.', help='Base directory to search for pom.xml files')
|
||||
parser.add_argument('--specific-pom', '-p', help='Path to a specific pom.xml file to process')
|
||||
args = parser.parse_args()
|
||||
|
||||
pom_files = []
|
||||
if args.specific_pom:
|
||||
pom_files = [args.specific_pom]
|
||||
else:
|
||||
pom_files = find_pom_files(args.dir)
|
||||
|
||||
if not pom_files:
|
||||
print(f"No pom.xml files found in {args.dir}")
|
||||
return
|
||||
|
||||
print(f"Found {len(pom_files)} pom.xml files")
|
||||
|
||||
# Parse all POM files
|
||||
pom_infos = []
|
||||
for pom_file in pom_files:
|
||||
info = parse_pom_xml(pom_file)
|
||||
if info:
|
||||
pom_infos.append(info)
|
||||
|
||||
if not pom_infos:
|
||||
print("No valid POM files could be parsed")
|
||||
return
|
||||
|
||||
# Generate the workflow content
|
||||
workflow_content = generate_gitea_workflow(pom_infos)
|
||||
|
||||
if not workflow_content:
|
||||
return
|
||||
|
||||
# Create the .gitea/workflows directory if it doesn't exist
|
||||
workflow_dir = Path(".gitea/workflows")
|
||||
workflow_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Write the workflow file
|
||||
workflow_file = workflow_dir / "maven_build.yaml"
|
||||
with open(workflow_file, "w") as f:
|
||||
f.write(workflow_content)
|
||||
|
||||
print(f"Gitea workflow generated at: {workflow_file}")
|
||||
print(f"This workflow will build {len(pom_infos)} Maven projects")
|
||||
|
||||
# Print summary of detected projects
|
||||
print("\nDetected projects:")
|
||||
for info in pom_infos:
|
||||
kotlin_info = "with Kotlin" if info["is_kotlin"] else "Java only"
|
||||
build_command = info["default_goal"] if info["default_goal"] else "clean package"
|
||||
print(
|
||||
f"- {info['name']} ({info['artifact_id']}): {kotlin_info}, Java {info['java_version']}, build: {build_command}")
|
||||
|
||||
# Create a simple direct build script as fallback
|
||||
with open("build.sh", "w") as f:
|
||||
f.write("""#!/bin/bash
|
||||
# Direct build script for Maven project
|
||||
|
||||
echo "Current directory: $(pwd)"
|
||||
echo "Building project with Maven..."
|
||||
|
||||
# Run Maven build using the exact pom.xml location
|
||||
mvn clean package -f "$(pwd)/pom.xml"
|
||||
|
||||
echo "Build complete. JAR file should be in target/ directory."
|
||||
""")
|
||||
|
||||
# Make it executable
|
||||
os.chmod("build.sh", 0o755)
|
||||
print(f"Simple build script generated at: build.sh")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -2,99 +2,186 @@ 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 java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import org.jfree.chart.*;
|
||||
import org.jfree.chart.axis.NumberAxis;
|
||||
import org.jfree.chart.plot.*;
|
||||
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
|
||||
import org.jfree.data.xy.*;
|
||||
import org.jfree.chart.renderer.xy.XYSplineRenderer;
|
||||
import java.awt.BasicStroke;
|
||||
|
||||
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;
|
||||
private final JLabel leistungLabel;
|
||||
private final JLabel drehmomentLabel;
|
||||
private final AnalogMeter rpmMeter;
|
||||
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");
|
||||
setSize(1000, 700); // Increased size for more elements
|
||||
setSize(1000, 700);
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setLayout(new BorderLayout());
|
||||
setLayout(new BorderLayout(10, 10));
|
||||
|
||||
// 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);
|
||||
// Top panel: analog meters
|
||||
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);
|
||||
|
||||
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 for labels and chart
|
||||
JPanel centerPanel = new JPanel(new BorderLayout());
|
||||
// Center panel: chart and labels
|
||||
JPanel centerPanel = new JPanel(new BorderLayout(10, 10));
|
||||
centerPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
|
||||
// Labels panel with BoxLayout for better scaling
|
||||
JPanel labelPanel = new JPanel();
|
||||
labelPanel.setLayout(new BoxLayout(labelPanel, BoxLayout.Y_AXIS));
|
||||
labelPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
labelPanel.add(Box.createVerticalStrut(5));
|
||||
labelPanel.add(maxLeistungLabel);
|
||||
labelPanel.add(Box.createVerticalStrut(5));
|
||||
labelPanel.add(maxDrehmomentLabel);
|
||||
centerPanel.add(labelPanel, BorderLayout.NORTH);
|
||||
|
||||
// Chart setup
|
||||
// Data series
|
||||
powerSeries = new XYSeries("Power (PS)");
|
||||
torqueSeries = new XYSeries("Torque (Nm)");
|
||||
XYSeriesCollection dataset = new XYSeriesCollection();
|
||||
dataset.addSeries(powerSeries);
|
||||
dataset.addSeries(torqueSeries);
|
||||
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"));
|
||||
|
||||
// Create chart
|
||||
JFreeChart chart = ChartFactory.createXYLineChart(
|
||||
"Leistungsdiagramm - Motorleistung",
|
||||
"LeistungsPrüfstand - Puch Maxi (" + dateStr + ")",
|
||||
"U/min",
|
||||
"Leistung [PS]",
|
||||
dataset,
|
||||
powerDataset,
|
||||
PlotOrientation.VERTICAL,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
);
|
||||
|
||||
XYPlot plot = chart.getXYPlot();
|
||||
chart.setBackgroundPaint(Color.WHITE);
|
||||
chart.getTitle().setFont(new Font("SansSerif", Font.BOLD, 18));
|
||||
|
||||
XYPlot plot = chart.getXYPlot();
|
||||
plot.setBackgroundPaint(new Color(245, 245, 245));
|
||||
plot.setDomainGridlinePaint(Color.LIGHT_GRAY);
|
||||
plot.setRangeGridlinePaint(Color.LIGHT_GRAY);
|
||||
plot.setOutlineVisible(false);
|
||||
plot.setDomainCrosshairVisible(true);
|
||||
plot.setRangeCrosshairVisible(true);
|
||||
|
||||
// Power axis (left)
|
||||
NumberAxis powerAxis = (NumberAxis) plot.getRangeAxis();
|
||||
powerAxis.setRange(0.0, 10.0);
|
||||
powerAxis.setLabel("Leistung [PS]");
|
||||
powerAxis.setTickLabelFont(new Font("SansSerif", Font.PLAIN, 12));
|
||||
powerAxis.setLabelFont(new Font("SansSerif", Font.BOLD, 14));
|
||||
|
||||
// Torque axis (right)
|
||||
NumberAxis torqueAxis = new NumberAxis("Drehmoment [Nm]");
|
||||
torqueAxis.setRange(0.0, 15.0);
|
||||
torqueAxis.setTickLabelFont(new Font("SansSerif", Font.PLAIN, 12));
|
||||
torqueAxis.setLabelFont(new Font("SansSerif", Font.BOLD, 14));
|
||||
plot.setRangeAxis(1, torqueAxis);
|
||||
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);
|
||||
xAxis.setLabel("U/min");
|
||||
xAxis.setTickLabelFont(new Font("SansSerif", Font.PLAIN, 12));
|
||||
xAxis.setLabelFont(new Font("SansSerif", Font.BOLD, 14));
|
||||
|
||||
XYLineAndShapeRenderer renderer1 = new XYLineAndShapeRenderer();
|
||||
renderer1.setSeriesPaint(0, Color.RED);
|
||||
renderer1.setSeriesShapesVisible(0, false);
|
||||
plot.setRenderer(0, renderer1);
|
||||
// Renderers
|
||||
org.jfree.chart.renderer.xy.XYSplineRenderer powerRenderer = new org.jfree.chart.renderer.xy.XYSplineRenderer();
|
||||
powerRenderer.setSeriesPaint(0, new Color(220, 50, 47)); // Red
|
||||
powerRenderer.setSeriesStroke(0, new BasicStroke(2f));
|
||||
plot.setRenderer(0, powerRenderer);
|
||||
|
||||
XYLineAndShapeRenderer renderer2 = new XYLineAndShapeRenderer();
|
||||
renderer2.setSeriesPaint(0, Color.BLUE);
|
||||
renderer2.setSeriesShapesVisible(0, false);
|
||||
plot.setRenderer(1, renderer2);
|
||||
org.jfree.chart.renderer.xy.XYSplineRenderer torqueRenderer = new org.jfree.chart.renderer.xy.XYSplineRenderer();
|
||||
torqueRenderer.setSeriesPaint(0, new Color(38, 139, 210)); // Blue
|
||||
torqueRenderer.setSeriesStroke(0, new BasicStroke(2f));
|
||||
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(600, 400));
|
||||
chartPanel.setPreferredSize(new Dimension(800, 400));
|
||||
chartPanel.setMouseWheelEnabled(true);
|
||||
chartPanel.setPopupMenu(null);
|
||||
centerPanel.add(chartPanel, BorderLayout.CENTER);
|
||||
add(centerPanel, BorderLayout.CENTER);
|
||||
|
||||
add(centerPanel, BorderLayout.CENTER);
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
@@ -102,7 +189,6 @@ public class DynoGUI extends JFrame {
|
||||
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) {
|
||||
@@ -114,17 +200,65 @@ public class DynoGUI extends JFrame {
|
||||
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);
|
||||
// keine Plot-Aktualisierung hier – siehe addPlotData()
|
||||
}
|
||||
|
||||
public void resetChart() {
|
||||
powerSeries.clear();
|
||||
torqueSeries.clear();
|
||||
efficiencySeries.clear();
|
||||
}
|
||||
|
||||
public void resetMaxHold() {
|
||||
maxLeistung = 0.0;
|
||||
maxDrehmoment = 0.0;
|
||||
maxLeistungRpm = 0.0;
|
||||
maxLeistungNmAtHp = 0.0;
|
||||
maxDrehmomentRpm = 0.0;
|
||||
maxDrehmomentPsAtNm = 0.0;
|
||||
efficiencySeries.clear();
|
||||
maxLeistungLabel.setText("Max Leistung: 0.00 PS");
|
||||
maxDrehmomentLabel.setText("Max Drehmoment: 0.00 Nm");
|
||||
}
|
||||
|
||||
public JFreeChart getChart() {
|
||||
return chartPanel.getChart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fügt Datenpunkte in den Diagrammen hinzu. Nur während aktiver Messung aufrufen.
|
||||
*/
|
||||
public void addPlotData(double leistung, double drehmoment, double rpm) {
|
||||
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;
|
||||
if (maxDrehmomentRpm != maxLeistungRpm) {
|
||||
efficiencySeries.add(maxDrehmomentRpm, effNm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
powerSeries.add(rpm, leistung);
|
||||
torqueSeries.add(rpm, drehmoment);
|
||||
}
|
||||
}
|
||||
@@ -29,66 +29,36 @@ public class Main {
|
||||
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 final double WHEEL_RADIUS_METERS = 0.25;
|
||||
|
||||
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 = 100.0; // Increased for faster simulation
|
||||
private static SerialPort currentSerialPort;
|
||||
private static ScheduledExecutorService serialExecutor;
|
||||
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 CSVLogger csvLogger;
|
||||
private static boolean measurementActive = false;
|
||||
|
||||
private static TreeMap<Double, Double> simulatedPowerCurve;
|
||||
private static TreeMap<Double, Double> simulatedTorqueCurve;
|
||||
private static double peakRpmDuringRun = 0.0;
|
||||
private static int belowThresholdCounter = 0;
|
||||
private static final int BELOW_THRESHOLD_LIMIT = 20; // 20 * 10ms = 200ms
|
||||
private static final double RPM_DROP_FACTOR = 0.3; // 30% of peak
|
||||
|
||||
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();
|
||||
startSerialPortListener(); // Start listening for serial data immediately
|
||||
});
|
||||
}
|
||||
|
||||
private static void addControlPanel() {
|
||||
JPanel controlPanel = new JPanel();
|
||||
|
||||
JButton toggleModeButton = new JButton("Toggle Test Mode");
|
||||
toggleModeButton.addActionListener(e -> toggleTestMode());
|
||||
controlPanel.add(toggleModeButton);
|
||||
@@ -105,11 +75,32 @@ 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();
|
||||
}
|
||||
|
||||
private static void reconnectSerial() {
|
||||
stopSerialPortListener();
|
||||
startSerialPortListener();
|
||||
}
|
||||
|
||||
private static void toggleTestMode() {
|
||||
testMode = !testMode;
|
||||
if (testMode) {
|
||||
@@ -133,69 +124,46 @@ public class Main {
|
||||
}
|
||||
|
||||
dynoGUI.resetChart();
|
||||
lastTimestamp = 0; // Reset for a new run
|
||||
measurementActive = true;
|
||||
lastTimestamp = 0;
|
||||
lastOmega = 0.0;
|
||||
testRpmCurrentIndex = 0;
|
||||
testRpmCurrentValue = TEST_RPM_VALUES[0]; // Start at the first RPM value
|
||||
peakRpmDuringRun = 0.0;
|
||||
belowThresholdCounter = 0;
|
||||
|
||||
// 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
|
||||
}
|
||||
if (csvLogger != null) { try { csvLogger.close(); } catch (Exception ex) {} }
|
||||
try { csvLogger = new CSVLogger(new java.io.File("logs")); } catch(Exception ex) { csvLogger = null; }
|
||||
|
||||
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;
|
||||
final long simStartTime = System.currentTimeMillis();
|
||||
|
||||
testDataExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
testDataExecutor.scheduleAtFixedRate(() -> {
|
||||
simulateSmoothRpmRun();
|
||||
}, 0, 1, TimeUnit.MILLISECONDS); // Update RPM every 10ms for faster animation
|
||||
}
|
||||
// Simulate a realistic dyno run pattern
|
||||
long currentTime = System.currentTimeMillis();
|
||||
double elapsedSeconds = (currentTime - simStartTime) / 1000.0;
|
||||
|
||||
private static void simulateSmoothRpmRun() {
|
||||
if (smoothRpmDataIndex >= smoothRpmData.size()) {
|
||||
stopTestRun();
|
||||
JOptionPane.showMessageDialog(null, "Test run finished.", "Info", JOptionPane.INFORMATION_MESSAGE);
|
||||
showPrintableChart();
|
||||
return;
|
||||
}
|
||||
// Simulate acceleration, hold, then deceleration
|
||||
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();
|
||||
}
|
||||
|
||||
double currentSimulatedRpm = smoothRpmData.get(smoothRpmDataIndex);
|
||||
processRPMData(currentSimulatedRpm, TEST_SIMULATION_DELTA_TIME);
|
||||
smoothRpmDataIndex++;
|
||||
// Add some realistic noise
|
||||
simulatedRpm += (Math.random() * 200 - 100);
|
||||
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() {
|
||||
@@ -203,14 +171,17 @@ public class Main {
|
||||
testDataExecutor.shutdownNow();
|
||||
testDataExecutor = null;
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
dynoGUI.updateWerte(0, 0, 0); // Reset gauges
|
||||
dynoGUI.updateWerte(0, 0, 0);
|
||||
if (csvLogger != null) {
|
||||
try { csvLogger.close(); } catch (Exception ex) { ex.printStackTrace(); }
|
||||
csvLogger = null;
|
||||
}
|
||||
showPrintableChart();
|
||||
});
|
||||
measurementActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static SerialPort currentSerialPort;
|
||||
private static ScheduledExecutorService serialExecutor;
|
||||
|
||||
private static void startSerialPortListener() {
|
||||
SerialPort[] comPorts = SerialPort.getCommPorts();
|
||||
if (comPorts.length == 0) {
|
||||
@@ -250,23 +221,29 @@ public class Main {
|
||||
return;
|
||||
}
|
||||
|
||||
final SerialPort finalChosenPort = chosenPort;
|
||||
currentSerialPort = finalChosenPort; // Store for later closing
|
||||
currentSerialPort = chosenPort;
|
||||
chosenPort.setBaudRate(9600);
|
||||
chosenPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 100, 0);
|
||||
|
||||
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()));
|
||||
if (chosenPort.openPort()) {
|
||||
System.out.println("Serial port " + chosenPort.getSystemPortName() + " opened successfully.");
|
||||
serialReader = new BufferedReader(new InputStreamReader(chosenPort.getInputStream()));
|
||||
|
||||
serialExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
serialExecutor.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
if (reader.ready()) {
|
||||
String line = reader.readLine();
|
||||
if (serialReader.ready()) {
|
||||
String line = serialReader.readLine();
|
||||
if (line != null && !line.trim().isEmpty()) {
|
||||
try {
|
||||
double rpm = Double.parseDouble(line.trim());
|
||||
processRPMData(rpm); // Call the main processRPMData for live data
|
||||
// Handle potential incomplete lines or multiple values
|
||||
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) {
|
||||
System.err.println("Received non-numeric data: " + line);
|
||||
}
|
||||
@@ -276,124 +253,159 @@ public class Main {
|
||||
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);
|
||||
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 {
|
||||
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() {
|
||||
if (serialExecutor != null) {
|
||||
serialExecutor.shutdownNow();
|
||||
try {
|
||||
if (!serialExecutor.awaitTermination(500, TimeUnit.MILLISECONDS)) {
|
||||
serialExecutor.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
serialExecutor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
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()) {
|
||||
currentSerialPort.closePort();
|
||||
System.out.println("Serial port " + currentSerialPort.getSystemPortName() + " closed.");
|
||||
currentSerialPort = null;
|
||||
}
|
||||
|
||||
if (csvLogger != null) {
|
||||
try { csvLogger.close(); } catch (Exception ex) { ex.printStackTrace(); }
|
||||
csvLogger = null;
|
||||
}
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
dynoGUI.updateWerte(0, 0, 0); // Reset gauges
|
||||
});
|
||||
}
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
if (lastTimestamp == 0) {
|
||||
lastTimestamp = currentTimestamp;
|
||||
lastOmega = currentOmega;
|
||||
lastOmega = rpmToOmega(rpm);
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
dynoGUI.updateWerte(0, 0, rpm);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
double deltaTime = (currentTimestamp - lastTimestamp) / 1000.0;
|
||||
if (deltaTime <= 0) deltaTime = 0.001;
|
||||
|
||||
double currentOmega = rpmToOmega(rpm);
|
||||
double drehmoment = leistungBerechner.berechneDrehmoment(lastOmega, currentOmega, deltaTime);
|
||||
double leistung = leistungBerechner.berechneLeistung(drehmoment, currentOmega);
|
||||
|
||||
if (measurementActive) {
|
||||
if (rpm > peakRpmDuringRun) {
|
||||
peakRpmDuringRun = rpm;
|
||||
}
|
||||
if (peakRpmDuringRun > 0 && rpm < peakRpmDuringRun * RPM_DROP_FACTOR) {
|
||||
belowThresholdCounter++;
|
||||
} else {
|
||||
belowThresholdCounter = 0;
|
||||
}
|
||||
|
||||
if (belowThresholdCounter >= BELOW_THRESHOLD_LIMIT) {
|
||||
SwingUtilities.invokeLater(() -> stopMeasurement());
|
||||
}
|
||||
}
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
dynoGUI.updateWerte(leistung, drehmoment, rpm);
|
||||
if (measurementActive) {
|
||||
dynoGUI.addPlotData(leistung, drehmoment, rpm);
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
private static void processRPMData(double rpm, double forcedDeltaTime) {
|
||||
// For test mode with forced delta time
|
||||
double currentOmega = rpmToOmega(rpm);
|
||||
double drehmoment = leistungBerechner.berechneDrehmoment(lastOmega, currentOmega, forcedDeltaTime);
|
||||
double leistung = leistungBerechner.berechneLeistung(drehmoment, currentOmega);
|
||||
|
||||
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
|
||||
if (measurementActive) {
|
||||
if (rpm > peakRpmDuringRun) {
|
||||
peakRpmDuringRun = rpm;
|
||||
}
|
||||
if (peakRpmDuringRun > 0 && rpm < peakRpmDuringRun * RPM_DROP_FACTOR) {
|
||||
belowThresholdCounter++;
|
||||
} else {
|
||||
belowThresholdCounter = 0;
|
||||
}
|
||||
if (belowThresholdCounter >= BELOW_THRESHOLD_LIMIT) {
|
||||
SwingUtilities.invokeLater(() -> stopMeasurement());
|
||||
}
|
||||
}
|
||||
|
||||
double x1 = lower.getKey();
|
||||
double y1 = lower.getValue();
|
||||
double x2 = upper.getKey();
|
||||
double y2 = upper.getValue();
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
dynoGUI.updateWerte(leistung, drehmoment, rpm);
|
||||
if (measurementActive) {
|
||||
dynoGUI.addPlotData(leistung, drehmoment, rpm);
|
||||
}
|
||||
});
|
||||
|
||||
// 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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
return y1 + ((y2 - y1) / (x2 - x1)) * (rpm - x1);
|
||||
}
|
||||
|
||||
private static double rpmToOmega(double rpm) {
|
||||
@@ -418,7 +430,9 @@ public class Main {
|
||||
try {
|
||||
job.print();
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -426,9 +440,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);
|
||||
@@ -451,27 +469,92 @@ public class Main {
|
||||
File finalFile = new File(filePath);
|
||||
|
||||
try {
|
||||
// Use the dimensions of the ChartPanel to create the PDF document
|
||||
Document document = new Document(new Rectangle(chartPanel.getWidth(), chartPanel.getHeight()));
|
||||
PdfWriter.getInstance(document, new FileOutputStream(finalFile));
|
||||
document.open();
|
||||
|
||||
// Create BufferedImage from the ChartPanel's chart
|
||||
java.awt.Image awtImage = chartPanel.getChart().createBufferedImage(chartPanel.getWidth(), chartPanel.getHeight());
|
||||
java.awt.Image awtImage = chartPanel.getChart().createBufferedImage(
|
||||
chartPanel.getWidth(), chartPanel.getHeight());
|
||||
Image pdfImage = Image.getInstance(awtImage, null);
|
||||
|
||||
// Scale image to fit document
|
||||
pdfImage.scaleToFit(document.getPageSize().getWidth() - 20, document.getPageSize().getHeight() - 20); // Add some padding
|
||||
pdfImage.scaleToFit(document.getPageSize().getWidth() - 20,
|
||||
document.getPageSize().getHeight() - 20);
|
||||
pdfImage.setAlignment(Image.ALIGN_CENTER);
|
||||
|
||||
document.add(pdfImage);
|
||||
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) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
peakRpmDuringRun = 0.0;
|
||||
belowThresholdCounter = 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
// Snapshot chart for printing
|
||||
showPrintableChart();
|
||||
peakRpmDuringRun = 0.0;
|
||||
belowThresholdCounter = 0;
|
||||
JOptionPane.showMessageDialog(null, "Messung gestoppt.", "Info", JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user