using CoreOSC; using CoreOSC.IO; using CoreOSC.Types; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Net; using System.Net.Sockets; using System.Security.Cryptography; using System.Text; using System.Threading; namespace Wolf { internal class OSCClient { private UdpClient udpClientSend; private UdpClient udpClientReceive; private readonly SemaphoreSlim _requestSemaphore; private string _lastSentAddress = ""; private object[] _lastSentArguments = Array.Empty(); public OSCClient(string hostname, int port) { // 1. Lokalen Port binden (für Empfang) udpClientSend = new UdpClient(hostname, port); udpClientReceive = new UdpClient(new IPEndPoint(IPAddress.Any, port)); _requestSemaphore = new SemaphoreSlim(1, 1); } public async Task SendMessage(string endpoint) { await _requestSemaphore.WaitAsync(); try { var message = new OscMessage(new Address(endpoint)); _lastSentAddress = endpoint; _lastSentArguments = Array.Empty(); await udpClientSend.SendMessageAsync(message); } finally { _requestSemaphore.Release(); } } public async Task SpeakId(int textId) { await _requestSemaphore.WaitAsync(); try { var message = new OscMessage( address: new Address("/speak"), arguments: new object[] { textId }); _lastSentAddress = "/speak"; _lastSentArguments = new object[] { textId }; await udpClientSend.SendMessageAsync(message); } finally { _requestSemaphore.Release(); } } public async Task Read(string text, bool readStory = true) { await _requestSemaphore.WaitAsync(); try { string endpoint = readStory ? "/read_story" : "/read"; var message = new OscMessage( address: new Address(endpoint), arguments: new object[] { text, 2 }); _lastSentAddress = endpoint; _lastSentArguments = new object[] { text, 2 }; await udpClientSend.SendMessageAsync(message); } finally { _requestSemaphore.Release(); } } public async Task SendMessageWithArguments(string endpoint, params object[] param) { await _requestSemaphore.WaitAsync(); try { var message = new OscMessage( address: new Address(endpoint), arguments: param ); _lastSentAddress = endpoint; _lastSentArguments = param; await udpClientSend.SendMessageAsync(message); } finally { _requestSemaphore.Release(); } } public async Task ReceiveMessageWithoutArguments() { var response = await udpClientReceive.ReceiveMessageAsync(); Console.WriteLine(response.Address.Value); // Do something with the arguments, (depending on the address) foreach (var argument in response.Arguments) { Console.WriteLine(argument.GetType()); } } public async Task ReceiveMessageWithArguments() { var response = await udpClientReceive.ReceiveMessageAsync(); if (response.Address.Value == "/example4") { Console.WriteLine("/example4"); var stringArgument = (string)response.Arguments.ElementAt(0); var intArgument = (int)response.Arguments.ElementAt(1); var falseArgument = (OscFalse)response.Arguments.ElementAt(2); Console.WriteLine($"First string argument: {stringArgument}"); Console.WriteLine($"Second int argument: {intArgument}"); Console.WriteLine($"Third OscFalse argument:{falseArgument}"); } } /// /// Generische Methode zur Ausführung von Request-Response-Kommunikation mit Retry-Logik. /// private async Task ExecuteRequestWithRetry( string expectedAddress, OscMessage requestMessage, Func responseParser, int maxRetries = 50, int timeoutSeconds = 20) { await _requestSemaphore.WaitAsync(); try { _lastSentAddress = requestMessage.Address.Value; _lastSentArguments = requestMessage.Arguments.ToArray(); for (int attempt = 1; attempt <= maxRetries; attempt++) { try { // Anfrage senden await udpClientSend.SendMessageAsync(requestMessage); // Auf Response warten mit Timeout var receiveTask = udpClientReceive.ReceiveMessageAsync(); var timeoutTask = Task.Delay(TimeSpan.FromSeconds(timeoutSeconds)); var completed = await Task.WhenAny(receiveTask, timeoutTask); if (completed == receiveTask) { var response = await receiveTask; // Prüfen, ob es die erwartete Antwort ist if (response.Address.Value == expectedAddress) { try { return responseParser(response); } catch (Exception parseEx) { // Argument-Parsing-Fehler, ignorieren und nächste Anfrage senden System.Diagnostics.Debug.WriteLine($"Parse error on attempt {attempt}: {parseEx.Message}"); continue; } } // Falsche Antwort → ignorieren und weiter versuchen } // Timeout → erneut versuchen } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Request attempt {attempt} failed: {ex.Message}"); } } // Alle Versuche fehlgeschlagen throw new TimeoutException($"Request to {expectedAddress} failed after {maxRetries} attempts"); } finally { _requestSemaphore.Release(); } } public async Task> GetAllIds() { var message = new OscMessage(new Address("/getAllIds")); try { var result = await ExecuteRequestWithRetry( expectedAddress: "/getAllIds", requestMessage: message, responseParser: response => { var playerIds = new List(); foreach (var argument in response.Arguments) { playerIds.Add((int)argument); } return playerIds; }); return result; } catch (TimeoutException) { return new List(); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"GetAllIds error: {ex.Message}"); return new List(); } } // < 0 = no confirmation; 0 = confirm; > 0 = player id //public async Task GetPlayerId(List targets, List pois) //{ // //SendMessageWithArguments("/getPlayerID", targets.Select(p => p.id).ToArray(), pois.Select(p => p.id).ToArray()); // var message = new OscMessage( // address: new Address("/getPlayerID"), // //arguments: new object[] { targets.Select(p => p.id).ToArray(), pois.Select(p => p.id).ToArray() } // arguments: Concat(targets.Select(p => p.id).Cast().ToArray().Concat(pois.Select(p => p.id).Cast()).ToArray() // ); // await udpClientSend.SendMessageAsync(message); // var response = await udpClientReceive.ReceiveMessageAsync(); // if (response.Address.Value == "/getPlayerID") // { // return (int)response.Arguments.ElementAt(0); // } // return -1; //} public async Task GetPlayerId(List targets, List pois) { // Arrays extrahieren var arr2 = targets.Select(p => p.id).ToArray(); var arr1 = pois.Select(p => p.id).ToArray(); // OSC-kompatibles int-Argument-Array bauen: // Länge1 | Werte1 | Länge2 | Werte2 var args = new List(); args.Add(arr1.Length); foreach (var id in arr1) args.Add(id); args.Add(arr2.Length); foreach (var id in arr2) args.Add(id); // Nachricht vorbereiten var message = new OscMessage( new Address("/getPlayerID"), args.ToArray() ); try { var result = await ExecuteRequestWithRetry( expectedAddress: "/getPlayerID", requestMessage: message, responseParser: response => { if (response.Arguments.Count() > 0) { return (int)response.Arguments.ElementAt(0); } throw new InvalidOperationException("No arguments in response"); }, maxRetries: 50, timeoutSeconds: 20); return result; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"GetPlayerId error: {ex.Message}"); return -1; } } public async Task GetConfirmation(List players) { var args = new List(); foreach (var p in players) { args.Add(p.id); } var message = new OscMessage( new Address("/confirmation"), args.ToArray() ); try { var result = await ExecuteRequestWithRetry( expectedAddress: "/confirmation", requestMessage: message, responseParser: response => { if (response.Arguments.Count() > 0) { var firstArg = response.Arguments.ElementAt(0); if (firstArg is OscFalse) { return false; } if (firstArg is OscTrue) { return true; } throw new InvalidOperationException($"Unexpected argument type: {firstArg.GetType().Name}"); } throw new InvalidOperationException("No arguments in response"); }, maxRetries: 50, timeoutSeconds: 20); return result; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"GetConfirmation error: {ex.Message}"); return false; } } public async Task GetConfirmationEveryoneAsleep() { var message = new OscMessage(new Address("/everyoneAsleep")); try { var result = await ExecuteRequestWithRetry( expectedAddress: "/everyoneAsleep", requestMessage: message, responseParser: response => { if (response.Arguments.Count() > 0) { var firstArg = response.Arguments.ElementAt(0); if (firstArg is OscFalse) { return false; } if (firstArg is OscTrue) { return true; } throw new InvalidOperationException($"Unexpected argument type: {firstArg.GetType().Name}"); } throw new InvalidOperationException("No arguments in response"); }, maxRetries: 50, timeoutSeconds: 30); return result; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"GetConfirmationEveryoneAsleep error: {ex.Message}"); return false; } } } }