Merge pull request 'feature/data' (#2) from feature/data into pre_int

Reviewed-on: #2
This commit is contained in:
Niklas Aumueller 2026-02-12 21:21:10 +00:00
commit a459d3f535
12 changed files with 381 additions and 28 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/nbproject/

BIN
data/health.db Normal file

Binary file not shown.

25
pom.xml
View File

@ -48,6 +48,31 @@
<artifactId>sqlite-jdbc</artifactId>
<version>3.51.1.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.6.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.6.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.6.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.11.0</version>
<scope>test</scope>
<type>jar</type>
</dependency>
</dependencies>
<build>
<plugins>

View File

@ -1,6 +1,7 @@
package efi.projekt.virtueller_gesundheitsassistent;
import efi.projekt.virtueller_gesundheitsassistent.model.AppState;
import efi.projekt.virtueller_gesundheitsassistent.service.BinaryEventService;
import efi.projekt.virtueller_gesundheitsassistent.service.DataPersistenceService;
import efi.projekt.virtueller_gesundheitsassistent.service.EvaluationService;
import efi.projekt.virtueller_gesundheitsassistent.service.MqttClientService;
@ -29,6 +30,7 @@ public class App extends Application {
private EvaluationService evaluationService;
private UnrealWebSocketService unrealService;
private MqttClientService mqttService;
private BinaryEventService binaryEventService;
@Override
public void start(Stage primaryStage) throws IOException {
@ -38,7 +40,7 @@ public class App extends Application {
// =========================
// Unreal-WebService
unrealService = new UnrealWebSocketService("127.0.0.1");
unrealService = new UnrealWebSocketService("ws://localhost:8080/avatar");
// MQTT-Service
mqttService = new MqttClientService();
@ -47,13 +49,21 @@ public class App extends Application {
persistenceService = new DataPersistenceService();
// Statistik-Service
statisticsService = new StatisticsService();
statisticsService = new StatisticsService(persistenceService);
// Evaluationsservice
evaluationService = new EvaluationService(statisticsService, appState, unrealService);
binaryEventService = new BinaryEventService(persistenceService, evaluationService);
// =========================
// MQTT Subscribe
// =========================
mqttService.subscribe("STATE", payload ->
binaryEventService.handlePayload(payload)
);
// =========================
// UI laden
// =========================

View File

@ -0,0 +1,40 @@
package efi.projekt.virtueller_gesundheitsassistent.service;
import efi.projekt.virtueller_gesundheitsassistent.util.Logger;
/**
*
* @author naumueller
*/
public class BinaryEventService {
private final DataPersistenceService persistenceService;
private final EvaluationService evaluationService;
public BinaryEventService(
DataPersistenceService persistenceService,
EvaluationService evaluationService
) {
this.persistenceService = persistenceService;
this.evaluationService = evaluationService;
}
public void handlePayload(String payload) {
try {
int value = Integer.parseInt(payload);
if (value != 0 && value != 1) {
Logger.warn("EVENT", "Ungültiger Wert: " + payload);
return;
}
persistenceService.store(value);
evaluationService.evaluate();
} catch (NumberFormatException e) {
Logger.warn("EVENT", "Payload nicht numerisch " + payload);
}
}
}

View File

@ -1,10 +1,7 @@
/*
* 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.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
@ -18,12 +15,34 @@ import java.time.LocalDateTime;
*/
public class DataPersistenceService {
private static final String DB_URL = "jdbc:sqlite:data/health.db";
private static final String DB_FOLDER = "data";
private final String DB_URL;
public DataPersistenceService() {
this("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()) {
@ -44,7 +63,10 @@ public class DataPersistenceService {
}
public void store(int value) {
try (Connection c = DriverManager.getConnection(DB_URL); PreparedStatement ps = c.prepareStatement("INSERT INTO binary_event (value, timestamp) VALUES (?, ?)")) {
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();
@ -52,4 +74,8 @@ public class DataPersistenceService {
Logger.error("DB", "Database Insert fehlgeschlagen", e);
}
}
public String getDbUrl() {
return DB_URL;
}
}

View File

@ -1,7 +1,3 @@
/*
* 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;

View File

@ -1,7 +1,3 @@
/*
* 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;
@ -17,7 +13,11 @@ import java.sql.SQLException;
*/
public class StatisticsService {
private static final String DB_URL = "jdbc:sqlite:data/health.db";
private final DataPersistenceService persistenceService;
public StatisticsService(DataPersistenceService persistenceService) {
this.persistenceService = persistenceService;
}
public double getRatio(int lastN) {
String query = """
@ -25,12 +25,12 @@ public class StatisticsService {
FROM (
SELECT value
FROM binary_event
ORDER BY timestamp DESC
ORDER BY id DESC
LIMIT ?
)
""";
try (Connection c = DriverManager.getConnection(DB_URL); PreparedStatement ps = c.prepareStatement(query)) {
try (Connection c = DriverManager.getConnection(persistenceService.getDbUrl()); PreparedStatement ps = c.prepareStatement(query)) {
ps.setInt(1, lastN);
ResultSet rs = ps.executeQuery();

View File

@ -0,0 +1,58 @@
package efi.projekt.virtueller_gesundheitsassistent.service;
import org.junit.jupiter.api.*;
import static org.mockito.Mockito.*;
/**
*
* @author naumueller
*/
class BinaryEventServiceTest {
private DataPersistenceService persistenceService;
private EvaluationService evaluationService;
private BinaryEventService binaryEventService;
@BeforeAll
public static void setUpClass() {
}
@AfterAll
public static void tearDownClass() {
}
@BeforeEach
public void setUp() {
persistenceService = mock(DataPersistenceService.class);
evaluationService = mock(EvaluationService.class);
binaryEventService = new BinaryEventService(persistenceService, evaluationService);
}
@AfterEach
public void tearDown() {
persistenceService = null;
evaluationService = null;
binaryEventService = null;
}
@Test
void testHandlePayloadValid() {
binaryEventService.handlePayload("1");
verify(persistenceService).store(1);
verify(evaluationService).evaluate();
}
@Test
void testHandlePayloadInvalidValue() {
binaryEventService.handlePayload("5");
verify(persistenceService, never()).store(anyInt());
verify(evaluationService, never()).evaluate();
}
@Test
void testHandlePayloadNonNumeric() {
binaryEventService.handlePayload("abc");
verify(persistenceService, never()).store(anyInt());
verify(evaluationService, never()).evaluate();
}
}

View File

@ -0,0 +1,56 @@
package efi.projekt.virtueller_gesundheitsassistent.service;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.*;
/**
*
* @author naumueller
*/
public class DataPersistenceServiceTest {
private DataPersistenceService persistenceService;
private static final String IN_MEMORY_DB = "jdbc:sqlite:data/health.db";
public DataPersistenceServiceTest() {
}
@BeforeAll
public static void setUpClass() {
}
@AfterAll
public static void tearDownClass() {
}
@BeforeEach
public void setUp() {
persistenceService = new DataPersistenceService(IN_MEMORY_DB);
}
@AfterEach
public void tearDown() {
persistenceService = null;
}
@Test
void testStoreAndRetrieveValue() throws SQLException {
persistenceService.store(1);
try (Connection c = DriverManager.getConnection(IN_MEMORY_DB);
PreparedStatement ps = c.prepareStatement("SELECT value FROM binary_event")) {
ResultSet rs = ps.executeQuery();
Assertions.assertTrue(rs.next());
Assertions.assertEquals(1, rs.getInt("value"));
}
}
}

View File

@ -0,0 +1,59 @@
package efi.projekt.virtueller_gesundheitsassistent.service;
import efi.projekt.virtueller_gesundheitsassistent.model.AppState;
import efi.projekt.virtueller_gesundheitsassistent.model.ProblemLevel;
import org.junit.jupiter.api.*;
import static org.mockito.Mockito.*;
/**
*
* @author naumueller
*/
class EvaluationServiceTest {
private StatisticsService statisticsService;
private AppState appState;
private UnrealWebSocketService unrealService;
private EvaluationService evaluationService;
@BeforeAll
public static void setUpClass() {
}
@AfterAll
public static void tearDownClass() {
}
@BeforeEach
public void setUp() {
statisticsService = mock(StatisticsService.class);
appState = new AppState();
unrealService = mock(UnrealWebSocketService.class);
evaluationService = new EvaluationService(statisticsService, appState, unrealService);
}
@AfterEach
public void tearDown() {
statisticsService = null;
appState = null;
unrealService = null;
evaluationService = null;
}
@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());
}
}

View File

@ -0,0 +1,82 @@
package efi.projekt.virtueller_gesundheitsassistent.service;
import java.io.File;
import org.junit.jupiter.api.*;
import java.sql.*;
import static org.junit.jupiter.api.Assertions.*;
/**
*
* @author naumueller
*/
class StatisticsServiceTest {
private String DB_URL;
private DataPersistenceService persistenceService;
private StatisticsService statisticsService;
private File tempDbFile;
@BeforeAll
public static void setUpClass() {
}
@AfterAll
public static void tearDownClass() {
}
@BeforeEach
public void setUp() throws SQLException {
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
public 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() throws SQLException {
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() throws SQLException {
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 nur die letzten N Werte berücksichtigen");
}
}