opcua-service/main/java/de/opcua/app/ServiceMain.java
2026-05-11 19:40:18 +02:00

204 lines
7.9 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package de.opcua.app;
import de.opcua.app.config.Settings;
import de.opcua.app.config.SettingsService;
import de.opcua.app.model.NodeAction;
import de.opcua.app.opc.OpcUaService;
import de.opcua.app.rest.OpcUaRestApi;
import de.opcua.app.scripting.Store;
import de.opcua.app.service.ActionService;
import de.opcua.app.service.ActionPersistenceService;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.*;
/**
* Enhanced Service Mode for Windows/Linux background operation
* Runs autonomously without GUI
*/
public final class ServiceMain {
private ServiceMain() {}
public static void run(String[] args) {
System.out.println("═══════════════════════════════════════════════════════════");
System.out.println(" OPC UA GUI - Service Mode");
System.out.println(" Started: " + Instant.now());
System.out.println("═══════════════════════════════════════════════════════════");
// Load configuration
SettingsService settingsService = new SettingsService();
Settings settings = settingsService.load();
System.out.println("[Config] Endpoint: " + settings.endpoint());
System.out.println("[Config] Host: " + settings.host());
// Initialize services
OpcUaService opc = new OpcUaService();
Store store = new Store();
ActionService actionService = new ActionService(opc, store);
ActionPersistenceService persistence = new ActionPersistenceService();
// Initialize REST API (adjust port as needed)
OpcUaRestApi restApi = new OpcUaRestApi(opc, 8081, actionService);
try {
restApi.start();
} catch (Exception e) {
System.err.println("[REST API] Failed to start: " + e.getMessage());
}
// Load saved actions
Map<String, List<NodeAction>> savedActions = persistence.loadActions();
if (!savedActions.isEmpty()) {
System.out.println("[Actions] Loading " + savedActions.size() + " saved node configurations...");
actionService.importActionsFromList(savedActions);
} else {
System.out.println("[Actions] No saved actions found - service will monitor configured nodes");
}
// Auto-save every 60 seconds
persistence.startAutoSave(actionService, 60);
// Connect to OPC UA Server
System.out.println("[OPC UA] Connecting to " + settings.endpoint() + "...");
try {
opc.connect(settings.endpoint()).get(30, TimeUnit.SECONDS);
System.out.println("[OPC UA] ✅ Connected successfully");
// ✅ Digital Twin im Hintergrund bauen API antwortet sofort
restApi.triggerDigitalTwinBuild();
} catch (Exception e) {
System.err.println("[OPC UA] ❌ Connection failed: " + e.getMessage());
System.err.println("[OPC UA] Service will continue and retry...");
}
// Main service loop
ScheduledExecutorService exec = Executors.newScheduledThreadPool(2, r -> {
Thread t = new Thread(r, "service-loop");
t.setDaemon(false);
return t;
});
// Monitor and trigger actions
exec.scheduleWithFixedDelay(() -> {
try {
if (!opc.isConnected()) {
// Attempt reconnect
System.out.println("[OPC UA] Not connected, attempting reconnect...");
try {
opc.connect(settings.endpoint()).get(10, TimeUnit.SECONDS);
System.out.println("[OPC UA] ✅ Reconnected");
// ✅ Digital Twin nach Reconnect neu bauen
restApi.triggerDigitalTwinBuild();
} catch (Exception e) {
System.err.println("[OPC UA] Reconnect failed: " + e.getMessage());
return;
}
}
// Process all configured actions
Map<String, List<NodeAction>> allActions = actionService.getAllActions();
if (allActions.isEmpty()) {
return;
}
for (Map.Entry<String, List<NodeAction>> entry : allActions.entrySet()) {
String nodeId = entry.getKey();
try {
// Read current value
String value = opc.readValue(nodeId).get(5, TimeUnit.SECONDS);
// Trigger actions based on value
actionService.processValueChange(nodeId, value);
} catch (Exception e) {
System.err.println("[Service] Error reading " + nodeId + ": " + e.getMessage());
}
}
} catch (Exception e) {
System.err.println("[Service] Loop error: " + e.getMessage());
}
}, 0, 1, TimeUnit.SECONDS);
// Heartbeat / Status logging
exec.scheduleWithFixedDelay(() -> {
try {
Map<String, List<NodeAction>> allActions = actionService.getAllActions();
int totalActions = allActions.values().stream()
.mapToInt(List::size)
.sum();
long enabledActions = allActions.values().stream()
.flatMap(List::stream)
.filter(NodeAction::isEnabled)
.count();
System.out.println(
"[Heartbeat] " + Instant.now() +
" | Connected: " + (opc.isConnected() ? "" : "") +
" | Nodes: " + allActions.size() +
" | Actions: " + totalActions + " (enabled: " + enabledActions + ")"
);
} catch (Exception e) {
System.err.println("[Heartbeat] Error: " + e.getMessage());
}
}, 10, 30, TimeUnit.SECONDS);
// Shutdown hook for graceful cleanup
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("\n[Service] Shutdown signal received");
// Save actions before exit
System.out.println("[Service] Saving actions...");
persistence.saveActions(actionService.getAllActions());
// Disconnect OPC UA
System.out.println("[Service] Disconnecting from OPC UA...");
try {
opc.disconnect().get(5, TimeUnit.SECONDS);
} catch (Exception e) {
System.err.println("[Service] Disconnect error: " + e.getMessage());
}
// Stop REST API
restApi.stop();
// Shutdown action service
System.out.println("[Service] Shutting down action service...");
actionService.shutdown();
// Shutdown executor
System.out.println("[Service] Shutting down executor...");
exec.shutdown();
try {
if (!exec.awaitTermination(10, TimeUnit.SECONDS)) {
exec.shutdownNow();
}
} catch (InterruptedException e) {
exec.shutdownNow();
}
System.out.println("[Service] ✅ Shutdown complete");
}));
System.out.println("\n[Service] Service running - Press Ctrl+C to stop");
System.out.println("─────────────────────────────────────────────────────────────\n");
// Keep process alive
try {
new CountDownLatch(1).await();
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
}
}