204 lines
7.9 KiB
Java
204 lines
7.9 KiB
Java
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();
|
||
}
|
||
}
|
||
|
||
|
||
}
|