ProgessLevelBar, ProcessManagement with SignallingServer

This commit is contained in:
Niklas Aumueller 2026-02-24 16:48:21 +01:00
parent f2846aa46a
commit 99e768313d
21 changed files with 406 additions and 256 deletions

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated
View File

@ -1,5 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.12 (gesundheitsassistent)" />
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager"> <component name="MavenProjectsManager">
<option name="originalFiles"> <option name="originalFiles">
@ -8,7 +11,5 @@
</list> </list>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" project-jdk-name="ms-17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" project-jdk-name="ms-17" project-jdk-type="JavaSDK" />
<output url="file://$PROJECT_DIR$/out" />
</component>
</project> </project>

View File

@ -10,7 +10,7 @@ public class ApplicationContext {
private DataPersistenceService persistenceService; private DataPersistenceService persistenceService;
private StatisticsService statisticsService; private StatisticsService statisticsService;
private EvaluationService evaluationService; private EvaluationService evaluationService;
private UnrealWebSocketService unrealService; private AnimationFileService unrealService;
private MqttClientService mqttService; private MqttClientService mqttService;
private BinaryEventService binaryEventService; private BinaryEventService binaryEventService;
private ProcessManagerService processManagerService; private ProcessManagerService processManagerService;
@ -31,7 +31,7 @@ public class ApplicationContext {
this.evaluationService = evaluationService; this.evaluationService = evaluationService;
} }
public void setUnrealService(UnrealWebSocketService unrealService) { public void setAnimationFileService(AnimationFileService unrealService) {
this.unrealService = unrealService; this.unrealService = unrealService;
} }
@ -59,7 +59,7 @@ public class ApplicationContext {
return evaluationService; return evaluationService;
} }
public UnrealWebSocketService getUnrealService() { public AnimationFileService getAnimationFileService() {
return unrealService; return unrealService;
} }

View File

@ -1,14 +1,11 @@
package vassistent.bootstrap; package vassistent.bootstrap;
import com.formdev.flatlaf.FlatDarkLaf; import com.formdev.flatlaf.FlatDarkLaf;
import vassistent.controller.DashboardController;
import vassistent.model.AppState; import vassistent.model.AppState;
import vassistent.service.*; import vassistent.service.*;
import vassistent.util.ConfigLoader; import vassistent.util.ConfigLoader;
import vassistent.util.Logger;
import javax.swing.*; import javax.swing.*;
import java.io.InputStream;
import java.util.Properties; import java.util.Properties;
public class ApplicationInitializer { public class ApplicationInitializer {
@ -26,8 +23,8 @@ public class ApplicationInitializer {
context.setAppState(new AppState()); context.setAppState(new AppState());
// ===== Infrastructure Services ===== // ===== Infrastructure Services =====
context.setUnrealService( context.setAnimationFileService(
new UnrealWebSocketService("ws://localhost:8888/avatar") new AnimationFileService()
); );
context.setMqttService(new MqttClientService(context.getAppState())); context.setMqttService(new MqttClientService(context.getAppState()));
@ -42,7 +39,7 @@ public class ApplicationInitializer {
new EvaluationService( new EvaluationService(
context.getStatisticsService(), context.getStatisticsService(),
context.getAppState(), context.getAppState(),
context.getUnrealService() context.getAnimationFileService()
) )
); );

View File

@ -1,25 +1,58 @@
package vassistent.bootstrap; package vassistent.bootstrap;
import vassistent.service.MqttClientService;
import vassistent.service.ProcessManagerService;
import vassistent.util.Logger;
public class ApplicationShutdownManager { public class ApplicationShutdownManager {
private final ApplicationContext context; private final ApplicationContext context;
public ApplicationShutdownManager(ApplicationContext context) { public ApplicationShutdownManager(ApplicationContext context) {
this.context = context; this.context = context;
Runtime.getRuntime().addShutdownHook(
new Thread(this::shutdown)
);
} }
public void shutdown() { public void shutdown() {
if (context.getMqttService() != null) { Logger.info("SHUTDOWN", "Shutdown Sequencing gestartet");
context.getMqttService().disconnect();
}
if (context.getUnrealService() != null) { disconnectMqtt();
context.getUnrealService().disconnect(); stopExternalProcesses();
} }
if (context.getProcessManagerService() != null) { private void disconnectMqtt() {
context.getProcessManagerService().shutdown();
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);
} }
} }
} }

