Compare commits
No commits in common. "main" and "feature/views" have entirely different histories.
main
...
feature/vi
42
.gitignore
vendored
42
.gitignore
vendored
@ -1,42 +0,0 @@
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
.kotlin
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
lib/
|
||||
logs/
|
||||
data/
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
10
.idea/.gitignore
generated
vendored
10
.idea/.gitignore
generated
vendored
@ -1,10 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Ignored default folder with query files
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
1
.idea/.name
generated
1
.idea/.name
generated
@ -1 +0,0 @@
|
||||
Virtueller Gesundheitsassistent
|
||||
7
.idea/encodings.xml
generated
7
.idea/encodings.xml
generated
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
6
.idea/inspectionProfiles/profiles_settings.xml
generated
@ -1,6 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
15
.idea/misc.xml
generated
15
.idea/misc.xml
generated
@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.12 (gesundheitsassistent)" />
|
||||
</component>
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="ms-17" project-jdk-type="JavaSDK" />
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
18
README.md
Normal file
18
README.md
Normal file
@ -0,0 +1,18 @@
|
||||
Virtueller Gesundheitsassistent
|
||||
Überblick
|
||||
Dieses Projekt implementiert einen virtuellen Gesundheits- und Fahrassistenten
|
||||
auf Basis von JavaFX. Die Anwendung visualisiert den aktuellen Zustand des
|
||||
Fahrers (z.B. Müdigkeit, Ablenkung, Stress) und integriert:
|
||||
- ein ML-Modell, das Zustände klassifiziert
|
||||
- eine JavaFX-GUI mit mehreren Designvarianten
|
||||
- OpenCV für Kamera-Preview
|
||||
- Unreal Engine Pixel Streaming für einen sprechenden Avatar
|
||||
- WebSocket-Kommunikation zur Steuerung des Avatars
|
||||
|
||||
Features
|
||||
Architektur
|
||||
Services
|
||||
Views & UI-Struktur
|
||||
Java-Version & Build
|
||||
MQTT & Datenfluss
|
||||
OpenCV
|
||||
40
nbactions.xml
Normal file
40
nbactions.xml
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<actions>
|
||||
<action>
|
||||
<actionName>run</actionName>
|
||||
<packagings>
|
||||
<packaging>jar</packaging>
|
||||
</packagings>
|
||||
<goals>
|
||||
<goal>clean</goal>
|
||||
<goal>javafx:run</goal>
|
||||
</goals>
|
||||
</action>
|
||||
<action>
|
||||
<actionName>debug</actionName>
|
||||
<goals>
|
||||
<goal>clean</goal>
|
||||
<goal>javafx:run@ide-debug</goal>
|
||||
</goals>
|
||||
<properties>
|
||||
<jpda.listen>true</jpda.listen>
|
||||
</properties>
|
||||
</action>
|
||||
<action>
|
||||
<actionName>profile</actionName>
|
||||
<goals>
|
||||
<goal>clean</goal>
|
||||
<goal>javafx:run@ide-profile</goal>
|
||||
</goals>
|
||||
</action>
|
||||
<action>
|
||||
<actionName>CUSTOM-jlink</actionName>
|
||||
<displayName>jlink</displayName>
|
||||
<goals>
|
||||
<goal>clean</goal>
|
||||
<!-- compile not needed with javafx-maven-plugin v0.0.5 -->
|
||||
<goal>compile</goal>
|
||||
<goal>javafx:jlink</goal>
|
||||
</goals>
|
||||
</action>
|
||||
</actions>
|
||||
125
pom.xml
125
pom.xml
@ -1,62 +1,111 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>vassistent</groupId>
|
||||
<groupId>efi.projekt</groupId>
|
||||
<artifactId>Virtueller_Gesundheitsassistent</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.paho</groupId>
|
||||
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
|
||||
<version>1.2.5</version>
|
||||
<type>jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-web</artifactId>
|
||||
<version>21.0.9</version>
|
||||
<type>jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>21.0.9</version>
|
||||
<type>jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.websocket</groupId>
|
||||
<artifactId>jakarta.websocket-client-api</artifactId>
|
||||
<version>2.2.0</version>
|
||||
<type>jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>5.10.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
<version>2.0.2-beta</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.friwi</groupId>
|
||||
<artifactId>jcefmaven</artifactId>
|
||||
<version>141.0.10</version>
|
||||
<groupId>org.openpnp</groupId>
|
||||
<artifactId>opencv</artifactId>
|
||||
<version>4.12.0</version>
|
||||
<type>jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.51.2.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jfree</groupId>
|
||||
<artifactId>jfreechart</artifactId>
|
||||
<version>1.5.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.formdev</groupId>
|
||||
<artifactId>flatlaf</artifactId>
|
||||
<version>2.6</version>
|
||||
<version>3.51.1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.0</version>
|
||||
<configuration>
|
||||
<release>21</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-maven-plugin</artifactId>
|
||||
<version>0.0.4</version>
|
||||
<configuration>
|
||||
<mainClass>efi.projekt.virtueller_gesundheitsassistent.App</mainClass>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<!-- Default configuration for running -->
|
||||
<!-- Usage: mvn clean javafx:run -->
|
||||
<id>default-cli</id>
|
||||
</execution>
|
||||
<execution>
|
||||
<!-- Configuration for manual attach debugging -->
|
||||
<!-- Usage: mvn clean javafx:run@debug -->
|
||||
<id>debug</id>
|
||||
<configuration>
|
||||
<options>
|
||||
<option>-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=localhost:8000</option>
|
||||
</options>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<!-- Configuration for automatic IDE debugging -->
|
||||
<id>ide-debug</id>
|
||||
<configuration>
|
||||
<options>
|
||||
<option>-agentlib:jdwp=transport=dt_socket,server=n,address=${jpda.address}</option>
|
||||
</options>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<!-- Configuration for automatic IDE profiling -->
|
||||
<id>ide-profile</id>
|
||||
<configuration>
|
||||
<options>
|
||||
<option>${profiler.jvmargs.arg1}</option>
|
||||
<option>${profiler.jvmargs.arg2}</option>
|
||||
<option>${profiler.jvmargs.arg3}</option>
|
||||
<option>${profiler.jvmargs.arg4}</option>
|
||||
<option>${profiler.jvmargs.arg5}</option>
|
||||
</options>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@ -0,0 +1,84 @@
|
||||
package efi.projekt.virtueller_gesundheitsassistent;
|
||||
|
||||
import efi.projekt.virtueller_gesundheitsassistent.model.AppState;
|
||||
import efi.projekt.virtueller_gesundheitsassistent.service.DataPersistenceService;
|
||||
import efi.projekt.virtueller_gesundheitsassistent.service.EvaluationService;
|
||||
import efi.projekt.virtueller_gesundheitsassistent.service.MqttClientService;
|
||||
import efi.projekt.virtueller_gesundheitsassistent.service.StatisticsService;
|
||||
import efi.projekt.virtueller_gesundheitsassistent.service.UnrealWebSocketService;
|
||||
import java.io.IOException;
|
||||
import javafx.application.Application;
|
||||
import static javafx.application.Application.launch;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
|
||||
/**
|
||||
* JavaFX App
|
||||
* @author naumueller
|
||||
*/
|
||||
public class App extends Application {
|
||||
|
||||
private final AppState appState = new AppState();
|
||||
|
||||
// Services
|
||||
private DataPersistenceService persistenceService;
|
||||
private StatisticsService statisticsService;
|
||||
private EvaluationService evaluationService;
|
||||
private UnrealWebSocketService unrealService;
|
||||
private MqttClientService mqttService;
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws IOException {
|
||||
|
||||
// =========================
|
||||
// Services initialisieren
|
||||
// =========================
|
||||
|
||||
// Unreal-WebService
|
||||
unrealService = new UnrealWebSocketService("127.0.0.1");
|
||||
|
||||
// MQTT-Service
|
||||
mqttService = new MqttClientService();
|
||||
|
||||
// Datenbank initialisieren
|
||||
persistenceService = new DataPersistenceService();
|
||||
|
||||
// Statistik-Service
|
||||
statisticsService = new StatisticsService();
|
||||
|
||||
// Evaluationsservice
|
||||
evaluationService = new EvaluationService(statisticsService, appState, unrealService);
|
||||
|
||||
|
||||
|
||||
// =========================
|
||||
// UI laden
|
||||
// =========================
|
||||
|
||||
FXMLLoader loader = new FXMLLoader(
|
||||
getClass().getResource(
|
||||
"/efi/projekt/virtueller_gesundheitsassistent/view/MainView.fxml"
|
||||
)
|
||||
);
|
||||
|
||||
Scene scene = new Scene(loader.load(), 1400, 900);
|
||||
|
||||
primaryStage.setTitle("Virtueller Gesundheitsassistent");
|
||||
primaryStage.setScene(scene);
|
||||
primaryStage.show();
|
||||
}
|
||||
|
||||
private static Parent loadFXML(String fxml) throws IOException {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(
|
||||
App.class.getResource("/efi/projekt/virtueller_gesundheitsassistent/view/" + fxml + ".fxml")
|
||||
);
|
||||
return fxmlLoader.load();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package efi.projekt.virtueller_gesundheitsassistent;
|
||||
|
||||
public class SystemInfo {
|
||||
|
||||
public static String javaVersion() {
|
||||
return System.getProperty("java.version");
|
||||
}
|
||||
|
||||
public static String javafxVersion() {
|
||||
return System.getProperty("javafx.version");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
package efi.projekt.virtueller_gesundheitsassistent.controller;
|
||||
|
||||
import efi.projekt.virtueller_gesundheitsassistent.service.CameraService;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.image.ImageView;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author naumueller
|
||||
*/
|
||||
public class CameraController {
|
||||
|
||||
@FXML private ImageView cameraView;
|
||||
|
||||
private final CameraService cameraService = new CameraService();
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void start() {
|
||||
cameraService.start(image ->
|
||||
Platform.runLater(() -> cameraView.setImage(image))
|
||||
);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void stopCamera() {
|
||||
cameraService.stop();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package efi.projekt.virtueller_gesundheitsassistent.model;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author naumueller
|
||||
*/
|
||||
public class AppState {
|
||||
|
||||
private final ObjectProperty<ProblemLevel> problemLevel =
|
||||
new SimpleObjectProperty<>(ProblemLevel.NONE);
|
||||
|
||||
public ObjectProperty<ProblemLevel> problemLevelProperty() {
|
||||
return problemLevel;
|
||||
}
|
||||
|
||||
public ProblemLevel getProblemLevel() {
|
||||
return problemLevel.get();
|
||||
}
|
||||
|
||||
public void setProblemLevel(ProblemLevel level) {
|
||||
problemLevel.set(level);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package efi.projekt.virtueller_gesundheitsassistent.model;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author naumueller
|
||||
*/
|
||||
public record Classification (
|
||||
String label,
|
||||
String text,
|
||||
String mqttValue
|
||||
) {}
|
||||
@ -0,0 +1,12 @@
|
||||
package efi.projekt.virtueller_gesundheitsassistent.model;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author naumueller
|
||||
*/
|
||||
public enum ProblemLevel {
|
||||
NONE,
|
||||
WARNING,
|
||||
HIGH,
|
||||
DISASTER
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
package efi.projekt.virtueller_gesundheitsassistent.service;
|
||||
|
||||
import efi.projekt.virtueller_gesundheitsassistent.util.Logger;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javafx.scene.image.Image;
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfByte;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.opencv.videoio.VideoCapture;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author naumueller
|
||||
*/
|
||||
public class CameraService {
|
||||
|
||||
static {
|
||||
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
|
||||
}
|
||||
|
||||
private VideoCapture capture;
|
||||
private ScheduledExecutorService timer;
|
||||
|
||||
public void start(java.util.function.Consumer<Image> frameConsumer) {
|
||||
if (capture != null && capture.isOpened()) {
|
||||
return;
|
||||
}
|
||||
|
||||
capture = new VideoCapture(0);
|
||||
|
||||
if (!capture.isOpened()) {
|
||||
Logger.error("CAMERA", "Kamera konnte nicht geoeffnet werden");
|
||||
return;
|
||||
}
|
||||
|
||||
Runnable frameGrabber = () -> {
|
||||
Mat frame = new Mat();
|
||||
if (capture.read(frame)) {
|
||||
Image image = matToImage(frame);
|
||||
frameConsumer.accept(image);
|
||||
}
|
||||
};
|
||||
|
||||
timer = Executors.newSingleThreadScheduledExecutor();
|
||||
timer.scheduleAtFixedRate(frameGrabber, 0, 33, TimeUnit.MILLISECONDS);
|
||||
|
||||
Logger.info("CAMERA", "Kamera gestartet");
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (timer != null && !timer.isShutdown()) {
|
||||
timer.shutdown();
|
||||
}
|
||||
if (capture != null && capture.isOpened()) {
|
||||
capture.release();
|
||||
}
|
||||
Logger.info("CAMERA", "Kamera gestoppt");
|
||||
}
|
||||
|
||||
private Image matToImage(Mat frame) {
|
||||
MatOfByte buffer = new MatOfByte();
|
||||
Imgcodecs.imencode(".png", frame, buffer);
|
||||
return new Image(new ByteArrayInputStream(buffer.toArray()));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
|
||||
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
|
||||
*/
|
||||
package efi.projekt.virtueller_gesundheitsassistent.service;
|
||||
|
||||
import efi.projekt.virtueller_gesundheitsassistent.util.Logger;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author naumueller
|
||||
*/
|
||||
public class DataPersistenceService {
|
||||
|
||||
private static final String DB_URL = "jdbc:sqlite:data/health.db";
|
||||
|
||||
public DataPersistenceService() {
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
try (
|
||||
Connection c = DriverManager.getConnection(DB_URL); Statement s = c.createStatement()) {
|
||||
|
||||
s.execute("""
|
||||
CREATE TABLE IF NOT EXISTS binary_event (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
value INTEGER NOT NULL,
|
||||
timestamp DATETIME NOT NULL
|
||||
)
|
||||
""");
|
||||
|
||||
Logger.info("DB", "Datenbank initialisiert");
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.error("DB", "Datenbank initialisierung fehlgeschlagen", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void store(int value) {
|
||||
try (Connection c = DriverManager.getConnection(DB_URL); PreparedStatement ps = c.prepareStatement("INSERT INTO binary_event (value, timestamp) VALUES (?, ?)")) {
|
||||
ps.setInt(1, value);
|
||||
ps.setString(2, LocalDateTime.now().toString());
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
Logger.error("DB", "Database Insert fehlgeschlagen", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
|
||||
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
|
||||
*/
|
||||
package efi.projekt.virtueller_gesundheitsassistent.service;
|
||||
|
||||
import efi.projekt.virtueller_gesundheitsassistent.model.AppState;
|
||||
import efi.projekt.virtueller_gesundheitsassistent.model.ProblemLevel;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author naumueller
|
||||
*/
|
||||
public class EvaluationService {
|
||||
|
||||
private final StatisticsService statisticsService;
|
||||
private final AppState appState;
|
||||
private final UnrealWebSocketService unrealService;
|
||||
|
||||
public EvaluationService(
|
||||
StatisticsService statisticsService,
|
||||
AppState appState,
|
||||
UnrealWebSocketService unrealService
|
||||
) {
|
||||
|
||||
this.statisticsService = statisticsService;
|
||||
this.appState = appState;
|
||||
this.unrealService = unrealService;
|
||||
}
|
||||
|
||||
public void evaluate() {
|
||||
double ratio = statisticsService.getRatio(10);
|
||||
|
||||
ProblemLevel level;
|
||||
|
||||
if (ratio >= 0.9) {
|
||||
level = ProblemLevel.DISASTER;
|
||||
} else if (ratio >= 0.8) {
|
||||
level = ProblemLevel.HIGH;
|
||||
} else if (ratio >= 0.5) {
|
||||
level = ProblemLevel.WARNING;
|
||||
} else {
|
||||
level = ProblemLevel.NONE;
|
||||
}
|
||||
|
||||
if (level != appState.getProblemLevel()) {
|
||||
appState.setProblemLevel(level);
|
||||
unrealService.speak(level.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,28 +1,28 @@
|
||||
package vassistent.service;
|
||||
|
||||
import org.eclipse.paho.client.mqttv3.*;
|
||||
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
|
||||
import vassistent.model.AppState;
|
||||
import vassistent.util.Logger;
|
||||
package efi.projekt.virtueller_gesundheitsassistent.service;
|
||||
|
||||
import efi.projekt.virtueller_gesundheitsassistent.util.Logger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
import org.eclipse.paho.client.mqttv3.*;
|
||||
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author naumueller
|
||||
*/
|
||||
public class MqttClientService implements MqttCallback {
|
||||
|
||||
private AppState appState;
|
||||
|
||||
private static final String BROKER_URL = "tcp://localhost:1883";
|
||||
private static final String CLIENT_ID = "JavaClientPublisherSubscriber";
|
||||
|
||||
private final Map<String, Consumer<String>> topicListeners =
|
||||
|
||||
private final Map<String, Consumer<String>> topicListeners =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
private MqttClient client;
|
||||
|
||||
public MqttClientService(AppState appState) {
|
||||
this.appState = appState;
|
||||
public MqttClientService() {
|
||||
try {
|
||||
client = new MqttClient(
|
||||
BROKER_URL,
|
||||
@ -37,14 +37,13 @@ public class MqttClientService implements MqttCallback {
|
||||
|
||||
Logger.info("MQTT", "Verbinde mit Broker " + BROKER_URL);
|
||||
client.connect(options);
|
||||
appState.setMqttConnected(true);
|
||||
Logger.info("MQTT", "Verbindung hergestellt");
|
||||
|
||||
} catch (MqttException e) {
|
||||
Logger.error("MQTT", "Fehler beim Verbinden", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void disconnect() {
|
||||
try {
|
||||
if (client != null && client.isConnected()) {
|
||||
@ -55,7 +54,7 @@ public class MqttClientService implements MqttCallback {
|
||||
Logger.error("MQTT", "Fehler beim Trennen", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void subscribe(String topic, Consumer<String> listener) {
|
||||
topicListeners.put(topic, listener);
|
||||
try {
|
||||
@ -65,18 +64,18 @@ public class MqttClientService implements MqttCallback {
|
||||
Logger.error("MQTT", "Subscribe fehlgeschlagen: " + topic, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void publish(String topic, String message, int qos) {
|
||||
try {
|
||||
MqttMessage mqttMessage =
|
||||
MqttMessage mqttMessage =
|
||||
new MqttMessage(message.getBytes(StandardCharsets.UTF_8));
|
||||
mqttMessage.setQos(qos);
|
||||
|
||||
|
||||
client.publish(topic, mqttMessage);
|
||||
|
||||
|
||||
Logger.debug(
|
||||
"MQTT",
|
||||
"Publish -> Topic=" + topic + ", QoS=" + qos + ", Payload=" + message
|
||||
"MQTT",
|
||||
"Publish -> Topic=" + topic + ", QoS=" + qos + ", Payload=" + message
|
||||
);
|
||||
} catch (MqttException e) {
|
||||
Logger.error("MQTT", "Publish fehlgeschlagen", e);
|
||||
@ -89,28 +88,25 @@ public class MqttClientService implements MqttCallback {
|
||||
"MQTT",
|
||||
"Verbindung verloren: " + cause.getMessage()
|
||||
);
|
||||
|
||||
appState.setMqttConnected(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageArrived(String topic, MqttMessage message) throws Exception {
|
||||
String payload =
|
||||
String payload =
|
||||
new String(message.getPayload(), StandardCharsets.UTF_8);
|
||||
|
||||
//Logger.debug(
|
||||
// "MQTT",
|
||||
// "Nachricht empfangen -> Topic=" + topic + ", Payload=" + payload
|
||||
//);
|
||||
|
||||
|
||||
Logger.debug(
|
||||
"MQTT",
|
||||
"Nachricht empfangen -> Topic=" + topic + ", Payload=" + payload
|
||||
);
|
||||
|
||||
Consumer<String> listener = topicListeners.get(topic);
|
||||
if (listener != null) {
|
||||
listener.accept(payload);
|
||||
Logger.debug("MQTT", "Payload accepted");
|
||||
} else {
|
||||
Logger.warn(
|
||||
"MQTT",
|
||||
"Keine Listener für Topic: " + topic
|
||||
"MQTT",
|
||||
"Keine Listener für Topic: " + topic
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -118,10 +114,8 @@ public class MqttClientService implements MqttCallback {
|
||||
@Override
|
||||
public void deliveryComplete(IMqttDeliveryToken token) {
|
||||
Logger.debug(
|
||||
"MQTT",
|
||||
"Delivery complete, MessageId=" + token.getMessageId()
|
||||
"MQTT",
|
||||
"Delivery complete, MessageId=" + token.getMessageId()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package efi.projekt.virtueller_gesundheitsassistent.service;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.web.WebView;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author naumueller
|
||||
*/
|
||||
public class PixelStreamingService {
|
||||
private final WebView webView;
|
||||
|
||||
public PixelStreamingService(String url) {
|
||||
webView = new WebView();
|
||||
webView.getEngine().load(url);
|
||||
}
|
||||
|
||||
public Node getView() {
|
||||
return webView;
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
webView.getEngine().reload();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
webView.setVisible(false);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
webView.setVisible(true);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package efi.projekt.virtueller_gesundheitsassistent.service;
|
||||
|
||||
import efi.projekt.virtueller_gesundheitsassistent.model.AppState;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author naumueller
|
||||
*/
|
||||
public class StateService {
|
||||
|
||||
private static final AppState STATE = new AppState();
|
||||
|
||||
private StateService() {}
|
||||
|
||||
public static AppState getState() {
|
||||
return STATE;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
|
||||
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
|
||||
*/
|
||||
package efi.projekt.virtueller_gesundheitsassistent.service;
|
||||
|
||||
import efi.projekt.virtueller_gesundheitsassistent.util.Logger;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author naumueller
|
||||
*/
|
||||
public class StatisticsService {
|
||||
|
||||
private static final String DB_URL = "jdbc:sqlite:data/health.db";
|
||||
|
||||
public double getRatio(int lastN) {
|
||||
String query = """
|
||||
SELECT AVG(value)
|
||||
FROM (
|
||||
SELECT value
|
||||
FROM binary_event
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT ?
|
||||
)
|
||||
""";
|
||||
|
||||
try (Connection c = DriverManager.getConnection(DB_URL); PreparedStatement ps = c.prepareStatement(query)) {
|
||||
ps.setInt(1, lastN);
|
||||
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
return rs.getDouble(1);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
Logger.error("Evaluation", "Couldn't get ratio.", e);
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
package efi.projekt.virtueller_gesundheitsassistent.service;
|
||||
|
||||
import efi.projekt.virtueller_gesundheitsassistent.util.Logger;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import jakarta.websocket.*;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author naumueller
|
||||
*/
|
||||
@ClientEndpoint
|
||||
public class UnrealWebSocketService {
|
||||
|
||||
private Session session;
|
||||
private final URI serverUri;
|
||||
private final AtomicBoolean connected = new AtomicBoolean(false);
|
||||
|
||||
|
||||
public UnrealWebSocketService(String serverUrl) {
|
||||
this.serverUri = URI.create(serverUrl);
|
||||
connect();
|
||||
}
|
||||
|
||||
private void connect() {
|
||||
try {
|
||||
WebSocketContainer container =
|
||||
ContainerProvider.getWebSocketContainer();
|
||||
|
||||
Logger.info("UNREAL", "Verbinde zu " + serverUri);
|
||||
container.connectToServer(this, serverUri);
|
||||
|
||||
} catch (Exception e) {
|
||||
Logger.error("UNREAL", "WebSocket-Verbindung fehlgeschlagen", e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return connected.get();
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
try {
|
||||
if (session != null && session.isOpen()) {
|
||||
session.close();
|
||||
Logger.info("UNREAL", "WebSocket-Verbindung geschlossen");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.error("UNREAL", "Fehler beim Schließen der Verbindung", e);
|
||||
}
|
||||
}
|
||||
|
||||
@OnOpen
|
||||
public void onOpen(Session session) {
|
||||
this.session = session;
|
||||
connected.set(true);
|
||||
Logger.info("UNREAL", "WebSocket verbunden");
|
||||
}
|
||||
|
||||
@OnClose
|
||||
public void onClose(Session session, CloseReason reason) {
|
||||
connected.set(false);
|
||||
Logger.warn("UNREAL", "WebSocket geschlossen: " + reason.getReasonPhrase());
|
||||
}
|
||||
|
||||
@OnError
|
||||
public void onError(Session session, Throwable throwable) {
|
||||
connected.set(false);
|
||||
Logger.error("UNREAL", "WebSocket-Fehler", throwable);
|
||||
}
|
||||
|
||||
@OnMessage
|
||||
public void onMessage(String message) {
|
||||
Logger.debug("UNREAL", "Nachricht empfangen: " + message);
|
||||
}
|
||||
|
||||
public void speak(String text) {
|
||||
if (!connected.get()) {
|
||||
Logger.warn("UNREAL", "Nicht verbunden! Text wird nicht gesendet!");
|
||||
return;
|
||||
}
|
||||
|
||||
String json = buildSpeakMessage(text);
|
||||
|
||||
session.getAsyncRemote().sendText(json);
|
||||
Logger.info("UNREAL", "Sende Speak-Command: " + text);
|
||||
}
|
||||
|
||||
public String buildSpeakMessage(String text) {
|
||||
return "{"
|
||||
+ "\"type\":\"speak\","
|
||||
+ "\"text\":\"" + escape(text) + "\""
|
||||
+ "}";
|
||||
}
|
||||
|
||||
public String escape(String text) {
|
||||
return text.replace("\"", "\\\"");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
package efi.projekt.virtueller_gesundheitsassistent.util;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author naumueller
|
||||
*/
|
||||
public class Logger {
|
||||
public enum Level {
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARN,
|
||||
ERROR
|
||||
}
|
||||
|
||||
private static Level currentLevel = Level.DEBUG;
|
||||
|
||||
private static final DateTimeFormatter FORMATTER =
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
private Logger() {
|
||||
// Verhindert Instanziierung
|
||||
}
|
||||
|
||||
public static void setLevel(Level level) {
|
||||
currentLevel = level;
|
||||
}
|
||||
|
||||
public static void debug(String source, String message) {
|
||||
log(Level.DEBUG, source, message, null);
|
||||
}
|
||||
|
||||
public static void info(String source, String message) {
|
||||
log(Level.INFO, source, message, null);
|
||||
}
|
||||
|
||||
public static void warn(String source, String message) {
|
||||
log(Level.WARN, source, message, null);
|
||||
}
|
||||
|
||||
public static void error(String source, String message) {
|
||||
log(Level.ERROR, source, message, null);
|
||||
}
|
||||
|
||||
public static void error(String source, String message, Throwable throwable) {
|
||||
log(Level.ERROR, source, message, throwable);
|
||||
}
|
||||
|
||||
public static synchronized void log(
|
||||
Level level,
|
||||
String source,
|
||||
String message,
|
||||
Throwable throwable
|
||||
) {
|
||||
// Log-Level filtern
|
||||
if (level.ordinal() < currentLevel.ordinal()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String timestamp = LocalDateTime.now().format(FORMATTER);
|
||||
|
||||
String logLine = String.format(
|
||||
"[%s] [%s] [%s] %s",
|
||||
timestamp,
|
||||
level,
|
||||
source,
|
||||
message
|
||||
);
|
||||
|
||||
if (level == Level.ERROR) {
|
||||
System.err.println(logLine);
|
||||
if (throwable != null) {
|
||||
throwable.printStackTrace(System.err);
|
||||
}
|
||||
} else {
|
||||
System.out.println(logLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/main/java/module-info.java
Normal file
15
src/main/java/module-info.java
Normal file
@ -0,0 +1,15 @@
|
||||
module efi.projekt.virtueller_gesundheitsassistent {
|
||||
requires javafx.controls;
|
||||
requires javafx.fxml;
|
||||
requires java.base;
|
||||
requires org.eclipse.paho.client.mqttv3;
|
||||
requires javafx.web;
|
||||
requires jakarta.websocket.client;
|
||||
requires opencv;
|
||||
requires java.sql;
|
||||
|
||||
|
||||
opens efi.projekt.virtueller_gesundheitsassistent to javafx.fxml;
|
||||
|
||||
exports efi.projekt.virtueller_gesundheitsassistent;
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
package vassistent;
|
||||
|
||||
import vassistent.bootstrap.ApplicationContext;
|
||||
import vassistent.bootstrap.ApplicationInitializer;
|
||||
import vassistent.controller.AppWindowController;
|
||||
import vassistent.service.*;
|
||||
import vassistent.util.Logger;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.File;
|
||||
|
||||
|
||||
public class App {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ApplicationInitializer.initLookAndFeel();
|
||||
|
||||
ApplicationContext context =
|
||||
ApplicationInitializer.initialize();
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
|
||||
Logger.info("SYSTEM", "Programm wird beendet");
|
||||
|
||||
deleteDatabase();
|
||||
|
||||
Logger.shutdown();
|
||||
|
||||
}));
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
new AppWindowController(context)
|
||||
.createAndShowWindow();
|
||||
});
|
||||
}
|
||||
|
||||
private static void deleteDatabase() {
|
||||
|
||||
try {
|
||||
|
||||
File dbFile = new File("data/health.db");
|
||||
|
||||
if (dbFile.exists()) {
|
||||
|
||||
boolean deleted = dbFile.delete();
|
||||
|
||||
if (deleted) {
|
||||
Logger.info("SYSTEM", "Datenbank gelöscht");
|
||||
} else {
|
||||
Logger.warn("SYSTEM", "Datenbank konnte nicht gelöscht werden");
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Logger.error("SYSTEM", "DB Löschung fehlgeschlagen", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,81 +0,0 @@
|
||||
package vassistent.bootstrap;
|
||||
|
||||
import vassistent.model.AppState;
|
||||
import vassistent.service.*;
|
||||
|
||||
public class ApplicationContext {
|
||||
|
||||
private AppState appState;
|
||||
|
||||
private DataPersistenceService persistenceService;
|
||||
private StatisticsService statisticsService;
|
||||
private EvaluationService evaluationService;
|
||||
private AnimationFileService unrealService;
|
||||
private MqttClientService mqttService;
|
||||
private BinaryEventService binaryEventService;
|
||||
private ProcessManagerService processManagerService;
|
||||
|
||||
public void setAppState(AppState appState) {
|
||||
this.appState = appState;
|
||||
}
|
||||
|
||||
public void setPersistenceService(DataPersistenceService persistenceService) {
|
||||
this.persistenceService = persistenceService;
|
||||
}
|
||||
|
||||
public void setStatisticsService(StatisticsService statisticsService) {
|
||||
this.statisticsService = statisticsService;
|
||||
}
|
||||
|
||||
public void setEvaluationService(EvaluationService evaluationService) {
|
||||
this.evaluationService = evaluationService;
|
||||
}
|
||||
|
||||
public void setAnimationFileService(AnimationFileService unrealService) {
|
||||
this.unrealService = unrealService;
|
||||
}
|
||||
|
||||
public void setMqttService(MqttClientService mqttService) {
|
||||
this.mqttService = mqttService;
|
||||
}
|
||||
|
||||
public void setBinaryEventService(BinaryEventService binaryEventService) {
|
||||
this.binaryEventService = binaryEventService;
|
||||
}
|
||||
|
||||
public AppState getAppState() {
|
||||
return appState;
|
||||
}
|
||||
|
||||
public DataPersistenceService getPersistenceService() {
|
||||
return persistenceService;
|
||||
}
|
||||
|
||||
public StatisticsService getStatisticsService() {
|
||||
return statisticsService;
|
||||
}
|
||||
|
||||
public EvaluationService getEvaluationService() {
|
||||
return evaluationService;
|
||||
}
|
||||
|
||||
public AnimationFileService getAnimationFileService() {
|
||||
return unrealService;
|
||||
}
|
||||
|
||||
public MqttClientService getMqttService() {
|
||||
return mqttService;
|
||||
}
|
||||
|
||||
public BinaryEventService getBinaryEventService() {
|
||||
return binaryEventService;
|
||||
}
|
||||
|
||||
public ProcessManagerService getProcessManagerService() {
|
||||
return processManagerService;
|
||||
}
|
||||
|
||||
public void setProcessManagerService(ProcessManagerService processManagerService) {
|
||||
this.processManagerService = processManagerService;
|
||||
}
|
||||
}
|
||||
@ -1,72 +0,0 @@
|
||||
package vassistent.bootstrap;
|
||||
|
||||
import com.formdev.flatlaf.FlatDarkLaf;
|
||||
import vassistent.model.AppState;
|
||||
import vassistent.service.*;
|
||||
import vassistent.util.ConfigLoader;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.Properties;
|
||||
|
||||
public class ApplicationInitializer {
|
||||
|
||||
private static Properties config =
|
||||
ConfigLoader.loadProperties(
|
||||
"config/application.properties"
|
||||
);
|
||||
|
||||
public static ApplicationContext initialize() {
|
||||
|
||||
ApplicationContext context = new ApplicationContext();
|
||||
|
||||
// ===== Model State =====
|
||||
context.setAppState(new AppState());
|
||||
|
||||
// ===== Infrastructure Services =====
|
||||
context.setAnimationFileService(
|
||||
new AnimationFileService()
|
||||
);
|
||||
|
||||
context.setMqttService(new MqttClientService(context.getAppState()));
|
||||
|
||||
context.setPersistenceService(new DataPersistenceService());
|
||||
|
||||
context.setStatisticsService(
|
||||
new StatisticsService(context.getPersistenceService())
|
||||
);
|
||||
|
||||
context.setEvaluationService(
|
||||
new EvaluationService(
|
||||
context.getStatisticsService(),
|
||||
context.getAppState(),
|
||||
context.getAnimationFileService()
|
||||
)
|
||||
);
|
||||
|
||||
context.setBinaryEventService(
|
||||
new BinaryEventService(
|
||||
context.getPersistenceService(),
|
||||
context.getEvaluationService()
|
||||
)
|
||||
);
|
||||
|
||||
context.getMqttService().subscribe(
|
||||
config.getProperty("mqtt.topic"),
|
||||
payload -> context.getBinaryEventService()
|
||||
.handlePayload(payload)
|
||||
);
|
||||
|
||||
context.setProcessManagerService(new ProcessManagerService(config));
|
||||
context.getProcessManagerService().startProcesses();
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
public static void initLookAndFeel() {
|
||||
try {
|
||||
UIManager.setLookAndFeel(new FlatDarkLaf());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
package vassistent.bootstrap;
|
||||
|
||||
import vassistent.service.MqttClientService;
|
||||
import vassistent.service.ProcessManagerService;
|
||||
import vassistent.util.Logger;
|
||||
|
||||
public class ApplicationShutdownManager {
|
||||
|
||||
private final ApplicationContext context;
|
||||
|
||||
public ApplicationShutdownManager(ApplicationContext context) {
|
||||
this.context = context;
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(
|
||||
new Thread(this::shutdown)
|
||||
);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
|
||||
Logger.info("SHUTDOWN", "Shutdown Sequencing gestartet");
|
||||
|
||||
disconnectMqtt();
|
||||
stopExternalProcesses();
|
||||
}
|
||||
|
||||
private void disconnectMqtt() {
|
||||
|
||||
try {
|
||||
MqttClientService mqtt = context.getMqttService();
|
||||
|
||||
if (mqtt != null) {
|
||||
mqtt.disconnect();
|
||||
}
|
||||
|
||||
Logger.info("SHUTDOWN", "MQTT getrennt");
|
||||
} catch (Exception e) {
|
||||
Logger.error("SHUTDOWN", "MQTT Shutdown Fehler", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopExternalProcesses() {
|
||||
|
||||
try {
|
||||
|
||||
ProcessManagerService pm = context.getProcessManagerService();
|
||||
|
||||
if (pm != null) {
|
||||
pm.shutdown();
|
||||
}
|
||||
|
||||
Logger.info("SHUTDOWN", "Externe Prozesse beendet");
|
||||
|
||||
} catch (Exception e) {
|
||||
Logger.error("SHUWTDOWN", "Process Shutdown Fehler", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,110 +0,0 @@
|
||||
package vassistent.controller;
|
||||
|
||||
import vassistent.bootstrap.ApplicationContext;
|
||||
import vassistent.bootstrap.ApplicationShutdownManager;
|
||||
import vassistent.model.AppState;
|
||||
import vassistent.ui.AppWindow;
|
||||
import vassistent.ui.DashboardView;
|
||||
import vassistent.ui.PixelStreamingView;
|
||||
import vassistent.util.Logger;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
|
||||
public class AppWindowController {
|
||||
|
||||
private final ApplicationContext context;
|
||||
private final ApplicationShutdownManager shutdownManager;
|
||||
|
||||
private AppWindow window;
|
||||
|
||||
public AppWindowController(ApplicationContext context) {
|
||||
this.context = context;
|
||||
this.shutdownManager = new ApplicationShutdownManager(context);
|
||||
subscribeAppState();
|
||||
}
|
||||
|
||||
public void createAndShowWindow() {
|
||||
|
||||
window = new AppWindow();
|
||||
|
||||
DashboardView dashboardView = window.getDashboardView();
|
||||
PixelStreamingView pixelStreamingView = window.getStreamingView();
|
||||
|
||||
DashboardController dashboardController =
|
||||
new DashboardController(
|
||||
context.getStatisticsService(),
|
||||
dashboardView,
|
||||
context.getAppState()
|
||||
);
|
||||
|
||||
StreamingController previewController = new StreamingController(pixelStreamingView);
|
||||
|
||||
dashboardView.getReloadPixelStreamingViewButton()
|
||||
.addActionListener(e ->
|
||||
previewController.reloadStream("http://141.75.215.233")
|
||||
);
|
||||
|
||||
dashboardView.getOpenFullscreenButton()
|
||||
.addActionListener(e ->
|
||||
window.getStreamingView().requestFocus());
|
||||
|
||||
window.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
shutdown();
|
||||
}
|
||||
});
|
||||
|
||||
window.setVisible(true);
|
||||
}
|
||||
|
||||
private void shutdown() {
|
||||
|
||||
shutdownManager.shutdown();
|
||||
|
||||
if (window != null)
|
||||
window.dispose();
|
||||
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
private void subscribeAppState() {
|
||||
|
||||
Logger.info("Controller",
|
||||
"Subscribe AppState Observer gestartet");
|
||||
|
||||
AppState state = context.getAppState();
|
||||
|
||||
state.addListener(this::onStateChanged);
|
||||
|
||||
Logger.info("Controller",
|
||||
"AppState Observer registriert");
|
||||
}
|
||||
|
||||
private void onStateChanged(AppState appState) {
|
||||
|
||||
if (window == null) {
|
||||
Logger.warn("Controller",
|
||||
"Window ist null, UI Update übersprungen");
|
||||
return;
|
||||
}
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
|
||||
window.updateProblemLevel(
|
||||
appState.getProblemLevel().name()
|
||||
);
|
||||
|
||||
window.updateMqttStatus(
|
||||
appState.isMqttConnected()
|
||||
);
|
||||
|
||||
Logger.debug("Controller",
|
||||
"ProblemLevel UI aktualisiert → "
|
||||
+ appState.getProblemLevel());
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
package vassistent.controller;
|
||||
|
||||
import vassistent.model.AppState;
|
||||
import vassistent.model.RatioPoint;
|
||||
import vassistent.service.StatisticsService;
|
||||
import vassistent.ui.DashboardView;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.List;
|
||||
|
||||
public class DashboardController {
|
||||
|
||||
private final StatisticsService statisticsService;
|
||||
private final DashboardView dashboardView;
|
||||
private long lastDataVersion = -1;
|
||||
|
||||
public DashboardController(
|
||||
StatisticsService statisticsService,
|
||||
DashboardView dashboardView,
|
||||
AppState appState
|
||||
) {
|
||||
this.statisticsService = statisticsService;
|
||||
this.dashboardView = dashboardView;
|
||||
|
||||
appState.addListener(this::onStateChanged);
|
||||
}
|
||||
|
||||
private void onStateChanged(AppState state) {
|
||||
|
||||
List<RatioPoint> points =
|
||||
statisticsService.getLastNAverages(20);
|
||||
|
||||
if (points.isEmpty())
|
||||
return;
|
||||
|
||||
double ratio = points.get(points.size() - 1).getRatio();
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
|
||||
dashboardView.updateChart(points);
|
||||
dashboardView.updateProblemLevel(ratio);
|
||||
});
|
||||
}
|
||||
|
||||
public void loadChartData() {
|
||||
|
||||
List<RatioPoint> points = statisticsService.getLastNAverages(20);
|
||||
|
||||
dashboardView.updateChart(points);
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
package vassistent.controller;
|
||||
|
||||
import vassistent.ui.PixelStreamingView;
|
||||
|
||||
public class StreamingController {
|
||||
|
||||
private final PixelStreamingView view;
|
||||
|
||||
public StreamingController(PixelStreamingView view) {
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
public void reloadStream(String url) {
|
||||
view.loadStream(url);
|
||||
}
|
||||
|
||||
public void toggleFullscreen() {
|
||||
view.requestFocus();
|
||||
}
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
package vassistent.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class AppState {
|
||||
|
||||
private long dataVersion = 0;
|
||||
private boolean mqttConnected;
|
||||
private ProblemLevel problemLevel = ProblemLevel.NONE;
|
||||
|
||||
private final List<Consumer<AppState>> listeners =
|
||||
new ArrayList<>();
|
||||
|
||||
public void addListener(Consumer<AppState> listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
private void notifyListeners() {
|
||||
listeners.forEach(l -> l.accept(this));
|
||||
}
|
||||
|
||||
public ProblemLevel getProblemLevel() {
|
||||
return problemLevel;
|
||||
}
|
||||
|
||||
public void setProblemLevel(ProblemLevel problemLevel) {
|
||||
this.problemLevel = problemLevel;
|
||||
dataVersion++;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public boolean isMqttConnected() {
|
||||
return mqttConnected;
|
||||
}
|
||||
|
||||
public void setMqttConnected(boolean mqttConnected) {
|
||||
if (this.mqttConnected != mqttConnected) {
|
||||
this.mqttConnected = mqttConnected;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
package vassistent.model;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class DatabaseEntry {
|
||||
|
||||
private final LocalDateTime timestamp;
|
||||
private final double value;
|
||||
|
||||
|
||||
public DatabaseEntry(LocalDateTime timestamp, double value) {
|
||||
this.timestamp = timestamp;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public LocalDateTime getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public double getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
package vassistent.model;
|
||||
|
||||
public enum ProblemLevel {
|
||||
NONE,
|
||||
WARNING,
|
||||
HIGH,
|
||||
DISASTER
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
package vassistent.model;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class RatioPoint {
|
||||
|
||||
private final LocalDateTime timestamp;
|
||||
private final double ratio;
|
||||
|
||||
|
||||
public RatioPoint(LocalDateTime timestamp, double ratio) {
|
||||
this.timestamp = timestamp;
|
||||
this.ratio = ratio;
|
||||
}
|
||||
|
||||
public LocalDateTime getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public double getRatio() {
|
||||
return ratio;
|
||||
}
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
package vassistent.service;
|
||||
|
||||
import vassistent.model.ProblemLevel;
|
||||
import vassistent.util.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import jakarta.websocket.*;
|
||||
|
||||
public class AnimationFileService {
|
||||
private static final String PATH = "C:\\Privat\\Dokumente\\Niklas_Aumueller\\TH\\MSY\\Semester_2\\animation.json";
|
||||
|
||||
public void wirteAnimationState(ProblemLevel level) {
|
||||
|
||||
String animation = mapLevelToAnimation(level);
|
||||
|
||||
File file = new File(PATH);
|
||||
|
||||
try {
|
||||
|
||||
String json = """
|
||||
{
|
||||
"animation": "%s"
|
||||
}
|
||||
""".formatted(animation);
|
||||
|
||||
try (FileWriter writer = new FileWriter(file, false)) {
|
||||
writer.write(json);
|
||||
}
|
||||
|
||||
Logger.info("ANIMATION FILE", "Animation json geschrieben");
|
||||
|
||||
} catch (IOException e) {
|
||||
Logger.error("ANIMATION FILE", "Fehler beim Schreiben der Animation Datei", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String mapLevelToAnimation(ProblemLevel level) {
|
||||
|
||||
return switch (level) {
|
||||
case NONE -> "no";
|
||||
case WARNING -> "warning";
|
||||
case HIGH -> "high";
|
||||
case DISASTER -> "disaster";
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
package vassistent.service;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import vassistent.util.Logger;
|
||||
|
||||
public class BinaryEventService {
|
||||
private final DataPersistenceService persistenceService;
|
||||
private final EvaluationService evaluationService;
|
||||
|
||||
private Integer lastId = null;
|
||||
|
||||
public BinaryEventService(
|
||||
DataPersistenceService persistenceService,
|
||||
EvaluationService evaluationService
|
||||
) {
|
||||
this.persistenceService = persistenceService;
|
||||
this.evaluationService = evaluationService;
|
||||
}
|
||||
|
||||
public synchronized void handlePayload(String payload) {
|
||||
|
||||
try {
|
||||
|
||||
JsonObject json = JsonParser.parseString(payload).getAsJsonObject();
|
||||
|
||||
if (!json.has("valid") || !json.get("valid").getAsBoolean()) {
|
||||
Logger.warn("EVENT", "Payload ist nicht valid");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json.has("_id") || !json.has("prediction")) {
|
||||
Logger.warn("EVENT", "Payload unvollständig");
|
||||
return;
|
||||
}
|
||||
|
||||
int id = json.get("_id").getAsInt();
|
||||
|
||||
if (lastId != null && lastId == id) {
|
||||
Logger.warn("EVENT", "Payload ID bereits verarbeitet: " + id);
|
||||
return;
|
||||
}
|
||||
|
||||
lastId = id;
|
||||
|
||||
int prediction = json.get("prediction").getAsInt();
|
||||
|
||||
if (prediction != 0 && prediction != 1) {
|
||||
Logger.warn("EVENT", "Ungültige Prediction: " + prediction);
|
||||
return;
|
||||
}
|
||||
|
||||
persistenceService.store(prediction);
|
||||
evaluationService.evaluate();
|
||||
|
||||
//Logger.debug("EVENT", "Prediction verarbeitet: " + prediction);
|
||||
|
||||
} catch (Exception e) {
|
||||
Logger.error("EVENT", "Payload Verarbeitung fehlgeschlagen: " + payload, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,115 +0,0 @@
|
||||
package vassistent.service;
|
||||
|
||||
import vassistent.model.DatabaseEntry;
|
||||
import vassistent.util.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class DataPersistenceService {
|
||||
|
||||
private static final String DB_FOLDER = "data";
|
||||
private final String DB_URL;
|
||||
|
||||
public DataPersistenceService() {
|
||||
DB_URL = "jdbc:sqlite:data/health.db";
|
||||
createDatabaseFolder();
|
||||
init();
|
||||
}
|
||||
|
||||
// Für Tests
|
||||
public DataPersistenceService(String DB_URL) {
|
||||
this.DB_URL = DB_URL;
|
||||
createDatabaseFolder();
|
||||
init();
|
||||
}
|
||||
|
||||
public void createDatabaseFolder() {
|
||||
File folder = new File(DB_FOLDER);
|
||||
if (!folder.exists()) {
|
||||
boolean created = folder.mkdirs();
|
||||
if (created) {
|
||||
Logger.info("DB", "Datenbank-Ordner erstellt");
|
||||
} else {
|
||||
Logger.warn("DB", "Konnte DB-Ordner nicht erstellen");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void init() {
|
||||
try (
|
||||
Connection c = DriverManager.getConnection(DB_URL); Statement s = c.createStatement()) {
|
||||
|
||||
s.execute("""
|
||||
CREATE TABLE IF NOT EXISTS binary_event (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
value INTEGER NOT NULL,
|
||||
timestamp DATETIME NOT NULL
|
||||
)
|
||||
""");
|
||||
|
||||
Logger.info("DB", "Datenbank initialisiert");
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.error("DB", "Datenbank initialisierung fehlgeschlagen", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void store(int value) {
|
||||
try (
|
||||
Connection c = DriverManager.getConnection(DB_URL); PreparedStatement ps = c.prepareStatement(
|
||||
"INSERT INTO binary_event (value, timestamp) VALUES (?, ?)"
|
||||
)) {
|
||||
ps.setInt(1, value);
|
||||
ps.setString(2, LocalDateTime.now().toString());
|
||||
ps.executeUpdate();
|
||||
//Logger.debug("DB", "Database Insert erfolgreich: " + ps.toString());
|
||||
} catch (SQLException e) {
|
||||
Logger.error("DB", "Database Insert fehlgeschlagen", e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<DatabaseEntry> getLastEntries(int count) {
|
||||
List<DatabaseEntry> result = new ArrayList<>();
|
||||
|
||||
String query = """
|
||||
SELECT value, timestamp
|
||||
FROM binary_event
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT ?
|
||||
""";
|
||||
try (Connection c = DriverManager.getConnection(DB_URL); PreparedStatement ps = c.prepareStatement(query)) {
|
||||
|
||||
ps.setInt(1, count);
|
||||
|
||||
ResultSet rs = ps.executeQuery();
|
||||
|
||||
while (rs.next()) {
|
||||
|
||||
String ts = rs.getString("timestamp");
|
||||
|
||||
LocalDateTime timestamp =
|
||||
LocalDateTime.parse(ts);
|
||||
|
||||
double value = rs.getDouble("value");
|
||||
|
||||
result.add(new DatabaseEntry(timestamp, value));
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.error("DB",
|
||||
"Fehler beim Laden der letzten Einträge", e);
|
||||
}
|
||||
|
||||
Collections.reverse(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public String getDbUrl() {
|
||||
return DB_URL;
|
||||
}
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
package vassistent.service;
|
||||
|
||||
|
||||
import vassistent.model.AppState;
|
||||
import vassistent.model.ProblemLevel;
|
||||
|
||||
public class EvaluationService {
|
||||
private final StatisticsService statisticsService;
|
||||
private final AppState appState;
|
||||
private final AnimationFileService animationFileService;
|
||||
|
||||
public EvaluationService(
|
||||
StatisticsService statisticsService,
|
||||
AppState appState,
|
||||
AnimationFileService animationFileService
|
||||
) {
|
||||
|
||||
this.statisticsService = statisticsService;
|
||||
this.appState = appState;
|
||||
this.animationFileService = animationFileService;
|
||||
}
|
||||
|
||||
public void evaluate() {
|
||||
double ratio = statisticsService.getRatio(20);
|
||||
|
||||
ProblemLevel level = calculateLevel(ratio);
|
||||
|
||||
appState.setProblemLevel(level);
|
||||
animationFileService.wirteAnimationState(level);
|
||||
}
|
||||
|
||||
private ProblemLevel calculateLevel(Double ratio) {
|
||||
if (ratio >= 0.9) {
|
||||
return ProblemLevel.DISASTER;
|
||||
} else if (ratio >= 0.8) {
|
||||
return ProblemLevel.HIGH;
|
||||
} else if (ratio >= 0.5) {
|
||||
return ProblemLevel.WARNING;
|
||||
} else {
|
||||
return ProblemLevel.NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,146 +0,0 @@
|
||||
package vassistent.service;
|
||||
|
||||
import vassistent.util.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
public class ProcessManagerService {
|
||||
|
||||
private final Properties config;
|
||||
|
||||
private Process pythonProcess;
|
||||
private Process unrealProcess;
|
||||
private Process unrealSignallingProcess;
|
||||
|
||||
public ProcessManagerService(Properties config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public void startProcesses() {
|
||||
|
||||
startPythonIfEnabled();
|
||||
startUnrealIfEnabled();
|
||||
}
|
||||
|
||||
private void startPythonIfEnabled() {
|
||||
|
||||
boolean enabled = Boolean.parseBoolean(
|
||||
config.getProperty("mqtt_sim.enabled", "false")
|
||||
);
|
||||
|
||||
if (!enabled) return;
|
||||
|
||||
try {
|
||||
String script = config.getProperty("mqtt_sim.script");
|
||||
String python = config.getProperty("python.path");
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(python, script);
|
||||
pb.redirectErrorStream(true);
|
||||
|
||||
pythonProcess = pb.start();
|
||||
|
||||
Logger.info("PROCESS", "Mqtt Simulator gestartet");
|
||||
} catch (IOException e) {
|
||||
Logger.error("PROCESS", "Mqtt Simulator Start fehlgeschlagen", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void startUnrealIfEnabled() {
|
||||
|
||||
boolean enabled = Boolean.parseBoolean(config.getProperty("unreal.enabled", "false"));
|
||||
|
||||
if (!enabled) return;
|
||||
|
||||
try {
|
||||
|
||||
startSignallingServer();
|
||||
startUnrealEngine();
|
||||
|
||||
} catch (IOException e) {
|
||||
Logger.error("PROCESS", "Unreal Start fehlgeschlagen", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void startSignallingServer() throws IOException {
|
||||
|
||||
String script =
|
||||
config.getProperty("unreal.signalling_server.script");
|
||||
|
||||
if (script == null) return;
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(
|
||||
"cmd",
|
||||
"/c",
|
||||
script);
|
||||
|
||||
pb.redirectErrorStream(true);
|
||||
|
||||
unrealSignallingProcess = pb.start();
|
||||
|
||||
Logger.info("PROCESS",
|
||||
"Unreal Signalling Server gestartet" + pb.command());
|
||||
}
|
||||
|
||||
private void startUnrealEngine() throws IOException {
|
||||
|
||||
String exe =
|
||||
config.getProperty("unreal.executable");
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(
|
||||
exe,
|
||||
"-PixelStreamingURL=ws://127.0.0.1:8888",
|
||||
"-RenderOffscreen",
|
||||
"-NoSound"
|
||||
);
|
||||
|
||||
pb.directory(new File(exe).getParentFile());
|
||||
|
||||
pb.redirectErrorStream(true);
|
||||
|
||||
unrealProcess = pb.start();
|
||||
|
||||
Logger.info("PROCESS",
|
||||
"Unreal Engine gestartet" + pb.command());
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
|
||||
Logger.info("PROCESS", "Shutdown externe Prozesse gestartet");
|
||||
|
||||
terminateProcess(pythonProcess);
|
||||
terminateProcess(unrealProcess);
|
||||
terminateProcess(unrealSignallingProcess);
|
||||
|
||||
Logger.info("PROCESS", "Externe Prozesse beendet");
|
||||
}
|
||||
|
||||
private void terminateProcess(Process process) {
|
||||
|
||||
if (process == null)
|
||||
return;
|
||||
|
||||
try {
|
||||
|
||||
long pid = process.pid();
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(
|
||||
"taskkill",
|
||||
"/PID",
|
||||
String.valueOf(pid),
|
||||
"/T",
|
||||
"/F"
|
||||
);
|
||||
|
||||
pb.start().waitFor();
|
||||
|
||||
Logger.info("PROCESS",
|
||||
"Process Tree beendet → PID " + pid);
|
||||
|
||||
} catch (Exception e) {
|
||||
Logger.error("PROCESS",
|
||||
"Fehler beim Prozess Kill", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,69 +0,0 @@
|
||||
package vassistent.service;
|
||||
|
||||
import vassistent.model.DatabaseEntry;
|
||||
import vassistent.model.RatioPoint;
|
||||
import vassistent.util.Logger;
|
||||
|
||||
import java.sql.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class StatisticsService {
|
||||
private final DataPersistenceService persistenceService;
|
||||
|
||||
public StatisticsService(DataPersistenceService persistenceService) {
|
||||
this.persistenceService = persistenceService;
|
||||
}
|
||||
|
||||
public double getRatio(int lastN) {
|
||||
String query = """
|
||||
SELECT AVG(value)
|
||||
FROM (
|
||||
SELECT value
|
||||
FROM binary_event
|
||||
ORDER BY id DESC
|
||||
LIMIT ?
|
||||
)
|
||||
""";
|
||||
|
||||
try (Connection c = DriverManager.getConnection(persistenceService.getDbUrl()); PreparedStatement ps = c.prepareStatement(query)) {
|
||||
ps.setInt(1, lastN);
|
||||
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
return rs.getDouble(1);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
Logger.error("Evaluation", "Couldn't get ratio.", e);
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
public List<RatioPoint> getLastNAverages(int lastN) {
|
||||
|
||||
List<DatabaseEntry> entries = persistenceService.getLastEntries(30);
|
||||
|
||||
List<RatioPoint> result = new ArrayList<>();
|
||||
|
||||
if (entries.isEmpty())
|
||||
return result;
|
||||
|
||||
for (int i = 10; i < entries.size(); i++) {
|
||||
double sum = 0;
|
||||
|
||||
for (int j = i - 10; j < i; j++) {
|
||||
sum += entries.get(j).getValue();
|
||||
}
|
||||
|
||||
double avg = sum / 10.0;
|
||||
|
||||
LocalDateTime timestamp = entries.get(i).getTimestamp();
|
||||
|
||||
result.add(new RatioPoint(timestamp, avg));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -1,91 +0,0 @@
|
||||
package vassistent.ui;
|
||||
|
||||
import vassistent.bootstrap.ApplicationContext;
|
||||
import vassistent.controller.DashboardController;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
public class AppWindow extends JFrame {
|
||||
|
||||
private PixelStreamingView streamingView;
|
||||
private DashboardView dashboardView;
|
||||
private JTabbedPane tabs;
|
||||
|
||||
private JLabel mqttStatusLabel;
|
||||
private JLabel problemLevelLabel;
|
||||
|
||||
public AppWindow() {
|
||||
|
||||
setTitle("Virtueller Gesundheitsassistent");
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setSize(1400, 850);
|
||||
setLocationRelativeTo(null);
|
||||
|
||||
setLayout(new BorderLayout());
|
||||
|
||||
streamingView = new PixelStreamingView(
|
||||
"http://localhost:80",
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
dashboardView = new DashboardView();
|
||||
|
||||
tabs = createTabPane();
|
||||
add(tabs, BorderLayout.CENTER);
|
||||
add(createStatusBar(), BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
// ---------------- Tabs ----------------
|
||||
|
||||
private JTabbedPane createTabPane() {
|
||||
|
||||
JTabbedPane tabs = new JTabbedPane();
|
||||
|
||||
JPanel streamingPanel = new JPanel(new BorderLayout());
|
||||
streamingPanel.add(streamingView, BorderLayout.CENTER);
|
||||
|
||||
tabs.addTab("Avatar Streaming", streamingView);
|
||||
tabs.addTab("Dashboard", dashboardView);
|
||||
|
||||
return tabs;
|
||||
}
|
||||
|
||||
// ---------------- Status Bar ----------------
|
||||
|
||||
private JPanel createStatusBar() {
|
||||
|
||||
JPanel statusBar = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
|
||||
mqttStatusLabel = new JLabel("MQTT: Disconnected");
|
||||
problemLevelLabel = new JLabel("Problem: NONE");
|
||||
|
||||
statusBar.add(mqttStatusLabel);
|
||||
statusBar.add(new JLabel(" | "));
|
||||
statusBar.add(problemLevelLabel);
|
||||
|
||||
return statusBar;
|
||||
}
|
||||
|
||||
public void updateMqttStatus(boolean connected) {
|
||||
mqttStatusLabel.setText("MQTT: " +
|
||||
(connected ? "Connected" : "Disconnected"));
|
||||
}
|
||||
|
||||
public void updateProblemLevel(String level) {
|
||||
problemLevelLabel.setText("Problem: " + level);
|
||||
}
|
||||
|
||||
public DashboardView getDashboardView() {
|
||||
return dashboardView;
|
||||
}
|
||||
|
||||
public PixelStreamingView getStreamingView() {
|
||||
return streamingView;
|
||||
}
|
||||
|
||||
public JTabbedPane getTabs() {
|
||||
return tabs;
|
||||
}
|
||||
}
|
||||
@ -1,101 +0,0 @@
|
||||
package vassistent.ui;
|
||||
|
||||
import org.jfree.chart.ChartFactory;
|
||||
import org.jfree.chart.ChartPanel;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.jfree.data.category.DefaultCategoryDataset;
|
||||
import org.jfree.data.time.Millisecond;
|
||||
import org.jfree.data.time.TimeSeries;
|
||||
import org.jfree.data.time.TimeSeriesCollection;
|
||||
import vassistent.model.ProblemLevel;
|
||||
import vassistent.model.RatioPoint;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class DashboardView extends JPanel {
|
||||
|
||||
private TimeSeries series;
|
||||
private ProblemLevelBar levelBar;
|
||||
|
||||
private JButton reloadPixelStreamingViewButton;
|
||||
private JButton openFullscreenButton;
|
||||
|
||||
public DashboardView() {
|
||||
setLayout(new BorderLayout());
|
||||
|
||||
// ---------- TOP: Problem Level ----------
|
||||
levelBar = new ProblemLevelBar();
|
||||
add(levelBar, BorderLayout.NORTH);
|
||||
|
||||
// ---------- CENTER: Chart ----------
|
||||
|
||||
series = new TimeSeries("Ratio");
|
||||
|
||||
TimeSeriesCollection dataset = new TimeSeriesCollection(series);
|
||||
|
||||
JFreeChart chart = ChartFactory.createTimeSeriesChart(
|
||||
"Ratio Verlauf",
|
||||
"Zeit",
|
||||
"Durchschnitt",
|
||||
dataset
|
||||
);
|
||||
|
||||
add(new ChartPanel(chart), BorderLayout.CENTER);
|
||||
|
||||
// ---------- SOUTH: Preview + Buttons ----------
|
||||
add(createBottomPanel(), BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
private JPanel createBottomPanel() {
|
||||
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
|
||||
JPanel buttonPanel = new JPanel(new FlowLayout());
|
||||
|
||||
reloadPixelStreamingViewButton = new JButton("Reload Preview");
|
||||
openFullscreenButton = new JButton("Open Fullscreen");
|
||||
|
||||
buttonPanel.add(reloadPixelStreamingViewButton);
|
||||
buttonPanel.add(openFullscreenButton);
|
||||
|
||||
panel.add(buttonPanel, BorderLayout.SOUTH);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
public void updateChart(List<RatioPoint> points) {
|
||||
series.clear();
|
||||
|
||||
for (RatioPoint point : points) {
|
||||
|
||||
series.addOrUpdate(
|
||||
new Millisecond(
|
||||
Date.from(
|
||||
point.getTimestamp()
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toInstant()
|
||||
)
|
||||
),
|
||||
point.getRatio()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateProblemLevel(double ratio) {
|
||||
levelBar.setRatio(ratio);
|
||||
}
|
||||
|
||||
public JButton getReloadPixelStreamingViewButton() {
|
||||
return reloadPixelStreamingViewButton;
|
||||
}
|
||||
|
||||
public JButton getOpenFullscreenButton() {
|
||||
return openFullscreenButton;
|
||||
}
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
package vassistent.ui;
|
||||
|
||||
import org.cef.CefApp;
|
||||
import org.cef.CefClient;
|
||||
import org.cef.CefSettings;
|
||||
import org.cef.browser.CefBrowser;
|
||||
import org.cef.handler.CefAppHandlerAdapter;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
public class PixelStreamingView extends JPanel {
|
||||
|
||||
private static final long serialVersionUID = -5570653778104813836L;
|
||||
private final CefApp cefApp;
|
||||
private final CefClient client;
|
||||
private final CefBrowser browser;
|
||||
private final Component browserUI_;
|
||||
|
||||
public PixelStreamingView(String startURL, boolean useOSR, boolean isTransparent) {
|
||||
super(new BorderLayout());
|
||||
|
||||
CefApp.addAppHandler(new CefAppHandlerAdapter(null) {
|
||||
@Override
|
||||
public void stateHasChanged(CefApp.CefAppState state) {
|
||||
if (state == CefApp.CefAppState.TERMINATED){
|
||||
System.out.println("TERMINATED");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
CefSettings settings = new CefSettings();
|
||||
settings.windowless_rendering_enabled = useOSR;
|
||||
|
||||
cefApp = CefApp.getInstance(settings);
|
||||
client = cefApp.createClient();
|
||||
|
||||
browser = client.createBrowser(startURL, useOSR, isTransparent);
|
||||
browserUI_ = browser.getUIComponent();
|
||||
|
||||
add(browserUI_, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
if (browser != null) browser.close(true);
|
||||
if (client != null) client.dispose();
|
||||
if (cefApp != null) cefApp.dispose();
|
||||
}
|
||||
|
||||
public void loadStream(String url) {
|
||||
browser.loadURL(url);
|
||||
}
|
||||
}
|
||||
@ -1,89 +0,0 @@
|
||||
package vassistent.ui;
|
||||
|
||||
import vassistent.model.ProblemLevel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
public class ProblemLevelBar extends JPanel {
|
||||
|
||||
private double ratio = 0.0;
|
||||
private ProblemLevel level = ProblemLevel.NONE;
|
||||
|
||||
public ProblemLevelBar() {
|
||||
setPreferredSize(new Dimension(100, 50));
|
||||
}
|
||||
|
||||
public void setRatio(double ratio) {
|
||||
this.ratio = Math.max(0.0, Math.min(1.0, ratio));
|
||||
this.level = calculateLevel(this.ratio);
|
||||
repaint();
|
||||
}
|
||||
|
||||
private ProblemLevel calculateLevel(Double ratio) {
|
||||
if (ratio >= 0.9) {
|
||||
return ProblemLevel.DISASTER;
|
||||
} else if (ratio >= 0.8) {
|
||||
return ProblemLevel.HIGH;
|
||||
} else if (ratio >= 0.5) {
|
||||
return ProblemLevel.WARNING;
|
||||
} else {
|
||||
return ProblemLevel.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
|
||||
Graphics2D g2 = (Graphics2D) g;
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
// --- Hintergrund ---
|
||||
g2.setColor(Color.LIGHT_GRAY);
|
||||
g2.fillRoundRect(0, 0, width, height, 20, 20);
|
||||
|
||||
// --- Gefüllter Bereich ---
|
||||
int filledWidth = (int) (width * ratio);
|
||||
|
||||
Color baseColor = getColorForLevel(level);
|
||||
Color darkerColor = baseColor.darker();
|
||||
|
||||
GradientPaint gradient = new GradientPaint(
|
||||
0, 0, darkerColor,
|
||||
filledWidth, 0, baseColor
|
||||
);
|
||||
|
||||
g2.setPaint(gradient);
|
||||
g2.fillRoundRect(0, 0, filledWidth, height, 20, 20);
|
||||
|
||||
// --- Text ---
|
||||
String text = level.name() + " (" + (int)(ratio * 100) + "%)";
|
||||
FontMetrics fm = g2.getFontMetrics();
|
||||
int textWidth = fm.stringWidth(text);
|
||||
|
||||
g2.setColor(Color.BLACK);
|
||||
g2.drawString(
|
||||
text,
|
||||
(width - textWidth) / 2,
|
||||
(height + fm.getAscent()) / 2 - 3
|
||||
);
|
||||
}
|
||||
|
||||
private Color getColorForLevel(ProblemLevel level) {
|
||||
switch (level) {
|
||||
case DISASTER:
|
||||
return new Color(200, 0, 0);
|
||||
case HIGH:
|
||||
return new Color(255, 140, 0);
|
||||
case WARNING:
|
||||
return new Color(255, 215, 0);
|
||||
default:
|
||||
return new Color(0, 170, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
package vassistent.util;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
public class ConfigLoader {
|
||||
|
||||
public ConfigLoader() {}
|
||||
|
||||
public static Properties loadProperties(String path) {
|
||||
|
||||
Properties props = new Properties();
|
||||
|
||||
try (InputStream is =
|
||||
Logger.class.getClassLoader()
|
||||
.getResourceAsStream(path)) {
|
||||
|
||||
if (is != null)
|
||||
props.load(is);
|
||||
|
||||
} catch (Exception e) {
|
||||
Logger.error("CONFIG",
|
||||
"Properties Laden fehlgeschlagen", e);
|
||||
}
|
||||
|
||||
return props;
|
||||
}
|
||||
}
|
||||
@ -1,199 +0,0 @@
|
||||
package vassistent.util;
|
||||
|
||||
import java.io.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
public class Logger {
|
||||
|
||||
public enum Level {
|
||||
DEBUG, INFO, WARN, ERROR
|
||||
}
|
||||
|
||||
private static Level currentLevel = Level.DEBUG;
|
||||
|
||||
private static boolean logToFile = true;
|
||||
private static String logFilePath = "logs/application.log";
|
||||
private static int maxFileSizeMB = 10;
|
||||
|
||||
private static final DateTimeFormatter FORMATTER =
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
private static final BlockingQueue<String> logQueue =
|
||||
new LinkedBlockingQueue<>();
|
||||
|
||||
static {
|
||||
loadConfig();
|
||||
startLogWriterThread();
|
||||
}
|
||||
|
||||
private Logger() {}
|
||||
|
||||
// ================= CONFIG =================
|
||||
|
||||
private static void loadConfig() {
|
||||
try (InputStream is = Logger.class.getClassLoader()
|
||||
.getResourceAsStream("config/logger.properties")) {
|
||||
|
||||
if (is == null) return;
|
||||
|
||||
Properties props = new Properties();
|
||||
props.load(is);
|
||||
|
||||
currentLevel =
|
||||
Level.valueOf(
|
||||
props.getProperty("logger.level", "DEBUG"));
|
||||
|
||||
logToFile =
|
||||
Boolean.parseBoolean(
|
||||
props.getProperty("logger.file.enabled", "true"));
|
||||
|
||||
logFilePath =
|
||||
props.getProperty("logger.file", "logs/application.log");
|
||||
|
||||
maxFileSizeMB =
|
||||
Integer.parseInt(
|
||||
props.getProperty("logger.max.size.mb", "10"));
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Logger Config Fehler: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ================= API =================
|
||||
|
||||
public static void debug(String source, String message) {
|
||||
log(Level.DEBUG, source, message, null);
|
||||
}
|
||||
|
||||
public static void info(String source, String message) {
|
||||
log(Level.INFO, source, message, null);
|
||||
}
|
||||
|
||||
public static void warn(String source, String message) {
|
||||
log(Level.WARN, source, message, null);
|
||||
}
|
||||
|
||||
public static void error(String source, String message) {
|
||||
log(Level.ERROR, source, message, null);
|
||||
}
|
||||
|
||||
public static void error(String source, String message, Throwable t) {
|
||||
log(Level.ERROR, source, message, t);
|
||||
}
|
||||
|
||||
// ================= CORE =================
|
||||
|
||||
public static void log(Level level, String source, String message, Throwable throwable) {
|
||||
|
||||
if (level.ordinal() < currentLevel.ordinal()) return;
|
||||
|
||||
String timestamp =
|
||||
LocalDateTime.now().format(FORMATTER);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(String.format("[%s] [%s] [%s] %s",
|
||||
timestamp, level, source, message));
|
||||
|
||||
if (throwable != null) {
|
||||
StringWriter sw = new StringWriter();
|
||||
throwable.printStackTrace(new PrintWriter(sw));
|
||||
sb.append("\n").append(sw);
|
||||
}
|
||||
|
||||
logQueue.offer(sb.toString());
|
||||
}
|
||||
|
||||
// ================= BACKGROUND WRITER =================
|
||||
|
||||
private static void startLogWriterThread() {
|
||||
|
||||
Thread writerThread = new Thread(() -> {
|
||||
|
||||
while (true) {
|
||||
|
||||
try {
|
||||
|
||||
String logEntry = logQueue.take();
|
||||
|
||||
if (logToFile) {
|
||||
|
||||
rotateIfNeeded();
|
||||
|
||||
File logFile = new File(logFilePath);
|
||||
logFile.getParentFile().mkdirs();
|
||||
|
||||
try (FileWriter fw = new FileWriter(logFile, true)) {
|
||||
fw.write(logEntry + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println(logEntry);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Logger Writer Fehler: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
writerThread.setDaemon(true);
|
||||
writerThread.start();
|
||||
}
|
||||
|
||||
// ================= ROTATION =================
|
||||
|
||||
private static void rotateIfNeeded() {
|
||||
|
||||
try {
|
||||
|
||||
File file = new File(logFilePath);
|
||||
|
||||
if (!file.exists()) return;
|
||||
|
||||
long maxBytes =
|
||||
maxFileSizeMB * 1024L * 1024L;
|
||||
|
||||
if (file.length() > maxBytes) {
|
||||
|
||||
String archiveName =
|
||||
logFilePath.replace(".log",
|
||||
"_" + System.currentTimeMillis() + ".log");
|
||||
|
||||
File archive = new File(archiveName);
|
||||
|
||||
boolean renamed = file.renameTo(archive);
|
||||
|
||||
if (renamed) {
|
||||
System.out.println("Logger Rotation: " + archiveName);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Logger Rotation Fehler: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
|
||||
System.out.println("Logger Shutdown gestartet");
|
||||
|
||||
try {
|
||||
Thread.sleep(500); // Warte auf Queue Flush
|
||||
|
||||
while (!logQueue.isEmpty()) {
|
||||
String log = logQueue.poll();
|
||||
if (log != null) {
|
||||
System.out.println(log);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Logger Shutdown Fehler: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
# ===== MODE =====
|
||||
app.mode=test
|
||||
|
||||
# ===== PYTHON =====
|
||||
python.path=C:\\Program Files\\PyManager\\python.exe
|
||||
|
||||
# ===== MQTT CLIENT =====
|
||||
mqtt.topic=PREDICTION
|
||||
|
||||
# ===== MQTT SIMULATOR =====
|
||||
mqtt_sim.enabled=true
|
||||
mqtt_sim.script=src/main/resources/scripts/mqtt_simulator.py
|
||||
|
||||
# ===== UNREAL ENGINE =====
|
||||
unreal.enabled=true
|
||||
unreal.executable=C:\\Privat\\Dokumente\\Niklas_Aumueller\\TH\\MSY\\Semester_2\\Projektarbeit\\Windows\\Prototyp1.exe
|
||||
unreal.signalling_server.script=C:\\Privat\\Dokumente\\Niklas_Aumueller\\TH\\MSY\\Semester_2\\Projektarbeit\\Windows\\Prototyp1\\Samples\\PixelStreaming\\WebServers\\SignallingWebServer\\platform_scripts\\cmd\\start_with_stun.bat
|
||||
@ -1,4 +0,0 @@
|
||||
logger.level=DEBUG
|
||||
logger.file.enabled=true
|
||||
logger.file=logs/application.log
|
||||
logger.max.size.mb=10
|
||||
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.Tab?>
|
||||
<?import javafx.scene.control.TabPane?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
|
||||
|
||||
<TabPane xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<tabs>
|
||||
<Tab text="Minimal">
|
||||
<fx:include source="designs/MinimalView.fxml"/>
|
||||
</Tab>
|
||||
<Tab text="Immersive">
|
||||
<fx:include source="designs/ImmersiveView.fxml"/>
|
||||
</Tab>
|
||||
<Tab text="Compact">
|
||||
<fx:include source="designs/CompactView.fxml"/>
|
||||
</Tab>
|
||||
<Tab text="Dashboard">
|
||||
<fx:include source="designs/DashboardView.fxml"/>
|
||||
</Tab>
|
||||
</tabs>
|
||||
</TabPane>
|
||||
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import java.lang.*?>
|
||||
<?import java.util.*?>
|
||||
<?import javafx.scene.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1">
|
||||
|
||||
</AnchorPane>
|
||||
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
|
||||
|
||||
<StackPane xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fx:controller="efi.projekt.virtueller_gesundheitsassistent.controller.CameraController">
|
||||
<children>
|
||||
<ImageView fx:id="cameraView" fitHeight="240.0" fitWidth="320.0" pickOnBounds="true" preserveRatio="true" />
|
||||
</children>
|
||||
</StackPane>
|
||||
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import java.lang.*?>
|
||||
<?import java.util.*?>
|
||||
<?import javafx.scene.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1">
|
||||
|
||||
</AnchorPane>
|
||||
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import java.lang.*?>
|
||||
<?import java.util.*?>
|
||||
<?import javafx.scene.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1">
|
||||
|
||||
</AnchorPane>
|
||||
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import java.lang.*?>
|
||||
<?import java.util.*?>
|
||||
<?import javafx.scene.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1">
|
||||
|
||||
</AnchorPane>
|
||||
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import java.lang.*?>
|
||||
<?import java.util.*?>
|
||||
<?import javafx.scene.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1">
|
||||
|
||||
</AnchorPane>
|
||||
@ -1,85 +0,0 @@
|
||||
import json
|
||||
|
||||
import paho.mqtt.client as mqtt
|
||||
import sys
|
||||
import random
|
||||
import time
|
||||
import logging
|
||||
|
||||
# ===== KONFIGURATION =====
|
||||
BROKER = "127.0.0.1"
|
||||
PORT = 1883
|
||||
TOPIC = "PREDICTION"
|
||||
USERNAME = None
|
||||
PASSWORD = None
|
||||
QOS = 0
|
||||
INTERVAL_SECONDS = 10
|
||||
# ==========================
|
||||
|
||||
# Logging konfigurieren (Console + Datei)
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(levelname)s - %(message)s",
|
||||
handlers=[
|
||||
logging.FileHandler("logs/mqtt_simulator.log"),
|
||||
logging.StreamHandler(sys.stdout)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def on_connect(client, userdata, flags, rc):
|
||||
if rc == 0:
|
||||
logging.info("Erfolgreich mit Broker verbunden")
|
||||
else:
|
||||
logging.error(f"Verbindung fehlgeschlagen mit Code {rc}")
|
||||
|
||||
|
||||
def main():
|
||||
logging.info("Python script gestartet")
|
||||
|
||||
client = mqtt.Client()
|
||||
|
||||
if USERNAME and PASSWORD:
|
||||
client.username_pw_set(USERNAME, PASSWORD)
|
||||
|
||||
client.on_connect = on_connect
|
||||
|
||||
try:
|
||||
logging.info(f"Verbinde mit Broker {BROKER}:{PORT}")
|
||||
client.connect(BROKER, PORT, 60)
|
||||
client.loop_start()
|
||||
|
||||
logging.info("Starte kontinuierliches Senden...")
|
||||
|
||||
current_id = 1
|
||||
|
||||
while True:
|
||||
payload = {
|
||||
"valid": True,
|
||||
"_id": current_id,
|
||||
"prediction": random.randint(0, 1)
|
||||
}
|
||||
|
||||
json_payload = json.dumps(payload)
|
||||
|
||||
client.publish(TOPIC, json_payload, qos=QOS)
|
||||
logging.info(f"Gesendet an '{TOPIC}': {json_payload}")
|
||||
|
||||
current_id += 1
|
||||
|
||||
time.sleep(INTERVAL_SECONDS)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logging.info("Publisher manuell beendet")
|
||||
|
||||
client.loop_stop()
|
||||
client.disconnect()
|
||||
sys.exit(0)
|
||||
|
||||
except Exception as e:
|
||||
logging.exception("Unerwarteter Fehler")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,49 +0,0 @@
|
||||
package vassistent.service;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class BinaryEventServiceTest {
|
||||
|
||||
private DataPersistenceService persistenceService;
|
||||
private EvaluationService evaluationService;
|
||||
private BinaryEventService binaryEventService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
persistenceService = mock(DataPersistenceService.class);
|
||||
evaluationService = mock(EvaluationService.class);
|
||||
binaryEventService = new BinaryEventService(persistenceService, evaluationService);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
persistenceService = null;
|
||||
evaluationService = null;
|
||||
binaryEventService = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
void handlePayloadValid() {
|
||||
binaryEventService.handlePayload("1");
|
||||
verify(persistenceService).store(anyInt());
|
||||
verify(evaluationService).evaluate();
|
||||
}
|
||||
|
||||
@Test
|
||||
void handlePayloadInvalidValue() {
|
||||
binaryEventService.handlePayload("5");
|
||||
verify(persistenceService, never()).store(anyInt());
|
||||
verify(evaluationService, never()).evaluate();
|
||||
}
|
||||
|
||||
@Test
|
||||
void handlePayloadNonNumeric() {
|
||||
binaryEventService.handlePayload("abc");
|
||||
verify(persistenceService, never()).store(anyInt());
|
||||
verify(evaluationService, never()).evaluate();
|
||||
}
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
package vassistent.service;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.Statement;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class DataPersistenceServiceTest {
|
||||
|
||||
private DataPersistenceService service;
|
||||
private static final String TEST_DB = "jdbc:sqlite:data/test_health.db";
|
||||
private static final String DB_FILE = "data/test_health.db";
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
service = new DataPersistenceService(TEST_DB);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanup() {
|
||||
deleteTestDatabase();
|
||||
}
|
||||
|
||||
|
||||
private void deleteTestDatabase() {
|
||||
File db = new File(DB_FILE);
|
||||
if (db.exists()) {
|
||||
assertTrue(db.delete(), "Testdatenbank konnte nicht gelöscht werden");
|
||||
}
|
||||
}
|
||||
|
||||
@org.junit.jupiter.api.Test
|
||||
void createDatabaseFolder() {
|
||||
File folder = new File("data");
|
||||
|
||||
assertTrue(folder.exists(), "Datenbankordner sollte existieren");
|
||||
}
|
||||
|
||||
@org.junit.jupiter.api.Test
|
||||
void store() throws Exception {
|
||||
service.store(1);
|
||||
|
||||
try (Connection c = DriverManager.getConnection(TEST_DB);
|
||||
Statement s = c.createStatement();
|
||||
ResultSet rs = s.executeQuery(
|
||||
"SELECT COUNT(*) FROM binary_event")) {
|
||||
|
||||
assertTrue(rs.next());
|
||||
int count = rs.getInt(1);
|
||||
|
||||
assertTrue(count > 0,
|
||||
"Es sollte mindestens ein Eintrag existieren");
|
||||
}
|
||||
}
|
||||
|
||||
@org.junit.jupiter.api.Test
|
||||
void getDbUrl() {
|
||||
assertEquals(TEST_DB, service.getDbUrl());
|
||||
}
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
package vassistent.service;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import vassistent.model.AppState;
|
||||
import vassistent.model.ProblemLevel;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class EvaluationServiceTest {
|
||||
|
||||
private StatisticsService statisticsService;
|
||||
private AppState appState;
|
||||
private AnimationFileService unrealService;
|
||||
private EvaluationService evaluationService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
statisticsService = mock(StatisticsService.class);
|
||||
appState = new AppState();
|
||||
unrealService = mock(AnimationFileService.class);
|
||||
evaluationService = new EvaluationService(statisticsService, appState, unrealService);
|
||||
}
|
||||
|
||||
@org.junit.jupiter.api.Test
|
||||
void tearDown() {
|
||||
statisticsService = null;
|
||||
appState = null;
|
||||
unrealService = null;
|
||||
evaluationService = null;
|
||||
}
|
||||
|
||||
@org.junit.jupiter.api.Test
|
||||
void testEvaluateDisasterLevel() {
|
||||
when(statisticsService.getRatio(anyInt())).thenReturn(0.95);
|
||||
evaluationService.evaluate();
|
||||
Assertions.assertEquals(ProblemLevel.DISASTER, appState.getProblemLevel());
|
||||
verify(unrealService).speak("DISASTER");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEvaluateNoChange() {
|
||||
appState.setProblemLevel(ProblemLevel.NONE);
|
||||
when(statisticsService.getRatio(anyInt())).thenReturn(0.1);
|
||||
evaluationService.evaluate();
|
||||
verify(unrealService, never()).speak(anyString());
|
||||
}
|
||||
}
|
||||
@ -1,74 +0,0 @@
|
||||
package vassistent.service;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class StatisticsServiceTest {
|
||||
|
||||
private String DB_URL;
|
||||
private DataPersistenceService persistenceService;
|
||||
private StatisticsService statisticsService;
|
||||
private File tempDbFile;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
try {
|
||||
tempDbFile = File.createTempFile("testdb", ".db");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Kann temporäre DB-Datei nicht erstellen", e);
|
||||
}
|
||||
|
||||
DB_URL = "jdbc:sqlite:" + tempDbFile.getAbsolutePath();
|
||||
persistenceService = new DataPersistenceService(DB_URL);
|
||||
statisticsService = new StatisticsService(persistenceService);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (tempDbFile != null && tempDbFile.exists()) {
|
||||
tempDbFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetRatioNoData() {
|
||||
double ratio = statisticsService.getRatio(10);
|
||||
assertEquals(0.0, ratio, "Ratio sollte 0 sein, wenn keine Daten vorhanden");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetRatioSingleValue() throws SQLException {
|
||||
persistenceService.store(1);
|
||||
|
||||
double ratio = statisticsService.getRatio(1);
|
||||
assertEquals(1.0, ratio, "Ratio sollte 1 sein, wenn nur 1 gespeichert wurde");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetRatioMultipleValues() {
|
||||
persistenceService.store(1);
|
||||
persistenceService.store(0);
|
||||
persistenceService.store(1);
|
||||
|
||||
double ratio = statisticsService.getRatio(10);
|
||||
assertEquals((1 + 0 + 1) / 3.0, ratio, 0.0001, "Ratio sollte Durchschnitt der letzten Werte sein");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetRatioLimitLastN() {
|
||||
persistenceService.store(1);
|
||||
persistenceService.store(1);
|
||||
persistenceService.store(0);
|
||||
persistenceService.store(0);
|
||||
persistenceService.store(1);
|
||||
|
||||
double ratio = statisticsService.getRatio(3);
|
||||
assertEquals((1 + 0 + 0) / 3.0, ratio, 0.0001, "Ratio sollte Durchschnitt der letzten Werte sein");
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user