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> 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> allActions = actionService.getAllActions(); if (allActions.isEmpty()) { return; } for (Map.Entry> 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> 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(); } } }