View File

@ -5,8 +5,11 @@ import vassistent.bootstrap.ApplicationShutdownManager;
import vassistent.model.AppState; import vassistent.model.AppState;
import vassistent.ui.AppWindow; import vassistent.ui.AppWindow;
import vassistent.ui.DashboardView; import vassistent.ui.DashboardView;
import vassistent.ui.PixelStreamingView;
import vassistent.util.Logger; import vassistent.util.Logger;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter; import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent; import java.awt.event.WindowEvent;
@ -28,6 +31,7 @@ public class AppWindowController {
window = new AppWindow(); window = new AppWindow();
DashboardView dashboardView = window.getDashboardView(); DashboardView dashboardView = window.getDashboardView();
PixelStreamingView pixelStreamingView = window.getStreamingView();
DashboardController dashboardController = DashboardController dashboardController =
new DashboardController( new DashboardController(
@ -36,8 +40,16 @@ public class AppWindowController {
context.getAppState() context.getAppState()
); );
window.getRefreshDashboardButton() StreamingController previewController = new StreamingController(pixelStreamingView);
.addActionListener(e -> dashboardController.loadChartData());
dashboardView.getReloadPixelStreamingViewButton()
.addActionListener(e ->
previewController.reloadStream("http://141.75.215.233")
);
dashboardView.getOpenFullscreenButton()
.addActionListener(e ->
window.getStreamingView().requestFocus());
window.addWindowListener(new WindowAdapter() { window.addWindowListener(new WindowAdapter() {
@Override @Override
@ -66,17 +78,21 @@ public class AppWindowController {
AppState state = context.getAppState(); AppState state = context.getAppState();
state.addListener(appState -> { state.addListener(this::onStateChanged);
/*Logger.debug("Controller", Logger.info("Controller",
"AppState Update erhalten: " + "AppState Observer registriert");
appState.getProblemLevel());*/ }
if (window == null) { private void onStateChanged(AppState appState) {
Logger.warn("Controller",
"Window ist null, UI Update übersprungen"); if (window == null) {
return; Logger.warn("Controller",
} "Window ist null, UI Update übersprungen");
return;
}
SwingUtilities.invokeLater(() -> {
window.updateProblemLevel( window.updateProblemLevel(
appState.getProblemLevel().name() appState.getProblemLevel().name()
@ -87,11 +103,8 @@ public class AppWindowController {
); );
Logger.debug("Controller", Logger.debug("Controller",
"ProblemLevel UI aktualisiert → " + "ProblemLevel UI aktualisiert → "
appState.getProblemLevel()); + appState.getProblemLevel());
}); });
Logger.info("Controller",
"AppState Observer registriert");
} }
} }

View File

@ -5,6 +5,7 @@ import vassistent.model.RatioPoint;
import vassistent.service.StatisticsService; import vassistent.service.StatisticsService;
import vassistent.ui.DashboardView; import vassistent.ui.DashboardView;
import javax.swing.*;
import java.util.List; import java.util.List;
public class DashboardController { public class DashboardController {
@ -21,14 +22,23 @@ public class DashboardController {
this.statisticsService = statisticsService; this.statisticsService = statisticsService;
this.dashboardView = dashboardView; this.dashboardView = dashboardView;
appState.addListener(state -> { appState.addListener(this::onStateChanged);
}
if (state.getDataVersion() != lastDataVersion) { private void onStateChanged(AppState state) {
lastDataVersion = state.getDataVersion();
List<RatioPoint> points = statisticsService.getLastNAverages(20); List<RatioPoint> points =
dashboardView.updateChart(points); statisticsService.getLastNAverages(20);
}
if (points.isEmpty())
return;
double ratio = points.get(points.size() - 1).getRatio();
SwingUtilities.invokeLater(() -> {
dashboardView.updateChart(points);
dashboardView.updateProblemLevel(ratio);
}); });
} }

View File

@ -27,6 +27,7 @@ public class AppState {
public void setProblemLevel(ProblemLevel problemLevel) { public void setProblemLevel(ProblemLevel problemLevel) {
this.problemLevel = problemLevel; this.problemLevel = problemLevel;
dataVersion++;
notifyListeners(); notifyListeners();
} }
@ -40,13 +41,4 @@ public class AppState {
notifyListeners(); notifyListeners();
} }
} }
public long getDataVersion() {
return dataVersion;
}
public void incrementDataVersion() {
dataVersion++;
notifyListeners();
}
} }

View File

@ -0,0 +1,50 @@
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";
};
}
}

