From 8274e78d1bf382c324402dabd3664a08d156ea4c Mon Sep 17 00:00:00 2001 From: "tom.hempel" Date: Tue, 21 Oct 2025 18:17:41 +0200 Subject: [PATCH] Added UDP control functionality to TaskTimer, allowing timer commands via UDP. Updated README to include new timer commands: START, PAUSE, RESUME, and RESET. --- README.md | 7 ++ Unity/Assets/Scripts/TaskTimer.cs | 150 +++++++++++++++++++++++++++++- 2 files changed, 156 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 89494c5..13f1ee0 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,13 @@ switch_scene("VR-Task1") - `AR-Task1`, `AR-Task2`, `AR-Task3`, `AR-Task4` - `Lobby` +**Timer Commands:** + +- TIMER:START - Start the timer +- TIMER:PAUSE - Pause the timer +- TIMER:RESUME - Resume the timer +- TIMER:RESET - Reset the timer to initial duration + --- ### Chat Application (`Chat-App/`) diff --git a/Unity/Assets/Scripts/TaskTimer.cs b/Unity/Assets/Scripts/TaskTimer.cs index aa51ffe..2011dd5 100644 --- a/Unity/Assets/Scripts/TaskTimer.cs +++ b/Unity/Assets/Scripts/TaskTimer.cs @@ -2,6 +2,10 @@ using UnityEngine; using UnityEngine.UI; using UnityEngine.SceneManagement; using TMPro; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; public class TaskTimer : MonoBehaviour { @@ -15,6 +19,12 @@ public class TaskTimer : MonoBehaviour [Tooltip("Show warning when time is low (in seconds)")] public float warningThreshold = 30f; + [Header("UDP Control Settings")] + [SerializeField] private int udpPort = 5555; + [SerializeField] private bool enableUDPControl = true; + [Tooltip("If enabled, timer will only start via UDP command (ignores autoStart)")] + [SerializeField] private bool waitForUDPStart = false; + [Header("UI References")] [Tooltip("TextMeshPro component to display timer (recommended for VR)")] public TextMeshProUGUI timerTextTMP; @@ -35,11 +45,24 @@ public class TaskTimer : MonoBehaviour private float timeRemaining; private bool isRunning; + // UDP infrastructure + private UdpClient udpClient; + private Thread udpListenerThread; + private bool isListening = false; + private string pendingCommand = null; + private readonly object commandLock = new object(); + private void Start() { timeRemaining = taskDuration; - if (autoStart) + if (enableUDPControl) + { + StartUDPListener(); + } + + // Only auto-start if not waiting for UDP command + if (autoStart && !waitForUDPStart) { StartTimer(); } @@ -49,6 +72,16 @@ public class TaskTimer : MonoBehaviour private void Update() { + // Check for pending UDP commands (must be done on main thread) + lock (commandLock) + { + if (!string.IsNullOrEmpty(pendingCommand)) + { + ProcessTimerCommand(pendingCommand); + pendingCommand = null; + } + } + if (!isRunning) return; @@ -155,5 +188,120 @@ public class TaskTimer : MonoBehaviour { return 1f - (timeRemaining / taskDuration); } + + // UDP Control Methods + private void StartUDPListener() + { + try + { + isListening = true; + udpClient = new UdpClient(udpPort); + udpListenerThread = new Thread(new ThreadStart(ListenForUDPCommands)); + udpListenerThread.IsBackground = true; + udpListenerThread.Start(); + Debug.Log($"TaskTimer: UDP Listener started on port {udpPort}"); + } + catch (System.Exception e) + { + Debug.LogError($"TaskTimer: Failed to start UDP listener: {e.Message}"); + } + } + + private void ListenForUDPCommands() + { + IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, udpPort); + + while (isListening) + { + try + { + byte[] data = udpClient.Receive(ref remoteEndPoint); + string message = Encoding.UTF8.GetString(data); + ProcessUDPCommand(message); + } + catch (System.Exception e) + { + if (isListening) + { + Debug.LogError($"TaskTimer: UDP receive error: {e.Message}"); + } + } + } + } + + private void ProcessUDPCommand(string message) + { + Debug.Log($"TaskTimer: Received UDP command: {message}"); + + if (string.IsNullOrEmpty(message)) + return; + + // Parse command format: TIMER:COMMAND (e.g., TIMER:START, TIMER:PAUSE, TIMER:RESET, TIMER:RESUME) + string[] parts = message.Split(':'); + if (parts.Length == 2 && parts[0].Trim().ToUpper() == "TIMER") + { + string command = parts[1].Trim().ToUpper(); + lock (commandLock) + { + pendingCommand = command; + } + Debug.Log($"TaskTimer: Scheduled command: {command}"); + } + else + { + Debug.LogWarning($"TaskTimer: Invalid UDP command format: {message}. Expected format: TIMER:COMMAND (e.g., TIMER:START)"); + } + } + + private void ProcessTimerCommand(string command) + { + switch (command) + { + case "START": + StartTimer(); + break; + case "PAUSE": + PauseTimer(); + break; + case "RESUME": + ResumeTimer(); + break; + case "RESET": + ResetTimer(); + break; + default: + Debug.LogWarning($"TaskTimer: Unknown command: {command}. Valid commands: START, PAUSE, RESUME, RESET"); + break; + } + } + + private void OnDestroy() + { + StopUDPListener(); + } + + private void OnApplicationQuit() + { + StopUDPListener(); + } + + private void StopUDPListener() + { + isListening = false; + + if (udpClient != null) + { + udpClient.Close(); + udpClient = null; + } + + if (udpListenerThread != null && udpListenerThread.IsAlive) + { + udpListenerThread.Abort(); + udpListenerThread = null; + } + + Debug.Log("TaskTimer: UDP Listener stopped"); + } }