Merge pull request 'feature/views' (#1) from feature/views into pre_int

Reviewed-on: #1
This commit is contained in:
Niklas Aumueller 2026-02-11 17:00:58 +00:00
commit de38f446fe
20 changed files with 264 additions and 94 deletions

18
README.md Normal file
View 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

View File

@ -43,6 +43,11 @@
<version>4.12.0</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.51.1.0</version>
</dependency>
</dependencies>
<build>
<plugins>

View File

@ -1,5 +1,11 @@
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;
@ -15,15 +21,50 @@ import javafx.stage.Stage;
*/
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 {
// Lade FXML
FXMLLoader loader = new FXMLLoader(getClass().getResource("/efi/projekt/virtueller_gesundheitsassistent/view/FxView.fxml"));
Parent root = loader.load();
// Erzeuge Model & ViewModel
// =========================
// Services initialisieren
// =========================
Scene scene = new Scene(root, 1280, 720);
// 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);
@ -38,7 +79,6 @@ public class App extends Application {
}
public static void main(String[] args) {
launch();
launch(args);
}
}

View File

@ -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);
}
}

View File

@ -1,34 +0,0 @@
package efi.projekt.virtueller_gesundheitsassistent.model;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
/**
*
* @author naumueller
*/
public class AssistantState {
private final ObjectProperty<ClassificationType> classification =
new SimpleObjectProperty<>(ClassificationType.NORMAL);
private final BooleanProperty speaking =
new SimpleBooleanProperty(false);
private final BooleanProperty monitoring =
new SimpleBooleanProperty(true);
public ObjectProperty<ClassificationType> classificationProperty() {
return classification;
}
public BooleanProperty speakingProperty() {
return speaking;
}
public BooleanProperty monitoringProperty() {
return monitoring;
}
}

View File

@ -1,13 +0,0 @@
package efi.projekt.virtueller_gesundheitsassistent.model;
/**
*
* @author naumueller
*/
public enum ClassificationType {
NORMAL,
MUEDIGKEIT,
ABLENKUNG,
STRESS,
REAKTION
}

View File

@ -0,0 +1,12 @@
package efi.projekt.virtueller_gesundheitsassistent.model;
/**
*
* @author naumueller
*/
public enum ProblemLevel {
NONE,
WARNING,
HIGH,
DISASTER
}

View File

@ -1,37 +0,0 @@
package efi.projekt.virtueller_gesundheitsassistent.service;
import efi.projekt.virtueller_gesundheitsassistent.model.ClassificationType;
import efi.projekt.virtueller_gesundheitsassistent.util.Logger;
import javafx.application.Platform;
/**
*
* @author naumueller
*/
public class ClassificationService {
private static final String TOPIC = "Text";
public ClassificationService(MqttClientService mqtt) {
mqtt.subscribe(TOPIC, payload -> {
ClassificationType type = switch (payload) {
case "0" -> ClassificationType.NORMAL;
case "1" -> ClassificationType.MUEDIGKEIT;
case "2" -> ClassificationType.ABLENKUNG;
case "3" -> ClassificationType.REAKTION;
case "4" -> ClassificationType.STRESS;
default -> ClassificationType.NORMAL;
};
Logger.info("STATE", "Neue Klassifizierung: " + type);
Platform.runLater(() ->
StateService.getState()
.classificationProperty()
.set(type)
);
});
}
}

View File

@ -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);
}
}
}

View File

@ -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());
}
}
}

View File

@ -1,6 +1,6 @@
package efi.projekt.virtueller_gesundheitsassistent.service;
import efi.projekt.virtueller_gesundheitsassistent.model.AssistantState;
import efi.projekt.virtueller_gesundheitsassistent.model.AppState;
/**
*
@ -8,11 +8,11 @@ import efi.projekt.virtueller_gesundheitsassistent.model.AssistantState;
*/
public class StateService {
private static final AssistantState STATE = new AssistantState();
private static final AppState STATE = new AppState();
private StateService() {}
public static AssistantState getState() {
public static AppState getState() {
return STATE;
}
}

View File

@ -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;
}
}

View File

@ -6,6 +6,7 @@ module efi.projekt.virtueller_gesundheitsassistent {
requires javafx.web;
requires jakarta.websocket.client;
requires opencv;
requires java.sql;
opens efi.projekt.virtueller_gesundheitsassistent to javafx.fxml;