View File

@ -7,28 +7,26 @@ import vassistent.model.ProblemLevel;
public class EvaluationService { public class EvaluationService {
private final StatisticsService statisticsService; private final StatisticsService statisticsService;
private final AppState appState; private final AppState appState;
private final UnrealWebSocketService unrealService; private final AnimationFileService animationFileService;
public EvaluationService( public EvaluationService(
StatisticsService statisticsService, StatisticsService statisticsService,
AppState appState, AppState appState,
UnrealWebSocketService unrealService AnimationFileService animationFileService
) { ) {
this.statisticsService = statisticsService; this.statisticsService = statisticsService;
this.appState = appState; this.appState = appState;
this.unrealService = unrealService; this.animationFileService = animationFileService;
} }
public void evaluate() { public void evaluate() {
double ratio = statisticsService.getRatio(10); double ratio = statisticsService.getRatio(20);
ProblemLevel level = calculateLevel(ratio); ProblemLevel level = calculateLevel(ratio);
appState.setProblemLevel(level); appState.setProblemLevel(level);
unrealService.speak(level.name()); animationFileService.wirteAnimationState(level);
appState.incrementDataVersion();
} }
private ProblemLevel calculateLevel(Double ratio) { private ProblemLevel calculateLevel(Double ratio) {

View File

@ -23,7 +23,6 @@ public class MqttClientService implements MqttCallback {
public MqttClientService(AppState appState) { public MqttClientService(AppState appState) {
this.appState = appState; this.appState = appState;
try { try {
client = new MqttClient( client = new MqttClient(
BROKER_URL, BROKER_URL,
@ -107,6 +106,7 @@ public class MqttClientService implements MqttCallback {
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",

View File

@ -1,5 +0,0 @@
package vassistent.service;
public class PixelStreamingService {
}

View File

@ -12,6 +12,7 @@ public class ProcessManagerService {
private Process pythonProcess; private Process pythonProcess;
private Process unrealProcess; private Process unrealProcess;
private Process unrealSignallingProcess;
public ProcessManagerService(Properties config) { public ProcessManagerService(Properties config) {
this.config = config; this.config = config;
@ -54,34 +55,92 @@ public class ProcessManagerService {
try { try {
String exe = config.getProperty("unreal.executable"); startSignallingServer();
startUnrealEngine();
ProcessBuilder pb = new ProcessBuilder(
exe,
"-RenderOffScreen",
"-NoSound",
"-PixelStreamingSignallingURL=ws://127.0.0.1:8888"
);
pb.redirectErrorStream(true);
unrealProcess = pb.start();
Logger.info("PROCESS", "Unreal Engine Avatar");
} catch (IOException e) { } catch (IOException e) {
Logger.error("PROCESS", "Unreal Engine Start fehlgeschlagen", 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() { public void shutdown() {
if (pythonProcess != null) Logger.info("PROCESS", "Shutdown externe Prozesse gestartet");
pythonProcess.destroyForcibly();
if (unrealProcess != null) terminateProcess(pythonProcess);
unrealProcess.destroyForcibly(); terminateProcess(unrealProcess);
terminateProcess(unrealSignallingProcess);
Logger.info("PROCESS", "Externe Prozesse beendet"); 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);
}
}
} }

View File

@ -1,13 +0,0 @@
package vassistent.service;
import vassistent.model.AppState;
public class StateService {
private static final AppState STATE = new AppState();
private StateService() {}
public static AppState getState() {
return STATE;
}
}

View File

@ -1,95 +0,0 @@
package vassistent.service;
import vassistent.util.Logger;
import java.net.URI;
import java.util.concurrent.atomic.AtomicBoolean;
import jakarta.websocket.*;
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("\"", "\\\"");
}
}

View File

@ -10,15 +10,11 @@ public class AppWindow extends JFrame {
private PixelStreamingView streamingView; private PixelStreamingView streamingView;
private DashboardView dashboardView; private DashboardView dashboardView;
private JTabbedPane tabs;
private JLabel mqttStatusLabel; private JLabel mqttStatusLabel;
private JLabel websocketStatusLabel;
private JLabel problemLevelLabel; private JLabel problemLevelLabel;
private JButton refreshDashboardButton;
private JButton reloadStreamButton;
private JButton fullscreenButton;
public AppWindow() { public AppWindow() {
setTitle("Virtueller Gesundheitsassistent"); setTitle("Virtueller Gesundheitsassistent");
@ -28,41 +24,27 @@ public class AppWindow extends JFrame {
setLayout(new BorderLayout()); setLayout(new BorderLayout());
add(createToolbar(), BorderLayout.NORTH); streamingView = new PixelStreamingView(
add(createTabPane(), BorderLayout.CENTER); "http://localhost:80",
false,
false
);
dashboardView = new DashboardView();
tabs = createTabPane();
add(tabs, BorderLayout.CENTER);
add(createStatusBar(), BorderLayout.SOUTH); add(createStatusBar(), BorderLayout.SOUTH);
} }
// ---------------- Toolbar ----------------
private JPanel createToolbar() {
JPanel toolbar = new JPanel(new FlowLayout(FlowLayout.LEFT));
refreshDashboardButton = new JButton("Dashboard Refresh");
reloadStreamButton = new JButton("Stream Reload");
fullscreenButton = new JButton("Fullscreen Stream");
toolbar.add(refreshDashboardButton);
toolbar.add(reloadStreamButton);
toolbar.add(fullscreenButton);
return toolbar;
}
// ---------------- Tabs ---------------- // ---------------- Tabs ----------------
private JTabbedPane createTabPane() { private JTabbedPane createTabPane() {
JTabbedPane tabs = new JTabbedPane(); JTabbedPane tabs = new JTabbedPane();
streamingView = new PixelStreamingView( JPanel streamingPanel = new JPanel(new BorderLayout());
"http://141.75.215.233", streamingPanel.add(streamingView, BorderLayout.CENTER);
false,
false
);
dashboardView = new DashboardView();
tabs.addTab("Avatar Streaming", streamingView); tabs.addTab("Avatar Streaming", streamingView);
tabs.addTab("Dashboard", dashboardView); tabs.addTab("Dashboard", dashboardView);
@ -77,13 +59,10 @@ public class AppWindow extends JFrame {
JPanel statusBar = new JPanel(new FlowLayout(FlowLayout.LEFT)); JPanel statusBar = new JPanel(new FlowLayout(FlowLayout.LEFT));
mqttStatusLabel = new JLabel("MQTT: Disconnected"); mqttStatusLabel = new JLabel("MQTT: Disconnected");
websocketStatusLabel = new JLabel();
problemLevelLabel = new JLabel("Problem: NONE"); problemLevelLabel = new JLabel("Problem: NONE");
statusBar.add(mqttStatusLabel); statusBar.add(mqttStatusLabel);
statusBar.add(new JLabel(" | ")); statusBar.add(new JLabel(" | "));
statusBar.add(websocketStatusLabel);
statusBar.add(new JLabel(" | "));
statusBar.add(problemLevelLabel); statusBar.add(problemLevelLabel);
return statusBar; return statusBar;
@ -94,11 +73,6 @@ public class AppWindow extends JFrame {
(connected ? "Connected" : "Disconnected")); (connected ? "Connected" : "Disconnected"));
} }
public void updateWebsocketStatus(boolean connected) {
websocketStatusLabel.setText("WebSocket: " +
(connected ? "Connected" : "Disconnected"));
}
public void updateProblemLevel(String level) { public void updateProblemLevel(String level) {
problemLevelLabel.setText("Problem: " + level); problemLevelLabel.setText("Problem: " + level);
} }
@ -111,15 +85,7 @@ public class AppWindow extends JFrame {
return streamingView; return streamingView;
} }
public JButton getRefreshDashboardButton() { public JTabbedPane getTabs() {
return refreshDashboardButton; return tabs;
}
public JButton getReloadStreamButton() {
return reloadStreamButton;
}
public JButton getFullscreenButton() {
return fullscreenButton;
} }
} }

View File

@ -7,6 +7,7 @@ import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.time.Millisecond; import org.jfree.data.time.Millisecond;
import org.jfree.data.time.TimeSeries; import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection; import org.jfree.data.time.TimeSeriesCollection;
import vassistent.model.ProblemLevel;
import vassistent.model.RatioPoint; import vassistent.model.RatioPoint;
import javax.swing.*; import javax.swing.*;
@ -20,14 +21,23 @@ import java.util.List;
public class DashboardView extends JPanel { public class DashboardView extends JPanel {
private TimeSeries series; private TimeSeries series;
private ProblemLevelBar levelBar;
private JButton reloadPixelStreamingViewButton;
private JButton openFullscreenButton;
public DashboardView() { public DashboardView() {
setLayout(new BorderLayout()); setLayout(new BorderLayout());
// ---------- TOP: Problem Level ----------
levelBar = new ProblemLevelBar();
add(levelBar, BorderLayout.NORTH);
// ---------- CENTER: Chart ----------
series = new TimeSeries("Ratio"); series = new TimeSeries("Ratio");
TimeSeriesCollection dataset = TimeSeriesCollection dataset = new TimeSeriesCollection(series);
new TimeSeriesCollection(series);
JFreeChart chart = ChartFactory.createTimeSeriesChart( JFreeChart chart = ChartFactory.createTimeSeriesChart(
"Ratio Verlauf", "Ratio Verlauf",
@ -36,9 +46,27 @@ public class DashboardView extends JPanel {
dataset dataset
); );
ChartPanel chartPanel = new ChartPanel(chart); add(new ChartPanel(chart), BorderLayout.CENTER);
add(chartPanel, 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) { public void updateChart(List<RatioPoint> points) {
@ -59,5 +87,15 @@ public class DashboardView extends JPanel {
} }
} }
public void updateProblemLevel(double ratio) {
levelBar.setRatio(ratio);
}
public JButton getReloadPixelStreamingViewButton() {
return reloadPixelStreamingViewButton;
}
public JButton getOpenFullscreenButton() {
return openFullscreenButton;
}
} }

View File

@ -0,0 +1,89 @@
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);
}
}
}

View File

@ -2,16 +2,16 @@
app.mode=test app.mode=test
# ===== PYTHON ===== # ===== PYTHON =====
python.path="C:\\Program Files\\PyManager\\python.exe" python.path=C:\\Program Files\\PyManager\\python.exe
# ===== MQTT CLIENT ===== # ===== MQTT CLIENT =====
mqtt.topic=PREDICTION mqtt.topic=PREDICTION
# ===== MQTT SIMULATOR ===== # ===== MQTT SIMULATOR =====
mqtt_sim.enabled=false mqtt_sim.enabled=true
mqtt_sim.script=src/main/resources/scripts/mqtt_simulator.py mqtt_sim.script=src/main/resources/scripts/mqtt_simulator.py
# ===== UNREAL ENGINE ===== # ===== UNREAL ENGINE =====
unreal.enabled=false unreal.enabled=true
unreal.executable=external/unreal/avatar.exe unreal.executable=C:\\Privat\\Dokumente\\Niklas_Aumueller\\TH\\MSY\\Semester_2\\Projektarbeit\\Windows\\Prototyp1.exe
unreal.signalling_server.script=external/unreal/start.bat 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

View File

@ -1,3 +1,5 @@
import json
import paho.mqtt.client as mqtt import paho.mqtt.client as mqtt
import sys import sys
import random import random
@ -44,17 +46,26 @@ def main():
try: try:
logging.info(f"Verbinde mit Broker {BROKER}:{PORT}") logging.info(f"Verbinde mit Broker {BROKER}:{PORT}")
client.connect(BROKER, PORT, 60) client.connect(BROKER, PORT, 60)
client.loop_start() client.loop_start()
logging.info("Starte kontinuierliches Senden...") logging.info("Starte kontinuierliches Senden...")
while True: current_id = 1
message = random.randint(0, 1)
client.publish(TOPIC, message, qos=QOS) while True:
logging.info(f"Gesendet an '{TOPIC}': {message}") 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) time.sleep(INTERVAL_SECONDS)

View File

@ -12,14 +12,14 @@ class EvaluationServiceTest {
private StatisticsService statisticsService; private StatisticsService statisticsService;
private AppState appState; private AppState appState;
private UnrealWebSocketService unrealService; private AnimationFileService unrealService;
private EvaluationService evaluationService; private EvaluationService evaluationService;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
statisticsService = mock(StatisticsService.class); statisticsService = mock(StatisticsService.class);
appState = new AppState(); appState = new AppState();
unrealService = mock(UnrealWebSocketService.class); unrealService = mock(AnimationFileService.class);
evaluationService = new EvaluationService(statisticsService, appState, unrealService); evaluationService = new EvaluationService(statisticsService, appState, unrealService);
} }