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>
|
||||||
123
pom.xml
123
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"
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>efi.projekt</groupId>
|
||||||
<groupId>vassistent</groupId>
|
|
||||||
<artifactId>Virtueller_Gesundheitsassistent</artifactId>
|
<artifactId>Virtueller_Gesundheitsassistent</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>17</maven.compiler.source>
|
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-controls</artifactId>
|
||||||
|
<version>13</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.paho</groupId>
|
<groupId>org.eclipse.paho</groupId>
|
||||||
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
|
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
|
||||||
<version>1.2.5</version>
|
<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>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>jakarta.websocket</groupId>
|
<groupId>jakarta.websocket</groupId>
|
||||||
<artifactId>jakarta.websocket-client-api</artifactId>
|
<artifactId>jakarta.websocket-client-api</artifactId>
|
||||||
<version>2.2.0</version>
|
<version>2.2.0</version>
|
||||||
|
<type>jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.openpnp</groupId>
|
||||||
<artifactId>junit-jupiter-api</artifactId>
|
<artifactId>opencv</artifactId>
|
||||||
<version>5.10.2</version>
|
<version>4.12.0</version>
|
||||||
<scope>test</scope>
|
<type>jar</type>
|
||||||
</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>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.xerial</groupId>
|
<groupId>org.xerial</groupId>
|
||||||
<artifactId>sqlite-jdbc</artifactId>
|
<artifactId>sqlite-jdbc</artifactId>
|
||||||
<version>3.51.2.0</version>
|
<version>3.51.1.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>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
<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>
|
</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,18 +1,19 @@
|
|||||||
package vassistent.service;
|
package efi.projekt.virtueller_gesundheitsassistent.service;
|
||||||
|
|
||||||
import org.eclipse.paho.client.mqttv3.*;
|
|
||||||
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
|
|
||||||
import vassistent.model.AppState;
|
|
||||||
import vassistent.util.Logger;
|
|
||||||
|
|
||||||
|
import efi.projekt.virtueller_gesundheitsassistent.util.Logger;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.function.Consumer;
|
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 {
|
public class MqttClientService implements MqttCallback {
|
||||||
|
|
||||||
private AppState appState;
|
|
||||||
private static final String BROKER_URL = "tcp://localhost:1883";
|
private static final String BROKER_URL = "tcp://localhost:1883";
|
||||||
private static final String CLIENT_ID = "JavaClientPublisherSubscriber";
|
private static final String CLIENT_ID = "JavaClientPublisherSubscriber";
|
||||||
|
|
||||||
@ -21,8 +22,7 @@ public class MqttClientService implements MqttCallback {
|
|||||||
|
|
||||||
private MqttClient client;
|
private MqttClient client;
|
||||||
|
|
||||||
public MqttClientService(AppState appState) {
|
public MqttClientService() {
|
||||||
this.appState = appState;
|
|
||||||
try {
|
try {
|
||||||
client = new MqttClient(
|
client = new MqttClient(
|
||||||
BROKER_URL,
|
BROKER_URL,
|
||||||
@ -37,7 +37,6 @@ public class MqttClientService implements MqttCallback {
|
|||||||
|
|
||||||
Logger.info("MQTT", "Verbinde mit Broker " + BROKER_URL);
|
Logger.info("MQTT", "Verbinde mit Broker " + BROKER_URL);
|
||||||
client.connect(options);
|
client.connect(options);
|
||||||
appState.setMqttConnected(true);
|
|
||||||
Logger.info("MQTT", "Verbindung hergestellt");
|
Logger.info("MQTT", "Verbindung hergestellt");
|
||||||
|
|
||||||
} catch (MqttException e) {
|
} catch (MqttException e) {
|
||||||
@ -75,8 +74,8 @@ public class MqttClientService implements MqttCallback {
|
|||||||
client.publish(topic, mqttMessage);
|
client.publish(topic, mqttMessage);
|
||||||
|
|
||||||
Logger.debug(
|
Logger.debug(
|
||||||
"MQTT",
|
"MQTT",
|
||||||
"Publish -> Topic=" + topic + ", QoS=" + qos + ", Payload=" + message
|
"Publish -> Topic=" + topic + ", QoS=" + qos + ", Payload=" + message
|
||||||
);
|
);
|
||||||
} catch (MqttException e) {
|
} catch (MqttException e) {
|
||||||
Logger.error("MQTT", "Publish fehlgeschlagen", e);
|
Logger.error("MQTT", "Publish fehlgeschlagen", e);
|
||||||
@ -89,8 +88,6 @@ public class MqttClientService implements MqttCallback {
|
|||||||
"MQTT",
|
"MQTT",
|
||||||
"Verbindung verloren: " + cause.getMessage()
|
"Verbindung verloren: " + cause.getMessage()
|
||||||
);
|
);
|
||||||
|
|
||||||
appState.setMqttConnected(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -98,19 +95,18 @@ public class MqttClientService implements MqttCallback {
|
|||||||
String payload =
|
String payload =
|
||||||
new String(message.getPayload(), StandardCharsets.UTF_8);
|
new String(message.getPayload(), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
//Logger.debug(
|
Logger.debug(
|
||||||
// "MQTT",
|
"MQTT",
|
||||||
// "Nachricht empfangen -> Topic=" + topic + ", Payload=" + payload
|
"Nachricht empfangen -> Topic=" + topic + ", Payload=" + payload
|
||||||
//);
|
);
|
||||||
|
|
||||||
Consumer<String> listener = topicListeners.get(topic);
|
Consumer<String> listener = topicListeners.get(topic);
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
listener.accept(payload);
|
listener.accept(payload);
|
||||||
Logger.debug("MQTT", "Payload accepted");
|
|
||||||
} else {
|
} else {
|
||||||
Logger.warn(
|
Logger.warn(
|
||||||
"MQTT",
|
"MQTT",
|
||||||
"Keine Listener für Topic: " + topic
|
"Keine Listener für Topic: " + topic
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,10 +114,8 @@ public class MqttClientService implements MqttCallback {
|
|||||||
@Override
|
@Override
|
||||||
public void deliveryComplete(IMqttDeliveryToken token) {
|
public void deliveryComplete(IMqttDeliveryToken token) {
|
||||||
Logger.debug(
|
Logger.debug(
|
||||||
"MQTT",
|
"MQTT",
|
||||||
"Delivery complete, MessageId=" + token.getMessageId()
|
"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